diff options
Diffstat (limited to 'src/main/java/com/jsyn/ports/UnitDataQueuePort.java')
-rw-r--r-- | src/main/java/com/jsyn/ports/UnitDataQueuePort.java | 466 |
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; + } +} |