diff options
-rw-r--r-- | src/com/jsyn/JSyn.java | 6 | ||||
-rw-r--r-- | src/com/jsyn/data/DoubleTable.java | 12 | ||||
-rw-r--r-- | src/com/jsyn/midi/MidiSynthesizer.java | 20 | ||||
-rw-r--r-- | src/com/jsyn/unitgen/EdgeDetector.java | 13 | ||||
-rw-r--r-- | src/com/jsyn/util/MultiChannelSynthesizer.java | 60 | ||||
-rw-r--r-- | src/com/jsyn/util/PolyphonicInstrument.java | 2 | ||||
-rw-r--r-- | tests/com/jsyn/examples/HearSinePM.java | 8 | ||||
-rw-r--r-- | tests/com/jsyn/midi/TestMidiLoop.java | 98 |
8 files changed, 191 insertions, 28 deletions
diff --git a/src/com/jsyn/JSyn.java b/src/com/jsyn/JSyn.java index 7598305..8eed8a9 100644 --- a/src/com/jsyn/JSyn.java +++ b/src/com/jsyn/JSyn.java @@ -56,10 +56,10 @@ public class JSyn { // Update these for every release. private final static int VERSION_MAJOR = 16; private final static int VERSION_MINOR = 7; - private final static int VERSION_REVISION = 6; - public final static int BUILD_NUMBER = 460; + private final static int VERSION_REVISION = 8; + public final static int BUILD_NUMBER = 462; private final static long BUILD_TIME = new GregorianCalendar(2016, - GregorianCalendar.AUGUST, 9).getTime().getTime(); + GregorianCalendar.NOVEMBER, 30).getTime().getTime(); public final static String VERSION = VERSION_MAJOR + "." + VERSION_MINOR + "." + VERSION_REVISION; diff --git a/src/com/jsyn/data/DoubleTable.java b/src/com/jsyn/data/DoubleTable.java index 0a34a95..ca64c94 100644 --- a/src/com/jsyn/data/DoubleTable.java +++ b/src/com/jsyn/data/DoubleTable.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.exceptions.ChannelMismatchException; /** * Evaluate a Function by interpolating between the values in a table. This can be used for * wavetable lookup or waveshaping. - * + * * @author Phil Burk (C) 2010 Mobileer Inc */ public class DoubleTable implements Function { @@ -60,6 +60,10 @@ public class DoubleTable implements Function { table = new double[numFrames]; } + public int length() { + return table.length; + } + public void write(double[] data) { write(0, data, 0, data.length); } @@ -79,7 +83,7 @@ public class DoubleTable implements Function { /** * Treat the double array as a lookup table with a domain of -1.0 to 1.0. If the input is out of * range then the output will clip to the end values. - * + * * @param input * @return interpolated value from table */ diff --git a/src/com/jsyn/midi/MidiSynthesizer.java b/src/com/jsyn/midi/MidiSynthesizer.java index e011430..30204b0 100644 --- a/src/com/jsyn/midi/MidiSynthesizer.java +++ b/src/com/jsyn/midi/MidiSynthesizer.java @@ -16,8 +16,28 @@ package com.jsyn.midi; +import com.jsyn.instruments.DualOscillatorSynthVoice; import com.jsyn.util.MultiChannelSynthesizer; +/** + * Map MIDI messages into calls to a MultiChannelSynthesizer. + * Handles CONTROLLER_MOD_WHEEL, TIMBRE, VOLUME and PAN. + * Handles Bend Range RPN. + * + * <pre><code> + voiceDescription = DualOscillatorSynthVoice.getVoiceDescription(); + multiSynth = new MultiChannelSynthesizer(); + final int startChannel = 0; + multiSynth.setup(synth, startChannel, NUM_CHANNELS, VOICES_PER_CHANNEL, voiceDescription); + midiSynthesizer = new MidiSynthesizer(multiSynth); + // pass MIDI bytes + midiSynthesizer.onReceive(bytes, 0, bytes.length); + </code></pre> + * + * See the example UseMidiKeyboard.java + * + * @author Phil Burk (C) 2016 Mobileer Inc + */ public class MidiSynthesizer extends MessageParser { private MultiChannelSynthesizer multiSynth; diff --git a/src/com/jsyn/unitgen/EdgeDetector.java b/src/com/jsyn/unitgen/EdgeDetector.java index a5bff9d..e314f7d 100644 --- a/src/com/jsyn/unitgen/EdgeDetector.java +++ b/src/com/jsyn/unitgen/EdgeDetector.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,14 +20,13 @@ package com.jsyn.unitgen; * Output 1.0 if the input crosses from zero while rising. Otherwise output zero. The output is a * single sample wide impulse. This can be used with a Latch to implement a "sample and hold" * circuit. - * + * * @author (C) 1997-2010 Phil Burk, Mobileer Inc * @see Latch */ public class EdgeDetector extends UnitFilter { private double previous = 0.0; - /* Define Unit Ports used by connect() and set(). */ public EdgeDetector() { } @@ -37,12 +36,8 @@ public class EdgeDetector extends UnitFilter { double[] outputs = output.getValues(); for (int i = start; i < limit; i++) { - double value = 0.0; double in = inputs[i]; - if ((previous <= 0.0) && (in > 0.0)) { - value = 1.0; - } - outputs[i] = value; + outputs[i] = ((previous <= 0.0) && (in > 0.0)) ? 1.0 : 0.0; previous = in; } } diff --git a/src/com/jsyn/util/MultiChannelSynthesizer.java b/src/com/jsyn/util/MultiChannelSynthesizer.java index 6db0790..8027994 100644 --- a/src/com/jsyn/util/MultiChannelSynthesizer.java +++ b/src/com/jsyn/util/MultiChannelSynthesizer.java @@ -52,6 +52,8 @@ public class MultiChannelSynthesizer { private Synthesizer synth; private TwoInDualOut outputUnit; private ChannelContext[] channels; + private final static int MAX_VELOCITY = 127; + private double mMasterAmplitude = 0.25; private class ChannelGroupContext { private VoiceDescription voiceDescription; @@ -83,7 +85,6 @@ public class MultiChannelSynthesizer { private Pan panner; private double vibratoRate = 5.0; private double bendRangeOctaves = 2.0 / 12.0; -// private double bendRangeOctaves = 0.0 / 12.0; private int presetIndex; private ChannelGroupContext groupContext; VoiceOperation voiceOperation = new VoiceOperation() { @@ -120,7 +121,6 @@ public class MultiChannelSynthesizer { volumeMultiplier.output.connect(panner.input); panner.output.connect(0, outputUnit.inputA, 0); // Use MultiPassthrough panner.output.connect(1, outputUnit.inputB, 0); - } private void connectVoice(UnitVoice voice) { @@ -157,13 +157,12 @@ public class MultiChannelSynthesizer { presetIndex = programWrapped; } - void noteOff(int noteNumber, int velocity) { + void noteOff(int noteNumber, double amplitude) { groupContext.allocator.noteOff(noteNumber, synth.createTimeStamp()); } - void noteOn(int noteNumber, int velocity) { + void noteOn(int noteNumber, double amplitude) { double frequency = AudioMath.pitchToFrequency(noteNumber); - double amplitude = velocity / (4 * 128.0); TimeStamp timeStamp = synth.createTimeStamp(); //System.out.println("noteOn(noteNumber) -> " + frequency + " Hz"); groupContext.allocator.noteOn(noteNumber, frequency, amplitude, voiceOperation, timeStamp); @@ -257,14 +256,48 @@ public class MultiChannelSynthesizer { channelContext.programChange(program); } + + /** + * Turn off a note. + * @param channel + * @param noteNumber + * @param velocity between 0 and 127, will be scaled by masterAmplitude + */ public void noteOff(int channel, int noteNumber, int velocity) { + double amplitude = velocity * (1.0 / MAX_VELOCITY); + noteOff(channel, noteNumber, amplitude); + } + + /** + * Turn off a note. + * @param channel + * @param noteNumber + * @param amplitude between 0 and 1.0, will be scaled by masterAmplitude + */ + public void noteOff(int channel, int noteNumber, double amplitude) { ChannelContext channelContext = channels[channel]; - channelContext.noteOff(noteNumber, velocity); + channelContext.noteOff(noteNumber, amplitude * mMasterAmplitude); } + /** + * Turn on a note. + * @param channel + * @param noteNumber + * @param velocity between 0 and 127, will be scaled by masterAmplitude + */ public void noteOn(int channel, int noteNumber, int velocity) { + double amplitude = velocity * (1.0 / MAX_VELOCITY); + noteOn(channel, noteNumber, amplitude); + } + /** + * Turn on a note. + * @param channel + * @param noteNumber + * @param amplitude between 0 and 1.0, will be scaled by masterAmplitude + */ + public void noteOn(int channel, int noteNumber, double amplitude) { ChannelContext channelContext = channels[channel]; - channelContext.noteOn(noteNumber, velocity); + channelContext.noteOn(noteNumber, amplitude * mMasterAmplitude); } /** @@ -321,8 +354,21 @@ public class MultiChannelSynthesizer { channelContext.setPan(pan); } + /** + * @return stereo output port + */ public UnitOutputPort getOutput() { return outputUnit.output; } + /** + * Set amplitude for a single voice when the velocity is 127. + * @param masterAmplitude + */ + public void setMasterAmplitude(double masterAmplitude) { + mMasterAmplitude = masterAmplitude; + } + public double getMasterAmplitude() { + return mMasterAmplitude; + } } diff --git a/src/com/jsyn/util/PolyphonicInstrument.java b/src/com/jsyn/util/PolyphonicInstrument.java index 2cba78f..08460d0 100644 --- a/src/com/jsyn/util/PolyphonicInstrument.java +++ b/src/com/jsyn/util/PolyphonicInstrument.java @@ -86,7 +86,7 @@ public class PolyphonicInstrument extends Circuit implements UnitSource, Instrum * @param portName * @see exportAllInputPorts */ - void exportNamedInputPort(String portName) { + public void exportNamedInputPort(String portName) { UnitInputPort voicePort = null; PassThrough fanout = new PassThrough(); for (UnitVoice voice : voices) { diff --git a/tests/com/jsyn/examples/HearSinePM.java b/tests/com/jsyn/examples/HearSinePM.java index 2949605..1e19505 100644 --- a/tests/com/jsyn/examples/HearSinePM.java +++ b/tests/com/jsyn/examples/HearSinePM.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. @@ -39,7 +39,7 @@ import com.jsyn.unitgen.SineOscillatorPhaseModulated; /** * Play a tone using a phase modulated sinewave oscillator. Phase modulation (PM) is very similar to * frequency modulation (FM) but is easier to control. - * + * * @author Phil Burk (C) 2010 Mobileer Inc */ public class HearSinePM extends JApplet { @@ -64,7 +64,7 @@ public class HearSinePM extends JApplet { carrier.output.connect(0, lineOut.input, 0); carrier.output.connect(0, lineOut.input, 1); modulator.amplitude.setup(0.0, 1.0, 10.0); - carrier.amplitude.setup(0.0, 1.0, 1.0); + carrier.amplitude.setup(0.0, 0.25, 1.0); setupGUI(); } diff --git a/tests/com/jsyn/midi/TestMidiLoop.java b/tests/com/jsyn/midi/TestMidiLoop.java new file mode 100644 index 0000000..5837696 --- /dev/null +++ b/tests/com/jsyn/midi/TestMidiLoop.java @@ -0,0 +1,98 @@ +/* + * 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.midi; + +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 com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.devices.javasound.MidiDeviceTools; +import com.jsyn.instruments.DualOscillatorSynthVoice; +import com.jsyn.midi.MidiSynthesizer; +import com.jsyn.unitgen.LineOut; +import com.jsyn.util.MultiChannelSynthesizer; +import com.jsyn.util.VoiceDescription; + +/** + * Connect a USB MIDI Keyboard to the internal MIDI Synthesizer using JavaSound. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class TestMidiLoop { + + public static void main(String[] args) { + TestMidiLoop app = new TestMidiLoop(); + int result = 0; + try { + for (int i = 0; i < 3 && result == 0; i++) { + result = app.test(); + } + } catch (MidiUnavailableException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.print("Test finished."); + System.exit((result == 0) ? 0 : 1); + } + + // Write a Receiver to get the messages from a Transmitter. + class CustomReceiver implements Receiver { + @Override + public void close() { + System.out.print("Receiver.close() was called."); + } + + @Override + public void send(MidiMessage message, long timeStamp) { + byte[] bytes = message.getMessage(); + System.out.println("Got " + bytes.length + " bytes."); + } + } + + public int test() throws MidiUnavailableException, IOException, InterruptedException { + + int result = -1; + 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; + Thread.sleep(4000); + System.out.println("Close the keyboard. It may not work after this according to the docs!"); + keyboard.close(); + } else { + System.out.println("Could not find a keyboard."); + } + return result; + } + + +} |