aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/com/jsyn/ports
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/jsyn/ports')
-rw-r--r--src/main/java/com/jsyn/ports/ConnectableInput.java38
-rw-r--r--src/main/java/com/jsyn/ports/ConnectableOutput.java23
-rw-r--r--src/main/java/com/jsyn/ports/GettablePort.java27
-rw-r--r--src/main/java/com/jsyn/ports/InputMixingBlockPart.java112
-rw-r--r--src/main/java/com/jsyn/ports/PortBlockPart.java210
-rw-r--r--src/main/java/com/jsyn/ports/QueueDataCommand.java170
-rw-r--r--src/main/java/com/jsyn/ports/QueueDataEvent.java80
-rw-r--r--src/main/java/com/jsyn/ports/SequentialDataCrossfade.java139
-rw-r--r--src/main/java/com/jsyn/ports/SettablePort.java28
-rw-r--r--src/main/java/com/jsyn/ports/UnitBlockPort.java110
-rw-r--r--src/main/java/com/jsyn/ports/UnitDataQueueCallback.java31
-rw-r--r--src/main/java/com/jsyn/ports/UnitDataQueuePort.java466
-rw-r--r--src/main/java/com/jsyn/ports/UnitFunctionPort.java48
-rw-r--r--src/main/java/com/jsyn/ports/UnitGatePort.java158
-rw-r--r--src/main/java/com/jsyn/ports/UnitInputPort.java254
-rw-r--r--src/main/java/com/jsyn/ports/UnitOutputPort.java103
-rw-r--r--src/main/java/com/jsyn/ports/UnitPort.java85
-rw-r--r--src/main/java/com/jsyn/ports/UnitSpectralInputPort.java83
-rw-r--r--src/main/java/com/jsyn/ports/UnitSpectralOutputPort.java69
-rw-r--r--src/main/java/com/jsyn/ports/UnitVariablePort.java64
-rw-r--r--src/main/java/com/jsyn/ports/package.html13
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