diff options
Diffstat (limited to 'src/main/java/com/jsyn/ports')
21 files changed, 2311 insertions, 0 deletions
diff --git a/src/main/java/com/jsyn/ports/ConnectableInput.java b/src/main/java/com/jsyn/ports/ConnectableInput.java new file mode 100644 index 0000000..3dae876 --- /dev/null +++ b/src/main/java/com/jsyn/ports/ConnectableInput.java @@ -0,0 +1,38 @@ +/* + * Copyright 2011 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; + +/** + * This interface lets you pass either an input port, or a single part of an input port. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public interface ConnectableInput { + public void connect(ConnectableOutput other); + + public void disconnect(ConnectableOutput other); + + /** + * This is used internally by PortBlockPart to make a connection between specific parts of a + * port. + * + * @return + */ + public PortBlockPart getPortBlockPart(); + + public void pullData(long frameCount, int start, int limit); +} diff --git a/src/main/java/com/jsyn/ports/ConnectableOutput.java b/src/main/java/com/jsyn/ports/ConnectableOutput.java new file mode 100644 index 0000000..f42a799 --- /dev/null +++ b/src/main/java/com/jsyn/ports/ConnectableOutput.java @@ -0,0 +1,23 @@ +/* + * 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; + +public interface ConnectableOutput { + public void connect(ConnectableInput other); + + public void disconnect(ConnectableInput other); +} diff --git a/src/main/java/com/jsyn/ports/GettablePort.java b/src/main/java/com/jsyn/ports/GettablePort.java new file mode 100644 index 0000000..aabf5ca --- /dev/null +++ b/src/main/java/com/jsyn/ports/GettablePort.java @@ -0,0 +1,27 @@ +/* + * 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; + +public interface GettablePort { + String getName(); + + int getNumParts(); + + double getValue(int partNum); + + Object getUnitGenerator(); +} diff --git a/src/main/java/com/jsyn/ports/InputMixingBlockPart.java b/src/main/java/com/jsyn/ports/InputMixingBlockPart.java new file mode 100644 index 0000000..2d28888 --- /dev/null +++ b/src/main/java/com/jsyn/ports/InputMixingBlockPart.java @@ -0,0 +1,112 @@ +/* + * 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.io.PrintStream; + +import com.jsyn.Synthesizer; +import com.jsyn.unitgen.UnitGenerator; + +/** + * A UnitInputPort has an array of these, one for each part. + * + * @author Phil Burk 2009 Mobileer Inc + */ + +public class InputMixingBlockPart extends PortBlockPart { + private double[] mixer = new double[Synthesizer.FRAMES_PER_BLOCK]; + private double current; + private UnitInputPort unitInputPort; + + InputMixingBlockPart(UnitInputPort unitInputPort, double defaultValue) { + super(unitInputPort, defaultValue); + this.unitInputPort = unitInputPort; + } + + @Override + public double getValue() { + return current; + } + + @Override + protected void setValue(double value) { + current = value; + super.setValue(value); + } + + @Override + public double[] getValues() { + double[] result; + int numConnections = getConnectionCount(); + // LOGGER.debug("numConnection = " + numConnections + " for " + + // this ); + if (numConnections == 0) { + // No connection so just use our own data. + result = super.getValues(); + } else { + // Mix all of the connected ports. + double[] inputs; + int jCon = 0; + PortBlockPart otherPart; + // Choose value to initialize the mixer array. + if (unitInputPort.isValueAdded()) { + inputs = super.getValues(); // prime mixer with the set() values + jCon = 0; + } else { + otherPart = getConnection(jCon); + inputs = otherPart.getValues(); // prime mixer with first connected + jCon = 1; + } + for (int i = 0; i < mixer.length; i++) { + mixer[i] = inputs[i]; + } + // Now mix in the remaining inputs. + for (; jCon < numConnections; jCon++) { + otherPart = getConnection(jCon); + inputs = otherPart.getValues(); + for (int i = 0; i < mixer.length; i++) { + mixer[i] += inputs[i]; + } + } + result = mixer; + } + current = result[0]; + return result; + } + + private void printIndentation(PrintStream out, int level) { + for (int i = 0; i < level; i++) { + out.print(" "); + } + } + + private String portToString(UnitBlockPort port) { + UnitGenerator ugen = port.getUnitGenerator(); + return ugen.getClass().getSimpleName() + "." + port.getName(); + } + + public void printConnections(PrintStream out, int level) { + for (int i = 0; i < getConnectionCount(); i++) { + PortBlockPart part = getConnection(i); + + printIndentation(out, level); + out.println(portToString(getPort()) + " <--- " + portToString(part.getPort())); + + part.getPort().getUnitGenerator().printConnections(out, level + 1); + } + } +} diff --git a/src/main/java/com/jsyn/ports/PortBlockPart.java b/src/main/java/com/jsyn/ports/PortBlockPart.java new file mode 100644 index 0000000..b1ced32 --- /dev/null +++ b/src/main/java/com/jsyn/ports/PortBlockPart.java @@ -0,0 +1,210 @@ +/* + * 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.ArrayList; + +import com.jsyn.Synthesizer; +import com.jsyn.engine.SynthesisEngine; +import com.softsynth.shared.time.ScheduledCommand; +import com.softsynth.shared.time.TimeStamp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Part of a multi-part port, for example, the left side of a stereo port. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class PortBlockPart implements ConnectableOutput, ConnectableInput { + + private static final Logger LOGGER = LoggerFactory.getLogger(PortBlockPart.class); + + private double[] values = new double[Synthesizer.FRAMES_PER_BLOCK]; + private ArrayList<PortBlockPart> connections = new ArrayList<PortBlockPart>(); + private UnitBlockPort unitBlockPort; + + protected PortBlockPart(UnitBlockPort unitBlockPort, double defaultValue) { + this.unitBlockPort = unitBlockPort; + setValue(defaultValue); + } + + public double[] getValues() { + return values; + } + + public double getValue() { + return values[0]; + } + + public double get() { + return values[0]; + } + + protected void setValue(double value) { + for (int i = 0; i < values.length; i++) { + values[i] = value; + } + } + + protected boolean isConnected() { + return (connections.size() > 0); + } + + private void addConnection(PortBlockPart otherPart) { + // LOGGER.debug("addConnection from " + this + " to " + otherPart + // ); + if (connections.contains(otherPart)) { + LOGGER.debug("addConnection already had connection from " + this + " to " + + otherPart); + } else { + connections.add(otherPart); + } + } + + private void removeConnection(PortBlockPart otherPart) { + // LOGGER.debug("removeConnection from " + this + " to " + + // otherPart ); + connections.remove(otherPart); + } + + private void connectNow(PortBlockPart otherPart) { + addConnection(otherPart); + otherPart.addConnection(this); + } + + private void disconnectNow(PortBlockPart otherPart) { + removeConnection(otherPart); + otherPart.removeConnection(this); + } + + private void disconnectAllNow() { + for (PortBlockPart part : connections) { + part.removeConnection(this); + } + connections.clear(); + } + + public PortBlockPart getConnection(int i) { + return connections.get(i); + } + + public int getConnectionCount() { + return connections.size(); + } + + /** Set all values to the last value. */ + protected void flatten() { + double lastValue = values[values.length - 1]; + for (int i = 0; i < values.length - 1; i++) { + values[i] = lastValue; + } + } + + protected UnitBlockPort getPort() { + return unitBlockPort; + } + + private void checkConnection(PortBlockPart destination) { + SynthesisEngine sourceSynth = unitBlockPort.getSynthesisEngine(); + SynthesisEngine destSynth = destination.unitBlockPort.getSynthesisEngine(); + if ((sourceSynth != destSynth) && (sourceSynth != null) && (destSynth != null)) { + throw new RuntimeException("Connection between units on different synths."); + } + } + + protected void connect(final PortBlockPart destination) { + checkConnection(destination); + unitBlockPort.queueCommand(new ScheduledCommand() { + @Override + public void run() { + connectNow(destination); + } + }); + } + + protected void connect(final PortBlockPart destination, TimeStamp timeStamp) { + unitBlockPort.scheduleCommand(timeStamp, new ScheduledCommand() { + @Override + public void run() { + connectNow(destination); + } + }); + } + + protected void disconnect(final PortBlockPart destination) { + unitBlockPort.queueCommand(new ScheduledCommand() { + @Override + public void run() { + disconnectNow(destination); + } + }); + } + + protected void disconnect(final PortBlockPart destination, TimeStamp timeStamp) { + unitBlockPort.scheduleCommand(timeStamp, new ScheduledCommand() { + @Override + public void run() { + disconnectNow(destination); + } + }); + } + + protected void disconnectAll() { + unitBlockPort.queueCommand(new ScheduledCommand() { + @Override + public void run() { + disconnectAllNow(); + } + }); + } + + @Override + public void connect(ConnectableInput other) { + connect(other.getPortBlockPart()); + } + + @Override + public void connect(ConnectableOutput other) { + other.connect(this); + } + + @Override + public void disconnect(ConnectableOutput other) { + other.disconnect(this); + } + + @Override + public void disconnect(ConnectableInput other) { + disconnect(other.getPortBlockPart()); + } + + /** To implement ConnectableInput */ + @Override + public PortBlockPart getPortBlockPart() { + return this; + } + + @Override + public void pullData(long frameCount, int start, int limit) { + for (int i = 0; i < getConnectionCount(); i++) { + PortBlockPart part = getConnection(i); + part.getPort().getUnitGenerator().pullData(frameCount, start, limit); + } + } + +} diff --git a/src/main/java/com/jsyn/ports/QueueDataCommand.java b/src/main/java/com/jsyn/ports/QueueDataCommand.java new file mode 100644 index 0000000..0ef36e2 --- /dev/null +++ b/src/main/java/com/jsyn/ports/QueueDataCommand.java @@ -0,0 +1,170 @@ +/* + * 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 com.jsyn.data.SequentialData; +import com.softsynth.shared.time.ScheduledCommand; + +/** + * A command that can be used to queue SequentialData to a UnitDataQueuePort. Here is an example of + * queuing data with a callback using this command. + * + * <pre> + * <code> + * // Queue an envelope with a completion callback. + * QueueDataCommand command = envelopePlayer.dataQueue.createQueueDataCommand( envelope, 0, + * envelope.getNumFrames() ); + * // Create an object to be called when the queued data is done. + * TestQueueCallback callback = new TestQueueCallback(); + * command.setCallback( callback ); + * command.setNumLoops( 2 ); + * envelopePlayer.rate.set( 0.2 ); + * synth.queueCommand( command ); + * </code> + * </pre> + * + * The callback will be passed QueueDataEvents. + * + * <pre> + * <code> + * class TestQueueCallback implements UnitDataQueueCallback + * { + * public void started( QueueDataEvent event ) + * { + * LOGGER.debug("CALLBACK: Envelope started."); + * } + * + * public void looped( QueueDataEvent event ) + * { + * LOGGER.debug("CALLBACK: Envelope looped."); + * } + * + * public void finished( QueueDataEvent event ) + * { + * LOGGER.debug("CALLBACK: Envelope finished."); + * } + * } + * </code> + * </pre> + * + * @author Phil Burk 2009 Mobileer Inc + */ +public abstract class QueueDataCommand extends QueueDataEvent implements ScheduledCommand { + + protected SequentialDataCrossfade crossfadeData; + protected SequentialData currentData; + + private static final long serialVersionUID = -1185274459972359536L; + private UnitDataQueueCallback callback; + + public QueueDataCommand(UnitDataQueuePort port, SequentialData sequentialData, int startFrame, + int numFrames) { + super(port); + + if ((startFrame + numFrames) > sequentialData.getNumFrames()) { + throw new IllegalArgumentException("tried to queue past end of data, " + (startFrame + numFrames)); + } else if (startFrame < 0) { + throw new IllegalArgumentException("tried to queue before start of data, " + startFrame); + } + this.sequentialData = sequentialData; + this.currentData = sequentialData; + crossfadeData = new SequentialDataCrossfade(); + this.startFrame = startFrame; + this.numFrames = numFrames; + } + + @Override + public abstract void run(); + + /** + * If true then this item will be skipped if other items are queued after it. This flag allows + * you to queue lots of small pieces of sound without making the queue very long. + * + * @param skipIfOthers + */ + public void setSkipIfOthers(boolean skipIfOthers) { + this.skipIfOthers = skipIfOthers; + } + + /** + * If true then the queue will be cleared and this item will be started immediately. It is + * better to use this flag than to clear the queue from the application because there could be a + * gap before the next item is available. This is most useful when combined with + * setCrossFadeIn(). + * + * @param immediate + */ + public void setImmediate(boolean immediate) { + this.immediate = immediate; + } + + public UnitDataQueueCallback getCallback() { + return callback; + } + + public void setCallback(UnitDataQueueCallback callback) { + this.callback = callback; + } + + public SequentialDataCrossfade getCrossfadeData() { + return crossfadeData; + } + + public void setCrossfadeData(SequentialDataCrossfade crossfadeData) { + this.crossfadeData = crossfadeData; + } + + public SequentialData getCurrentData() { + return currentData; + } + + public void setCurrentData(SequentialData currentData) { + this.currentData = currentData; + } + + /** + * Stop the unit that contains this port after this command has finished. + * + * @param autoStop + */ + public void setAutoStop(boolean autoStop) { + this.autoStop = autoStop; + } + + /** + * Set how many time the block should be repeated after the first time. For example, if you set + * numLoops to zero the block will only be played once. If you set numLoops to one the block + * will be played twice. + * + * @param numLoops number of times to loop back + */ + public void setNumLoops(int numLoops) { + this.numLoops = numLoops; + } + + /** + * Number of frames to cross fade from the previous block to this block. This can be used to + * avoid pops when making abrupt transitions. There must be frames available after the end of + * the previous block to use for crossfading. The crossfade is linear. + * + * @param size + */ + public void setCrossFadeIn(int size) { + this.crossFadeIn = size; + } + +} diff --git a/src/main/java/com/jsyn/ports/QueueDataEvent.java b/src/main/java/com/jsyn/ports/QueueDataEvent.java new file mode 100644 index 0000000..2b93fab --- /dev/null +++ b/src/main/java/com/jsyn/ports/QueueDataEvent.java @@ -0,0 +1,80 @@ +/* + * 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.EventObject; + +import com.jsyn.data.SequentialData; + +/** + * An event that is passed to a UnitDataQueueCallback when the element in the queue is played.. + * + * @author Phil Burk 2009 Mobileer Inc + */ +public class QueueDataEvent extends EventObject { + private static final long serialVersionUID = 176846633064538053L; + protected SequentialData sequentialData; + protected int startFrame; + protected int numFrames; + protected int numLoops; + protected int loopsLeft; + protected int crossFadeIn; + protected boolean skipIfOthers; + protected boolean autoStop; + protected boolean immediate; + + public QueueDataEvent(Object arg0) { + super(arg0); + } + + public boolean isSkipIfOthers() { + return skipIfOthers; + } + + public boolean isImmediate() { + return immediate; + } + + public SequentialData getSequentialData() { + return sequentialData; + } + + public int getCrossFadeIn() { + return crossFadeIn; + } + + public int getStartFrame() { + return startFrame; + } + + public int getNumFrames() { + return numFrames; + } + + public int getNumLoops() { + return numLoops; + } + + public int getLoopsLeft() { + return loopsLeft; + } + + public boolean isAutoStop() { + return autoStop; + } + +} diff --git a/src/main/java/com/jsyn/ports/SequentialDataCrossfade.java b/src/main/java/com/jsyn/ports/SequentialDataCrossfade.java new file mode 100644 index 0000000..0c3d3b2 --- /dev/null +++ b/src/main/java/com/jsyn/ports/SequentialDataCrossfade.java @@ -0,0 +1,139 @@ +/* + * 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 com.jsyn.data.SequentialData; +import com.jsyn.data.SequentialDataCommon; + +/** + * A SequentialData object that will crossfade between two other SequentialData objects. The + * crossfade is linear. This could, for example, be used to create a smooth transition between two + * samples, or between two arbitrary regions in one sample. As an example, consider a sample that + * has a length of 200000 frames. You could specify a sample loop that started arbitrarily at frame + * 50000 and with a size of 30000 frames. Unless you got lucky with the zero crossings, it is likely + * that you will hear a pop when this sample loops. To prevent the pop you could crossfade the + * beginning of the loop with the region immediately after the end of the loop. To crossfade with + * 5000 samples after the loop: + * + * <pre> + * SequentialDataCrossfade xfade = new SequentialDataCrossfade(sample, (50000 + 30000), 5000, sample, + * 50000, 30000); + * </pre> + * + * After the crossfade you will hear the rest of the target at full volume. There are two regions + * that determine what is returned from readDouble() + * <ol> + * <li>Crossfade region with size crossFadeFrames. It fades smoothly from source to target.</li> + * <li>Steady region that is simply the target values with size (numFrames-crossFadeFrames).</li> + * </ol> + * + * <pre> + * "Crossfade Region" "Steady Region" + * |-- source fading out --| + * |-- target fading in --|-- remainder of target at original volume --| + * </pre> + * + * @author Phil Burk + */ +class SequentialDataCrossfade extends SequentialDataCommon { + private SequentialData source; + private int sourceStartIndex; + + private SequentialData target; + private int targetStartIndex; + + private int crossFadeFrames; + private double frameScaler; + + /** + * @param source SequentialData that will be at full volume at the beginning of the crossfade + * region. + * @param sourceStartFrame Frame in source to begin the crossfade. + * @param crossFadeFrames Number of frames in the crossfaded region. + * @param target SequentialData that will be at full volume at the end of the crossfade region. + * @param targetStartFrame Frame in target to begin the crossfade. + * @param numFrames total number of frames in this data object. + */ + public void setup(SequentialData source, int sourceStartFrame, int crossFadeFrames, + SequentialData target, int targetStartFrame, int numFrames) { + + assert ((sourceStartFrame + crossFadeFrames) <= source.getNumFrames()); + assert ((targetStartFrame + numFrames) <= target.getNumFrames()); + + // LOGGER.debug( "WARNING! sourceStartFrame = " + sourceStartFrame + // + ", crossFadeFrames = " + crossFadeFrames + ", maxFrame = " + // + source.getNumFrames() + ", source = " + source ); + // LOGGER.debug( " targetStartFrame = " + targetStartFrame + // + ", numFrames = " + numFrames + ", maxFrame = " + // + target.getNumFrames() + ", target = " + target ); + + // There is a danger that we might nest SequentialDataCrossfades deeply + // as source. If past crossfade region then pull out the target. + if (source instanceof SequentialDataCrossfade) { + SequentialDataCrossfade crossfade = (SequentialDataCrossfade) source; + // If we are starting past the crossfade region then just use the + // target. + if (sourceStartFrame >= crossfade.crossFadeFrames) { + source = crossfade.target; + sourceStartFrame += crossfade.targetStartIndex / source.getChannelsPerFrame(); + } + } + + if (target instanceof SequentialDataCrossfade) { + SequentialDataCrossfade crossfade = (SequentialDataCrossfade) target; + target = crossfade.target; + targetStartFrame += crossfade.targetStartIndex / target.getChannelsPerFrame(); + } + + this.source = source; + this.target = target; + this.sourceStartIndex = sourceStartFrame * source.getChannelsPerFrame(); + this.crossFadeFrames = crossFadeFrames; + this.targetStartIndex = targetStartFrame * target.getChannelsPerFrame(); + + frameScaler = (crossFadeFrames == 0) ? 1.0 : (1.0 / crossFadeFrames); + this.numFrames = numFrames; + } + + @Override + public void writeDouble(int index, double value) { + } + + @Override + public double readDouble(int index) { + int frame = index / source.getChannelsPerFrame(); + if (frame < crossFadeFrames) { + double factor = frame * frameScaler; + double value = (1.0 - factor) * source.readDouble(index + sourceStartIndex); + value += (factor * target.readDouble(index + targetStartIndex)); + return value; + } else { + return target.readDouble(index + targetStartIndex); + } + } + + @Override + public double getRateScaler(int index, double synthesisRate) { + return target.getRateScaler(index, synthesisRate); + } + + @Override + public int getChannelsPerFrame() { + return target.getChannelsPerFrame(); + } + +} diff --git a/src/main/java/com/jsyn/ports/SettablePort.java b/src/main/java/com/jsyn/ports/SettablePort.java new file mode 100644 index 0000000..e0db05c --- /dev/null +++ b/src/main/java/com/jsyn/ports/SettablePort.java @@ -0,0 +1,28 @@ +/* + * 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 com.softsynth.shared.time.TimeStamp; + +/** + * Port whose parts can be set. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public interface SettablePort extends GettablePort { + void set(int partNum, double value, TimeStamp timeStamp); +} diff --git a/src/main/java/com/jsyn/ports/UnitBlockPort.java b/src/main/java/com/jsyn/ports/UnitBlockPort.java new file mode 100644 index 0000000..d7fc82f --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitBlockPort.java @@ -0,0 +1,110 @@ +/* + * 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; + +/** + * A port that contains multiple parts with blocks of data. + * + * @author Phil Burk 2009 Mobileer Inc + */ +public class UnitBlockPort extends UnitPort { + PortBlockPart[] parts; + + public UnitBlockPort(int numParts, String name, double defaultValue) { + super(name); + makeParts(numParts, defaultValue); + } + + public UnitBlockPort(String name) { + this(1, name, 0.0); + } + + protected void makeParts(int numParts, double defaultValue) { + parts = new PortBlockPart[numParts]; + for (int i = 0; i < numParts; i++) { + parts[i] = new PortBlockPart(this, defaultValue); + } + } + + @Override + public int getNumParts() { + return parts.length; + } + + /** + * Convenience call to get(0). + * + * @return value of 0th part as set + */ + public double get() { + return get(0); + } + + public double getValue() { + return getValue(0); + } + + /** + * This is used inside UnitGenerators to get the current values for a port. It works regardless + * of whether the port is connected or not. + * + * @return + */ + public double[] getValues() { + return parts[0].getValues(); + } + + /** Only for use in the audio thread when implementing UnitGenerators. */ + public double[] getValues(int partNum) { + return parts[partNum].getValues(); + } + + /** Get the immediate current value of the port. */ + public double getValue(int partNum) { + return parts[partNum].getValue(); + } + + public double get(int partNum) { + return parts[partNum].get(); + } + + /** Only for use in the audio thread when implementing UnitGenerators. */ + protected void setValueInternal(int partNum, double value) { + parts[partNum].setValue(value); + } + + /** Only for use in the audio thread when implementing UnitGenerators. */ + public void setValueInternal(double value) { + setValueInternal(0, value); + } + + public boolean isConnected() { + return isConnected(0); + } + + public boolean isConnected(int partNum) { + return parts[partNum].isConnected(); + } + + public void disconnectAll(int partNum) { + parts[partNum].disconnectAll(); + } + + public void disconnectAll() { + disconnectAll(0); + } +} diff --git a/src/main/java/com/jsyn/ports/UnitDataQueueCallback.java b/src/main/java/com/jsyn/ports/UnitDataQueueCallback.java new file mode 100644 index 0000000..dca4adc --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitDataQueueCallback.java @@ -0,0 +1,31 @@ +/* + * Copyright 2011 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; + +/** + * This is called when a block of data that is queued to a UnitDataQueuePort starts, loops, or + * finishes. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public interface UnitDataQueueCallback { + public void started(QueueDataEvent event); + + public void looped(QueueDataEvent event); + + public void finished(QueueDataEvent event); +} 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; + } +} diff --git a/src/main/java/com/jsyn/ports/UnitFunctionPort.java b/src/main/java/com/jsyn/ports/UnitFunctionPort.java new file mode 100644 index 0000000..e45241a --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitFunctionPort.java @@ -0,0 +1,48 @@ +/* + * 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 com.jsyn.data.Function; + +/** + * Port for holding a Function object. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class UnitFunctionPort extends UnitPort { + private static NullFunction nullFunction = new NullFunction(); + private Function function = nullFunction; + + private static class NullFunction implements Function { + @Override + public double evaluate(double input) { + return 0.0; + } + } + + public UnitFunctionPort(String name) { + super(name); + } + + public void set(Function function) { + this.function = function; + } + + public Function get() { + return function; + } +} diff --git a/src/main/java/com/jsyn/ports/UnitGatePort.java b/src/main/java/com/jsyn/ports/UnitGatePort.java new file mode 100644 index 0000000..700aef8 --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitGatePort.java @@ -0,0 +1,158 @@ +/* + * 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 com.jsyn.unitgen.UnitGenerator; +import com.softsynth.shared.time.ScheduledCommand; +import com.softsynth.shared.time.TimeStamp; + +public class UnitGatePort extends UnitInputPort { + private boolean autoDisableEnabled = false; + private boolean triggered = false; + private boolean off = true; + private UnitGenerator gatedUnit; + public static final double THRESHOLD = 0.01; + + public UnitGatePort(String name) { + super(name); + } + + public void on() { + setOn(true); + } + + public void off() { + setOn(false); + } + + public void off(TimeStamp timeStamp) { + setOn(false, timeStamp); + } + + public void on(TimeStamp timeStamp) { + setOn(true, timeStamp); + } + + private void setOn(final boolean on) { + queueCommand(new ScheduledCommand() { + @Override + public void run() { + setOnInternal(on); + } + }); + } + + private void setOn(final boolean on, TimeStamp timeStamp) { + scheduleCommand(timeStamp, new ScheduledCommand() { + @Override + public void run() { + setOnInternal(on); + } + }); + } + + private void setOnInternal(boolean on) { + if (on) { + triggerInternal(); + } + setValueInternal(on ? 1.0 : 0.0); + } + + private void triggerInternal() { + getGatedUnit().setEnabled(true); + triggered = true; + } + + public void trigger() { + queueCommand(new ScheduledCommand() { + @Override + public void run() { + triggerInternal(); + } + }); + } + + public void trigger(TimeStamp timeStamp) { + scheduleCommand(timeStamp, new ScheduledCommand() { + @Override + public void run() { + triggerInternal(); + } + }); + } + + /** + * This is called by UnitGenerators. It sets the off value that can be tested using isOff(). + * + * @param i + * @return true if triggered by a positive edge. + */ + public boolean checkGate(int i) { + double[] inputs = getValues(); + boolean result = triggered; + triggered = false; + if (off) { + if (inputs[i] >= THRESHOLD) { + result = true; + off = false; + } + } else { + if (inputs[i] < THRESHOLD) { + off = true; + } + } + return result; + } + + public boolean isOff() { + return off; + } + + public boolean isAutoDisableEnabled() { + return autoDisableEnabled; + } + + /** + * Request the containing UnitGenerator be disabled when checkAutoDisabled() is called. This can + * be used to reduce CPU load. + * + * @param autoDisableEnabled + */ + public void setAutoDisableEnabled(boolean autoDisableEnabled) { + this.autoDisableEnabled = autoDisableEnabled; + } + + /** + * Called by UnitGenerator when an envelope reaches the end of its contour. + */ + public void checkAutoDisable() { + if (autoDisableEnabled) { + getGatedUnit().setEnabled(false); + } + } + + private UnitGenerator getGatedUnit() { + return (gatedUnit == null) ? getUnitGenerator() : gatedUnit; + } + + public void setupAutoDisable(UnitGenerator unit) { + gatedUnit = unit; + setAutoDisableEnabled(true); + // Start off disabled so we don't immediately swamp the CPU. + gatedUnit.setEnabled(false); + } +} diff --git a/src/main/java/com/jsyn/ports/UnitInputPort.java b/src/main/java/com/jsyn/ports/UnitInputPort.java new file mode 100644 index 0000000..3eda1f6 --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitInputPort.java @@ -0,0 +1,254 @@ +/* + * 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.io.PrintStream; + +import com.softsynth.shared.time.ScheduledCommand; +import com.softsynth.shared.time.TimeStamp; + +/** + * A port that is used to pass values into a UnitGenerator. + * + * @author Phil Burk 2009 Mobileer Inc + */ +public class UnitInputPort extends UnitBlockPort implements ConnectableInput, SettablePort { + private double minimum = 0.0; + private double maximum = 1.0; + private double defaultValue = 0.0; + private double[] setValues; + private boolean valueAdded = false; + + /** + * @param numParts typically 1, use 2 for stereo ports + * @param name name that may be used in GUIs + * @param defaultValue + */ + public UnitInputPort(int numParts, String name, double defaultValue) { + super(numParts, name, defaultValue); + setDefault(defaultValue); + setValues = new double[numParts]; + for (int i = 0; i < numParts; i++) { + setValues[i] = defaultValue; + } + } + + public UnitInputPort(String name, double defaultValue) { + this(1, name, defaultValue); + } + + public UnitInputPort(String name) { + this(1, name, 0.0); + } + + public UnitInputPort(int numParts, String name) { + this(numParts, name, 0.0); + } + + @Override + protected void makeParts(int numParts, double defaultValue) { + parts = new InputMixingBlockPart[numParts]; + for (int i = 0; i < numParts; i++) { + parts[i] = new InputMixingBlockPart(this, defaultValue); + } + } + + /** + * This is used internally by the SynthesisEngine to execute units based on their connections. + * + * @param frameCount + * @param start + * @param limit + */ + @Override + public void pullData(long frameCount, int start, int limit) { + for (PortBlockPart part : parts) { + ((InputMixingBlockPart) part).pullData(frameCount, start, limit); + } + } + + @Override + protected void setValueInternal(int partNum, double value) { + super.setValueInternal(partNum, value); + setValues[partNum] = value; + } + + public void set(double value) { + set(0, value); + } + + public void set(final int partNum, final double value) { + // Trigger exception now if out of range. + setValues[partNum] = value; + queueCommand(new ScheduledCommand() { + @Override + public void run() { + setValueInternal(partNum, value); + } + }); + } + + public void set(double value, TimeStamp time) { + set(0, value, time); + } + + public void set(double value, double time) { + set(0, value, time); + } + + public void set(int partNum, double value, double time) { + set(partNum, value, new TimeStamp(time)); + } + + @Override + public void set(final int partNum, final double value, TimeStamp timeStamp) { + // Trigger exception now if out of range. + getValue(partNum); + scheduleCommand(timeStamp, new ScheduledCommand() { + @Override + public void run() { + setValueInternal(partNum, value); + } + }); + } + + /** + * Value of a port based on the set() calls. Not affected by connected ports. + * + * @param partNum + * @return value as set + */ + @Override + public double get(int partNum) { + return setValues[partNum]; + } + + public double getMaximum() { + return maximum; + } + + /** + * The minimum and maximum are only used when setting up knobs or other control systems. The + * internal values are not clipped to this range. + * + * @param maximum + */ + public void setMaximum(double maximum) { + this.maximum = maximum; + } + + public double getMinimum() { + return minimum; + } + + public void setMinimum(double minimum) { + this.minimum = minimum; + } + + public double getDefault() { + return defaultValue; + } + + public void setDefault(double defaultValue) { + this.defaultValue = defaultValue; + } + + /** + * Convenience function for setting limits on a port. These limits are recommended values when + * setting up a GUI. It is possible to set a port to a value outside these limits. + * + * @param minimum + * @param value default value, will be clipped to min/max + * @param maximum + */ + public void setup(double minimum, double value, double maximum) { + setMinimum(minimum); + setMaximum(maximum); + setDefault(value); + set(value); + } + + // Grab min, max, default from another port. + public void setup(UnitInputPort other) { + setup(other.getMinimum(), other.getDefault(), other.getMaximum()); + } + + public boolean isValueAdded() { + return valueAdded; + } + + /** + * If set false then the set() value will be ignored when other ports are connected to this port. + * The sum of the connected port values will be used instead. + * + * If set true then the set() value will be added to the sum of the connected port values. + * This is useful when you want to modulate the set value. + * + * The default is false. + * + * @param valueAdded + */ + public void setValueAdded(boolean valueAdded) { + this.valueAdded = valueAdded; + } + + public void connect(int thisPartNum, UnitOutputPort otherPort, int otherPartNum, + TimeStamp timeStamp) { + otherPort.connect(otherPartNum, this, thisPartNum, timeStamp); + } + + /** Connect an input to an output port. */ + public void connect(int thisPartNum, UnitOutputPort otherPort, int otherPartNum) { + // Typically connections are made from output to input because it is + // more intuitive. + otherPort.connect(otherPartNum, this, thisPartNum); + } + + public void connect(UnitOutputPort otherPort) { + connect(0, otherPort, 0); + } + + @Override + public void connect(ConnectableOutput other) { + other.connect(this); + } + + public void disconnect(int thisPartNum, UnitOutputPort otherPort, int otherPartNum) { + otherPort.disconnect(otherPartNum, this, thisPartNum); + } + + @Override + public PortBlockPart getPortBlockPart() { + return parts[0]; + } + + public ConnectableInput getConnectablePart(int i) { + return parts[i]; + } + + @Override + public void disconnect(ConnectableOutput other) { + other.disconnect(this); + } + + public void printConnections(PrintStream out, int level) { + for (PortBlockPart part : parts) { + ((InputMixingBlockPart) part).printConnections(out, level); + } + } + +} diff --git a/src/main/java/com/jsyn/ports/UnitOutputPort.java b/src/main/java/com/jsyn/ports/UnitOutputPort.java new file mode 100644 index 0000000..6fcd758 --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitOutputPort.java @@ -0,0 +1,103 @@ +/* + * 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 com.jsyn.unitgen.UnitSink; +import com.softsynth.shared.time.TimeStamp; + +/** + * Units write to their output port blocks. Other multiple connected input ports read from them. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ + +public class UnitOutputPort extends UnitBlockPort implements ConnectableOutput, GettablePort { + public UnitOutputPort() { + this("Output"); + } + + public UnitOutputPort(String name) { + this(1, name, 0.0); + } + + public UnitOutputPort(int numParts, String name) { + this(numParts, name, 0.0); + } + + public UnitOutputPort(int numParts, String name, double defaultValue) { + super(numParts, name, defaultValue); + } + + public void flatten() { + for (PortBlockPart part : parts) { + part.flatten(); + } + } + + public void connect(int thisPartNum, UnitInputPort otherPort, int otherPartNum) { + PortBlockPart source = parts[thisPartNum]; + PortBlockPart destination = otherPort.parts[otherPartNum]; + source.connect(destination); + } + + public void connect(int thisPartNum, UnitInputPort otherPort, int otherPartNum, + TimeStamp timeStamp) { + PortBlockPart source = parts[thisPartNum]; + PortBlockPart destination = otherPort.parts[otherPartNum]; + source.connect(destination, timeStamp); + } + + public void connect(UnitInputPort input) { + connect(0, input, 0); + } + + @Override + public void connect(ConnectableInput input) { + parts[0].connect(input); + } + + public void connect(UnitSink sink) { + connect(0, sink.getInput(), 0); + } + + public void disconnect(int thisPartNum, UnitInputPort otherPort, int otherPartNum) { + PortBlockPart source = parts[thisPartNum]; + PortBlockPart destination = otherPort.parts[otherPartNum]; + source.disconnect(destination); + } + + public void disconnect(int thisPartNum, UnitInputPort otherPort, int otherPartNum, + TimeStamp timeStamp) { + PortBlockPart source = parts[thisPartNum]; + PortBlockPart destination = otherPort.parts[otherPartNum]; + source.disconnect(destination, timeStamp); + } + + public void disconnect(UnitInputPort otherPort) { + disconnect(0, otherPort, 0); + } + + @Override + public void disconnect(ConnectableInput input) { + parts[0].disconnect(input); + } + + public ConnectableOutput getConnectablePart(int i) { + return parts[i]; + } + +} diff --git a/src/main/java/com/jsyn/ports/UnitPort.java b/src/main/java/com/jsyn/ports/UnitPort.java new file mode 100644 index 0000000..a652e68 --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitPort.java @@ -0,0 +1,85 @@ +/* + * 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 com.jsyn.engine.SynthesisEngine; +import com.jsyn.unitgen.UnitGenerator; +import com.softsynth.shared.time.ScheduledCommand; +import com.softsynth.shared.time.TimeStamp; + +/** + * Basic audio port for JSyn unit generators. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class UnitPort { + private String name; + private UnitGenerator unit; + + public UnitPort(String name) { + this.name = name; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setUnitGenerator(UnitGenerator unit) { + // If a port is in a circuit then we want to just use the lower level + // unit that instantiated the circuit. + if (this.unit == null) { + this.unit = unit; + } + } + + public UnitGenerator getUnitGenerator() { + return unit; + } + + SynthesisEngine getSynthesisEngine() { + if (unit == null) { + return null; + } + return unit.getSynthesisEngine(); + } + + public int getNumParts() { + return 1; + } + + public void scheduleCommand(TimeStamp timeStamp, ScheduledCommand scheduledCommand) { + if (getSynthesisEngine() == null) { + scheduledCommand.run(); + } else { + getSynthesisEngine().scheduleCommand(timeStamp, scheduledCommand); + } + } + + public void queueCommand(ScheduledCommand scheduledCommand) { + if (getSynthesisEngine() == null) { + scheduledCommand.run(); + } else { + getSynthesisEngine().scheduleCommand(getSynthesisEngine().createTimeStamp(), + scheduledCommand); + } + } + +} diff --git a/src/main/java/com/jsyn/ports/UnitSpectralInputPort.java b/src/main/java/com/jsyn/ports/UnitSpectralInputPort.java new file mode 100644 index 0000000..bdf0ff5 --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitSpectralInputPort.java @@ -0,0 +1,83 @@ +/* + * 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 com.jsyn.data.Spectrum; + +public class UnitSpectralInputPort extends UnitPort implements ConnectableInput { + private UnitSpectralOutputPort other; + + private Spectrum spectrum; + + public UnitSpectralInputPort() { + this("Output"); + } + + public UnitSpectralInputPort(String name) { + super(name); + } + + public void setSpectrum(Spectrum spectrum) { + this.spectrum = spectrum; + } + + public Spectrum getSpectrum() { + if (other == null) { + return spectrum; + } else { + return other.getSpectrum(); + } + } + + @Override + public void connect(ConnectableOutput other) { + if (other instanceof UnitSpectralOutputPort) { + this.other = (UnitSpectralOutputPort) other; + } else { + throw new RuntimeException( + "Can only connect UnitSpectralOutputPort to UnitSpectralInputPort!"); + } + } + + @Override + public void disconnect(ConnectableOutput other) { + if (this.other == other) { + this.other = null; + } + } + + @Override + public PortBlockPart getPortBlockPart() { + return null; + } + + @Override + public void pullData(long frameCount, int start, int limit) { + if (other != null) { + other.getUnitGenerator().pullData(frameCount, start, limit); + } + } + + public boolean isAvailable() { + if (other != null) { + return other.isAvailable(); + } else { + return (spectrum != null); + } + } + +} diff --git a/src/main/java/com/jsyn/ports/UnitSpectralOutputPort.java b/src/main/java/com/jsyn/ports/UnitSpectralOutputPort.java new file mode 100644 index 0000000..51633ce --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitSpectralOutputPort.java @@ -0,0 +1,69 @@ +/* + * 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 com.jsyn.data.Spectrum; + +public class UnitSpectralOutputPort extends UnitPort implements ConnectableOutput { + private Spectrum spectrum; + private boolean available; + + public UnitSpectralOutputPort() { + this("Output"); + } + + public UnitSpectralOutputPort(int size) { + this("Output", size); + } + + public UnitSpectralOutputPort(String name) { + super(name); + spectrum = new Spectrum(); + } + + public UnitSpectralOutputPort(String name, int size) { + super(name); + spectrum = new Spectrum(size); + } + + public void setSize(int size) { + spectrum.setSize(size); + } + + public Spectrum getSpectrum() { + return spectrum; + } + + public void advance() { + available = true; + } + + @Override + public void connect(ConnectableInput other) { + other.connect(this); + } + + @Override + public void disconnect(ConnectableInput other) { + other.disconnect(this); + } + + public boolean isAvailable() { + return available; + } + +} diff --git a/src/main/java/com/jsyn/ports/UnitVariablePort.java b/src/main/java/com/jsyn/ports/UnitVariablePort.java new file mode 100644 index 0000000..60b64fd --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitVariablePort.java @@ -0,0 +1,64 @@ +/* + * 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 com.softsynth.shared.time.ScheduledCommand; +import com.softsynth.shared.time.TimeStamp; + +public class UnitVariablePort extends UnitPort implements SettablePort { + private double value; + + public UnitVariablePort(String name, double defaultValue) { + super(name); + value = defaultValue; + } + + public UnitVariablePort(String name) { + super(name); + } + + public void setValue(double value) { + this.value = value; + } + + public void set(double value) { + this.value = value; + } + + public double get() { + return value; + } + + public double getValue() { + return value; + } + + @Override + public double getValue(int partNum) { + return value; + } + + @Override + public void set(int partNum, final double value, TimeStamp timeStamp) { + scheduleCommand(timeStamp, new ScheduledCommand() { + @Override + public void run() { + set(value); + } + }); + } +} diff --git a/src/main/java/com/jsyn/ports/package.html b/src/main/java/com/jsyn/ports/package.html new file mode 100644 index 0000000..3547618 --- /dev/null +++ b/src/main/java/com/jsyn/ports/package.html @@ -0,0 +1,13 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> +<title>JSyn Ports</title> +</head> +<body> +<p>Ports are used to pass audio data in and out of UnitGenerators. +They can also be used to connect UnitGenerators together so that signals can flow between them. +The UnitDataQueuePort contains a FIFO that will accept envelope and sample data. +</p> +</body> +</html>
\ No newline at end of file |