aboutsummaryrefslogtreecommitdiffstats
path: root/src/com/jsyn
diff options
context:
space:
mode:
authorPhil Burk <[email protected]>2016-11-29 08:50:45 -0800
committerGitHub <[email protected]>2016-11-29 08:50:45 -0800
commit69568a0ab6aaba08eb366b63ce8748fc3a7f256d (patch)
treea3a9d56ae6001dd80b8633e2781467024e532391 /src/com/jsyn
parentbcd89345103f895b2b5fd508fca4cbe7af033861 (diff)
parentc081ad0ff9f10c0ab530088dc73c1e7f6735c600 (diff)
Merge pull request #41 from philburk/instruments
Instruments
Diffstat (limited to 'src/com/jsyn')
-rw-r--r--src/com/jsyn/apps/InstrumentTester.java93
-rw-r--r--src/com/jsyn/engine/MultiTable.java6
-rw-r--r--src/com/jsyn/engine/SynthesisEngine.java10
-rw-r--r--src/com/jsyn/instruments/DualOscillatorSynthVoice.java301
-rw-r--r--src/com/jsyn/instruments/JSynInstrumentLibrary.java13
-rw-r--r--src/com/jsyn/instruments/SubtractiveSynthVoice.java9
-rw-r--r--src/com/jsyn/midi/MessageParser.java90
-rw-r--r--src/com/jsyn/midi/MidiConstants.java33
-rw-r--r--src/com/jsyn/midi/MidiSynthesizer.java98
-rw-r--r--src/com/jsyn/ports/InputMixingBlockPart.java48
-rw-r--r--src/com/jsyn/ports/UnitGatePort.java8
-rw-r--r--src/com/jsyn/ports/UnitInputPort.java35
-rw-r--r--src/com/jsyn/scope/AudioScope.java12
-rw-r--r--src/com/jsyn/scope/TriggerModel.java5
-rw-r--r--src/com/jsyn/scope/swing/AudioScopeView.java6
-rw-r--r--src/com/jsyn/scope/swing/ScopeTriggerPanel.java5
-rw-r--r--src/com/jsyn/swing/EnvelopeEditorBox.java2
-rw-r--r--src/com/jsyn/swing/SoundTweaker.java10
-rw-r--r--src/com/jsyn/unitgen/Circuit.java42
-rw-r--r--src/com/jsyn/unitgen/EnvelopeDAHDSR.java6
-rw-r--r--src/com/jsyn/unitgen/FilterFourPoles.java49
-rw-r--r--src/com/jsyn/unitgen/LineOut.java6
-rw-r--r--src/com/jsyn/unitgen/LinearRamp.java50
-rw-r--r--src/com/jsyn/unitgen/MorphingOscillatorBL.java72
-rw-r--r--src/com/jsyn/unitgen/PitchToFrequency.java26
-rw-r--r--src/com/jsyn/unitgen/PowerOfTwo.java79
-rw-r--r--src/com/jsyn/unitgen/PulseOscillatorBL.java14
-rw-r--r--src/com/jsyn/unitgen/SawtoothOscillatorDPW.java6
-rw-r--r--src/com/jsyn/unitgen/SquareOscillatorBL.java9
-rw-r--r--src/com/jsyn/unitgen/TunableFilter.java10
-rw-r--r--src/com/jsyn/unitgen/UnitGenerator.java32
-rw-r--r--src/com/jsyn/unitgen/UnitOscillator.java16
-rw-r--r--src/com/jsyn/util/MultiChannelSynthesizer.java303
-rw-r--r--src/com/jsyn/util/PolyphonicInstrument.java12
-rw-r--r--src/com/jsyn/util/VoiceAllocator.java44
-rw-r--r--src/com/jsyn/util/VoiceOperation.java7
36 files changed, 1356 insertions, 211 deletions
diff --git a/src/com/jsyn/apps/InstrumentTester.java b/src/com/jsyn/apps/InstrumentTester.java
index 4186703..6e347cd 100644
--- a/src/com/jsyn/apps/InstrumentTester.java
+++ b/src/com/jsyn/apps/InstrumentTester.java
@@ -4,9 +4,9 @@
* 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.
@@ -17,12 +17,19 @@
package com.jsyn.apps;
import java.awt.BorderLayout;
+import java.io.IOException;
+import javax.sound.midi.MidiDevice;
+import javax.sound.midi.MidiMessage;
+import javax.sound.midi.MidiUnavailableException;
+import javax.sound.midi.Receiver;
import javax.swing.JApplet;
import com.jsyn.JSyn;
import com.jsyn.Synthesizer;
+import com.jsyn.devices.javasound.MidiDeviceTools;
import com.jsyn.instruments.JSynInstrumentLibrary;
+import com.jsyn.midi.MessageParser;
import com.jsyn.swing.InstrumentBrowser;
import com.jsyn.swing.JAppletFrame;
import com.jsyn.swing.PresetSelectionListener;
@@ -32,11 +39,14 @@ import com.jsyn.unitgen.UnitSource;
import com.jsyn.unitgen.UnitVoice;
import com.jsyn.util.PolyphonicInstrument;
import com.jsyn.util.VoiceDescription;
+import com.softsynth.math.AudioMath;
+import com.softsynth.shared.time.TimeStamp;
/**
- * Let the user select an instrument using the InstrumentBrowser and play them using the ASCII
- * keyboard. Sound parameters can be tweaked using faders.
- *
+ * Let the user select an instrument using the InstrumentBrowser and play
+ * them using the ASCII keyboard or with MIDI.
+ * Sound parameters can be tweaked using faders.
+ *
* @author Phil Burk (C) 2012 Mobileer Inc
*/
public class InstrumentTester extends JApplet {
@@ -44,6 +54,64 @@ public class InstrumentTester extends JApplet {
private Synthesizer synth;
private LineOut lineOut;
private SoundTweaker tweaker;
+ protected PolyphonicInstrument instrument;
+ private MyParser messageParser;
+
+ class MyParser extends MessageParser {
+
+ @Override
+ public void controlChange(int channel, int index, int value) {
+ }
+
+ @Override
+ public void noteOff(int channel, int noteNumber, int velocity) {
+ instrument.noteOff(noteNumber, synth.createTimeStamp());
+ }
+
+ @Override
+ public void noteOn(int channel, int noteNumber, int velocity) {
+ double frequency = AudioMath.pitchToFrequency(noteNumber);
+ double amplitude = velocity / (4 * 128.0);
+ TimeStamp timeStamp = synth.createTimeStamp();
+ instrument.noteOn(noteNumber, frequency, amplitude, timeStamp);
+ }
+
+ }
+
+ // Write a Receiver to get the messages from a Transmitter.
+ class CustomReceiver implements Receiver {
+ @Override
+ public void close() {
+ System.out.print("Closed.");
+ }
+
+ @Override
+ public void send(MidiMessage message, long timeStamp) {
+ byte[] bytes = message.getMessage();
+ messageParser.parse(bytes);
+ }
+ }
+
+ public int setupMidiKeyboard() throws MidiUnavailableException, IOException, InterruptedException {
+ messageParser = new MyParser();
+
+ int result = 2;
+ MidiDevice keyboard = MidiDeviceTools.findKeyboard();
+ Receiver receiver = new CustomReceiver();
+ // Just use default synthesizer.
+ if (keyboard != null) {
+ // If you forget to open them you will hear no sound.
+ keyboard.open();
+ // Put the receiver in the transmitter.
+ // This gives fairly low latency playing.
+ keyboard.getTransmitter().setReceiver(receiver);
+ System.out.println("Play MIDI keyboard: " + keyboard.getDeviceInfo().getDescription());
+ result = 0;
+ } else {
+ System.out.println("Could not find a keyboard.");
+ }
+ return result;
+ }
@Override
public void init() {
@@ -61,7 +129,7 @@ public class InstrumentTester extends JApplet {
for (int i = 0; i < voices.length; i++) {
voices[i] = voiceDescription.createUnitVoice();
}
- PolyphonicInstrument instrument = new PolyphonicInstrument(voices);
+ instrument = new PolyphonicInstrument(voices);
synth.add(instrument);
instrument.usePreset(presetIndex, synth.createTimeStamp());
String title = voiceDescription.getVoiceClassName() + ": "
@@ -71,6 +139,19 @@ public class InstrumentTester extends JApplet {
});
add(browser, BorderLayout.NORTH);
+ try {
+ setupMidiKeyboard();
+ } catch (MidiUnavailableException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
validate();
}
diff --git a/src/com/jsyn/engine/MultiTable.java b/src/com/jsyn/engine/MultiTable.java
index 48b03cd..6606639 100644
--- a/src/com/jsyn/engine/MultiTable.java
+++ b/src/com/jsyn/engine/MultiTable.java
@@ -4,9 +4,9 @@
* 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.
@@ -20,7 +20,7 @@ package com.jsyn.engine;
* Multiple tables of sawtooth data.
* organized by octaves below the Nyquist Rate.
* used to generate band-limited Sawtooth, Impulse, Pulse, Square and Triangle BL waveforms
- *
+ *
<pre>
Analysis of octave requirements for tables.
diff --git a/src/com/jsyn/engine/SynthesisEngine.java b/src/com/jsyn/engine/SynthesisEngine.java
index e966b30..ae16405 100644
--- a/src/com/jsyn/engine/SynthesisEngine.java
+++ b/src/com/jsyn/engine/SynthesisEngine.java
@@ -222,13 +222,6 @@ public class SynthesisEngine implements Synthesizer {
this.frameRate = frameRate;
this.framePeriod = 1.0 / frameRate;
- // Set rate for any units that have already been added.
- for (UnitGenerator ugen : allUnitList) {
- ugen.setFrameRate(frameRate);
- }
-
- // this.numInputChannels = numInputChannels;
- // this.numOutputChannels = numOutputChannels;
setupAudioBuffers(numInputChannels, numOutputChannels);
logger.info("Pure Java JSyn from www.softsynth.com, rate = " + frameRate + ", "
@@ -652,9 +645,6 @@ public class SynthesisEngine implements Synthesizer {
public void add(UnitGenerator ugen) {
ugen.setSynthesisEngine(this);
allUnitList.add(ugen);
- if (frameRate > 0) {
- ugen.setFrameRate(frameRate);
- }
}
@Override
diff --git a/src/com/jsyn/instruments/DualOscillatorSynthVoice.java b/src/com/jsyn/instruments/DualOscillatorSynthVoice.java
new file mode 100644
index 0000000..c81041f
--- /dev/null
+++ b/src/com/jsyn/instruments/DualOscillatorSynthVoice.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright 2010 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.instruments;
+
+import com.jsyn.ports.UnitInputPort;
+import com.jsyn.ports.UnitOutputPort;
+import com.jsyn.unitgen.Add;
+import com.jsyn.unitgen.Circuit;
+import com.jsyn.unitgen.EnvelopeDAHDSR;
+import com.jsyn.unitgen.FilterFourPoles;
+import com.jsyn.unitgen.MorphingOscillatorBL;
+import com.jsyn.unitgen.Multiply;
+import com.jsyn.unitgen.UnitVoice;
+import com.jsyn.util.VoiceDescription;
+import com.softsynth.math.AudioMath;
+import com.softsynth.shared.time.TimeStamp;
+
+/**
+ * Synthesizer voice with two morphing oscillators and a four-pole resonant filter.
+ * Modulate the amplitude and filter using DAHDSR envelopes.
+ */
+public class DualOscillatorSynthVoice extends Circuit implements UnitVoice {
+ private Multiply frequencyMultiplier;
+ private Multiply amplitudeMultiplier;
+ private Multiply detuneScaler1;
+ private Multiply detuneScaler2;
+ private Multiply amplitudeBoost;
+ private MorphingOscillatorBL osc1;
+ private MorphingOscillatorBL osc2;
+ private FilterFourPoles filter;
+ private EnvelopeDAHDSR ampEnv;
+ private EnvelopeDAHDSR filterEnv;
+ private Add cutoffAdder;
+
+ private static MyVoiceDescription voiceDescription;
+
+ public UnitInputPort amplitude;
+ public UnitInputPort frequency;
+ /**
+ * This scales the frequency value. You can use this to modulate a group of instruments using a
+ * shared LFO and they will stay in tune. Set to 1.0 for no modulation.
+ */
+ public UnitInputPort frequencyScaler;
+ public UnitInputPort oscShape1;
+ public UnitInputPort oscShape2;
+// public UnitInputPort oscDetune1;
+// public UnitInputPort oscDetune2;
+ public UnitInputPort cutoff;
+ public UnitInputPort filterEnvDepth;
+ public UnitInputPort Q;
+
+ public DualOscillatorSynthVoice() {
+ add(frequencyMultiplier = new Multiply());
+ add(amplitudeMultiplier = new Multiply());
+ add(amplitudeBoost = new Multiply());
+ add(detuneScaler1 = new Multiply());
+ add(detuneScaler2 = new Multiply());
+ // Add tone generators.
+ add(osc1 = new MorphingOscillatorBL());
+ add(osc2 = new MorphingOscillatorBL());
+
+ // Use an envelope to control the amplitude.
+ add(ampEnv = new EnvelopeDAHDSR());
+
+ // Use an envelope to control the filter cutoff.
+ add(filterEnv = new EnvelopeDAHDSR());
+ add(filter = new FilterFourPoles());
+ add(cutoffAdder = new Add());
+
+ filterEnv.output.connect(cutoffAdder.inputA);
+ cutoffAdder.output.connect(filter.frequency);
+ frequencyMultiplier.output.connect(detuneScaler1.inputA);
+ frequencyMultiplier.output.connect(detuneScaler2.inputA);
+ detuneScaler1.output.connect(osc1.frequency);
+ detuneScaler2.output.connect(osc2.frequency);
+ osc1.output.connect(amplitudeMultiplier.inputA); // mix oscillators
+ osc2.output.connect(amplitudeMultiplier.inputA);
+ amplitudeMultiplier.output.connect(filter.input);
+ filter.output.connect(amplitudeBoost.inputA);
+ amplitudeBoost.output.connect(ampEnv.amplitude);
+
+ addPort(amplitude = amplitudeMultiplier.inputB, PORT_NAME_AMPLITUDE);
+ addPort(frequency = frequencyMultiplier.inputA, PORT_NAME_FREQUENCY);
+ addPort(oscShape1 = osc1.shape, "OscShape1");
+ addPort(oscShape2 = osc2.shape, "OscShape2");
+// addPort(oscDetune1 = osc1.shape, "OscDetune1");
+// addPort(oscDetune2 = osc2.shape, "OscDetune2");
+ addPort(cutoff = cutoffAdder.inputB, PORT_NAME_CUTOFF);
+ addPortAlias(cutoff, PORT_NAME_TIMBRE);
+ addPort(Q = filter.Q);
+ addPort(frequencyScaler = frequencyMultiplier.inputB, PORT_NAME_FREQUENCY_SCALER);
+ addPort(filterEnvDepth = filterEnv.amplitude, "FilterEnvDepth");
+
+ filterEnv.export(this, "Filter");
+ ampEnv.export(this, "Amp");
+
+ frequency.setup(osc1.frequency);
+ frequencyScaler.setup(0.2, 1.0, 4.0);
+ cutoff.setup(filter.frequency);
+ // Allow negative filter sweeps
+ filterEnvDepth.setup(-4000.0, 2000.0, 4000.0);
+
+ // set amplitudes slightly different so that they never entirely cancel
+ osc1.amplitude.set(0.5);
+ osc2.amplitude.set(0.4);
+ // Make the circuit turn off when the envelope finishes to reduce CPU load.
+ ampEnv.setupAutoDisable(this);
+ // Add named port for mapping pressure.
+ amplitudeBoost.inputB.setup(1.0, 1.0, 4.0);
+ addPortAlias(amplitudeBoost.inputB, PORT_NAME_PRESSURE);
+
+ usePreset(0);
+ }
+
+ /**
+ * The first oscillator will be tuned UP by semitoneOffset/2.
+ * The second oscillator will be tuned DOWN by semitoneOffset/2.
+ * @param semitoneOffset
+ */
+ private void setDetunePitch(double semitoneOffset) {
+ double halfOffset = semitoneOffset * 0.5;
+ setDetunePitch1(halfOffset);
+ setDetunePitch2(-halfOffset);
+ }
+
+ /**
+ * Set the detuning for osc1 in semitones.
+ * @param semitoneOffset
+ */
+ private void setDetunePitch1(double semitoneOffset) {
+ double scale = AudioMath.semitonesToFrequencyScaler(semitoneOffset);
+ detuneScaler1.inputB.set(scale);
+ }
+
+ /**
+ * Set the detuning for osc2 in semitones.
+ * @param semitoneOffset
+ */
+ private void setDetunePitch2(double semitoneOffset) {
+ double scale = AudioMath.semitonesToFrequencyScaler(semitoneOffset);
+ detuneScaler2.inputB.set(scale);
+ }
+
+ @Override
+ public void noteOff(TimeStamp timeStamp) {
+ ampEnv.input.off(timeStamp);
+ filterEnv.input.off(timeStamp);
+ }
+
+ @Override
+ public void noteOn(double freq, double ampl, TimeStamp timeStamp) {
+ frequency.set(freq, timeStamp);
+ amplitude.set(ampl, timeStamp);
+ ampEnv.input.on(timeStamp);
+ filterEnv.input.on(timeStamp);
+ }
+
+ @Override
+ public UnitOutputPort getOutput() {
+ return ampEnv.output;
+ }
+
+ // Reset to basic voice.
+ public void reset() {
+ osc1.shape.set(0.0);
+ osc2.shape.set(0.0);
+ ampEnv.attack.set(0.005);
+ ampEnv.decay.set(0.2);
+ ampEnv.sustain.set(0.5);
+ ampEnv.release.set(1.0);
+ filterEnv.attack.set(0.01);
+ filterEnv.decay.set(0.6);
+ filterEnv.sustain.set(0.4);
+ filterEnv.release.set(1.0);
+ cutoff.set(500.0);
+ filterEnvDepth.set(3000.0);
+ filter.reset();
+ filter.Q.set(3.9);
+ setDetunePitch(0.02);
+ }
+
+ @Override
+ public void usePreset(int presetIndex) {
+ reset(); // start from known configuration
+ int n = presetIndex % presetNames.length;
+ switch (n) {
+ case 0:
+ break;
+ case 1:
+ ampEnv.attack.set(0.1);
+ ampEnv.decay.set(0.9);
+ ampEnv.sustain.set(0.1);
+ ampEnv.release.set(0.1);
+ cutoff.set(500.0);
+ filterEnvDepth.set(500.0);
+ filter.Q.set(3.0);
+ break;
+ case 2:
+ ampEnv.attack.set(0.1);
+ ampEnv.decay.set(0.3);
+ ampEnv.release.set(0.5);
+ cutoff.set(2000.0);
+ filterEnvDepth.set(500.0);
+ filter.Q.set(2.0);
+ break;
+ case 3:
+ osc1.shape.set(-0.9);
+ osc2.shape.set(-0.8);
+ ampEnv.attack.set(0.3);
+ ampEnv.decay.set(0.8);
+ ampEnv.release.set(0.2);
+ filterEnv.sustain.set(0.7);
+ cutoff.set(500.0);
+ filterEnvDepth.set(500.0);
+ filter.Q.set(3.0);
+ break;
+ case 4:
+ osc1.shape.set(1.0);
+ osc2.shape.set(0.0);
+ break;
+ case 5:
+ osc1.shape.set(1.0);
+ setDetunePitch1(0.0);
+ osc2.shape.set(0.9);
+ setDetunePitch1(7.0);
+ break;
+ case 6:
+ osc1.shape.set(0.6);
+ osc2.shape.set(-0.2);
+ setDetunePitch1(0.01);
+ ampEnv.attack.set(0.005);
+ ampEnv.decay.set(0.09);
+ ampEnv.sustain.set(0.0);
+ ampEnv.release.set(1.0);
+ filterEnv.attack.set(0.005);
+ filterEnv.decay.set(0.1);
+ filterEnv.sustain.set(0.4);
+ filterEnv.release.set(1.0);
+ cutoff.set(2000.0);
+ filterEnvDepth.set(5000.0);
+ filter.Q.set(7.02);
+ break;
+ default:
+ break;
+ }
+ }
+
+ private static final String[] presetNames = {
+ "FastSaw", "SlowSaw", "BrightSaw",
+ "SoftSine", "SquareSaw", "SquareFifth",
+ "Blip"
+ };
+
+ static class MyVoiceDescription extends VoiceDescription {
+ String[] tags = {
+ "electronic", "filter", "analog", "subtractive"
+ };
+
+ public MyVoiceDescription() {
+ super(DualOscillatorSynthVoice.class.getName(), presetNames);
+ }
+
+ @Override
+ public UnitVoice createUnitVoice() {
+ return new DualOscillatorSynthVoice();
+ }
+
+ @Override
+ public String[] getTags(int presetIndex) {
+ return tags;
+ }
+
+ @Override
+ public String getVoiceClassName() {
+ return DualOscillatorSynthVoice.class.getName();
+ }
+ }
+
+ public static VoiceDescription getVoiceDescription() {
+ if (voiceDescription == null) {
+ voiceDescription = new MyVoiceDescription();
+ }
+ return voiceDescription;
+ }
+
+
+}
diff --git a/src/com/jsyn/instruments/JSynInstrumentLibrary.java b/src/com/jsyn/instruments/JSynInstrumentLibrary.java
index c5ed91e..9f111c3 100644
--- a/src/com/jsyn/instruments/JSynInstrumentLibrary.java
+++ b/src/com/jsyn/instruments/JSynInstrumentLibrary.java
@@ -4,9 +4,9 @@
* 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.
@@ -22,15 +22,18 @@ import com.jsyn.util.VoiceDescription;
/**
* Stock instruments provided with the JSyn distribution.
- *
+ *
* @author Phil Burk (C) 2011 Mobileer Inc
* @see InstrumentBrowser
*/
public class JSynInstrumentLibrary implements InstrumentLibrary {
static VoiceDescription[] descriptions = {
- WaveShapingVoice.getVoiceDescription(), SubtractiveSynthVoice.getVoiceDescription(),
- NoiseHit.getVoiceDescription(), DrumWoodFM.getVoiceDescription()
+ WaveShapingVoice.getVoiceDescription(),
+ SubtractiveSynthVoice.getVoiceDescription(),
+ DualOscillatorSynthVoice.getVoiceDescription(),
+ NoiseHit.getVoiceDescription(),
+ DrumWoodFM.getVoiceDescription()
};
@Override
diff --git a/src/com/jsyn/instruments/SubtractiveSynthVoice.java b/src/com/jsyn/instruments/SubtractiveSynthVoice.java
index af3329e..5cfc4b9 100644
--- a/src/com/jsyn/instruments/SubtractiveSynthVoice.java
+++ b/src/com/jsyn/instruments/SubtractiveSynthVoice.java
@@ -4,9 +4,9 @@
* 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.
@@ -30,13 +30,12 @@ import com.jsyn.util.VoiceDescription;
import com.softsynth.shared.time.TimeStamp;
/**
- * Typical synthesizer voice with an oscillator and resonant filter. Modulate the amplitude and
+ * Typical synthesizer voice with one oscillator and a biquad resonant filter. Modulate the amplitude and
* filter using DAHDSR envelopes.
- *
+ *
* @author Phil Burk (C) 2010 Mobileer Inc
*/
public class SubtractiveSynthVoice extends Circuit implements UnitVoice {
- private static final long serialVersionUID = -2704222221111608377L;
private UnitOscillator osc;
private FilterLowPass filter;
private EnvelopeDAHDSR ampEnv;
diff --git a/src/com/jsyn/midi/MessageParser.java b/src/com/jsyn/midi/MessageParser.java
index 43d10c8..d0f5d4d 100644
--- a/src/com/jsyn/midi/MessageParser.java
+++ b/src/com/jsyn/midi/MessageParser.java
@@ -4,9 +4,9 @@
* 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.
@@ -18,10 +18,15 @@ package com.jsyn.midi;
/**
* Parse the message and call the appropriate method to handle it.
- *
+ *
* @author Phil Burk (C) 2010 Mobileer Inc
*/
public class MessageParser {
+ private int[] parameterIndices = new int[MidiConstants.MAX_CHANNELS];
+ private int[] parameterValues = new int[MidiConstants.MAX_CHANNELS];
+ private int BIT_NON_RPM = 1 << 14;
+ private int MASK_14BIT = (1 << 14) - 1;
+
public void parse(byte[] message) {
int status = message[0];
int command = status & 0xF0;
@@ -41,27 +46,102 @@ public class MessageParser {
noteOff(channel, message[1], message[2]);
break;
+ case MidiConstants.POLYPHONIC_AFTERTOUCH:
+ polyphonicAftertouch(channel, message[1], message[2]);
+ break;
+
+ case MidiConstants.CHANNEL_PRESSURE:
+ channelPressure(channel, message[1]);
+ break;
+
case MidiConstants.CONTROL_CHANGE:
- controlChange(channel, message[1], message[2]);
+ rawControlChange(channel, message[1], message[2]);
+ break;
+
+ case MidiConstants.PROGRAM_CHANGE:
+ programChange(channel, message[1]);
break;
case MidiConstants.PITCH_BEND:
- int bend = (((message[2]) & 0x007F) << 7) + ((message[1]) & 0x007F);
+ int bend = (message[2] << 7) + message[1];
pitchBend(channel, bend);
break;
}
}
+ public void rawControlChange(int channel, int index, int value) {
+ int paramIndex;
+ int paramValue;
+ switch(index) {
+ case MidiConstants.CONTROLLER_DATA_ENTRY:
+ parameterValues[channel] = value << 7;
+ fireParameterChange(channel);
+ break;
+ case MidiConstants.CONTROLLER_DATA_ENTRY_LSB:
+ paramValue = parameterValues[channel] & ~0x7F;
+ paramValue |= value;
+ parameterValues[channel] = paramValue;
+ fireParameterChange(channel);
+ break;
+ case MidiConstants.CONTROLLER_NRPN_LSB:
+ paramIndex = parameterIndices[channel] & ~0x7F;
+ paramIndex |= value | BIT_NON_RPM;
+ parameterIndices[channel] = paramIndex;
+ break;
+ case MidiConstants.CONTROLLER_NRPN_MSB:
+ parameterIndices[channel] = (value << 7) | BIT_NON_RPM;;
+ break;
+ case MidiConstants.CONTROLLER_RPN_LSB:
+ paramIndex = parameterIndices[channel] & ~0x7F;
+ paramIndex |= value;
+ parameterIndices[channel] = paramIndex;
+ break;
+ case MidiConstants.CONTROLLER_RPN_MSB:
+ parameterIndices[channel] = value << 7;
+ break;
+ default:
+ controlChange(channel, index, value);
+ break;
+
+ }
+ }
+
+ private void fireParameterChange(int channel) {
+ int paramIndex;
+ paramIndex = parameterIndices[channel];
+ if ((paramIndex & BIT_NON_RPM) == 0) {
+ registeredParameter(channel, paramIndex, parameterValues[channel]);
+ } else {
+ nonRegisteredParameter(channel, paramIndex & MASK_14BIT, parameterValues[channel]);
+ }
+ }
+
+ public void nonRegisteredParameter(int channel, int index14, int value14) {
+ }
+
+ public void registeredParameter(int channel, int index14, int value14) {
+ }
+
public void pitchBend(int channel, int bend) {
}
+ public void programChange(int channel, int program) {
+ }
+
+ public void polyphonicAftertouch(int channel, int pitch, int pressure) {
+ }
+
+ public void channelPressure(int channel, int pressure) {
+ }
+
public void controlChange(int channel, int index, int value) {
}
public void noteOn(int channel, int pitch, int velocity) {
}
+ // If a NOTE_ON with zero velocity is received then noteOff will be called.
public void noteOff(int channel, int pitch, int velocity) {
}
}
diff --git a/src/com/jsyn/midi/MidiConstants.java b/src/com/jsyn/midi/MidiConstants.java
index dae9390..8c92119 100644
--- a/src/com/jsyn/midi/MidiConstants.java
+++ b/src/com/jsyn/midi/MidiConstants.java
@@ -4,9 +4,9 @@
* 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.
@@ -18,10 +18,12 @@ package com.jsyn.midi;
/**
* Constants that define the MIDI standard.
- *
+ *
* @author Phil Burk (C) 2010 Mobileer Inc
*/
public class MidiConstants {
+
+ public static final int MAX_CHANNELS = 16;
// Basic commands.
public static final int NOTE_OFF = 0x80;
public static final int NOTE_ON = 0x90;
@@ -29,10 +31,33 @@ public class MidiConstants {
public static final int CONTROL_CHANGE = 0xB0;
public static final int PROGRAM_CHANGE = 0xC0;
public static final int CHANNEL_AFTERTOUCH = 0xD0;
+ public static final int CHANNEL_PRESSURE = CHANNEL_AFTERTOUCH;
public static final int PITCH_BEND = 0xE0;
public static final int SYSTEM_COMMON = 0xF0;
- public static final int PITCH_BEND_CENTER = 8192;
+ public static final int PITCH_BEND_CENTER = 0x2000;
+
+ public static final int CONTROLLER_BANK_SELECT = 0;
+ public static final int CONTROLLER_MOD_WHEEL = 1;
+ public static final int CONTROLLER_BREATH = 2;
+ public static final int CONTROLLER_DATA_ENTRY = 6;
+ public static final int CONTROLLER_VOLUME = 7;
+ public static final int CONTROLLER_PAN = 10;
+
+ public static final int CONTROLLER_LSB_OFFSET = 32;
+ public static final int CONTROLLER_DATA_ENTRY_LSB = CONTROLLER_DATA_ENTRY + CONTROLLER_LSB_OFFSET;
+
+ public static final int CONTROLLER_TIMBRE = 74; // Often used by MPE for Y axis control.
+
+ public static final int CONTROLLER_DATA_INCREMENT = 96;
+ public static final int CONTROLLER_DATA_DECREMENT = 97;
+ public static final int CONTROLLER_NRPN_LSB = 98;
+ public static final int CONTROLLER_NRPN_MSB = 99;
+ public static final int CONTROLLER_RPN_LSB = 100;
+ public static final int CONTROLLER_RPN_MSB = 101;
+
+ public static final int RPN_BEND_RANGE = 0;
+ public static final int RPN_FINE_TUNING = 1;
public static final String PITCH_NAMES[] = {
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
diff --git a/src/com/jsyn/midi/MidiSynthesizer.java b/src/com/jsyn/midi/MidiSynthesizer.java
new file mode 100644
index 0000000..e011430
--- /dev/null
+++ b/src/com/jsyn/midi/MidiSynthesizer.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2016 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.midi;
+
+import com.jsyn.util.MultiChannelSynthesizer;
+
+public class MidiSynthesizer extends MessageParser {
+
+ private MultiChannelSynthesizer multiSynth;
+
+ public MidiSynthesizer(MultiChannelSynthesizer multiSynth) {
+ this.multiSynth = multiSynth;
+ }
+
+ @Override
+ public void controlChange(int channel, int index, int value) {
+ //System.out.println("controlChange(" + channel + ", " + index + ", " + value + ")");
+ double normalized = value * (1.0 / 127.0);
+ switch (index) {
+ case MidiConstants.CONTROLLER_MOD_WHEEL:
+ double vibratoDepth = 0.1 * normalized;
+ System.out.println( "vibratoDepth = " + vibratoDepth );
+ multiSynth.setVibratoDepth(channel, vibratoDepth);
+ break;
+ case MidiConstants.CONTROLLER_TIMBRE:
+ multiSynth.setTimbre(channel, normalized);
+ break;
+ case MidiConstants.CONTROLLER_VOLUME:
+ multiSynth.setVolume(channel, normalized);
+ break;
+ case MidiConstants.CONTROLLER_PAN:
+ // convert to -1 to +1 range
+ multiSynth.setPan(channel, (normalized * 2.0) - 1.0);
+ break;
+ }
+ }
+
+ @Override
+ public void registeredParameter(int channel, int index14, int value14) {
+ switch(index14) {
+ case MidiConstants.RPN_BEND_RANGE:
+ int semitones = value14 >> 7;
+ int cents = value14 & 0x7F;
+ double bendRange = semitones + (cents * 0.01);
+ multiSynth.setBendRange(channel, bendRange);
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void programChange(int channel, int program) {
+ multiSynth.programChange(channel, program);
+ }
+
+ @Override
+ public void channelPressure(int channel, int value) {
+ double normalized = value * (1.0 / 127.0);
+ multiSynth.setPressure(channel, normalized);
+ }
+
+ @Override
+ public void noteOff(int channel, int noteNumber, int velocity) {
+ multiSynth.noteOff(channel, noteNumber, velocity);
+ }
+
+ @Override
+ public void noteOn(int channel, int noteNumber, int velocity) {
+ multiSynth.noteOn(channel, noteNumber, velocity);
+ }
+
+ @Override
+ public void pitchBend(int channel, int bend) {
+ double offset = (bend - MidiConstants.PITCH_BEND_CENTER)
+ * (1.0 / (MidiConstants.PITCH_BEND_CENTER));
+ multiSynth.setPitchBend(channel, offset);
+ }
+
+ public void onReceive(byte[] bytes, int i, int length) {
+ parse(bytes); // TODO
+ }
+
+}
diff --git a/src/com/jsyn/ports/InputMixingBlockPart.java b/src/com/jsyn/ports/InputMixingBlockPart.java
index 3211342..5b54b99 100644
--- a/src/com/jsyn/ports/InputMixingBlockPart.java
+++ b/src/com/jsyn/ports/InputMixingBlockPart.java
@@ -4,9 +4,9 @@
* 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.
@@ -23,16 +23,18 @@ 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(UnitBlockPort unitBlockPort, double defaultValue) {
- super(unitBlockPort, defaultValue);
+ InputMixingBlockPart(UnitInputPort unitInputPort, double defaultValue) {
+ super(unitInputPort, defaultValue);
+ this.unitInputPort = unitInputPort;
}
@Override
@@ -52,30 +54,32 @@ public class InputMixingBlockPart extends PortBlockPart {
int numConnections = getConnectionCount();
// System.out.println("numConnection = " + numConnections + " for " +
// this );
- if (numConnections == 0)
- // No connection so just use our own data.
- {
+ if (numConnections == 0) {
+ // No connection so just use our own data.
result = super.getValues();
- } else if (numConnections == 1)
- // Grab values from one connected port.
- {
- PortBlockPart otherPart = getConnection(0);
- result = otherPart.getValues();
- } else
- // Mix all of the inputs.
- {
- PortBlockPart otherPart = getConnection(0);
- double[] inputs = otherPart.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]; // set directly instead of zeroing first
+ mixer[i] = inputs[i];
}
// Now mix in the remaining inputs.
- for (int jCon = 1; jCon < numConnections; jCon++) {
+ for (; jCon < numConnections; jCon++) {
otherPart = getConnection(jCon);
-
inputs = otherPart.getValues();
for (int i = 0; i < mixer.length; i++) {
- mixer[i] += inputs[i]; // mix with previous inputs
+ mixer[i] += inputs[i];
}
}
result = mixer;
diff --git a/src/com/jsyn/ports/UnitGatePort.java b/src/com/jsyn/ports/UnitGatePort.java
index 43d5e7f..700aef8 100644
--- a/src/com/jsyn/ports/UnitGatePort.java
+++ b/src/com/jsyn/ports/UnitGatePort.java
@@ -4,9 +4,9 @@
* 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.
@@ -97,7 +97,7 @@ public class UnitGatePort extends UnitInputPort {
/**
* 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.
*/
@@ -129,7 +129,7 @@ public class UnitGatePort extends UnitInputPort {
/**
* 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) {
diff --git a/src/com/jsyn/ports/UnitInputPort.java b/src/com/jsyn/ports/UnitInputPort.java
index 93a7f7a..3eda1f6 100644
--- a/src/com/jsyn/ports/UnitInputPort.java
+++ b/src/com/jsyn/ports/UnitInputPort.java
@@ -4,9 +4,9 @@
* 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.
@@ -23,15 +23,15 @@ 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
@@ -69,7 +69,7 @@ public class UnitInputPort extends UnitBlockPort implements ConnectableInput, Se
/**
* This is used internally by the SynthesisEngine to execute units based on their connections.
- *
+ *
* @param frameCount
* @param start
* @param limit
@@ -128,7 +128,7 @@ public class UnitInputPort extends UnitBlockPort implements ConnectableInput, Se
/**
* Value of a port based on the set() calls. Not affected by connected ports.
- *
+ *
* @param partNum
* @return value as set
*/
@@ -144,7 +144,7 @@ public class UnitInputPort extends UnitBlockPort implements ConnectableInput, Se
/**
* 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) {
@@ -170,7 +170,7 @@ public class UnitInputPort extends UnitBlockPort implements ConnectableInput, Se
/**
* 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
@@ -187,6 +187,25 @@ public class UnitInputPort extends UnitBlockPort implements ConnectableInput, Se
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);
diff --git a/src/com/jsyn/scope/AudioScope.java b/src/com/jsyn/scope/AudioScope.java
index 32268cd..9ab4a73 100644
--- a/src/com/jsyn/scope/AudioScope.java
+++ b/src/com/jsyn/scope/AudioScope.java
@@ -4,9 +4,9 @@
* 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.
@@ -27,7 +27,7 @@ import com.jsyn.scope.swing.AudioScopeView;
// TODO Continuous capture
/**
* Digital oscilloscope for JSyn.
- *
+ *
* @author Phil Burk (C) 2010 Mobileer Inc
*/
public class AudioScope {
@@ -90,7 +90,11 @@ public class AudioScope {
return getModel().getTriggerModel().getLevelModel().getDoubleValue();
}
- public void setViewMode(ViewMode waveform) {
+ /**
+ * Not yet implemented.
+ * @param waveform
+ */
+ public void setViewMode(ViewMode viewMode) {
// TODO Auto-generated method stub
}
diff --git a/src/com/jsyn/scope/TriggerModel.java b/src/com/jsyn/scope/TriggerModel.java
index 7081b2c..0367d71 100644
--- a/src/com/jsyn/scope/TriggerModel.java
+++ b/src/com/jsyn/scope/TriggerModel.java
@@ -4,9 +4,9 @@
* 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.
@@ -16,7 +16,6 @@
package com.jsyn.scope;
-import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import com.jsyn.scope.AudioScope.TriggerMode;
diff --git a/src/com/jsyn/scope/swing/AudioScopeView.java b/src/com/jsyn/scope/swing/AudioScopeView.java
index 31f1264..ec1afa3 100644
--- a/src/com/jsyn/scope/swing/AudioScopeView.java
+++ b/src/com/jsyn/scope/swing/AudioScopeView.java
@@ -4,9 +4,9 @@
* 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.
@@ -77,7 +77,7 @@ public class AudioScopeView extends JPanel {
setMaximumSize(new Dimension(1200, 300));
}
- /** @deprecated */
+ /** @deprecated Use setControlsVisible() instead. */
@Deprecated
public void setShowControls(boolean show) {
setControlsVisible(show);
diff --git a/src/com/jsyn/scope/swing/ScopeTriggerPanel.java b/src/com/jsyn/scope/swing/ScopeTriggerPanel.java
index a5f1541..9c22aa1 100644
--- a/src/com/jsyn/scope/swing/ScopeTriggerPanel.java
+++ b/src/com/jsyn/scope/swing/ScopeTriggerPanel.java
@@ -4,9 +4,9 @@
* 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.
@@ -18,7 +18,6 @@ package com.jsyn.scope.swing;
import java.awt.BorderLayout;
-import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JPanel;
diff --git a/src/com/jsyn/swing/EnvelopeEditorBox.java b/src/com/jsyn/swing/EnvelopeEditorBox.java
index 44fe53c..aab5762 100644
--- a/src/com/jsyn/swing/EnvelopeEditorBox.java
+++ b/src/com/jsyn/swing/EnvelopeEditorBox.java
@@ -282,7 +282,7 @@ public class EnvelopeEditorBox extends XYController implements MouseListener, Mo
{
dragIndex = pnt;
if (dragIndex <= 0)
- dragLowLimit = 0.0; // FIXME
+ dragLowLimit = 0.0; // FIXME envelope drag limit
else
dragLowLimit = xPicked - points.getPoint(dragIndex)[0];
dragHighLimit = xPicked + (maximumXRange - points.getTotalDuration());
diff --git a/src/com/jsyn/swing/SoundTweaker.java b/src/com/jsyn/swing/SoundTweaker.java
index d41946d..043677e 100644
--- a/src/com/jsyn/swing/SoundTweaker.java
+++ b/src/com/jsyn/swing/SoundTweaker.java
@@ -4,9 +4,9 @@
* 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.
@@ -45,13 +45,12 @@ public class SoundTweaker extends JPanel {
this.synth = synth;
this.source = source;
- setLayout(new GridLayout(0, 1));
+ setLayout(new GridLayout(0, 2));
UnitGenerator ugen = source.getUnitGenerator();
ArrayList<Component> sliders = new ArrayList<Component>();
add(new JLabel(title));
- // Arrange the faders in a stack.
if (source instanceof Instrument) {
add(keyboard = createPolyphonicKeyboard());
@@ -59,6 +58,7 @@ public class SoundTweaker extends JPanel {
add(keyboard = createMonophonicKeyboard());
}
+ // Arrange the faders in a stack.
// Iterate through the ports.
for (UnitPort port : ugen.getPorts()) {
if (port instanceof UnitInputPort) {
@@ -90,13 +90,11 @@ public class SoundTweaker extends JPanel {
ASCIIMusicKeyboard keyboard = new ASCIIMusicKeyboard() {
@Override
public void keyOff(int pitch) {
- logger.info("-------------- keyOff " + pitch);
((Instrument) source).noteOff(pitch, synth.createTimeStamp());
}
@Override
public void keyOn(int pitch) {
- logger.info("-------------- keyOn " + pitch);
double freq = AudioMath.pitchToFrequency(pitch);
((Instrument) source).noteOn(pitch, freq, 0.5, synth.createTimeStamp());
}
diff --git a/src/com/jsyn/unitgen/Circuit.java b/src/com/jsyn/unitgen/Circuit.java
index a501600..c5a1dcf 100644
--- a/src/com/jsyn/unitgen/Circuit.java
+++ b/src/com/jsyn/unitgen/Circuit.java
@@ -4,9 +4,9 @@
* 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.
@@ -17,17 +17,21 @@
package com.jsyn.unitgen;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
import com.jsyn.engine.SynthesisEngine;
+import com.jsyn.ports.UnitPort;
/**
* Contains a list of units that are executed together.
- *
+ *
* @author Phil Burk (C) 2009 Mobileer Inc
*/
public class Circuit extends UnitGenerator {
private ArrayList<UnitGenerator> units = new ArrayList<UnitGenerator>();
+ private final LinkedHashMap<String, UnitPort> portAliases = new LinkedHashMap<String, UnitPort>();
+
@Override
public void generate(int start, int limit) {
for (UnitGenerator unit : units) {
@@ -57,6 +61,11 @@ public class Circuit extends UnitGenerator {
}
}
+ /**
+ * @deprecated ignored, frameRate comes from the SynthesisEngine
+ * @param rate
+ */
+ @Deprecated
@Override
public void setFrameRate(int frameRate) {
super.setFrameRate(frameRate);
@@ -83,4 +92,31 @@ public class Circuit extends UnitGenerator {
public void usePreset(int presetIndex) {
}
+
+
+ /**
+ * Add an alternate name for looking up a port.
+ * @param port
+ * @param alias
+ */
+ public void addPortAlias(UnitPort port, String alias) {
+ // Store in a hash table by an alternate name.
+ portAliases.put(alias.toLowerCase(), port);
+ }
+
+
+ /**
+ * Case-insensitive search for a port by its name or alias.
+ * @param portName
+ * @return matching port or null
+ */
+ @Override
+ public UnitPort getPortByName(String portName) {
+ UnitPort port = super.getPortByName(portName);
+ if (port == null) {
+ port = portAliases.get(portName.toLowerCase());
+ }
+ return port;
+ }
+
}
diff --git a/src/com/jsyn/unitgen/EnvelopeDAHDSR.java b/src/com/jsyn/unitgen/EnvelopeDAHDSR.java
index 6acd763..c5ebe83 100644
--- a/src/com/jsyn/unitgen/EnvelopeDAHDSR.java
+++ b/src/com/jsyn/unitgen/EnvelopeDAHDSR.java
@@ -4,9 +4,9 @@
* 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.
@@ -30,7 +30,7 @@ import com.jsyn.ports.UnitOutputPort;
* exponential Release will never reach 0.0. But when it reaches -96 dB the DAHDSR just sets its
* output to 0.0 and stops. There is an example program in the ZIP archive called HearDAHDSR. It
* drives a DAHDSR with a square wave.
- *
+ *
* @author Phil Burk (C) 2010 Mobileer Inc
* @see SegmentedEnvelope
*/
diff --git a/src/com/jsyn/unitgen/FilterFourPoles.java b/src/com/jsyn/unitgen/FilterFourPoles.java
index d4f80f4..39a47c7 100644
--- a/src/com/jsyn/unitgen/FilterFourPoles.java
+++ b/src/com/jsyn/unitgen/FilterFourPoles.java
@@ -4,9 +4,9 @@
* 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.
@@ -20,13 +20,13 @@ import com.jsyn.ports.UnitInputPort;
/**
* Resonant filter in the style of the Moog ladder filter. This implementation is loosely based on:
- * http://www.musicdsp.org/archive.php?classid=3#26
+ * http://www.musicdsp.org/archive.php?classid=3#26
* More interesting reading:
* http://dafx04.na.infn.it/WebProc/Proc/P_061.pdf
* http://www.acoustics.ed.ac.uk/wp-content/uploads/AMT_MSc_FinalProjects
* /2012__Daly__AMT_MSc_FinalProject_MoogVCF.pdf
* http://www.music.mcgill.ca/~ich/research/misc/papers/cr1071.pdf
- *
+ *
* @author Phil Burk (C) 2014 Mobileer Inc
* @see FilterLowPass
*/
@@ -37,6 +37,14 @@ public class FilterFourPoles extends TunableFilter {
private static final double MINIMUM_FREQUENCY = 1.0; // blows up if near 0.01
private static final double MINIMUM_Q = 0.00001;
+ //private static final double SATURATION_COEFFICIENT = 0.1666667;
+ private static final double SATURATION_COEFFICIENT = 0.2;
+ // Inflection point where slope is zero.
+ private static final double SATURATION_UPPER_INPUT = 1.0 / Math.sqrt(3.0 * SATURATION_COEFFICIENT);
+ private static final double SATURATION_LOWER_INPUT = 0.0 - SATURATION_UPPER_INPUT;
+ private static final double SATURATION_UPPER_OUTPUT = cubicPolynomial(SATURATION_UPPER_INPUT);
+ private static final double SATURATION_LOWER_OUTPUT = cubicPolynomial(SATURATION_LOWER_INPUT);
+
private double x1;
private double x2;
private double x3;
@@ -57,6 +65,7 @@ public class FilterFourPoles extends TunableFilter {
public FilterFourPoles() {
addPort(Q = new UnitInputPort("Q"));
+ frequency.setup(40.0, DEFAULT_FREQUENCY, 4000.0);
Q.setup(0.1, 2.0, 10.0);
}
@@ -111,7 +120,6 @@ public class FilterFourPoles extends TunableFilter {
oneSample(0.0);
}
oneSample(x0);
-
outputs[i] = y4;
}
@@ -143,8 +151,35 @@ public class FilterFourPoles extends TunableFilter {
this.oversampled = oversampled;
}
- private double clip(double x) {
- return x - (x * x * x * 0.1666667);
+ // Soft saturation. This used to blow up the filter!
+ private static double cubicPolynomial(double x) {
+ return x - (x * x * x * SATURATION_COEFFICIENT);
}
+ private static double clip(double x) {
+ if (x > SATURATION_UPPER_INPUT) {
+ return SATURATION_UPPER_OUTPUT;
+ } else if (x < SATURATION_LOWER_INPUT) {
+ return SATURATION_LOWER_OUTPUT;
+ } else {
+ return cubicPolynomial(x);
+ }
+ }
+
+ public void reset() {
+ x1 = 0.0;
+ x2 = 0.0;
+ x3 = 0.0;
+ x4 = 0.0;
+ y1 = 0.0;
+ y2 = 0.0;
+ y3 = 0.0;
+ y4 = 0.0;
+
+ previousFrequency = 0.0;
+ previousQ = 0.0;
+ f = 0.0;
+ fTo4th = 0.0;
+ feedback = 0.0;
+ }
}
diff --git a/src/com/jsyn/unitgen/LineOut.java b/src/com/jsyn/unitgen/LineOut.java
index 489033e..d58f211 100644
--- a/src/com/jsyn/unitgen/LineOut.java
+++ b/src/com/jsyn/unitgen/LineOut.java
@@ -4,9 +4,9 @@
* 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.
@@ -20,7 +20,7 @@ import com.jsyn.ports.UnitInputPort;
/**
* Input audio is sent to the external audio output device.
- *
+ *
* @author Phil Burk (C) 2009 Mobileer Inc
*/
public class LineOut extends UnitGenerator implements UnitSink {
diff --git a/src/com/jsyn/unitgen/LinearRamp.java b/src/com/jsyn/unitgen/LinearRamp.java
index 438adf6..cad53d5 100644
--- a/src/com/jsyn/unitgen/LinearRamp.java
+++ b/src/com/jsyn/unitgen/LinearRamp.java
@@ -4,9 +4,9 @@
* 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.
@@ -26,7 +26,7 @@ import com.jsyn.ports.UnitVariablePort;
* value toward the value of input. An internal phase value will go from 0.0 to 1.0 at a rate
* controlled by time. When the internal phase reaches 1.0, the output will equal input.
* <P>
- *
+ *
* @author (C) 1997 Phil Burk, SoftSynth.com
* @see ExponentialRamp
* @see AsymptoticRamp
@@ -52,33 +52,41 @@ public class LinearRamp extends UnitFilter {
public void generate(int start, int limit) {
double[] outputs = output.getValues();
double currentInput = input.getValues()[0];
- double currentTime = time.getValues()[0];
double currentValue = current.getValue();
- if (currentTime != timeHeld) {
- rate = convertTimeToRate(currentTime);
- timeHeld = currentTime;
- }
-
- /* If input has changed, start new segment */
- if (currentInput != target) /*
- * Equality check is OK because we set them exactly equal below.
- */
+ // If input has changed, start new segment.
+ // Equality check is OK because we set them exactly equal below.
+ if (currentInput != target)
{
source = currentValue;
phase = 0.0;
target = currentInput;
}
- for (int i = start; i < limit; i++) {
- if (phase < 1.0) {
- /* Interpolate current. */
- currentValue = source + (phase * (target - source));
- phase += rate;
- } else {
- currentValue = target;
+ if (currentValue == target) {
+ // at end of ramp
+ for (int i = start; i < limit; i++) {
+ outputs[i] = currentValue;
+ }
+ } else {
+ // in middle of ramp
+ double currentTime = time.getValues()[0];
+ // Has time changed?
+ if (currentTime != timeHeld) {
+ rate = convertTimeToRate(currentTime);
+ timeHeld = currentTime;
+ }
+
+ for (int i = start; i < limit; i++) {
+ if (phase < 1.0) {
+ /* Interpolate current. */
+ currentValue = source + (phase * (target - source));
+ phase += rate;
+ } else {
+ currentValue = target;
+ }
+ outputs[i] = currentValue;
}
- outputs[i] = currentValue;
}
current.setValue(currentValue);
diff --git a/src/com/jsyn/unitgen/MorphingOscillatorBL.java b/src/com/jsyn/unitgen/MorphingOscillatorBL.java
new file mode 100644
index 0000000..7ca440d
--- /dev/null
+++ b/src/com/jsyn/unitgen/MorphingOscillatorBL.java
@@ -0,0 +1,72 @@
+/*
+ * 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.unitgen;
+
+import com.jsyn.engine.MultiTable;
+import com.jsyn.ports.UnitInputPort;
+
+/**
+ * Oscillator that can change its shape from sine to sawtooth to pulse.
+ *
+ * @author Phil Burk (C) 2016 Mobileer Inc
+ */
+public class MorphingOscillatorBL extends PulseOscillatorBL {
+ /**
+ * Controls the shape of the waveform.
+ * The shape varies continuously from a sine wave at -1.0,
+ * to a sawtooth at 0.0 to a pulse wave at 1.0.
+ */
+ public UnitInputPort shape;
+
+ public MorphingOscillatorBL() {
+ addPort(shape = new UnitInputPort("Shape"));
+ shape.setMinimum(-1.0);
+ shape.setMaximum(1.0);
+ }
+
+ @Override
+ protected double generateBL(MultiTable multiTable, double currentPhase,
+ double positivePhaseIncrement, double flevel, int i) {
+ double[] shapes = shape.getValues();
+ double shape = shapes[i];
+
+ if (shape < 0.0) {
+ // Squeeze flevel towards the pure sine table.
+ flevel += flevel * shape;
+ return multiTable.calculateSawtooth(currentPhase, positivePhaseIncrement, flevel);
+ } else {
+ double[] widths = width.getValues();
+ double width = widths[i];
+ width = (width > 0.999) ? 0.999 : ((width < -0.999) ? -0.999 : width);
+
+ double val1 = multiTable.calculateSawtooth(currentPhase, positivePhaseIncrement, flevel);
+ // Generate second sawtooth so we can add them together.
+ double phase2 = currentPhase + 1.0 - width; // 180 degrees out of phase
+ if (phase2 >= 1.0) {
+ phase2 -= 2.0;
+ }
+ double val2 = multiTable.calculateSawtooth(phase2, positivePhaseIncrement, flevel);
+
+ /*
+ * Need to adjust amplitude based on positive phaseInc. little less than half at
+ * Nyquist/2.0!
+ */
+ double scale = 1.0 - positivePhaseIncrement;
+ return scale * (val1 - ((val2 + width) * shape)); // apply shape morphing
+ }
+ }
+}
diff --git a/src/com/jsyn/unitgen/PitchToFrequency.java b/src/com/jsyn/unitgen/PitchToFrequency.java
new file mode 100644
index 0000000..9086749
--- /dev/null
+++ b/src/com/jsyn/unitgen/PitchToFrequency.java
@@ -0,0 +1,26 @@
+package com.jsyn.unitgen;
+
+import com.softsynth.math.AudioMath;
+
+public class PitchToFrequency extends PowerOfTwo {
+
+ public PitchToFrequency() {
+ input.setup(0.0, 60.0, 127.0);
+ }
+
+ /**
+ * Convert from MIDI pitch to an octave offset from Concert A.
+ */
+ @Override
+ public double adjustInput(double in) {
+ return (in - AudioMath.CONCERT_A_PITCH) * (1.0/12.0);
+ }
+
+ /**
+ * Convert scaler to a frequency relative to Concert A.
+ */
+ @Override
+ public double adjustOutput(double out) {
+ return out * AudioMath.getConcertAFrequency();
+ }
+}
diff --git a/src/com/jsyn/unitgen/PowerOfTwo.java b/src/com/jsyn/unitgen/PowerOfTwo.java
index 5f1b4cd..5916860 100644
--- a/src/com/jsyn/unitgen/PowerOfTwo.java
+++ b/src/com/jsyn/unitgen/PowerOfTwo.java
@@ -4,9 +4,9 @@
* 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.
@@ -22,18 +22,23 @@ import com.jsyn.ports.UnitOutputPort;
/**
* output = (2.0^input) This is useful for converting a pitch modulation value into a frequency
* scaler. An input value of +1.0 will output 2.0 for an octave increase. An input value of -1.0
- * will output 0.5 for an octave decrease. This implementation uses a table lookup to optimize for
+ * will output 0.5 for an octave decrease.
+ *
+ * This implementation uses a table lookup to optimize for
* speed. It is accurate enough for tuning. It also checks to see if the current input value is the
* same as the previous input value. If so then it reuses the previous computed value.
- *
+ *
* @author Phil Burk (C) 2010 Mobileer Inc
*/
public class PowerOfTwo extends UnitGenerator {
+ /**
+ * Offset in octaves.
+ */
public UnitInputPort input;
public UnitOutputPort output;
private static double[] table;
- private static final int NUM_VALUES = 1024;
+ private static final int NUM_VALUES = 2048;
// Cached computation.
private double lastInput = 0.0;
private double lastOutput = 1.0;
@@ -61,39 +66,43 @@ public class PowerOfTwo extends UnitGenerator {
double[] inputs = input.getValues();
double[] outputs = output.getValues();
- if (true) {
- for (int i = start; i < limit; i++) {
- double in = inputs[i];
- // Can we reuse a previously computed value?
- if (in == lastInput) {
- outputs[i] = lastOutput;
- } else {
- int octave = (int) Math.floor(in);
- double normal = in - octave;
- // Do table lookup.
- double findex = normal * NUM_VALUES;
- int index = (int) findex;
- double fraction = findex - index;
- double value = table[index] + (fraction * (table[index + 1] - table[index]));
+ for (int i = start; i < limit; i++) {
+ double in = inputs[i];
+ // Can we reuse a previously computed value?
+ if (in == lastInput) {
+ outputs[i] = lastOutput;
+ } else {
+ lastInput = in;
+ double adjustedInput = adjustInput(in);
+ int octave = (int) Math.floor(adjustedInput);
+ double normal = adjustedInput - octave;
+ // Do table lookup.
+ double findex = normal * NUM_VALUES;
+ int index = (int) findex;
+ double fraction = findex - index;
+ double value = table[index] + (fraction * (table[index + 1] - table[index]));
- // Adjust for octave.
- while (octave > 0) {
- octave -= 1;
- value *= 2.0;
- }
- while (octave < 0) {
- octave += 1;
- value *= 0.5;
- }
- outputs[i] = value;
- lastInput = in;
- lastOutput = value;
+ // Adjust for octave.
+ while (octave > 0) {
+ octave -= 1;
+ value *= 2.0;
}
- }
- } else {
- for (int i = start; i < limit; i++) {
- outputs[i] = Math.pow(2.0, inputs[i]);
+ while (octave < 0) {
+ octave += 1;
+ value *= 0.5;
+ }
+ double adjustedOutput = adjustOutput(value);
+ outputs[i] = adjustedOutput;
+ lastOutput = adjustedOutput;
}
}
}
+
+ public double adjustInput(double in) {
+ return in;
+ }
+
+ public double adjustOutput(double out) {
+ return out;
+ }
}
diff --git a/src/com/jsyn/unitgen/PulseOscillatorBL.java b/src/com/jsyn/unitgen/PulseOscillatorBL.java
index 43fe27b..c0e234c 100644
--- a/src/com/jsyn/unitgen/PulseOscillatorBL.java
+++ b/src/com/jsyn/unitgen/PulseOscillatorBL.java
@@ -4,9 +4,9 @@
* 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.
@@ -20,11 +20,15 @@ import com.jsyn.engine.MultiTable;
import com.jsyn.ports.UnitInputPort;
/**
- * Pulse oscillator that uses two band limited sawtooth oscillators.
- *
+ * Pulse oscillator that uses two band limited sawtooth waveforms.
+ *
* @author Phil Burk (C) 2009 Mobileer Inc
*/
public class PulseOscillatorBL extends SawtoothOscillatorBL {
+ /** Controls the duty cycle of the pulse waveform.
+ * The width varies from -1.0 to +1.0.
+ * When width is zero the output is a square wave.
+ */
public UnitInputPort width;
public PulseOscillatorBL() {
@@ -48,7 +52,7 @@ public class PulseOscillatorBL extends SawtoothOscillatorBL {
double val2 = multiTable.calculateSawtooth(phase2, positivePhaseIncrement, flevel);
/*
- * Need to adjust amplitude based on positive phaseInc. little less than half at
+ * Need to adjust amplitude based on positive phaseInc and width. little less than half at
* Nyquist/2.0!
*/
double scale = 1.0 - positivePhaseIncrement;
diff --git a/src/com/jsyn/unitgen/SawtoothOscillatorDPW.java b/src/com/jsyn/unitgen/SawtoothOscillatorDPW.java
index 6868c15..27d0c5a 100644
--- a/src/com/jsyn/unitgen/SawtoothOscillatorDPW.java
+++ b/src/com/jsyn/unitgen/SawtoothOscillatorDPW.java
@@ -4,9 +4,9 @@
* 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.
@@ -20,7 +20,7 @@ package com.jsyn.unitgen;
* Sawtooth DPW oscillator (a sawtooth with reduced aliasing).
* Based on a paper by Antti Huovilainen and Vesa Valimaki:
* http://www.scribd.com/doc/33863143/New-Approaches-to-Digital-Subtractive-Synthesis
- *
+ *
* @author Phil Burk and Lisa Tolentino (C) 2009 Mobileer Inc
*/
public class SawtoothOscillatorDPW extends UnitOscillator {
diff --git a/src/com/jsyn/unitgen/SquareOscillatorBL.java b/src/com/jsyn/unitgen/SquareOscillatorBL.java
index cfe8541..cb9e141 100644
--- a/src/com/jsyn/unitgen/SquareOscillatorBL.java
+++ b/src/com/jsyn/unitgen/SquareOscillatorBL.java
@@ -4,9 +4,9 @@
* 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.
@@ -21,7 +21,7 @@ import com.jsyn.engine.MultiTable;
/**
* Band-limited square wave oscillator. This requires more CPU than a SquareOscillator but is less
* noisy at high frequencies.
- *
+ *
* @author Phil Burk (C) 2009 Mobileer Inc
*/
public class SquareOscillatorBL extends SawtoothOscillatorBL {
@@ -32,8 +32,9 @@ public class SquareOscillatorBL extends SawtoothOscillatorBL {
/* Generate second sawtooth so we can add them together. */
double phase2 = currentPhase + 1.0; /* 180 degrees out of phase. */
- if (phase2 >= 1.0)
+ if (phase2 >= 1.0) {
phase2 -= 2.0;
+ }
double val2 = multiTable.calculateSawtooth(phase2, positivePhaseIncrement, flevel);
/*
diff --git a/src/com/jsyn/unitgen/TunableFilter.java b/src/com/jsyn/unitgen/TunableFilter.java
index 1724ec1..31f6631 100644
--- a/src/com/jsyn/unitgen/TunableFilter.java
+++ b/src/com/jsyn/unitgen/TunableFilter.java
@@ -4,9 +4,9 @@
* 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.
@@ -15,7 +15,7 @@
*/
/**
* Aug 26, 2009
- * com.jsyn.engine.units.TunableFilter.java
+ * com.jsyn.engine.units.TunableFilter.java
*/
package com.jsyn.unitgen;
@@ -24,13 +24,13 @@ import com.jsyn.ports.UnitInputPort;
/**
* A UnitFilter with a frequency port.
- *
+ *
* @author Phil Burk (C) 2009 Mobileer Inc Translated from 'C' to Java by Lisa
* Tolenti.
*/
public abstract class TunableFilter extends UnitFilter {
- private static final double DEFAULT_FREQUENCY = 400;
+ static final double DEFAULT_FREQUENCY = 400;
public UnitInputPort frequency;
public TunableFilter() {
diff --git a/src/com/jsyn/unitgen/UnitGenerator.java b/src/com/jsyn/unitgen/UnitGenerator.java
index a9a7459..f8278ae 100644
--- a/src/com/jsyn/unitgen/UnitGenerator.java
+++ b/src/com/jsyn/unitgen/UnitGenerator.java
@@ -36,6 +36,20 @@ import com.softsynth.shared.time.TimeStamp;
*/
public abstract class UnitGenerator {
protected static final double VERY_SMALL_FLOAT = 1.0e-26;
+
+ // Some common port names.
+ public static final String PORT_NAME_INPUT = "Input";
+ public static final String PORT_NAME_OUTPUT = "Output";
+ public static final String PORT_NAME_PHASE = "Phase";
+ public static final String PORT_NAME_FREQUENCY = "Frequency";
+ public static final String PORT_NAME_FREQUENCY_SCALER = "FreqScaler";
+ public static final String PORT_NAME_AMPLITUDE = "Amplitude";
+ public static final String PORT_NAME_PAN = "Pan";
+ public static final String PORT_NAME_TIME = "Time";
+ public static final String PORT_NAME_CUTOFF = "Cutoff";
+ public static final String PORT_NAME_PRESSURE = "Pressure";
+ public static final String PORT_NAME_TIMBRE = "Timbre";
+
public static final double FALSE = 0.0;
public static final double TRUE = 1.0;
protected SynthesisEngine synthesisEngine;
@@ -75,6 +89,11 @@ public abstract class UnitGenerator {
addPort(port);
}
+ /**
+ * Case-insensitive search for a port by name.
+ * @param portName
+ * @return matching port or null
+ */
public UnitPort getPortByName(String portName) {
return ports.get(portName.toLowerCase());
}
@@ -141,8 +160,7 @@ public abstract class UnitGenerator {
if (halfLife < (2.0 * getFramePeriod())) {
return 1.0;
} else {
- // Strangely enough, this code is valid for both PeakFollower
- // and AsymptoticRamp.
+ // Oddly enough, this code is valid for both PeakFollower and AsymptoticRamp.
return 1.0 - Math.pow(0.5, 1.0 / (halfLife * getSynthesisEngine().getFrameRate()));
}
}
@@ -150,10 +168,11 @@ public abstract class UnitGenerator {
protected double incrementWrapPhase(double currentPhase, double phaseIncrement) {
currentPhase += phaseIncrement;
- if (currentPhase >= 1.0)
+ if (currentPhase >= 1.0) {
currentPhase -= 2.0;
- else if (currentPhase < -1.0)
+ } else if (currentPhase < -1.0) {
currentPhase += 2.0;
+ }
return currentPhase;
}
@@ -285,6 +304,11 @@ public abstract class UnitGenerator {
getSynthesisEngine().stopUnit(this, timeStamp);
}
+ /**
+ * @deprecated ignored, frameRate comes from the SynthesisEngine
+ * @param rate
+ */
+ @Deprecated
public void setFrameRate(int rate) {
this.frameRate = rate;
this.framePeriod = 1.0 / rate;
diff --git a/src/com/jsyn/unitgen/UnitOscillator.java b/src/com/jsyn/unitgen/UnitOscillator.java
index 4c02f09..5d4c6fa 100644
--- a/src/com/jsyn/unitgen/UnitOscillator.java
+++ b/src/com/jsyn/unitgen/UnitOscillator.java
@@ -4,9 +4,9 @@
* 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.
@@ -23,7 +23,7 @@ import com.softsynth.shared.time.TimeStamp;
/**
* Base class for all oscillators.
- *
+ *
* @author Phil Burk (C) 2009 Mobileer Inc
*/
public abstract class UnitOscillator extends UnitGenerator implements UnitVoice {
@@ -34,15 +34,15 @@ public abstract class UnitOscillator extends UnitGenerator implements UnitVoice
public UnitOutputPort output;
public static final double DEFAULT_FREQUENCY = 440.0;
- public static final double DEFAULT_AMPLITUDE = 0x7FFF / (double) 0x8000;
+ public static final double DEFAULT_AMPLITUDE = 1.0;
/* Define Unit Ports used by connect() and set(). */
public UnitOscillator() {
- addPort(frequency = new UnitInputPort("Frequency"));
+ addPort(frequency = new UnitInputPort(PORT_NAME_FREQUENCY));
frequency.setup(40.0, DEFAULT_FREQUENCY, 8000.0);
- addPort(amplitude = new UnitInputPort("Amplitude", DEFAULT_AMPLITUDE));
- addPort(phase = new UnitVariablePort("Phase"));
- addPort(output = new UnitOutputPort("Output"));
+ addPort(amplitude = new UnitInputPort(PORT_NAME_AMPLITUDE, DEFAULT_AMPLITUDE));
+ addPort(phase = new UnitVariablePort(PORT_NAME_PHASE));
+ addPort(output = new UnitOutputPort(PORT_NAME_OUTPUT));
}
/**
diff --git a/src/com/jsyn/util/MultiChannelSynthesizer.java b/src/com/jsyn/util/MultiChannelSynthesizer.java
new file mode 100644
index 0000000..c2d0e86
--- /dev/null
+++ b/src/com/jsyn/util/MultiChannelSynthesizer.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright 2016 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.util;
+
+import com.jsyn.Synthesizer;
+import com.jsyn.engine.SynthesisEngine;
+import com.jsyn.midi.MidiConstants;
+import com.jsyn.ports.UnitInputPort;
+import com.jsyn.ports.UnitOutputPort;
+import com.jsyn.unitgen.ExponentialRamp;
+import com.jsyn.unitgen.Multiply;
+import com.jsyn.unitgen.Pan;
+import com.jsyn.unitgen.LinearRamp;
+import com.jsyn.unitgen.PowerOfTwo;
+import com.jsyn.unitgen.SineOscillator;
+import com.jsyn.unitgen.TwoInDualOut;
+import com.jsyn.unitgen.UnitGenerator;
+import com.jsyn.unitgen.UnitOscillator;
+import com.jsyn.unitgen.UnitVoice;
+import com.softsynth.math.AudioMath;
+import com.softsynth.shared.time.TimeStamp;
+
+/**
+ * General purpose synthesizer with "channels"
+ * that could be used to implement a MIDI synthesizer.
+ *
+ * Each channel has:
+ * <pre><code>
+ * lfo -> pitchToLinear -> [VOICES] -> volume* -> panner
+ * bend --/
+ * </code></pre>
+ *
+ * Note: this class is experimental and subject to change.
+ *
+ * @author Phil Burk (C) 2016 Mobileer Inc
+ */
+public class MultiChannelSynthesizer {
+ private Synthesizer synth;
+ private TwoInDualOut outputUnit;
+ private ChannelContext[] channels;
+
+ private class ChannelContext {
+ private VoiceDescription voiceDescription;
+ private UnitVoice[] voices;
+ private VoiceAllocator allocator;
+ private UnitOscillator lfo;
+ private PowerOfTwo pitchToLinear;
+ private LinearRamp timbreRamp;
+ private LinearRamp pressureRamp;
+ private ExponentialRamp volumeRamp;
+ private Multiply volumeMultiplier;
+ private Pan panner;
+ private double vibratoRate = 5.0;
+ private double bendRangeOctaves = 24.0 / 12.0;
+// private double bendRangeOctaves = 0.0 / 12.0;
+ private int presetIndex;
+
+ void setup(int numVoices, VoiceDescription voiceDescription) {
+ this.voiceDescription = voiceDescription;
+ synth.add(pitchToLinear = new PowerOfTwo());
+ synth.add(lfo = new SineOscillator()); // TODO use a MorphingOscillator or switch
+ // between S&H etc.
+ // Use a ramp to smooth out the timbre changes.
+ // This helps reduce pops from changing filter cutoff too abruptly.
+ synth.add(timbreRamp = new LinearRamp());
+ timbreRamp.time.set(0.02);
+ synth.add(pressureRamp = new LinearRamp());
+ pressureRamp.time.set(0.02);
+ synth.add(volumeRamp = new ExponentialRamp());
+ volumeRamp.input.set(1.0);
+ volumeRamp.time.set(0.02);
+ synth.add(volumeMultiplier = new Multiply());
+ synth.add(panner = new Pan());
+
+ pitchToLinear.input.setValueAdded(true); // so we can sum pitch bend
+ lfo.output.connect(pitchToLinear.input);
+ lfo.amplitude.set(0.0);
+ lfo.frequency.set(vibratoRate);
+
+ voices = new UnitVoice[numVoices];
+ for (int i = 0; i < numVoices; i++) {
+ UnitVoice voice = voiceDescription.createUnitVoice();
+ UnitGenerator ugen = voice.getUnitGenerator();
+ synth.add(ugen);
+
+ // Hook up some channel controllers to standard ports on the voice.
+ UnitInputPort freqMod = (UnitInputPort) ugen
+ .getPortByName(UnitGenerator.PORT_NAME_FREQUENCY_SCALER);
+ if (freqMod != null) {
+ pitchToLinear.output.connect(freqMod);
+ }
+ UnitInputPort timbrePort = (UnitInputPort) ugen
+ .getPortByName(UnitGenerator.PORT_NAME_TIMBRE);
+ if (timbrePort != null) {
+ timbreRamp.output.connect(timbrePort);
+ timbreRamp.input.setup(timbrePort);
+ }
+ UnitInputPort pressurePort = (UnitInputPort) ugen
+ .getPortByName(UnitGenerator.PORT_NAME_PRESSURE);
+ if (pressurePort != null) {
+ pressureRamp.output.connect(pressurePort);
+ pressureRamp.input.setup(pressurePort);
+ }
+ voice.getOutput().connect(volumeMultiplier.inputA); // mono mix all the voices
+ voices[i] = voice;
+ }
+
+ volumeRamp.output.connect(volumeMultiplier.inputB);
+ volumeMultiplier.output.connect(panner.input);
+ panner.output.connect(0, outputUnit.inputA, 0); // Use MultiPassthrough
+ panner.output.connect(1, outputUnit.inputB, 0);
+
+ allocator = new VoiceAllocator(voices);
+ }
+
+ void programChange(int program) {
+ int programWrapped = program % voiceDescription.getPresetCount();
+ String name = voiceDescription.getPresetNames()[programWrapped];
+ System.out.println("Preset[" + program + "] = " + name);
+ presetIndex = programWrapped;
+ }
+
+ void noteOff(int noteNumber, int velocity) {
+ allocator.noteOff(noteNumber, synth.createTimeStamp());
+ }
+
+ void noteOn(int noteNumber, int velocity) {
+ double frequency = AudioMath.pitchToFrequency(noteNumber);
+ double amplitude = velocity / (4 * 128.0);
+ TimeStamp timeStamp = synth.createTimeStamp();
+ allocator.usePreset(presetIndex, timeStamp);
+ // System.out.println("noteOn(noteNumber) -> " + frequency + " Hz");
+ allocator.noteOn(noteNumber, frequency, amplitude, timeStamp);
+ }
+
+ public void setPitchBend(double offset) {
+ pitchToLinear.input.set(bendRangeOctaves * offset);
+ }
+
+ public void setBendRange(double semitones) {
+ bendRangeOctaves = semitones / 12.0;
+ }
+
+ public void setVibratoDepth(double semitones) {
+ lfo.amplitude.set(semitones);
+ }
+
+ public void setVolume(double volume) {
+ double min = SynthesisEngine.DB96;
+ double max = 1.0;
+ double ratio = max / min;
+ double value = min * Math.pow(ratio, volume);
+ volumeRamp.input.set(value);
+ }
+
+ public void setPan(double pan) {
+ panner.pan.set(pan);
+ }
+
+ /*
+ * @param timbre normalized 0 to 1
+ */
+ public void setTimbre(double timbre) {
+ double min = timbreRamp.input.getMinimum();
+ double max = timbreRamp.input.getMaximum();
+ double value = min + (timbre * (max - min));
+ timbreRamp.input.set(value);
+ }
+
+ /*
+ * @param pressure normalized 0 to 1
+ */
+ public void setPressure(double pressure) {
+ double min = pressureRamp.input.getMinimum();
+ double max = pressureRamp.input.getMaximum();
+ double ratio = max / min;
+ double value = min * Math.pow(ratio, pressure);
+ pressureRamp.input.set(value);
+ }
+ }
+
+ /**
+ * Construct a synthesizer with a maximum of 16 channels like MIDI.
+ */
+ public MultiChannelSynthesizer() {
+ this(MidiConstants.MAX_CHANNELS);
+ }
+
+
+ public MultiChannelSynthesizer(int maxChannels) {
+ channels = new ChannelContext[maxChannels];
+ for (int i = 0; i < channels.length; i++) {
+ channels[i] = new ChannelContext();
+ }
+ }
+
+ /**
+ * Specify a VoiceDescription to use with multiple channels.
+ *
+ * @param synth
+ * @param startChannel channel index is zero based
+ * @param numChannels
+ * @param voicesPerChannel
+ * @param voiceDescription
+ */
+ public void setup(Synthesizer synth, int startChannel, int numChannels, int voicesPerChannel,
+ VoiceDescription voiceDescription) {
+ this.synth = synth;
+ if (outputUnit == null) {
+ synth.add(outputUnit = new TwoInDualOut());
+ }
+ for (int i = 0; i < numChannels; i++) {
+ channels[startChannel + i].setup(voicesPerChannel, voiceDescription);
+ }
+ }
+
+ public void programChange(int channel, int program) {
+ ChannelContext channelContext = channels[channel];
+ channelContext.programChange(program);
+ }
+
+ public void noteOff(int channel, int noteNumber, int velocity) {
+ ChannelContext channelContext = channels[channel];
+ channelContext.noteOff(noteNumber, velocity);
+ }
+
+ public void noteOn(int channel, int noteNumber, int velocity) {
+ ChannelContext channelContext = channels[channel];
+ channelContext.noteOn(noteNumber, velocity);
+ }
+
+ /**
+ * Set a pitch offset that will be scaled by the range for the channel.
+ *
+ * @param channel
+ * @param offset ranges from -1.0 to +1.0
+ */
+ public void setPitchBend(int channel, double offset) {
+ ChannelContext channelContext = channels[channel];
+ channelContext.setPitchBend(offset);
+ }
+
+ public void setBendRange(int channel, double semitones) {
+ ChannelContext channelContext = channels[channel];
+ channelContext.setBendRange(semitones);
+ }
+
+ public void setPressure(int channel, double pressure) {
+ ChannelContext channelContext = channels[channel];
+ channelContext.setPressure(pressure);
+ }
+
+ public void setVibratoDepth(int channel, double semitones) {
+ ChannelContext channelContext = channels[channel];
+ channelContext.setVibratoDepth(semitones);
+ }
+
+ public void setTimbre(int channel, double timbre) {
+ ChannelContext channelContext = channels[channel];
+ channelContext.setTimbre(timbre);
+ }
+
+ /**
+ * Set volume for entire channel.
+ *
+ * @param channel
+ * @param volume normalized between 0.0 and 1.0
+ */
+ public void setVolume(int channel, double volume) {
+ ChannelContext channelContext = channels[channel];
+ channelContext.setVolume(volume);
+ }
+
+ /**
+ * Pan from left to right.
+ *
+ * @param channel
+ * @param offset ranges from -1.0 to +1.0
+ */
+ public void setPan(int channel, double pan) {
+ ChannelContext channelContext = channels[channel];
+ channelContext.setPan(pan);
+ }
+
+ public UnitOutputPort getOutput() {
+ return outputUnit.output;
+ }
+
+}
diff --git a/src/com/jsyn/util/PolyphonicInstrument.java b/src/com/jsyn/util/PolyphonicInstrument.java
index 8501554..2cba78f 100644
--- a/src/com/jsyn/util/PolyphonicInstrument.java
+++ b/src/com/jsyn/util/PolyphonicInstrument.java
@@ -4,9 +4,9 @@
* 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.
@@ -29,7 +29,7 @@ import com.softsynth.shared.time.TimeStamp;
/**
* The API for this class is likely to change. Please comment on its usefulness.
- *
+ *
* @author Phil Burk (C) 2011 Mobileer Inc
*/
@@ -56,6 +56,7 @@ public class PolyphonicInstrument extends Circuit implements UnitSource, Instrum
addPort(amplitude = mixer.inputB, "Amplitude");
amplitude.setup(0.0001, 0.4, 2.0);
+ exportAllInputPorts();
}
/**
@@ -81,11 +82,11 @@ public class PolyphonicInstrument extends Circuit implements UnitSource, Instrum
/**
* Create a UnitInputPort for the circuit that is connected to the named port on each voice
* through a PassThrough unit. This allows you to control all of the voices at once.
- *
+ *
* @param portName
* @see exportAllInputPorts
*/
- public void exportNamedInputPort(String portName) {
+ void exportNamedInputPort(String portName) {
UnitInputPort voicePort = null;
PassThrough fanout = new PassThrough();
for (UnitVoice voice : voices) {
@@ -103,7 +104,6 @@ public class PolyphonicInstrument extends Circuit implements UnitSource, Instrum
return mixer.output;
}
- // FIXME - no timestamp on UnitVoice
@Override
public void usePreset(int presetIndex) {
usePreset(presetIndex, getSynthesisEngine().createTimeStamp());
diff --git a/src/com/jsyn/util/VoiceAllocator.java b/src/com/jsyn/util/VoiceAllocator.java
index af37b91..f20f7a5 100644
--- a/src/com/jsyn/util/VoiceAllocator.java
+++ b/src/com/jsyn/util/VoiceAllocator.java
@@ -4,9 +4,9 @@
* 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.
@@ -25,7 +25,7 @@ import com.softsynth.shared.time.TimeStamp;
* Allocate voices based on an integer tag. The tag could, for example, be a MIDI note number. Or a
* tag could be an int that always increments. Use the same tag to refer to a voice for noteOn() and
* noteOff(). If no new voices are available then a voice in use will be stolen.
- *
+ *
* @author Phil Burk (C) 2011 Mobileer Inc
*/
public class VoiceAllocator implements Instrument {
@@ -33,12 +33,13 @@ public class VoiceAllocator implements Instrument {
private VoiceTracker[] trackers;
private long tick;
private Synthesizer synthesizer;
- private int presetIndex = -1;
+ private static final int UNASSIGNED_PRESET = -1;
+ private int mPresetIndex = UNASSIGNED_PRESET;
/**
* Create an allocator for the array of UnitVoices. The array must be full of instantiated
* UnitVoices that are connected to some kind of mixer.
- *
+ *
* @param voices
*/
public VoiceAllocator(UnitVoice[] voices) {
@@ -60,7 +61,7 @@ public class VoiceAllocator implements Instrument {
private class VoiceTracker {
UnitVoice voice;
int tag = -1;
- int presetIndex = -1;
+ int presetIndex = UNASSIGNED_PRESET;
long when;
boolean on;
@@ -121,7 +122,7 @@ public class VoiceAllocator implements Instrument {
* that tag. Next it will pick the oldest voice that is off. Next it will pick the oldest voice
* that is on. If you are using timestamps to play the voice in the future then you should use
* the noteOn() noteOff() and setPort() methods.
- *
+ *
* @param tag
* @return Voice that is most available.
*/
@@ -185,14 +186,35 @@ public class VoiceAllocator implements Instrument {
@Override
public void run() {
VoiceTracker voiceTracker = allocateTracker(tag);
- if (presetIndex != voiceTracker.presetIndex) {
- voiceTracker.voice.usePreset(presetIndex);
+ if (voiceTracker.presetIndex != mPresetIndex) {
+ voiceTracker.voice.usePreset(mPresetIndex);
+ voiceTracker.presetIndex = mPresetIndex;
}
voiceTracker.voice.noteOn(frequency, amplitude, getSynthesizer().createTimeStamp());
}
});
}
+ /**
+ * Play a note on the voice and associate it with the given tag. if needed a new voice will be
+ * allocated and an old voice may be turned off.
+ * Apply an operation to the voice.
+ */
+ public void noteOn(final int tag,
+ final double frequency,
+ final double amplitude,
+ final VoiceOperation operation,
+ TimeStamp timeStamp) {
+ getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() {
+ @Override
+ public void run() {
+ VoiceTracker voiceTracker = allocateTracker(tag);
+ operation.operate(voiceTracker.voice);
+ voiceTracker.voice.noteOn(frequency, amplitude, getSynthesizer().createTimeStamp());
+ }
+ });
+ }
+
/** Turn off the voice associated with the given tag if allocated. */
@Override
public void noteOff(final int tag, TimeStamp timeStamp) {
@@ -228,9 +250,7 @@ public class VoiceAllocator implements Instrument {
getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() {
@Override
public void run() {
- for (VoiceTracker tracker : trackers) {
- tracker.voice.usePreset(presetIndex);
- }
+ mPresetIndex = presetIndex;
}
});
}
diff --git a/src/com/jsyn/util/VoiceOperation.java b/src/com/jsyn/util/VoiceOperation.java
new file mode 100644
index 0000000..cd3b48e
--- /dev/null
+++ b/src/com/jsyn/util/VoiceOperation.java
@@ -0,0 +1,7 @@
+package com.jsyn.util;
+
+import com.jsyn.unitgen.UnitVoice;
+
+public interface VoiceOperation {
+ public void operate(UnitVoice voice);
+}