aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/com/jsyn/ports/UnitDataQueuePort.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/jsyn/ports/UnitDataQueuePort.java')
-rw-r--r--src/main/java/com/jsyn/ports/UnitDataQueuePort.java466
1 files changed, 466 insertions, 0 deletions
diff --git a/src/main/java/com/jsyn/ports/UnitDataQueuePort.java b/src/main/java/com/jsyn/ports/UnitDataQueuePort.java
new file mode 100644
index 0000000..13b2e2a
--- /dev/null
+++ b/src/main/java/com/jsyn/ports/UnitDataQueuePort.java
@@ -0,0 +1,466 @@
+/*
+ * Copyright 2009 Phil Burk, Mobileer Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jsyn.ports;
+
+import java.util.LinkedList;
+
+import com.jsyn.data.SequentialData;
+import com.jsyn.exceptions.ChannelMismatchException;
+import com.softsynth.shared.time.ScheduledCommand;
+import com.softsynth.shared.time.TimeStamp;
+
+/**
+ * Queue for SequentialData, samples or envelopes
+ *
+ * @author Phil Burk (C) 2009 Mobileer Inc
+ */
+public class UnitDataQueuePort extends UnitPort {
+ private final LinkedList<QueuedBlock> blocks = new LinkedList<QueuedBlock>();
+ private QueueDataCommand currentBlock;
+ private int frameIndex;
+ private int numChannels = 1;
+ private double normalizedRate;
+ private long framesMoved;
+ private boolean autoStopPending;
+ private boolean targetValid;
+ private QueueDataCommand finishingBlock;
+ private QueueDataCommand loopingBlock;
+ public static final int LOOP_IF_LAST = -1;
+
+ public UnitDataQueuePort(String name) {
+ super(name);
+ }
+
+ /** Hold a reference to part of a sample. */
+ @SuppressWarnings("serial")
+ private class QueuedBlock extends QueueDataCommand {
+
+ public QueuedBlock(SequentialData queueableData, int startFrame, int numFrames) {
+ super(UnitDataQueuePort.this, queueableData, startFrame, numFrames);
+ }
+
+ @Override
+ public void run() {
+ synchronized (blocks) {
+ // Remove last block if it can be skipped.
+ if (blocks.size() > 0) {
+ QueueDataEvent lastBlock = blocks.getLast();
+ if (lastBlock.isSkipIfOthers()) {
+ blocks.removeLast();
+ }
+ }
+
+ // If we are crossfading then figure out where to crossfade
+ // from.
+ if (getCrossFadeIn() > 0) {
+ if (isImmediate()) {
+ // Queue will be cleared so fade in from current.
+ if (currentBlock != null) {
+ setupCrossFade(currentBlock, frameIndex, this);
+ }
+ // else nothing is playing so don't crossfade.
+ } else {
+ QueueDataCommand endBlock = getEndBlock();
+ if (endBlock != null) {
+ setupCrossFade(endBlock,
+ endBlock.getStartFrame() + endBlock.getNumFrames(), this);
+ }
+ }
+ }
+
+ if (isImmediate()) {
+ clearQueue();
+ }
+
+ blocks.add(this);
+ }
+ }
+ }
+
+ // FIXME - determine crossfade on any transition between blocks or when looping back.
+
+ protected void setupCrossFade(QueueDataCommand sourceCommand, int sourceStartIndex,
+ QueueDataCommand targetCommand) {
+ int crossFrames = targetCommand.getCrossFadeIn();
+ SequentialData sourceData = sourceCommand.getCurrentData();
+ SequentialData targetData = targetCommand.getCurrentData();
+ int remainingSource = sourceData.getNumFrames() - sourceStartIndex;
+ // clip to end of source
+ if (crossFrames > remainingSource)
+ crossFrames = remainingSource;
+ if (crossFrames > 0) {
+ // The SequentialDataCrossfade should continue to the end of the target
+ // so that we can crossfade from it to the target.
+ int remainingTarget = targetData.getNumFrames() - targetCommand.getStartFrame();
+ targetCommand.crossfadeData.setup(sourceData, sourceStartIndex, crossFrames,
+ targetData, targetCommand.getStartFrame(), remainingTarget);
+ targetCommand.currentData = targetCommand.crossfadeData;
+ targetCommand.startFrame = 0;
+ }
+ }
+
+ public QueueDataCommand createQueueDataCommand(SequentialData queueableData) {
+ return createQueueDataCommand(queueableData, 0, queueableData.getNumFrames());
+ }
+
+ public QueueDataCommand createQueueDataCommand(SequentialData queueableData, int startFrame,
+ int numFrames) {
+ if (queueableData.getChannelsPerFrame() != UnitDataQueuePort.this.numChannels) {
+ throw new ChannelMismatchException("Tried to queue "
+ + queueableData.getChannelsPerFrame() + " channel data to a " + numChannels
+ + " channel port.");
+ }
+ return new QueuedBlock(queueableData, startFrame, numFrames);
+ }
+
+ public QueueDataCommand getEndBlock() {
+ if (blocks.size() > 0) {
+ return blocks.getLast();
+ } else if (currentBlock != null) {
+ return currentBlock;
+ } else {
+ return null;
+ }
+ }
+
+ public void setCurrentBlock(QueueDataCommand currentBlock) {
+ this.currentBlock = currentBlock;
+ }
+
+ public void firePendingCallbacks() {
+ if (loopingBlock != null) {
+ if (loopingBlock.getCallback() != null) {
+ loopingBlock.getCallback().looped(currentBlock);
+ }
+ loopingBlock = null;
+ }
+ if (finishingBlock != null) {
+ if (finishingBlock.getCallback() != null) {
+ finishingBlock.getCallback().finished(currentBlock); // FIXME - Should this pass
+ // finishingBlock?!
+ }
+ finishingBlock = null;
+ }
+ }
+
+ public boolean hasMore() {
+ return (currentBlock != null) || (blocks.size() > 0);
+ }
+
+ private void checkBlock() {
+ if (currentBlock == null) {
+ synchronized (blocks) {
+ setCurrentBlock(blocks.remove());
+ frameIndex = currentBlock.getStartFrame();
+ currentBlock.loopsLeft = currentBlock.getNumLoops();
+ if (currentBlock.getCallback() != null) {
+ currentBlock.getCallback().started(currentBlock);
+ }
+ }
+ }
+ }
+
+ private void advanceFrameIndex() {
+ frameIndex += 1;
+ framesMoved += 1;
+ // Are we done with this block?
+ if (frameIndex >= (currentBlock.getStartFrame() + currentBlock.getNumFrames())) {
+ // Should we loop on this block based on a counter?
+ if (currentBlock.loopsLeft > 0) {
+ currentBlock.loopsLeft -= 1;
+ loopToStart();
+ }
+ // Should we loop forever on this block?
+ else if ((blocks.size() == 0) && (currentBlock.loopsLeft < 0)) {
+ loopToStart();
+ }
+ // We are done.
+ else {
+ if (currentBlock.isAutoStop()) {
+ autoStopPending = true;
+ }
+ finishingBlock = currentBlock;
+ setCurrentBlock(null);
+ // LOGGER.debug("advanceFrameIndex: currentBlock set null");
+ }
+ }
+ }
+
+ private void loopToStart() {
+ if (currentBlock.getCrossFadeIn() > 0) {
+ setupCrossFade(currentBlock, frameIndex, currentBlock);
+ }
+ frameIndex = currentBlock.getStartFrame();
+ loopingBlock = currentBlock;
+ }
+
+ public double getNormalizedRate() {
+ return normalizedRate;
+ }
+
+ public double readCurrentChannelDouble(int channelIndex) {
+ return currentBlock.currentData.readDouble((frameIndex * numChannels) + channelIndex);
+ }
+
+ public void writeCurrentChannelDouble(int channelIndex, double value) {
+ currentBlock.currentData.writeDouble((frameIndex * numChannels) + channelIndex, value);
+ }
+
+ public void beginFrame(double synthesisPeriod) {
+ checkBlock();
+ normalizedRate = currentBlock.currentData.getRateScaler(frameIndex, synthesisPeriod);
+ }
+
+ public void endFrame() {
+ advanceFrameIndex();
+ targetValid = true;
+ }
+
+ public double readNextMonoDouble(double synthesisPeriod) {
+ beginFrame(synthesisPeriod);
+ double value = currentBlock.currentData.readDouble(frameIndex);
+ endFrame();
+ return value;
+ }
+
+ /** Write directly to the port queue. This is only called by unit tests! */
+ protected void addQueuedBlock(QueueDataEvent block) {
+ blocks.add((QueuedBlock) block);
+ }
+
+ /** Clear the queue. Internal use only. */
+ protected void clearQueue() {
+ synchronized (blocks) {
+ blocks.clear();
+ setCurrentBlock(null);
+ targetValid = false;
+ autoStopPending = false;
+ }
+ }
+
+ class ClearQueueCommand implements ScheduledCommand {
+ @Override
+ public void run() {
+ clearQueue();
+ }
+ }
+
+ /** Queue the data to the port at a future time. */
+ public void queue(SequentialData queueableData, int startFrame, int numFrames,
+ TimeStamp timeStamp) {
+ QueueDataCommand command = createQueueDataCommand(queueableData, startFrame, numFrames);
+ scheduleCommand(timeStamp, command);
+ }
+
+ /**
+ * Queue the data to the port at a future time. Command will clear the queue before executing.
+ */
+ public void queueImmediate(SequentialData queueableData, int startFrame, int numFrames,
+ TimeStamp timeStamp) {
+ QueueDataCommand command = createQueueDataCommand(queueableData, startFrame, numFrames);
+ command.setImmediate(true);
+ scheduleCommand(timeStamp, command);
+ }
+
+ /** Queue the data to the port at a future time. */
+ public void queueLoop(SequentialData queueableData, int startFrame, int numFrames,
+ TimeStamp timeStamp) {
+ queueLoop(queueableData, startFrame, numFrames, LOOP_IF_LAST, timeStamp);
+ }
+
+ /**
+ * Queue the data to the port at a future time with a specified number of loops.
+ */
+ public void queueLoop(SequentialData queueableData, int startFrame, int numFrames,
+ int numLoops, TimeStamp timeStamp) {
+ QueueDataCommand command = createQueueDataCommand(queueableData, startFrame, numFrames);
+ command.setNumLoops(numLoops);
+ scheduleCommand(timeStamp, command);
+ }
+
+ /** Queue the entire data object for looping. */
+ public void queueLoop(SequentialData queueableData) {
+ queueLoop(queueableData, 0, queueableData.getNumFrames());
+ }
+
+ /** Queue the data to the port for immediate use. */
+ public void queueLoop(SequentialData queueableData, int startFrame, int numFrames) {
+ queueLoop(queueableData, startFrame, numFrames, LOOP_IF_LAST);
+ }
+
+ /**
+ * Queue the data to the port for immediate use with a specified number of loops.
+ */
+ public void queueLoop(SequentialData queueableData, int startFrame, int numFrames, int numLoops) {
+ QueueDataCommand command = createQueueDataCommand(queueableData, startFrame, numFrames);
+ command.setNumLoops(numLoops);
+ queueCommand(command);
+ }
+
+ /**
+ * Queue the data to the port at a future time. Request that the unit stop when this block is
+ * finished.
+ */
+ public void queueStop(SequentialData queueableData, int startFrame, int numFrames,
+ TimeStamp timeStamp) {
+ QueueDataCommand command = createQueueDataCommand(queueableData, startFrame, numFrames);
+ command.setAutoStop(true);
+ scheduleCommand(timeStamp, command);
+ }
+
+ /** Queue the data to the port through the command queue ASAP. */
+ public void queue(SequentialData queueableData, int startFrame, int numFrames) {
+ QueueDataCommand command = createQueueDataCommand(queueableData, startFrame, numFrames);
+ queueCommand(command);
+ }
+
+ /**
+ * Queue entire amount of data with no options.
+ *
+ * @param queueableData
+ */
+ public void queue(SequentialData queueableData) {
+ queue(queueableData, 0, queueableData.getNumFrames());
+ }
+
+ /** Schedule queueOn now! */
+ public void queueOn(SequentialData queueableData) {
+ queueOn(queueableData, getSynthesisEngine().createTimeStamp());
+ }
+
+ /** Schedule queueOff now! */
+ public void queueOff(SequentialData queueableData) {
+ queueOff(queueableData, false);
+ }
+
+ /** Schedule queueOff now! */
+ public void queueOff(SequentialData queueableData, boolean ifStop) {
+ queueOff(queueableData, ifStop, getSynthesisEngine().createTimeStamp());
+ }
+
+ /**
+ * Convenience method that will queue the attack portion of a channelData and the sustain loop
+ * if it exists. This could be used to implement a NoteOn method.
+ */
+ public void queueOn(SequentialData queueableData, TimeStamp timeStamp) {
+
+ if (queueableData.getSustainBegin() < 0) {
+ // no sustain loop, handle release
+ if (queueableData.getReleaseBegin() < 0) {
+ // No loops
+ queueImmediate(queueableData, 0, queueableData.getNumFrames(), timeStamp);
+ } else {
+ queueImmediate(queueableData, 0, queueableData.getReleaseEnd(), timeStamp);
+ int size = queueableData.getReleaseEnd() - queueableData.getReleaseBegin();
+ queueLoop(queueableData, queueableData.getReleaseBegin(), size, timeStamp);
+ }
+ } else {
+ // yes sustain loop
+ if (queueableData.getSustainEnd() > 0) {
+ int frontSize = queueableData.getSustainBegin();
+ int loopSize = queueableData.getSustainEnd() - queueableData.getSustainBegin();
+ // Is there an initial portion before the sustain loop?
+ if (frontSize > 0) {
+ queueImmediate(queueableData, 0, frontSize, timeStamp);
+ }
+ loopSize = queueableData.getSustainEnd() - queueableData.getSustainBegin();
+ if (loopSize > 0) {
+ queueLoop(queueableData, queueableData.getSustainBegin(), loopSize, timeStamp);
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Convenience method that will queue the decay portion of a SequentialData object, or the gap
+ * and release loop portions if they exist. This could be used to implement a NoteOff method.
+ *
+ * @param ifStop Will setAutostop(true) if release portion queued without a release loop. This will
+ * stop execution of the unit.
+ */
+ public void queueOff(SequentialData queueableData, boolean ifStop, TimeStamp timeStamp) {
+ if (queueableData.getSustainBegin() >= 0) /* Sustain loop? */
+ {
+ int relSize = queueableData.getReleaseEnd() - queueableData.getReleaseBegin();
+ if (queueableData.getReleaseBegin() < 0) { /* Sustain loop, no release loop. */
+ int susEnd = queueableData.getSustainEnd();
+ int size = queueableData.getNumFrames() - susEnd;
+ // LOGGER.debug("queueOff: size = " + size );
+ if (size <= 0) {
+ // always queue something so that we can stop the loop
+ // 20001117
+ size = 1;
+ susEnd = queueableData.getNumFrames() - 1;
+ }
+ if (ifStop) {
+ queueStop(queueableData, susEnd, size, timeStamp);
+ } else {
+ queue(queueableData, susEnd, size, timeStamp);
+ }
+ } else if (queueableData.getReleaseBegin() > queueableData.getSustainEnd()) {
+ // Queue gap between sustain and release loop.
+ queue(queueableData, queueableData.getSustainEnd(), queueableData.getReleaseEnd()
+ - queueableData.getSustainEnd(), timeStamp);
+ if (relSize > 0)
+ queueLoop(queueableData, queueableData.getReleaseBegin(), relSize, timeStamp);
+ } else if (relSize > 0) {
+ // No gap between sustain and release.
+ queueLoop(queueableData, queueableData.getReleaseBegin(), relSize, timeStamp);
+ }
+ }
+ /* If no sustain loop, then nothing to do. */
+ }
+
+ public void clear(TimeStamp timeStamp) {
+ ScheduledCommand command = new ClearQueueCommand();
+ scheduleCommand(timeStamp, command);
+ }
+
+ public void clear() {
+ ScheduledCommand command = new ClearQueueCommand();
+ queueCommand(command);
+ }
+
+ public void writeNextDouble(double value) {
+ checkBlock();
+ currentBlock.currentData.writeDouble(frameIndex, value);
+ advanceFrameIndex();
+ }
+
+ public long getFrameCount() {
+ return framesMoved;
+ }
+
+ public boolean testAndClearAutoStop() {
+ boolean temp = autoStopPending;
+ autoStopPending = false;
+ return temp;
+ }
+
+ public boolean isTargetValid() {
+ return targetValid;
+ }
+
+ public void setNumChannels(int numChannels) {
+ this.numChannels = numChannels;
+ }
+
+ public int getNumChannels() {
+ return numChannels;
+ }
+}