diff options
author | RubbaBoy <[email protected]> | 2020-07-06 02:33:28 -0400 |
---|---|---|
committer | Phil Burk <[email protected]> | 2020-10-30 11:19:34 -0700 |
commit | 46888fae6eb7b1dd386f7af7d101ead99ae61981 (patch) | |
tree | 8969bbfd68d2fb5c0d8b86da49ec2eca230a72ab /examples | |
parent | c51e92e813dd481603de078f0778e1f75db2ab05 (diff) |
Restructured project, added gradle, JUnit, logger, and more
Added Gradle (and removed ant), modernized testing via the JUnit framework, moved standalone examples from the tests directory to a separate module, removed sparsely used Java logger and replaced it with SLF4J. More work could be done, however this is a great start to greatly improving the health of the codebase.
Diffstat (limited to 'examples')
41 files changed, 5153 insertions, 0 deletions
diff --git a/examples/build.gradle b/examples/build.gradle new file mode 100644 index 0000000..b3a9ef1 --- /dev/null +++ b/examples/build.gradle @@ -0,0 +1,14 @@ +apply plugin: 'java' + +version = '17.0.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + implementation project(':') + + implementation 'org.slf4j:slf4j-api:1.7.25' + implementation 'org.slf4j:slf4j-log4j12:1.7.25' +} diff --git a/examples/src/main/java/com/jsyn/examples/AudioPassThrough.java b/examples/src/main/java/com/jsyn/examples/AudioPassThrough.java new file mode 100644 index 0000000..383dba4 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/AudioPassThrough.java @@ -0,0 +1,74 @@ +/* + * 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.devices.AudioDeviceManager; +import com.jsyn.unitgen.LineIn; +import com.jsyn.unitgen.LineOut; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Pass audio input to audio output. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class AudioPassThrough { + + private static final Logger LOGGER = LoggerFactory.getLogger(AudioPassThrough.class); + + private void test() { + LineIn lineIn; + LineOut lineOut; + + // Create a context for the synthesizer. + var synth = JSyn.createSynthesizer(); + // Add an audio input. + synth.add(lineIn = new LineIn()); + // Add an audio output. + synth.add(lineOut = new LineOut()); + // Connect the input to the output. + lineIn.output.connect(0, lineOut.input, 0); + lineIn.output.connect(1, lineOut.input, 1); + + // Both stereo. + int numInputChannels = 2; + int numOutputChannels = 2; + synth.start(44100, AudioDeviceManager.USE_DEFAULT_DEVICE, numInputChannels, + AudioDeviceManager.USE_DEFAULT_DEVICE, numOutputChannels); + + // We only need to start the LineOut. It will pull data from the LineIn. + lineOut.start(); + LOGGER.debug("Audio passthrough started."); + // Sleep a while. + try { + double time = synth.getCurrentTime(); + // Sleep for a few seconds. + synth.sleepUntil(time + 8.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + // Stop everything. + synth.stop(); + LOGGER.debug("All done."); + } + + public static void main(String[] args) { + new AudioPassThrough().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/ChebyshevSong.java b/examples/src/main/java/com/jsyn/examples/ChebyshevSong.java new file mode 100644 index 0000000..6acd894 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/ChebyshevSong.java @@ -0,0 +1,185 @@ +/* + * 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.examples; + +import java.awt.BorderLayout; + +import javax.swing.JApplet; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.instruments.WaveShapingVoice; +import com.jsyn.scope.AudioScope; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.unitgen.Add; +import com.jsyn.unitgen.LineOut; +import com.jsyn.util.PseudoRandom; +import com.jsyn.util.VoiceAllocator; +import com.softsynth.math.AudioMath; +import com.softsynth.shared.time.TimeStamp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/*************************************************************** + * Play notes using a WaveShapingVoice. Allocate the notes using a VoiceAllocator. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class ChebyshevSong extends JApplet implements Runnable { + + private static final Logger LOGGER = LoggerFactory.getLogger(ChebyshevSong.class); + + private Synthesizer synth; + private Add mixer; + private LineOut lineOut; + private AudioScope scope; + private volatile boolean go = false; + private PseudoRandom pseudo = new PseudoRandom(); + private final static int MAX_VOICES = 8; + private final static int MAX_NOTES = 5; + private VoiceAllocator allocator; + private final static int scale[] = { + 0, 2, 4, 7, 9 + }; // pentatonic scale + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + ChebyshevSong applet = new ChebyshevSong(); + JAppletFrame frame = new JAppletFrame("ChebyshevSong", applet); + frame.setSize(640, 300); + frame.setVisible(true); + frame.test(); + } + + /* + * Setup synthesis. + */ + @Override + public void start() { + setLayout(new BorderLayout()); + + synth = JSyn.createSynthesizer(); + + // Use a submix so we can show it on the scope. + synth.add(mixer = new Add()); + synth.add(lineOut = new LineOut()); + + mixer.output.connect(0, lineOut.input, 0); + mixer.output.connect(0, lineOut.input, 1); + + WaveShapingVoice[] voices = new WaveShapingVoice[MAX_VOICES]; + for (int i = 0; i < MAX_VOICES; i++) { + WaveShapingVoice voice = new WaveShapingVoice(); + synth.add(voice); + voice.usePreset(0); + voice.getOutput().connect(mixer.inputA); + voices[i] = voice; + } + allocator = new VoiceAllocator(voices); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + lineOut.start(); + + // Use a scope to show the mixed output. + scope = new AudioScope(synth); + scope.addProbe(mixer.output); + scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); + scope.getView().setControlsVisible(false); + add(BorderLayout.CENTER, scope.getView()); + scope.start(); + + /* Synchronize Java display. */ + getParent().validate(); + getToolkit().sync(); + + // start thread that plays notes + Thread thread = new Thread(this); + go = true; + thread.start(); + + } + + @Override + public void stop() { + // tell song thread to finish + go = false; + removeAll(); + synth.stop(); + } + + double indexToFrequency(int index) { + int octave = index / scale.length; + int temp = index % scale.length; + int pitch = scale[temp] + (12 * octave); + return AudioMath.pitchToFrequency(pitch + 16); + } + + private void noteOff(double time, int noteNumber) { + allocator.noteOff(noteNumber, new TimeStamp(time)); + } + + private void noteOn(double time, int noteNumber) { + double frequency = indexToFrequency(noteNumber); + double amplitude = 0.1; + TimeStamp timeStamp = new TimeStamp(time); + allocator.noteOn(noteNumber, frequency, amplitude, timeStamp); + allocator.setPort(noteNumber, "Range", 0.7, synth.createTimeStamp()); + } + + @Override + public void run() { + // always choose a new song based on time&date + int savedSeed = (int) System.currentTimeMillis(); + // calculate tempo + double duration = 0.2; + // set time ahead of any system latency + double advanceTime = 0.5; + // time for next note to start + double nextTime = synth.getCurrentTime() + advanceTime; + // note is ON for half the duration + double onTime = duration / 2; + int beatIndex = 0; + try { + do { + // on every measure, maybe repeat previous pattern + if ((beatIndex & 7) == 0) { + if ((Math.random() < (1.0 / 2.0))) + pseudo.setSeed(savedSeed); + else if ((Math.random() < (1.0 / 2.0))) + savedSeed = pseudo.getSeed(); + } + + // Play a bunch of random notes in the scale. + int numNotes = pseudo.choose(MAX_NOTES); + for (int i = 0; i < numNotes; i++) { + int noteNumber = pseudo.choose(30); + noteOn(nextTime, noteNumber); + noteOff(nextTime + onTime, noteNumber); + } + + nextTime += duration; + beatIndex += 1; + + // wake up before we need to play note to cover system latency + synth.sleepUntil(nextTime - advanceTime); + } while (go); + } catch (InterruptedException e) { + LOGGER.error("Song exiting", e); + } + } +} diff --git a/examples/src/main/java/com/jsyn/examples/CircuitTester.java b/examples/src/main/java/com/jsyn/examples/CircuitTester.java new file mode 100644 index 0000000..0a429fb --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/CircuitTester.java @@ -0,0 +1,111 @@ +/* + * Copyright 2012 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.examples; + +import java.awt.BorderLayout; + +import javax.swing.JApplet; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.instruments.DualOscillatorSynthVoice; +import com.jsyn.scope.AudioScope; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.swing.SoundTweaker; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.UnitSource; + +/** + * Listen to a circuit while tweaking it knobs. Show output in a scope. + * + * @author Phil Burk (C) 2012 Mobileer Inc + */ +public class CircuitTester extends JApplet { + private Synthesizer synth; + private LineOut lineOut; + private SoundTweaker tweaker; + private UnitSource unitSource; + private AudioScope scope; + + @Override + public void init() { + setLayout(new BorderLayout()); + + synth = JSyn.createSynthesizer(); + synth.add(lineOut = new LineOut()); + + unitSource = createUnitSource(); + synth.add(unitSource.getUnitGenerator()); + + // Connect the source to both left and right speakers. + unitSource.getOutput().connect(0, lineOut.input, 0); + unitSource.getOutput().connect(0, lineOut.input, 1); + + tweaker = new SoundTweaker(synth, unitSource.getUnitGenerator().getClass().getName(), + unitSource); + add(tweaker, BorderLayout.CENTER); + + // Use a scope to see the output. + scope = new AudioScope(synth); + scope.addProbe(unitSource.getOutput()); + scope.setTriggerMode(AudioScope.TriggerMode.AUTO); + scope.getView().setControlsVisible(false); + add(BorderLayout.SOUTH, scope.getView()); + + validate(); + } + + /** + * Override this to test your own circuits. + * + * @return + */ + public UnitSource createUnitSource() { + //return new SampleHoldNoteBlaster(); + //return new com.syntona.exported.FMVoice(); + return new DualOscillatorSynthVoice(); + //return new WindCircuit(); + //return new WhiteNoise(); + //return new BrownNoise(); + } + + @Override + public void start() { + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // Start the LineOut. It will pull data from the other units. + lineOut.start(); + + scope.start(); + } + + @Override + public void stop() { + scope.stop(); + synth.stop(); + } + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + CircuitTester applet = new CircuitTester(); + JAppletFrame frame = new JAppletFrame("JSyn Circuit Tester", applet); + frame.setSize(600, 600); + frame.setVisible(true); + frame.test(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/CustomCubeUnit.java b/examples/src/main/java/com/jsyn/examples/CustomCubeUnit.java new file mode 100644 index 0000000..897052c --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/CustomCubeUnit.java @@ -0,0 +1,48 @@ +/* + * 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.examples; + +import com.jsyn.unitgen.UnitFilter; + +/** + * Custom unit generator that can be used with other JSyn units. Cube the input value and write it + * to output port. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class CustomCubeUnit extends UnitFilter { + + /** This is where the synthesis occurs. + * It is called in a high priority background thread so do not do + * anything crazy here like reading a file or doing network I/O. + * Just do fast arithmetic. + * <br> + * The start and limit allow us to do either block or single sample processing. + */ + @Override + public void generate(int start, int limit) { + // Get signal arrays from ports. + double[] inputs = input.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit; i++) { + double x = inputs[i]; + // Do the math. + outputs[i] = x * x * x; + } + } +} diff --git a/examples/src/main/java/com/jsyn/examples/DualOscilloscope.java b/examples/src/main/java/com/jsyn/examples/DualOscilloscope.java new file mode 100644 index 0000000..2c4caa3 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/DualOscilloscope.java @@ -0,0 +1,163 @@ +/* + * Copyright 2012 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.examples; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; + +import javax.swing.JApplet; +import javax.swing.JComboBox; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.devices.AudioDeviceFactory; +import com.jsyn.devices.AudioDeviceManager; +import com.jsyn.scope.AudioScope; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.unitgen.ChannelIn; +import com.jsyn.unitgen.PassThrough; + +/** + * Two channel oscilloscope that demonstrates the use of audio input. + * + * @author Phil Burk (C) 2012 Mobileer Inc + */ +public class DualOscilloscope extends JApplet { + private Synthesizer synth; + private ChannelIn channel1; + private ChannelIn channel2; + private PassThrough pass1; + private PassThrough pass2; + private AudioScope scope; + private AudioDeviceManager audioManager; + private int defaultInputId; + private ArrayList<String> deviceNames = new ArrayList<String>(); + private ArrayList<Integer> deviceMaxInputs = new ArrayList<Integer>(); + private ArrayList<Integer> deviceIds = new ArrayList<Integer>(); + private int defaultSelection; + private JComboBox deviceComboBox; + + @Override + public void init() { + audioManager = AudioDeviceFactory.createAudioDeviceManager(true); + synth = JSyn.createSynthesizer(audioManager); + + int numDevices = audioManager.getDeviceCount(); + defaultInputId = audioManager.getDefaultInputDeviceID(); + for (int i = 0; i < numDevices; i++) { + int maxInputs = audioManager.getMaxInputChannels(i); + if (maxInputs > 0) { + String deviceName = audioManager.getDeviceName(i); + String itemName = maxInputs + ", " + deviceName + " (#" + i + ")"; + if (i == defaultInputId) { + defaultSelection = deviceNames.size(); + itemName += " (Default)"; + } + deviceNames.add(itemName); + deviceMaxInputs.add(maxInputs); + deviceIds.add(i); + } + } + + synth.add(channel1 = new ChannelIn()); + channel1.setChannelIndex(0); + synth.add(channel2 = new ChannelIn()); + channel2.setChannelIndex(1); + + // Use PassThrough so we can easily disconnect input channels from the scope. + synth.add(pass1 = new PassThrough()); + synth.add(pass2 = new PassThrough()); + + setupGUI(); + } + + private void setupGUI() { + setLayout(new BorderLayout()); + + deviceComboBox = new JComboBox(deviceNames.toArray(new String[0])); + deviceComboBox.setSelectedIndex(defaultSelection); + add(deviceComboBox, BorderLayout.NORTH); + deviceComboBox.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + stopAudio(); + int itemIndex = deviceComboBox.getSelectedIndex(); + startAudio(itemIndex); + } + }); + + scope = new AudioScope(synth); + + scope.addProbe(pass1.output); + scope.addProbe(pass2.output); + + scope.setTriggerMode(AudioScope.TriggerMode.AUTO); + scope.getView().setControlsVisible(true); + add(scope.getView(), BorderLayout.CENTER); + validate(); + } + + protected void startAudio(int itemIndex) { + // Both stereo. + int numInputChannels = deviceMaxInputs.get(itemIndex); + if (numInputChannels > 2) + numInputChannels = 2; + int inputDeviceIndex = deviceIds.get(itemIndex); + synth.start(44100, inputDeviceIndex, numInputChannels, + AudioDeviceManager.USE_DEFAULT_DEVICE, 0); + + channel1.output.connect(pass1.input); + // Only connect second channel if more than one input channel. + if (numInputChannels > 1) { + channel2.output.connect(pass2.input); + } + + // We only need to start the LineOut. It will pull data from the + // channels. + scope.start(); + } + + @Override + public void start() { + startAudio(defaultSelection); + } + + public void stopAudio() { + pass1.input.disconnectAll(); + pass2.input.disconnectAll(); + scope.stop(); + synth.stop(); + } + + @Override + public void stop() { + stopAudio(); + } + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + DualOscilloscope applet = new DualOscilloscope(); + JAppletFrame frame = new JAppletFrame("Dual Oscilloscope", applet); + frame.setSize(640, 400); + frame.setVisible(true); + frame.test(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/EditEnvelope1.java b/examples/src/main/java/com/jsyn/examples/EditEnvelope1.java new file mode 100644 index 0000000..997b8b4 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/EditEnvelope1.java @@ -0,0 +1,148 @@ +/* + * Copyright 1997 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. + */ +/** + * Test Envelope using Java Audio Synthesizer + * Trigger attack or release portion. + * + * @author (C) 1997 Phil Burk + */ + +package com.jsyn.examples; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JApplet; +import javax.swing.JButton; +import javax.swing.JPanel; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.SegmentedEnvelope; +import com.jsyn.swing.EnvelopeEditorPanel; +import com.jsyn.swing.EnvelopePoints; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.unitgen.VariableRateDataReader; +import com.jsyn.unitgen.VariableRateMonoReader; + +public class EditEnvelope1 extends JApplet { + private Synthesizer synth; + private UnitOscillator osc; + private LineOut lineOut; + private SegmentedEnvelope envelope; + private VariableRateDataReader envelopePlayer; + + final int MAX_FRAMES = 16; + JButton hitme; + JButton attackButton; + JButton releaseButton; + private EnvelopeEditorPanel envEditor; + private EnvelopePoints points; + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + EditEnvelope1 applet = new EditEnvelope1(); + JAppletFrame frame = new JAppletFrame("Test SynthEnvelope", applet); + frame.setSize(440, 200); + frame.setVisible(true); + frame.test(); + } + + /* + * Setup synthesis. + */ + @Override + public void start() { + setLayout(new BorderLayout()); + + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + // Add a tone generator. + synth.add(osc = new SawtoothOscillatorBL()); + // Add an envelope player. + synth.add(envelopePlayer = new VariableRateMonoReader()); + + envelope = new SegmentedEnvelope(MAX_FRAMES); + + // Add an output mixer. + synth.add(lineOut = new LineOut()); + envelopePlayer.output.connect(osc.amplitude); + // Connect the oscillator to the output. + osc.output.connect(0, lineOut.input, 0); + osc.output.connect(0, lineOut.input, 1); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + JPanel bottomPanel = new JPanel(); + bottomPanel.add(hitme = new JButton("On")); + hitme.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + points.updateEnvelopeIfDirty(envelope); + envelopePlayer.dataQueue.queueOn(envelope); + } + }); + + bottomPanel.add(attackButton = new JButton("Off")); + attackButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + points.updateEnvelopeIfDirty(envelope); + envelopePlayer.dataQueue.queueOff(envelope); + } + }); + + bottomPanel.add(releaseButton = new JButton("Queue")); + releaseButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + points.updateEnvelopeIfDirty(envelope); + envelopePlayer.dataQueue.queue(envelope); + } + }); + + add(bottomPanel, BorderLayout.SOUTH); + lineOut.start(); + + // Create vector of points for editor. + points = new EnvelopePoints(); + points.setName(osc.amplitude.getName()); + + // Setup initial envelope shape. + points.add(0.5, 1.0); + points.add(0.5, 0.2); + points.add(0.5, 0.8); + points.add(0.5, 0.0); + points.updateEnvelope(envelope); + + // Add an envelope editor to the center of the panel. + add("Center", envEditor = new EnvelopeEditorPanel(points, MAX_FRAMES)); + + /* Synchronize Java display. */ + getParent().validate(); + getToolkit().sync(); + } + + @Override + public void stop() { + synth.stop(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/FFTPassthrough.java b/examples/src/main/java/com/jsyn/examples/FFTPassthrough.java new file mode 100644 index 0000000..e4b72c8 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/FFTPassthrough.java @@ -0,0 +1,105 @@ +/* + * 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.PassThrough; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.SpectralFFT; +import com.jsyn.unitgen.SpectralIFFT; +import com.jsyn.unitgen.UnitOscillator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play a sine sweep through an FFT/IFFT pair. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class FFTPassthrough { + + private static final Logger LOGGER = LoggerFactory.getLogger(FFTPassthrough.class); + + private Synthesizer synth; + private PassThrough center; + private UnitOscillator osc; + private UnitOscillator lfo; + private SpectralFFT fft; + private SpectralIFFT ifft1; + private LineOut lineOut; + private SpectralIFFT ifft2; + + private void test() { + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + // Add a tone generator. + synth.add(center = new PassThrough()); + // synth.add( osc = new SawtoothOscillatorBL() ); + synth.add(osc = new SineOscillator()); + synth.add(lfo = new SineOscillator()); + synth.add(fft = new SpectralFFT()); + synth.add(ifft1 = new SpectralIFFT()); + synth.add(ifft2 = new SpectralIFFT()); + // Add a stereo audio output unit. + synth.add(lineOut = new LineOut()); + + // Connect the oscillator to both channels of the output. + center.output.connect(osc.frequency); + lfo.output.connect(osc.frequency); + osc.output.connect(fft.input); + fft.output.connect(ifft1.input); + fft.output.connect(ifft2.input); + ifft1.output.connect(0, lineOut.input, 0); + ifft2.output.connect(0, lineOut.input, 1); + + // Set the frequency and amplitude for the modulated sine wave. + center.input.set(600.0); + lfo.frequency.set(0.2); + lfo.amplitude.set(400.0); + osc.amplitude.set(0.6); + + // We only need to start the LineOut. It will pull data through the + // chain. + lineOut.start(); + + LOGGER.debug("You should now be hearing a clean oscillator on the left channel,"); + LOGGER.debug("and the FFT->IFFT processed signal on the right channel."); + + // Sleep while the sound is generated in the background. + try { + double time = synth.getCurrentTime(); + // Sleep for a few seconds. + synth.sleepUntil(time + 20.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + LOGGER.debug("Stop playing. -------------------"); + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + new FFTPassthrough().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/GoogleWaveOscillator.java b/examples/src/main/java/com/jsyn/examples/GoogleWaveOscillator.java new file mode 100644 index 0000000..cf76298 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/GoogleWaveOscillator.java @@ -0,0 +1,72 @@ +/* + * 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.examples; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.unitgen.UnitOscillator; + +/** + * Custom unit generator to create the waveform shown on the Google home page on 2/22/12. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class GoogleWaveOscillator extends UnitOscillator { + public UnitInputPort variance; + private double phaseIncrement = 0.1; + private double previousY; + private double randomAmplitude = 0.0; + + public GoogleWaveOscillator() { + addPort(variance = new UnitInputPort("Variance", 0.0)); + } + + @Override + public void generate(int start, int limit) { + // Get signal arrays from ports. + double[] freqs = frequency.getValues(); + double[] outputs = output.getValues(); + double currentPhase = phase.getValue(); + double y; + + for (int i = start; i < limit; i++) { + if (currentPhase > 0.0) { + y = Math.sqrt(4.0 * (currentPhase * (1.0 - currentPhase))); + } else { + double p = -currentPhase; + y = -Math.sqrt(4.0 * (p * (1.0 - p))); + } + + if ((previousY * y) <= 0.0) { + // Calculate randomly offset phaseIncrement. + double v = variance.getValues()[0]; + double range = ((Math.random() - 0.5) * 4.0 * v); + double scale = Math.pow(2.0, range); + phaseIncrement = convertFrequencyToPhaseIncrement(freqs[i]) * scale; + + // Calculate random amplitude. + scale = 1.0 + ((Math.random() - 0.5) * 1.5 * v); + randomAmplitude = amplitude.getValues()[0] * scale; + } + + outputs[i] = y * randomAmplitude; + previousY = y; + + currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); + } + phase.setValue(currentPhase); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/HearDAHDSR.java b/examples/src/main/java/com/jsyn/examples/HearDAHDSR.java new file mode 100644 index 0000000..a6c03aa --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/HearDAHDSR.java @@ -0,0 +1,129 @@ +/* + * 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.examples; + +import java.awt.GridLayout; + +import javax.swing.BorderFactory; +import javax.swing.JApplet; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.ports.UnitInputPort; +import com.jsyn.swing.DoubleBoundedRangeModel; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.swing.PortModelFactory; +import com.jsyn.swing.RotaryTextController; +import com.jsyn.unitgen.EnvelopeDAHDSR; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.SquareOscillator; +import com.jsyn.unitgen.UnitOscillator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play a tone using a JSyn oscillator. Modulate the amplitude using a DAHDSR envelope. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class HearDAHDSR extends JApplet { + + private static final Logger LOGGER = LoggerFactory.getLogger(HearDAHDSR.class); + + private Synthesizer synth; + private UnitOscillator osc; + // Use a square wave to trigger the envelope. + private UnitOscillator gatingOsc; + private EnvelopeDAHDSR dahdsr; + private LineOut lineOut; + + @Override + public void init() { + synth = JSyn.createSynthesizer(); + + // Add a tone generator. + synth.add(osc = new SineOscillator()); + // Add a trigger. + synth.add(gatingOsc = new SquareOscillator()); + // Use an envelope to control the amplitude. + synth.add(dahdsr = new EnvelopeDAHDSR()); + // Add an output mixer. + synth.add(lineOut = new LineOut()); + + gatingOsc.output.connect(dahdsr.input); + dahdsr.output.connect(osc.amplitude); + dahdsr.attack.setup(0.001, 0.01, 2.0); + osc.output.connect(0, lineOut.input, 0); + osc.output.connect(0, lineOut.input, 1); + + gatingOsc.frequency.setup(0.001, 0.5, 10.0); + gatingOsc.frequency.setName("Rate"); + + osc.frequency.setup(50.0, 440.0, 2000.0); + osc.frequency.setName("Freq"); + + // Arrange the knob in a row. + setLayout(new GridLayout(1, 0)); + + setupPortKnob(osc.frequency); + setupPortKnob(gatingOsc.frequency); + setupPortKnob(dahdsr.attack); + setupPortKnob(dahdsr.hold); + setupPortKnob(dahdsr.decay); + setupPortKnob(dahdsr.sustain); + setupPortKnob(dahdsr.release); + + validate(); + } + + private void setupPortKnob(UnitInputPort port) { + + DoubleBoundedRangeModel model = PortModelFactory.createExponentialModel(port); + LOGGER.debug("Make knob for " + port.getName() + ", model.getDV = " + + model.getDoubleValue() + ", model.getV = " + model.getValue() + ", port.getV = " + + port.get()); + RotaryTextController knob = new RotaryTextController(model, 10); + knob.setBorder(BorderFactory.createTitledBorder(port.getName())); + knob.setTitle(port.getName()); + add(knob); + } + + @Override + public void start() { + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + } + + @Override + public void stop() { + synth.stop(); + } + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + HearDAHDSR applet = new HearDAHDSR(); + JAppletFrame frame = new JAppletFrame("Hear DAHDSR Envelope", applet); + frame.setSize(640, 200); + frame.setVisible(true); + frame.test(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/HearMoogFilter.java b/examples/src/main/java/com/jsyn/examples/HearMoogFilter.java new file mode 100644 index 0000000..2444dd7 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/HearMoogFilter.java @@ -0,0 +1,207 @@ +/* + * 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.examples; + +import java.awt.BorderLayout; +import java.awt.GridLayout; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JApplet; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.ports.UnitInputPort; +import com.jsyn.scope.AudioScope; +import com.jsyn.swing.DoubleBoundedRangeModel; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.swing.PortModelFactory; +import com.jsyn.swing.RotaryTextController; +import com.jsyn.unitgen.FilterFourPoles; +import com.jsyn.unitgen.FilterLowPass; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.LinearRamp; +import com.jsyn.unitgen.PassThrough; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.UnitOscillator; + +/** + * Play a sawtooth through a 4-pole filter. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class HearMoogFilter extends JApplet { + private Synthesizer synth; + private UnitOscillator oscillator; + private FilterFourPoles filterMoog; + private FilterLowPass filterBiquad; + private LinearRamp rampCutoff; + private PassThrough tieQ; + private PassThrough tieCutoff; + private PassThrough mixer; + private LineOut lineOut; + + private AudioScope scope; + private boolean useCutoffRamp = false; + + @Override + public void init() { + synth = JSyn.createSynthesizer(); + synth.add(oscillator = new SawtoothOscillatorBL()); + synth.add(rampCutoff = new LinearRamp()); + synth.add(tieQ = new PassThrough()); + synth.add(tieCutoff = new PassThrough()); + synth.add(filterMoog = new FilterFourPoles()); + synth.add(filterBiquad = new FilterLowPass()); + synth.add(mixer = new PassThrough()); + synth.add(lineOut = new LineOut()); + + oscillator.output.connect(filterMoog.input); + oscillator.output.connect(filterBiquad.input); + if (useCutoffRamp) { + rampCutoff.output.connect(filterMoog.frequency); + rampCutoff.output.connect(filterBiquad.frequency); + rampCutoff.time.set(0.000); + } else { + tieCutoff.output.connect(filterMoog.frequency); + tieCutoff.output.connect(filterBiquad.frequency); + } + tieQ.output.connect(filterMoog.Q); + tieQ.output.connect(filterBiquad.Q); + filterMoog.output.connect(mixer.input); + mixer.output.connect(0, lineOut.input, 0); + mixer.output.connect(0, lineOut.input, 1); + + filterBiquad.amplitude.set(0.1); + oscillator.frequency.setup(50.0, 130.0, 3000.0); + oscillator.amplitude.setup(0.0, 0.336, 1.0); + rampCutoff.input.setup(filterMoog.frequency); + tieCutoff.input.setup(filterMoog.frequency); + tieQ.input.setup(0.1, 0.7, 10.0); + setupGUI(); + } + + private void setupGUI() { + setLayout(new BorderLayout()); + + add(new JLabel("Sawtooth through a \"Moog\" style filter."), BorderLayout.NORTH); + + JPanel rackPanel = new JPanel(); + rackPanel.setLayout(new BoxLayout(rackPanel, BoxLayout.Y_AXIS)); + + JPanel buttonPanel = new JPanel(); + ButtonGroup cbg = new ButtonGroup(); + JRadioButton radioButton = new JRadioButton("Moog", true); + cbg.add(radioButton); + radioButton.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + mixer.input.disconnectAll(); + filterMoog.output.connect(mixer.input); + } + }); + buttonPanel.add(radioButton); + radioButton = new JRadioButton("Biquad", false); + cbg.add(radioButton); + radioButton.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + mixer.input.disconnectAll(); + filterBiquad.output.connect(mixer.input); + } + }); + buttonPanel.add(radioButton); + + /* + * buttonPanel.add( new JLabel("Show:") ); cbg = new ButtonGroup(); radioButton = new + * JRadioButton( "Waveform", true ); cbg.add( radioButton ); radioButton.addItemListener( + * new ItemListener() { public void itemStateChanged( ItemEvent e ) { scope.setViewMode( + * AudioScope.ViewMode.WAVEFORM ); } } ); buttonPanel.add( radioButton ); radioButton = new + * JRadioButton( "Spectrum", true ); cbg.add( radioButton ); radioButton.addItemListener( + * new ItemListener() { public void itemStateChanged( ItemEvent e ) { scope.setViewMode( + * AudioScope.ViewMode.SPECTRUM ); } } ); buttonPanel.add( radioButton ); + */ + + rackPanel.add(buttonPanel); + + // Arrange the knobs in a row. + JPanel knobPanel = new JPanel(); + knobPanel.setLayout(new GridLayout(1, 0)); + + knobPanel.add(setupPortKnob(oscillator.frequency, "OscFreq")); + knobPanel.add(setupPortKnob(oscillator.amplitude, "OscAmp")); + + if (useCutoffRamp) { + knobPanel.add(setupPortKnob(rampCutoff.input, "Cutoff")); + } else { + knobPanel.add(setupPortKnob(tieCutoff.input, "Cutoff")); + } + knobPanel.add(setupPortKnob(tieQ.input, "Q")); + rackPanel.add(knobPanel); + add(rackPanel, BorderLayout.SOUTH); + + scope = new AudioScope(synth); + scope.addProbe(oscillator.output); + scope.addProbe(filterMoog.output); + scope.addProbe(filterBiquad.output); + scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); + scope.getView().setControlsVisible(false); + add(scope.getView(), BorderLayout.CENTER); + scope.start(); + validate(); + } + + private RotaryTextController setupPortKnob(UnitInputPort port, String label) { + DoubleBoundedRangeModel model = PortModelFactory.createExponentialModel(port); + RotaryTextController knob = new RotaryTextController(model, 10); + knob.setBorder(BorderFactory.createTitledBorder(label)); + knob.setTitle(label); + return knob; + } + + @Override + public void start() { + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + scope.start(); + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + } + + @Override + public void stop() { + scope.stop(); + synth.stop(); + } + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + HearMoogFilter applet = new HearMoogFilter(); + JAppletFrame frame = new JAppletFrame("Hear Moog Style Filter", applet); + frame.setSize(800, 600); + frame.setVisible(true); + frame.test(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/HearSinePM.java b/examples/src/main/java/com/jsyn/examples/HearSinePM.java new file mode 100644 index 0000000..c567b03 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/HearSinePM.java @@ -0,0 +1,128 @@ +/* + * 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.examples; + +import java.awt.BorderLayout; +import java.awt.GridLayout; + +import javax.swing.BorderFactory; +import javax.swing.JApplet; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.ports.UnitInputPort; +import com.jsyn.scope.AudioScope; +import com.jsyn.swing.DoubleBoundedRangeModel; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.swing.PortModelFactory; +import com.jsyn.swing.RotaryTextController; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SineOscillator; +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 { + private Synthesizer synth; + SineOscillatorPhaseModulated carrier; + SineOscillator modulator; + LineOut lineOut; + AudioScope scope; + + @Override + public void init() { + synth = JSyn.createSynthesizer(); + // Add a tone generator. + synth.add(modulator = new SineOscillator()); + // Add a trigger. + synth.add(carrier = new SineOscillatorPhaseModulated()); + // Add an output mixer. + synth.add(lineOut = new LineOut()); + + modulator.output.connect(carrier.modulation); + 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, 0.25, 1.0); + setupGUI(); + } + + private void setupGUI() { + setLayout(new BorderLayout()); + + add(new JLabel("Show Phase Modulation in an AudioScope"), BorderLayout.NORTH); + + // Arrange the knob in a row. + JPanel knobPanel = new JPanel(); + knobPanel.setLayout(new GridLayout(1, 0)); + + knobPanel.add(setupPortKnob(modulator.frequency, "MFreq")); + knobPanel.add(setupPortKnob(modulator.amplitude, "MAmp")); + knobPanel.add(setupPortKnob(carrier.frequency, "CFreq")); + knobPanel.add(setupPortKnob(carrier.amplitude, "CAmp")); + add(knobPanel, BorderLayout.SOUTH); + + scope = new AudioScope(synth); + scope.addProbe(carrier.output); + scope.addProbe(modulator.output); + scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); + scope.getView().setControlsVisible(true); + add(scope.getView(), BorderLayout.CENTER); + scope.start(); + validate(); + } + + private RotaryTextController setupPortKnob(UnitInputPort port, String label) { + DoubleBoundedRangeModel model = PortModelFactory.createExponentialModel(port); + RotaryTextController knob = new RotaryTextController(model, 10); + knob.setBorder(BorderFactory.createTitledBorder(label)); + knob.setTitle(label); + return knob; + } + + @Override + public void start() { + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + scope.start(); + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + } + + @Override + public void stop() { + scope.stop(); + synth.stop(); + } + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + HearSinePM applet = new HearSinePM(); + JAppletFrame frame = new JAppletFrame("Hear Phase Modulation", applet); + frame.setSize(640, 400); + frame.setVisible(true); + frame.test(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/HearSpectralFilter.java b/examples/src/main/java/com/jsyn/examples/HearSpectralFilter.java new file mode 100644 index 0000000..cc5d8c9 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/HearSpectralFilter.java @@ -0,0 +1,211 @@ +/* + * 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.examples; + +import java.io.File; +import java.io.IOException; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.Spectrum; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.PassThrough; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.SpectralFilter; +import com.jsyn.unitgen.SpectralProcessor; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.unitgen.WhiteNoise; +import com.jsyn.util.WaveRecorder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play a sine sweep through an FFT/IFFT pair. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class HearSpectralFilter { + + private static final Logger LOGGER = LoggerFactory.getLogger(HearSpectralFilter.class); + + private Synthesizer synth; + private PassThrough center; + private UnitOscillator osc; + private UnitOscillator lfo; + private PassThrough mixer; + private SpectralFilter filter; + private LineOut lineOut; + private WaveRecorder recorder; + private final static boolean useRecorder = true; + private final static boolean useProcessor = true; + private final static int NUM_FFTS = 4; + private final static int SIZE_LOG_2 = 10; + private final static int SIZE = 1 << SIZE_LOG_2; + private SpectralProcessor[] processors; + private WhiteNoise noise; + private static int SAMPLE_RATE = 44100; + + private static class CustomSpectralProcessor extends SpectralProcessor { + public CustomSpectralProcessor() { + super(SIZE); + } + + @Override + public void processSpectrum(Spectrum inputSpectrum, Spectrum outputSpectrum) { + // pitchUpOctave( inputSpectrum, outputSpectrum ); + lowPassFilter(inputSpectrum, outputSpectrum, 1500.0); + } + + public void lowPassFilter(Spectrum inputSpectrum, Spectrum outputSpectrum, double frequency) { + inputSpectrum.copyTo(outputSpectrum); + double[] outReal = outputSpectrum.getReal(); + double[] outImag = outputSpectrum.getImaginary(); + // brickwall filter + int size = outReal.length; + int cutoff = (int) (frequency * size / SAMPLE_RATE); + int nyquist = size / 2; + for (int i = cutoff; i < nyquist; i++) { + // Bins above nyquist are mirror of ones below. + outReal[i] = outReal[size - i] = 0.0; + outImag[i] = outImag[size - i] = 0.0; + } + } + + // TODO Figure out why this sounds bad. + public void pitchUpOctave(Spectrum inputSpectrum, Spectrum outputSpectrum) { + outputSpectrum.clear(); + double[] inReal = inputSpectrum.getReal(); + double[] inImag = inputSpectrum.getImaginary(); + double[] outReal = outputSpectrum.getReal(); + double[] outImag = outputSpectrum.getImaginary(); + int size = inReal.length; + int nyquist = size / 2; + // Octave doubling by shifting the spectrum. + for (int i = nyquist - 2; i > 1; i--) { + int h = i / 2; + outReal[i] = inReal[h]; + outImag[i] = inImag[h]; + outReal[size - i] = inReal[size - h]; + outImag[size - i] = inImag[size - h]; + } + } + } + + private void test() throws IOException { + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + synth.setRealTime(true); + + if (useRecorder) { + File waveFile = new File("temp_recording.wav"); + // Default is stereo, 16 bits. + recorder = new WaveRecorder(synth, waveFile); + LOGGER.debug("Writing to WAV file " + waveFile.getAbsolutePath()); + } + + if (useProcessor) { + processors = new SpectralProcessor[NUM_FFTS]; + for (int i = 0; i < NUM_FFTS; i++) { + processors[i] = new CustomSpectralProcessor(); + } + } + + // Add a tone generator. + synth.add(center = new PassThrough()); + synth.add(lfo = new SineOscillator()); + synth.add(noise = new WhiteNoise()); + synth.add(mixer = new PassThrough()); + + synth.add(osc = new SawtoothOscillatorBL()); + // synth.add( osc = new SineOscillator() ); + + synth.add(filter = new SpectralFilter(NUM_FFTS, SIZE_LOG_2)); + // Add a stereo audio output unit. + synth.add(lineOut = new LineOut()); + + center.output.connect(osc.frequency); + lfo.output.connect(osc.frequency); + osc.output.connect(mixer.input); + noise.output.connect(mixer.input); + mixer.output.connect(filter.input); + if (useProcessor) { + // Pass spectra through a custom processor. + for (int i = 0; i < NUM_FFTS; i++) { + filter.getSpectralOutput(i).connect(processors[i].input); + processors[i].output.connect(filter.getSpectralInput(i)); + } + } else { + for (int i = 0; i < NUM_FFTS; i++) { + // Connect FFTs directly to IFFTs for passthrough. + filter.getSpectralOutput(i).connect(filter.getSpectralInput(i)); + } + + } + mixer.output.connect(0, lineOut.input, 0); + filter.output.connect(0, lineOut.input, 1); + + // Set the frequency and amplitude for the modulated sine wave. + center.input.set(600.0); + lfo.frequency.set(0.2); + lfo.amplitude.set(400.0); + osc.amplitude.set(0.2); + noise.amplitude.set(0.2); + + synth.start(SAMPLE_RATE); + + if (useRecorder) { + mixer.output.connect(0, recorder.getInput(), 0); + filter.output.connect(0, recorder.getInput(), 1); + // When we start the recorder it will pull data from the oscillator + // and sweeper. + recorder.start(); + } + + lineOut.start(); + + LOGGER.debug("You should now be hearing a clean oscillator on the left channel,"); + LOGGER.debug("and the FFT->IFFT processed signal on the right channel."); + + // Sleep while the sound is generated in the background. + try { + double time = synth.getCurrentTime(); + // Sleep for a few seconds. + synth.sleepUntil(time + 10.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (recorder != null) { + recorder.stop(); + recorder.close(); + } + + LOGGER.debug("Stop playing. -------------------"); + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + try { + new HearSpectralFilter().test(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/com/jsyn/examples/ListAudioDevices.java b/examples/src/main/java/com/jsyn/examples/ListAudioDevices.java new file mode 100644 index 0000000..097ffdc --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/ListAudioDevices.java @@ -0,0 +1,50 @@ +/* + * 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.examples; + +import com.jsyn.devices.AudioDeviceFactory; +import com.jsyn.devices.AudioDeviceManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ListAudioDevices { + + private static final Logger LOGGER = LoggerFactory.getLogger(ListAudioDevices.class); + + /** + * @param args + */ + public static void main(String[] args) { + AudioDeviceManager audioManager = AudioDeviceFactory.createAudioDeviceManager(); + + int numDevices = audioManager.getDeviceCount(); + for (int i = 0; i < numDevices; i++) { + String deviceName = audioManager.getDeviceName(i); + int maxInputs = audioManager.getMaxInputChannels(i); + int maxOutputs = audioManager.getMaxInputChannels(i); + boolean isDefaultInput = (i == audioManager.getDefaultInputDeviceID()); + boolean isDefaultOutput = (i == audioManager.getDefaultOutputDeviceID()); + LOGGER.debug("#" + i + " : " + deviceName); + LOGGER.debug(" max inputs : " + maxInputs + + (isDefaultInput ? " (default)" : "")); + LOGGER.debug(" max outputs: " + maxOutputs + + (isDefaultOutput ? " (default)" : "")); + } + + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/LongEcho.java b/examples/src/main/java/com/jsyn/examples/LongEcho.java new file mode 100644 index 0000000..16e71f3 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/LongEcho.java @@ -0,0 +1,128 @@ +/* + * 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.examples; + +import java.io.File; +import java.io.IOException; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.FloatSample; +import com.jsyn.devices.AudioDeviceManager; +import com.jsyn.unitgen.ChannelIn; +import com.jsyn.unitgen.ChannelOut; +import com.jsyn.unitgen.FixedRateMonoReader; +import com.jsyn.unitgen.FixedRateMonoWriter; +import com.jsyn.unitgen.Maximum; +import com.jsyn.unitgen.Minimum; +import com.jsyn.util.WaveFileWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Echo the input using a circular buffer in a sample. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class LongEcho { + + private static final Logger LOGGER = LoggerFactory.getLogger(LongEcho.class); + + final static int DELAY_SECONDS = 4; + Synthesizer synth; + ChannelIn channelIn; + ChannelOut channelOut; + FloatSample sample; + FixedRateMonoReader reader; + FixedRateMonoWriter writer; + Minimum minner; + Maximum maxxer; + + private void test() throws IOException { + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + synth.add(channelIn = new ChannelIn()); + synth.add(channelOut = new ChannelOut()); + + synth.add(minner = new Minimum()); + synth.add(maxxer = new Maximum()); + synth.add(reader = new FixedRateMonoReader()); + synth.add(writer = new FixedRateMonoWriter()); + + sample = new FloatSample(44100 * DELAY_SECONDS, 1); + + maxxer.inputB.set(-0.98); // clip + minner.inputB.set(0.98); + + // Connect the input to the output. + channelIn.output.connect(minner.inputA); + minner.output.connect(maxxer.inputA); + maxxer.output.connect(writer.input); + + reader.output.connect(channelOut.input); + + // Both stereo. + int numInputChannels = 2; + int numOutputChannels = 2; + synth.start(44100, AudioDeviceManager.USE_DEFAULT_DEVICE, numInputChannels, + AudioDeviceManager.USE_DEFAULT_DEVICE, numOutputChannels); + + writer.start(); + channelOut.start(); + + // For a long echo, read cursor should be just in front of the write cursor. + reader.dataQueue.queue(sample, 1000, sample.getNumFrames() - 1000); + // Loop both forever. + reader.dataQueue.queueLoop(sample, 0, sample.getNumFrames()); + writer.dataQueue.queueLoop(sample, 0, sample.getNumFrames()); + LOGGER.debug("Start talking. You should hear an echo after " + DELAY_SECONDS + + " seconds."); + // Sleep a while. + try { + double time = synth.getCurrentTime(); + // Sleep for a while + synth.sleepUntil(time + 30.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + saveEcho(new File("saved_echo.wav")); + // Stop everything. + synth.stop(); + } + + private void saveEcho(File file) throws IOException { + WaveFileWriter writer = new WaveFileWriter(file); + writer.setFrameRate(44100); + writer.setSamplesPerFrame(1); + writer.setBitsPerSample(16); + float[] buffer = new float[sample.getNumFrames()]; + sample.read(buffer); + for (float v : buffer) { + writer.write(v); + } + writer.close(); + } + + public static void main(String[] args) { + try { + new LongEcho().test(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/com/jsyn/examples/MonoPassThrough.java b/examples/src/main/java/com/jsyn/examples/MonoPassThrough.java new file mode 100644 index 0000000..0e81abf --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/MonoPassThrough.java @@ -0,0 +1,66 @@ +/* + * 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.devices.AudioDeviceManager; +import com.jsyn.unitgen.ChannelIn; +import com.jsyn.unitgen.ChannelOut; + +/** + * Pass audio input to audio output. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class MonoPassThrough { + Synthesizer synth; + ChannelIn channelIn; + ChannelOut channelOut; + + private void test() { + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + synth.add(channelIn = new ChannelIn()); + synth.add(channelOut = new ChannelOut()); + // Connect the input to the output. + channelIn.output.connect(channelOut.input); + + // Both stereo. + int numInputChannels = 2; + int numOutputChannels = 2; + synth.start(48000, AudioDeviceManager.USE_DEFAULT_DEVICE, numInputChannels, + AudioDeviceManager.USE_DEFAULT_DEVICE, numOutputChannels); + + // We only need to start the ChannelOut. It will pull data from the ChannelIn. + channelOut.start(); + // Sleep a while. + try { + double time = synth.getCurrentTime(); + // Sleep for a few seconds. + synth.sleepUntil(time + 4.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + new MonoPassThrough().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/NotesToTone.java b/examples/src/main/java/com/jsyn/examples/NotesToTone.java new file mode 100644 index 0000000..a1b06fa --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/NotesToTone.java @@ -0,0 +1,219 @@ +/* + * 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. + */ +/** + * If you play notes fast enough they become a tone. + * + * Play a sine wave modulated by an envelope. + * Speed up the envelope until it is playing at audio rate. + * Slow down the oscillator until it becomes an LFO amp modulator. + * Use a LatchZeroCrossing to stop at the end of a sine wave cycle when we are finished. + * + * @author Phil Burk, (C) 2010 Mobileer Inc + */ + +package com.jsyn.examples; + +import java.io.File; +import java.io.IOException; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.SegmentedEnvelope; +import com.jsyn.unitgen.ExponentialRamp; +import com.jsyn.unitgen.LatchZeroCrossing; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.unitgen.VariableRateDataReader; +import com.jsyn.unitgen.VariableRateMonoReader; +import com.jsyn.util.WaveRecorder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * When notes speed up they can become a new tone. <br> + * Multiply an oscillator and an envelope. Speed up the envelope until it becomes a tone. Slow down + * the oscillator until it acts like an envelope. Write the resulting audio to a WAV file. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ + +public class NotesToTone { + + private static final Logger LOGGER = LoggerFactory.getLogger(NotesToTone.class); + + private final static double SONG_AMPLITUDE = 0.7; + private final static double INTRO_DURATION = 2.0; + private final static double OUTRO_DURATION = 2.0; + private final static double RAMP_DURATION = 20.0; + private final static double LOW_FREQUENCY = 1.0; + private final static double HIGH_FREQUENCY = 800.0; + + private final static boolean useRecorder = true; + private WaveRecorder recorder; + + private Synthesizer synth; + private ExponentialRamp envSweeper; + private ExponentialRamp oscSweeper; + private VariableRateDataReader envelopePlayer; + private UnitOscillator osc; + private LatchZeroCrossing latch; + private LineOut lineOut; + private SegmentedEnvelope envelope; + + private void play() throws IOException { + synth = JSyn.createSynthesizer(); + synth.setRealTime(true); + + if (useRecorder) { + File waveFile = new File("notes_to_tone.wav"); + // Default is stereo, 16 bits. + recorder = new WaveRecorder(synth, waveFile, 1); + LOGGER.debug("Writing to WAV file " + waveFile.getAbsolutePath()); + } + + createUnits(); + + connectUnits(); + + setupEnvelope(); + + osc.amplitude.set(SONG_AMPLITUDE); + + // Ramp the rate of the envelope up until it becomes an audible tone. + envSweeper.current.set(LOW_FREQUENCY); + envSweeper.input.set(LOW_FREQUENCY); + envSweeper.time.set(RAMP_DURATION); + + // Ramp the rate of the oscillator down until it becomes an LFO. + oscSweeper.current.set(HIGH_FREQUENCY); + oscSweeper.input.set(HIGH_FREQUENCY); + oscSweeper.time.set(RAMP_DURATION); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + // When we start the recorder it will pull data from the oscillator and + // sweeper. + if (recorder != null) { + recorder.start(); + } + + // We also need to start the LineOut if we want to hear it now. + lineOut.start(); + + // Get synthesizer time in seconds. + double timeNow = synth.getCurrentTime(); + + // Schedule start of ramps. + double songDuration = INTRO_DURATION + RAMP_DURATION + OUTRO_DURATION; + envSweeper.input.set(HIGH_FREQUENCY, timeNow + INTRO_DURATION); + oscSweeper.input.set(LOW_FREQUENCY, timeNow + INTRO_DURATION); + + // Arm zero crossing latch + latch.gate.set(0.0, timeNow + songDuration); + + // Sleep while the sound is being generated in the background thread. + try { + synth.sleepUntil(timeNow + songDuration + 2.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (recorder != null) { + recorder.stop(); + recorder.close(); + } + // Stop everything. + synth.stop(); + } + + private void createUnits() { + // Add a tone generators. + synth.add(osc = new SineOscillator()); + // Add a controller that will sweep the envelope rate up. + synth.add(envSweeper = new ExponentialRamp()); + // Add a controller that will sweep the oscillator down. + synth.add(oscSweeper = new ExponentialRamp()); + + synth.add(latch = new LatchZeroCrossing()); + // Add an output unit. + synth.add(lineOut = new LineOut()); + + // Add an envelope player. + synth.add(envelopePlayer = new VariableRateMonoReader()); + } + + private void connectUnits() { + oscSweeper.output.connect(osc.frequency); + osc.output.connect(latch.input); + // Latch when sine LFO crosses zero. + latch.output.connect(envelopePlayer.amplitude); + + envSweeper.output.connect(envelopePlayer.rate); + + // Connect the envelope player to the audio output. + envelopePlayer.output.connect(0, lineOut.input, 0); + // crossFade.output.connect( 0, lineOut.input, 1 ); + + if (recorder != null) { + envelopePlayer.output.connect(0, recorder.getInput(), 0); + // crossFade.output.connect( 0, recorder.getInput(), 1 ); + } + } + + private void setupEnvelope() { + // Setup envelope. The envelope has a total duration of 1.0 seconds. + // Values are (duration,target) pairs. + double[] pairs = new double[5 * 2 * 2]; + int i = 0; + // duration, target for delay + pairs[i++] = 0.15; + pairs[i++] = 0.0; + // duration, target for attack + pairs[i++] = 0.05; + pairs[i++] = 1.0; + // duration, target for release + pairs[i++] = 0.1; + pairs[i++] = 0.6; + // duration, target for sustain + pairs[i++] = 0.1; + pairs[i++] = 0.6; + // duration, target for release + pairs[i++] = 0.1; + pairs[i++] = 0.0; + // Create mirror image of this envelope. + int halfLength = i; + while (i < pairs.length) { + pairs[i] = pairs[i - halfLength]; + i++; + pairs[i] = pairs[i - halfLength] * -1.0; + i++; + } + envelope = new SegmentedEnvelope(pairs); + + envelopePlayer.dataQueue.queueLoop(envelope, 0, envelope.getNumFrames()); + } + + public static void main(String[] args) { + try { + new NotesToTone().play(); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/PlayChords.java b/examples/src/main/java/com/jsyn/examples/PlayChords.java new file mode 100644 index 0000000..9bd9245 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlayChords.java @@ -0,0 +1,178 @@ +/* + * 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.instruments.SubtractiveSynthVoice; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.UnitVoice; +import com.jsyn.util.VoiceAllocator; +import com.softsynth.math.AudioMath; +import com.softsynth.shared.time.TimeStamp; + +/** + * Play chords and melody using the VoiceAllocator. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class PlayChords { + private static final int MAX_VOICES = 8; + private Synthesizer synth; + private VoiceAllocator allocator; + private LineOut lineOut; + /** Number of seconds to generate music in advance of presentation-time. */ + private double advance = 0.2; + private double secondsPerBeat = 0.6; + // on time over note duration + private double dutyCycle = 0.8; + private double measure = secondsPerBeat * 4.0; + private UnitVoice[] voices; + + private void test() { + synth = JSyn.createSynthesizer(); + + // Add an output. + synth.add(lineOut = new LineOut()); + + voices = new UnitVoice[MAX_VOICES]; + for (int i = 0; i < MAX_VOICES; i++) { + SubtractiveSynthVoice voice = new SubtractiveSynthVoice(); + synth.add(voice); + voice.getOutput().connect(0, lineOut.input, 0); + voice.getOutput().connect(0, lineOut.input, 1); + voices[i] = voice; + } + allocator = new VoiceAllocator(voices); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // We only need to start the LineOut. It will pull data from the + // voices. + lineOut.start(); + + // Get synthesizer time in seconds. + double timeNow = synth.getCurrentTime(); + + // Advance to a near future time so we have a clean start. + double time = timeNow + 1.0; + + try { + int tonic = 60 - 12; + for (int i = 0; i < 4; i++) { + playMajorMeasure1(time, tonic); + time += measure; + catchUp(time); + playMajorMeasure1(time, tonic + 4); + time += measure; + catchUp(time); + playMajorMeasure1(time, tonic + 7); + time += measure; + catchUp(time); + playMinorMeasure1(time, tonic + 2); // minor chord + time += measure; + catchUp(time); + } + time += secondsPerBeat; + catchUp(time); + + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // Stop everything. + synth.stop(); + } + + private void playMinorMeasure1(double time, int base) throws InterruptedException { + int p2 = base + 3; + int p3 = base + 7; + playChord1(time, base, p2, p3); + playNoodle1(time, base + 24, p2 + 24, p3 + 24); + } + + private void playMajorMeasure1(double time, int base) throws InterruptedException { + int p2 = base + 4; + int p3 = base + 7; + playChord1(time, base, p2, p3); + playNoodle1(time, base + 24, p2 + 24, p3 + 24); + } + + private void playNoodle1(double time, int p1, int p2, int p3) { + double secondsPerNote = secondsPerBeat * 0.5; + for (int i = 0; i < 8; i++) { + int p = pickFromThree(p1, p2, p3); + noteOn(time, p); + noteOff(time + dutyCycle * secondsPerNote, p); + time += secondsPerNote; + } + } + + private int pickFromThree(int p1, int p2, int p3) { + int r = (int) (Math.random() * 3.0); + if (r < 1) + return p1; + else if (r < 2) + return p2; + else + return p3; + } + + private void playChord1(double time, int p1, int p2, int p3) throws InterruptedException { + double dur = dutyCycle * secondsPerBeat; + playTriad(time, dur, p1, p2, p3); + time += secondsPerBeat; + playTriad(time, dur, p1, p2, p3); + time += secondsPerBeat; + playTriad(time, dur * 0.25, p1, p2, p3); + time += secondsPerBeat * 0.25; + playTriad(time, dur * 0.25, p1, p2, p3); + time += secondsPerBeat * 0.75; + playTriad(time, dur, p1, p2, p3); + time += secondsPerBeat; + } + + private void playTriad(double time, double dur, int p1, int p2, int p3) + throws InterruptedException { + noteOn(time, p1); + noteOn(time, p2); + noteOn(time, p3); + double offTime = time + dur; + noteOff(offTime, p1); + noteOff(offTime, p2); + noteOff(offTime, p3); + } + + private void catchUp(double time) throws InterruptedException { + synth.sleepUntil(time - advance); + } + + private void noteOff(double time, int noteNumber) { + allocator.noteOff(noteNumber, new TimeStamp(time)); + } + + private void noteOn(double time, int noteNumber) { + double frequency = AudioMath.pitchToFrequency(noteNumber); + double amplitude = 0.2; + TimeStamp timeStamp = new TimeStamp(time); + allocator.noteOn(noteNumber, frequency, amplitude, timeStamp); + } + + public static void main(String[] args) { + new PlayChords().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/PlayCustomUnit.java b/examples/src/main/java/com/jsyn/examples/PlayCustomUnit.java new file mode 100644 index 0000000..609c351 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlayCustomUnit.java @@ -0,0 +1,73 @@ +/* + * 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.UnitOscillator; + +/** + * Play a tone using a JSyn oscillator and process it using a custom unit generator. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PlayCustomUnit { + private Synthesizer synth; + private UnitOscillator osc; + private CustomCubeUnit cuber; + private LineOut lineOut; + + private void test() { + synth = JSyn.createSynthesizer(); + // Add a tone generator. + synth.add(osc = new SineOscillator()); + // Add a tone generator. + synth.add(cuber = new CustomCubeUnit()); + // Add an output to the DAC. + synth.add(lineOut = new LineOut()); + // Connect the oscillator to the cuber. + osc.output.connect(0, cuber.input, 0); + // Connect the cuber to the right output. + cuber.output.connect(0, lineOut.input, 1); + // Send the original to the left output for comparison. + osc.output.connect(0, lineOut.input, 0); + + osc.frequency.set(240.0); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // We only need to start the LineOut. + // It will pull data from the cuber and the oscillator. + lineOut.start(); + // Sleep while the sound is generated in the background. + try { + double time = synth.getCurrentTime(); + // Sleep for a few seconds. + synth.sleepUntil(time + 10.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + new PlayCustomUnit().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/PlayFunction.java b/examples/src/main/java/com/jsyn/examples/PlayFunction.java new file mode 100644 index 0000000..9c389c9 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlayFunction.java @@ -0,0 +1,90 @@ +/* + * 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.data.Function; +import com.jsyn.unitgen.FunctionOscillator; +import com.jsyn.unitgen.LineOut; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play a tone using a FunctionOscillator. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PlayFunction { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlayFunction.class); + + private void test() { + // Create a context for the synthesizer. + var synth = JSyn.createSynthesizer(); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + // Add a FunctionOscillator + var oscillator = new FunctionOscillator(); + synth.add(oscillator); + + // Define a function that gives the shape of the waveform. + Function func = input -> { + // Input ranges from -1.0 to 1.0 + double s = Math.sin(input * Math.PI * 2.0); + return s * s * s; + }; + + oscillator.function.set(func); + + // Add a stereo audio output unit. + var lineOut = new LineOut(); + synth.add(lineOut); + + // Connect the oscillator to both channels of the output. + oscillator.output.connect(0, lineOut.input, 0); + oscillator.output.connect(0, lineOut.input, 1); + + // Set the frequency and amplitude for the sine wave. + oscillator.frequency.set(345.0); + oscillator.amplitude.set(0.6); + + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + + LOGGER.debug("You should now be hearing a sine wave. ---------"); + + // Sleep while the sound is generated in the background. + try { + double time = synth.getCurrentTime(); + // Sleep for a few seconds. + synth.sleepUntil(time + 4.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + LOGGER.debug("Stop playing. -------------------"); + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + new PlayFunction().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/PlayGrains.java b/examples/src/main/java/com/jsyn/examples/PlayGrains.java new file mode 100644 index 0000000..82b390e --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlayGrains.java @@ -0,0 +1,211 @@ +/* + * Copyright 2011 Phil Burk, Mobileer Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.jsyn.examples; + +import java.awt.BorderLayout; +import java.awt.GridLayout; +import java.io.File; +import java.io.IOException; + +import javax.swing.BorderFactory; +import javax.swing.JApplet; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.FloatSample; +import com.jsyn.ports.UnitInputPort; +import com.jsyn.scope.AudioScope; +import com.jsyn.swing.DoubleBoundedRangeModel; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.swing.PortModelFactory; +import com.jsyn.swing.RotaryTextController; +import com.jsyn.unitgen.ContinuousRamp; +import com.jsyn.unitgen.GrainFarm; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SampleGrainFarm; +import com.jsyn.util.SampleLoader; +import com.jsyn.util.WaveRecorder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play with Granular Synthesis tools. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class PlayGrains extends JApplet { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlayGrains.class); + + private Synthesizer synth; + private LineOut lineOut; + private AudioScope scope; + private GrainFarm grainFarm; + private ContinuousRamp ramp; + private static final int NUM_GRAINS = 32; + private FloatSample sample; + private WaveRecorder recorder; + private final static boolean useRecorder = false; + + private static final boolean useSample = true; + // If you enable useSample then you will need to replace the file name below with a valid + // file name on your computer. + private static final File sampleFile = new File("notes_to_tone.wav"); + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + PlayGrains applet = new PlayGrains(); + JAppletFrame frame = new JAppletFrame("PlayGrains", applet); + frame.setSize(840, 500); + frame.setVisible(true); + frame.test(); + } + + private void setupGUI() { + setLayout(new BorderLayout()); + + add(BorderLayout.NORTH, + new JLabel("PlayGrains in an AudioScope - JSyn V" + synth.getVersion())); + + scope = new AudioScope(synth); + + // scope.addProbe( osc.output ); + scope.addProbe(grainFarm.output); + + scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); + scope.getView().setControlsVisible(true); + add(BorderLayout.CENTER, scope.getView()); + scope.start(); + + // Arrange the knob in a row. + JPanel knobPanel = new JPanel(); + knobPanel.setLayout(new GridLayout(1, 0)); + + if (useSample) { + SampleGrainFarm sampleGrainFarm = (SampleGrainFarm) grainFarm; + knobPanel.add(setupLinearPortKnob(ramp.time, 0.001, 10.0, "Time")); + knobPanel.add(setupLinearPortKnob(ramp.input, -1.0, 1.0, "Position")); + knobPanel.add(setupLinearPortKnob(sampleGrainFarm.positionRange, 0.0, 0.5, "PosRange")); + } + knobPanel.add(setupPortKnob(grainFarm.density, 1.0, "Density")); + knobPanel.add(setupPortKnob(grainFarm.rate, 4.0, "Rate")); + knobPanel.add(setupPortKnob(grainFarm.rateRange, 3.0, "RateRange")); + knobPanel.add(setupPortKnob(grainFarm.duration, 0.1, "Duration")); + knobPanel.add(setupPortKnob(grainFarm.durationRange, 3.0, "DurRange")); + knobPanel.add(setupPortKnob(grainFarm.amplitude, 6.0, "Amplitude")); + knobPanel.add(setupPortKnob(grainFarm.amplitudeRange, 1.0, "AmpRange")); + add(knobPanel, BorderLayout.SOUTH); + + validate(); + } + + private RotaryTextController setupLinearPortKnob(UnitInputPort port, double min, double max, + String label) { + port.setMinimum(min); + port.setMaximum(max); + + DoubleBoundedRangeModel model = PortModelFactory.createLinearModel(port); + RotaryTextController knob = new RotaryTextController(model, 10); + knob.setBorder(BorderFactory.createTitledBorder(label)); + knob.setTitle(label); + return knob; + } + + private RotaryTextController setupPortKnob(UnitInputPort port, double max, String label) { + port.setMinimum(0.0); + port.setMaximum(max); + + DoubleBoundedRangeModel model = PortModelFactory.createExponentialModel(port); + RotaryTextController knob = new RotaryTextController(model, 10); + knob.setBorder(BorderFactory.createTitledBorder(label)); + knob.setTitle(label); + return knob; + } + + @Override + public void start() { + synth = JSyn.createSynthesizer(); + + try { + + if (useRecorder) { + File waveFile = new File("temp_recording.wav"); + // Record mono 16 bits. + recorder = new WaveRecorder(synth, waveFile, 1); + LOGGER.debug("Writing to WAV file " + waveFile.getAbsolutePath()); + } + + if (useSample) { + sample = SampleLoader.loadFloatSample(sampleFile); + SampleGrainFarm sampleGrainFarm = new SampleGrainFarm(); + synth.add(ramp = new ContinuousRamp()); + sampleGrainFarm.setSample(sample); + ramp.output.connect(sampleGrainFarm.position); + grainFarm = sampleGrainFarm; + } else { + grainFarm = new GrainFarm(); + } + + synth.add(grainFarm); + + grainFarm.allocate(NUM_GRAINS); + + // Add an output so we can hear the grains. + synth.add(lineOut = new LineOut()); + + grainFarm.getOutput().connect(0, lineOut.input, 0); + grainFarm.getOutput().connect(0, lineOut.input, 1); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + if (useRecorder) { + grainFarm.output.connect(0, recorder.getInput(), 0); + // When we start the recorder it will pull data from the + // oscillator + // and sweeper. + recorder.start(); + } + + setupGUI(); + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + + @Override + public void stop() { + try { + if (recorder != null) { + recorder.stop(); + recorder.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + scope.stop(); + synth.stop(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/PlayMIDI.java b/examples/src/main/java/com/jsyn/examples/PlayMIDI.java new file mode 100644 index 0000000..92add86 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlayMIDI.java @@ -0,0 +1,233 @@ +/* + * 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.examples; + +import java.io.IOException; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.instruments.DualOscillatorSynthVoice; +import com.jsyn.midi.MidiConstants; +import com.jsyn.midi.MidiSynthesizer; +import com.jsyn.unitgen.LineOut; +import com.jsyn.util.MultiChannelSynthesizer; +import com.jsyn.util.VoiceDescription; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Send MIDI messages to JSyn based MIDI synthesizer. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PlayMIDI { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlayMIDI.class); + + private static final int NUM_CHANNELS = 16; + private static final int VOICES_PER_CHANNEL = 6; + private Synthesizer synth; + private MidiSynthesizer midiSynthesizer; + private LineOut lineOut; + + private VoiceDescription voiceDescription; + private MultiChannelSynthesizer multiSynth; + + public static void main(String[] args) { + PlayMIDI app = new PlayMIDI(); + try { + VoiceDescription description = DualOscillatorSynthVoice.getVoiceDescription(); + app.test(description); + LOGGER.debug("Test complete"); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + System.exit(0); + } + + public void sendMidiMessage(byte[] bytes) { + midiSynthesizer.onReceive(bytes, 0, bytes.length); + } + + public void sendNoteOff(int channel, int pitch, int velocity) { + midiCommand(MidiConstants.NOTE_OFF + channel, pitch, velocity); + } + + public void sendNoteOn(int channel, int pitch, int velocity) { + midiCommand(MidiConstants.NOTE_ON + channel, pitch, velocity); + } + + public void sendControlChange(int channel, int index, int value) { + midiCommand(MidiConstants.CONTROL_CHANGE + channel, index, value); + } + + /** + * @param channel + * @param program starts at zero + */ + private void sendProgramChange(int channel, int program) { + midiCommand(MidiConstants.PROGRAM_CHANGE + channel, program); + + } + + /** + * Send either RPN or NRPN. + */ + public void sendParameter(int channel, int index14, int value14, int controllerXPN) { + int indexLsb = index14 & 0x07F; + int indexMsb = (index14 >> 7) & 0x07F; + int valueLsb = value14 & 0x07F; + int valueMsb = (value14 >> 7) & 0x07F; + sendControlChange(channel, controllerXPN + 1, indexMsb); + sendControlChange(channel, controllerXPN, indexLsb); + sendControlChange(channel, MidiConstants.CONTROLLER_DATA_ENTRY, valueMsb); + sendControlChange(channel, MidiConstants.CONTROLLER_DATA_ENTRY_LSB, valueLsb); + sendControlChange(channel, controllerXPN + 1, 0x7F); // NULL RPN index + sendControlChange(channel, controllerXPN, 0x7F); // to deactivate RPN + } + + public void sendRPN(int channel, int index14, int value14) { + sendParameter(channel, index14, value14, MidiConstants.CONTROLLER_RPN_LSB); + } + + public void sendNRPN(int channel, int index14, int value14) { + sendParameter(channel, index14, value14, MidiConstants.CONTROLLER_NRPN_LSB); + } + + private void midiCommand(int status, int data1, int data2) { + byte[] buffer = new byte[3]; + buffer[0] = (byte) status; + buffer[1] = (byte) data1; + buffer[2] = (byte) data2; + sendMidiMessage(buffer); + } + + private void midiCommand(int status, int data1) { + byte[] buffer = new byte[2]; + buffer[0] = (byte) status; + buffer[1] = (byte) data1; + sendMidiMessage(buffer); + } + + public int test(VoiceDescription description) throws IOException, InterruptedException { + setupSynth(description); + + //playOctaveUsingBend(); + playSameNotesBent(); + + // Setup all the channels. + int maxChannels = 8; + for (int channel = 0; channel < maxChannels; channel++) { + sendProgramChange(channel, channel); + } + playNotePerChannel(maxChannels); + + return 0; + } + + private void playOctaveUsingBend() throws InterruptedException { + sendProgramChange(0, 0); + float range0 = 12.0f; + sendPitchBendRange(0, range0); + for(int i = 0; i < 13; i++) { + LOGGER.debug("Bend to pitch " + i); + sendPitchBend(0, i / range0); + sendNoteOn(0, 60, 100); + synth.sleepFor(0.5); + sendNoteOff(0, 60, 100); + synth.sleepFor(0.5); + } + } + + private void playSameNotesBent() throws InterruptedException { + sendProgramChange(0, 0); + sendProgramChange(1, 0); + float range0 = 2.3f; + float range1 = 6.8f; + sendPitchBendRange(0, range0); + sendPitchBendRange(1, range1); + sendPitchBend(0, 0.0f / range0); // bend by 0 semitones + sendPitchBend(1, 1.0f / range1); // bend by 1 semitones + + LOGGER.debug("These two notes should play at the same pitch."); + sendNoteOn(0, 61, 100); + synth.sleepFor(0.5); + sendNoteOff(0, 61, 100); + + sendNoteOn(1, 60, 100); + synth.sleepFor(0.5); + sendNoteOff(1, 60, 100); + + synth.sleepFor(2.0); + LOGGER.debug("------ done ---------------"); + } + + /** + * + * @param channel + * @param normalizedBend between -1 and +1 + */ + private void sendPitchBend(int channel, float normalizedBend) { + final int BEND_MIN = 0x0000; + final int BEND_CENTER = 0x2000; + final int BEND_MAX = 0x3FFF; + int bend = BEND_CENTER + (int)(BEND_CENTER * normalizedBend); + if (bend < BEND_MIN) bend = BEND_MIN; + else if (bend > BEND_MAX) bend = BEND_MAX; + int lsb = bend & 0x07F; + int msb = (bend >> 7) & 0x07F; + midiCommand(MidiConstants.PITCH_BEND + channel, lsb, msb); + } + + private void sendPitchBendRange(int channel, float range0) { + int semitones = (int)range0; + int cents = (int) (100 * (range0 - semitones)); + int value = (semitones << 7) + cents; + sendRPN(channel, MidiConstants.RPN_BEND_RANGE, value); + } + + private void playNotePerChannel(int maxChannels) throws InterruptedException { + // Play notes on those channels. + for (int channel = 0; channel < maxChannels; channel++) { + sendNoteOn(channel, 60 + channel, 100); + synth.sleepFor(0.5); + sendNoteOff(channel, 60 + channel, 100); + synth.sleepFor(0.5); + } + } + + private void setupSynth(VoiceDescription description) { + synth = JSyn.createSynthesizer(); + + // Add an output. + synth.add(lineOut = new LineOut()); + + voiceDescription = description; + multiSynth = new MultiChannelSynthesizer(); + final int startChannel = 0; + multiSynth.setup(synth, startChannel, NUM_CHANNELS, VOICES_PER_CHANNEL, voiceDescription); + midiSynthesizer = new MidiSynthesizer(multiSynth); + + multiSynth.getOutput().connect(0,lineOut.input, 0); + multiSynth.getOutput().connect(1,lineOut.input, 1); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + lineOut.start(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/PlayNotes.java b/examples/src/main/java/com/jsyn/examples/PlayNotes.java new file mode 100644 index 0000000..636dc1b --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlayNotes.java @@ -0,0 +1,102 @@ +/* + * 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SawtoothOscillator; +import com.softsynth.shared.time.TimeStamp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play notes using timestamped noteOn and noteOff methods of the UnitVoice. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class PlayNotes { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlayNotes.class); + + private void test() { + + // Create a context for the synthesizer. + var synth = JSyn.createSynthesizer(); + // Set output latency to 123 msec because this is not an interactive app. + synth.getAudioDeviceManager().setSuggestedOutputLatency(0.123); + + // Add a tone generator. + var voice = new SawtoothOscillator(); + synth.add(voice); + // synth.add( ugen = new SineOscillator() ); + // synth.add( ugen = new SubtractiveSynthVoice() ); + // Add an output mixer. + var lineOut = new LineOut(); + synth.add(lineOut); + + // Connect the oscillator to the left and right audio output. + voice.getOutput().connect(0, lineOut.input, 0); + voice.getOutput().connect(0, lineOut.input, 1); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + // Get synthesizer time in seconds. + double timeNow = synth.getCurrentTime(); + + // Advance to a near future time so we have a clean start. + TimeStamp timeStamp = new TimeStamp(timeNow + 0.5); + + // We only need to start the LineOut. It will pull data from the + // oscillator. + synth.startUnit(lineOut, timeStamp); + + // Schedule a note on and off. + double freq = 200.0; // hertz + double duration = 1.4; + double onTime = 1.0; + voice.noteOn(freq, 0.5, timeStamp); + voice.noteOff(timeStamp.makeRelative(onTime)); + + // Schedule this to happen a bit later. + timeStamp = timeStamp.makeRelative(duration); + freq *= 1.5; // up a perfect fifth + voice.noteOn(freq, 0.5, timeStamp); + voice.noteOff(timeStamp.makeRelative(onTime)); + + timeStamp = timeStamp.makeRelative(duration); + freq *= 4.0 / 5.0; // down a major third + voice.noteOn(freq, 0.5, timeStamp); + voice.noteOff(timeStamp.makeRelative(onTime)); + + // Sleep while the song is being generated in the background thread. + try { + LOGGER.debug("Sleep while synthesizing."); + synth.sleepUntil(timeStamp.getTime() + 2.0); + LOGGER.debug("Woke up..."); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + new PlayNotes().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/PlayPartials.java b/examples/src/main/java/com/jsyn/examples/PlayPartials.java new file mode 100644 index 0000000..72e1d7f --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlayPartials.java @@ -0,0 +1,115 @@ +/* + * 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.LinearRamp; +import com.jsyn.unitgen.Multiply; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.UnitOscillator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play a enharmonic sine tones using JSyn oscillators. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PlayPartials { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlayPartials.class); + + private Synthesizer synth; + private UnitOscillator[] osc; + private Multiply[] multipliers; + private LinearRamp ramp; + private LineOut lineOut; + private double[] amps = { + 0.2, 0.1, 0.3, 0.4 + }; + private double[] ratios = { + 1.0, Math.sqrt(2.0), Math.E, Math.PI + }; + + private void test() { + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + // Add a stereo audio output unit. + synth.add(lineOut = new LineOut()); + synth.add(ramp = new LinearRamp()); + + // Add a tone generator. + osc = new SineOscillator[amps.length]; + multipliers = new Multiply[ratios.length]; + + for (int i = 0; i < osc.length; i++) { + // Create unit generators and store them in arrays. + synth.add(osc[i] = new SineOscillator()); + synth.add(multipliers[i] = new Multiply()); + + // Connect each oscillator to both channels of the output. + // They will be mixed automatically. + osc[i].output.connect(0, lineOut.input, 0); + osc[i].output.connect(0, lineOut.input, 1); + + // Use a multiplier to scale the output of the ramp. + // output = inputA * inputB + ramp.output.connect(multipliers[i].inputA); + multipliers[i].output.connect(osc[i].frequency); + multipliers[i].inputB.set(ratios[i]); + + osc[i].amplitude.set(amps[i]); + } + + // start ramping up + ramp.current.set(100.0); + ramp.time.set(3.0); + ramp.input.set(700.0); + + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + + LOGGER.debug("You should now be hearing a sine wave. ---------"); + + // Sleep while the sound is generated in the background. + try { + // Sleep for a few seconds. + synth.sleepFor(4.0); + // ramp down + ramp.input.set(100.0); + synth.sleepFor(4.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + LOGGER.debug("Stop playing. -------------------"); + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + LOGGER.debug("Java version = " + System.getProperty("java.version")); + new PlayPartials().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/PlaySample.java b/examples/src/main/java/com/jsyn/examples/PlaySample.java new file mode 100644 index 0000000..280023e --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlaySample.java @@ -0,0 +1,126 @@ +/* + * 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.examples; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.FloatSample; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.VariableRateDataReader; +import com.jsyn.unitgen.VariableRateMonoReader; +import com.jsyn.unitgen.VariableRateStereoReader; +import com.jsyn.util.SampleLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play a sample from a WAV file using JSyn. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PlaySample { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlaySample.class); + + private Synthesizer synth; + private VariableRateDataReader samplePlayer; + private LineOut lineOut; + + private void test() { + + URL sampleFile; + try { + sampleFile = new URL("http://www.softsynth.com/samples/Clarinet.wav"); + // sampleFile = new URL("http://www.softsynth.com/samples/NotHereNow22K.wav"); + } catch (MalformedURLException e2) { + e2.printStackTrace(); + return; + } + + synth = JSyn.createSynthesizer(); + + FloatSample sample; + try { + // Add an output mixer. + synth.add(lineOut = new LineOut()); + + // Load the sample and display its properties. + SampleLoader.setJavaSoundPreferred(false); + sample = SampleLoader.loadFloatSample(sampleFile); + LOGGER.debug("Sample has: channels = " + sample.getChannelsPerFrame()); + LOGGER.debug(" frames = " + sample.getNumFrames()); + LOGGER.debug(" rate = " + sample.getFrameRate()); + LOGGER.debug(" loopStart = " + sample.getSustainBegin()); + LOGGER.debug(" loopEnd = " + sample.getSustainEnd()); + + if (sample.getChannelsPerFrame() == 1) { + synth.add(samplePlayer = new VariableRateMonoReader()); + samplePlayer.output.connect(0, lineOut.input, 0); + } else if (sample.getChannelsPerFrame() == 2) { + synth.add(samplePlayer = new VariableRateStereoReader()); + samplePlayer.output.connect(0, lineOut.input, 0); + samplePlayer.output.connect(1, lineOut.input, 1); + } else { + throw new RuntimeException("Can only play mono or stereo samples."); + } + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + samplePlayer.rate.set(sample.getFrameRate()); + + // We only need to start the LineOut. It will pull data from the + // sample player. + lineOut.start(); + + // We can simply queue the entire file. + // Or if it has a loop we can play the loop for a while. + if (sample.getSustainBegin() < 0) { + LOGGER.debug("queue the sample"); + samplePlayer.dataQueue.queue(sample); + } else { + LOGGER.debug("queueOn the sample"); + samplePlayer.dataQueue.queueOn(sample); + synth.sleepFor(8.0); + LOGGER.debug("queueOff the sample"); + samplePlayer.dataQueue.queueOff(sample); + } + + // Wait until the sample has finished playing. + do { + synth.sleepFor(1.0); + } while (samplePlayer.dataQueue.hasMore()); + + synth.sleepFor(0.5); + + } catch (IOException e1) { + e1.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + new PlaySample().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/PlaySampleCrossfade.java b/examples/src/main/java/com/jsyn/examples/PlaySampleCrossfade.java new file mode 100644 index 0000000..2157039 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlaySampleCrossfade.java @@ -0,0 +1,188 @@ +/* + * 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.examples; + +import java.awt.GridLayout; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.swing.JApplet; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.FloatSample; +import com.jsyn.devices.AudioDeviceFactory; +import com.jsyn.ports.QueueDataCommand; +import com.jsyn.swing.DoubleBoundedRangeModel; +import com.jsyn.swing.DoubleBoundedRangeSlider; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.swing.PortControllerFactory; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.VariableRateDataReader; +import com.jsyn.unitgen.VariableRateMonoReader; +import com.jsyn.unitgen.VariableRateStereoReader; +import com.jsyn.util.SampleLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play a sample from a WAV file using JSyn. Use a crossfade to play a loop at an arbitrary + * position. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PlaySampleCrossfade extends JApplet { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlaySampleCrossfade.class); + + private static final double LOOP_START_FRACTION = 0.2; + private Synthesizer synth; + private VariableRateDataReader samplePlayer; + private LineOut lineOut; + private FloatSample sample; + private DoubleBoundedRangeModel rangeModelSize; + private DoubleBoundedRangeModel rangeModelCrossfade; + private int loopStartFrame; + + @Override + public void init() { + + URL sampleFile; + try { + sampleFile = new URL("http://www.softsynth.com/samples/Clarinet.wav"); + } catch (MalformedURLException e2) { + e2.printStackTrace(); + return; + } + + synth = JSyn.createSynthesizer(AudioDeviceFactory.createAudioDeviceManager(true)); + + try { + // Add an output mixer. + synth.add(lineOut = new LineOut()); + + // Load the sample and display its properties. + SampleLoader.setJavaSoundPreferred(false); + sample = SampleLoader.loadFloatSample(sampleFile); + LOGGER.debug("Sample has: channels = " + sample.getChannelsPerFrame()); + LOGGER.debug(" frames = " + sample.getNumFrames()); + LOGGER.debug(" rate = " + sample.getFrameRate()); + LOGGER.debug(" loopStart = " + sample.getSustainBegin()); + LOGGER.debug(" loopEnd = " + sample.getSustainEnd()); + + if (sample.getChannelsPerFrame() == 1) { + synth.add(samplePlayer = new VariableRateMonoReader()); + samplePlayer.output.connect(0, lineOut.input, 0); + } else if (sample.getChannelsPerFrame() == 2) { + synth.add(samplePlayer = new VariableRateStereoReader()); + samplePlayer.output.connect(0, lineOut.input, 0); + samplePlayer.output.connect(1, lineOut.input, 1); + } else { + throw new RuntimeException("Can only play mono or stereo samples."); + } + + samplePlayer.rate.set(sample.getFrameRate()); + + } catch (IOException e1) { + e1.printStackTrace(); + } + + // Start at arbitrary position near beginning of sample. + loopStartFrame = (int) (sample.getNumFrames() * LOOP_START_FRACTION); + + // Arrange the faders in a stack. + setLayout(new GridLayout(0, 1)); + + samplePlayer.rate.setup(4000.0, sample.getFrameRate(), sample.getFrameRate() * 2.0); + add(PortControllerFactory.createExponentialPortSlider(samplePlayer.rate)); + + // Use fader to select arbitrary loop size. + rangeModelSize = new DoubleBoundedRangeModel("LoopSize", 10000, 0.01, + (1.0 - LOOP_START_FRACTION), 0.5); + rangeModelSize.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + queueNewLoop(); + } + + }); + add(new DoubleBoundedRangeSlider(rangeModelSize, 3)); + + // Use fader to set the size of the crossfade region. + rangeModelCrossfade = new DoubleBoundedRangeModel("Crossfade", 1000, 0.0, 1000.0, 0.0); + rangeModelCrossfade.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + queueNewLoop(); + } + + }); + + add(new DoubleBoundedRangeSlider(rangeModelCrossfade, 3)); + + validate(); + } + + private void queueNewLoop() { + int loopSize = (int) (sample.getNumFrames() * rangeModelSize.getDoubleValue()); + if ((loopStartFrame + loopSize) > sample.getNumFrames()) { + loopSize = sample.getNumFrames() - loopStartFrame; + } + int crossFadeSize = (int) (rangeModelCrossfade.getDoubleValue()); + + // For complex queuing operations, create a command and then customize it. + QueueDataCommand command = samplePlayer.dataQueue.createQueueDataCommand(sample, + loopStartFrame, loopSize); + command.setNumLoops(-1); + command.setSkipIfOthers(true); + command.setCrossFadeIn(crossFadeSize); + + LOGGER.debug("Queue: " + loopStartFrame + ", #" + loopSize + ", X=" + crossFadeSize); + synth.queueCommand(command); + } + + @Override + public void start() { + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // Start the LineOut. It will pull data from the oscillator. + lineOut.start(); + + // Queue attack portion of sample. + samplePlayer.dataQueue.queue(sample, 0, loopStartFrame); + queueNewLoop(); + } + + @Override + public void stop() { + synth.stop(); + synth.stop(); + } + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + PlaySampleCrossfade applet = new PlaySampleCrossfade(); + JAppletFrame frame = new JAppletFrame("PlaySampleCrossfade", applet); + frame.setSize(440, 300); + frame.setVisible(true); + frame.test(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/PlaySampleWaveShaper.java b/examples/src/main/java/com/jsyn/examples/PlaySampleWaveShaper.java new file mode 100644 index 0000000..3ac320b --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlaySampleWaveShaper.java @@ -0,0 +1,116 @@ +/* + * 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.examples; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.FloatSample; +import com.jsyn.unitgen.FunctionEvaluator; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.util.SampleLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play a sample from a WAV file using JSyn. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PlaySampleWaveShaper { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlaySampleWaveShaper.class); + + private Synthesizer synth; + private LineOut lineOut; + + private void test() { + + URL sampleFile; + try { + sampleFile = new URL("http://www.softsynth.com/samples/Clarinet.wav"); + // sampleFile = new URL("http://www.softsynth.com/samples/NotHereNow22K.wav"); + } catch (MalformedURLException e2) { + e2.printStackTrace(); + return; + } + + synth = JSyn.createSynthesizer(); + + FloatSample sample; + try { + // Add an output mixer. + synth.add(lineOut = new LineOut()); + + // Load the sample and display its properties. + SampleLoader.setJavaSoundPreferred(false); + sample = SampleLoader.loadFloatSample(sampleFile); + LOGGER.debug("Sample has: channels = " + sample.getChannelsPerFrame()); + LOGGER.debug(" frames = " + sample.getNumFrames()); + LOGGER.debug(" rate = " + sample.getFrameRate()); + LOGGER.debug(" loopStart = " + sample.getSustainBegin()); + LOGGER.debug(" loopEnd = " + sample.getSustainEnd()); + + if (sample.getChannelsPerFrame() != 1) { + throw new RuntimeException("Can only use mono samples."); + } + + LOGGER.debug("eval -1.1 = " + sample.evaluate(-1.1)); + LOGGER.debug("eval -1.0 = " + sample.evaluate(-1.0)); + LOGGER.debug("eval 0.3 = " + sample.evaluate(0.3)); + LOGGER.debug("eval 1.0 = " + sample.evaluate(1.0)); + LOGGER.debug("eval 1.1 = " + sample.evaluate(1.1)); + + FunctionEvaluator shaper = new FunctionEvaluator(); + shaper.function.set(sample); + synth.add(shaper); + + shaper.output.connect(0, lineOut.input, 0); + shaper.output.connect(0, lineOut.input, 1); + + SineOscillator osc = new SineOscillator(); + osc.frequency.set(0.2); + osc.output.connect(shaper.input); + synth.add(osc); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + // We only need to start the LineOut. It will pull data from the + // sample player. + lineOut.start(); + + // Wait until the sample has finished playing. + synth.sleepFor(5.0); + + } catch (IOException e1) { + e1.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + new PlaySampleWaveShaper().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/PlaySegmentedEnvelope.java b/examples/src/main/java/com/jsyn/examples/PlaySegmentedEnvelope.java new file mode 100644 index 0000000..b7fb217 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlaySegmentedEnvelope.java @@ -0,0 +1,156 @@ +/* + * 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.examples; + +import java.io.File; +import java.io.IOException; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.SegmentedEnvelope; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.unitgen.VariableRateDataReader; +import com.jsyn.unitgen.VariableRateMonoReader; +import com.jsyn.util.WaveRecorder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Modulate the amplitude of an oscillator using a segmented envelope. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PlaySegmentedEnvelope { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlaySegmentedEnvelope.class); + + private Synthesizer synth; + private UnitOscillator osc; + private LineOut lineOut; + private SegmentedEnvelope envelope; + private VariableRateDataReader envelopePlayer; + private WaveRecorder recorder; + private final static boolean useRecorder = true; + + private void test() throws IOException { + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + // Add a tone generator. + synth.add(osc = new SawtoothOscillatorBL()); + // Add an envelope player. + synth.add(envelopePlayer = new VariableRateMonoReader()); + + if (useRecorder) { + File waveFile = new File("temp_recording.wav"); + // Default is stereo, 16 bits. + recorder = new WaveRecorder(synth, waveFile); + LOGGER.debug("Writing to WAV file " + waveFile.getAbsolutePath()); + } + + // Create an envelope consisting of (duration,value) pairs. + double[] pairs = { + 0.1, 1.0, 0.2, 0.3, 0.6, 0.0 + }; + envelope = new SegmentedEnvelope(pairs); + + // Add an output mixer. + synth.add(lineOut = new LineOut()); + envelopePlayer.output.connect(osc.amplitude); + // Connect the oscillator to the output. + osc.output.connect(0, lineOut.input, 0); + osc.output.connect(0, lineOut.input, 1); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + if (useRecorder) { + osc.output.connect(0, recorder.getInput(), 0); + envelopePlayer.output.connect(0, recorder.getInput(), 1); + // When we start the recorder it will pull data from the oscillator + // and sweeper. + recorder.start(); + } + + // We only need to start the LineOut. It will pull data from the other + // units. + lineOut.start(); + + try { + // --------------------------------------------- + // Queue the entire envelope to play once. + envelopePlayer.dataQueue.queue(envelope); + synth.sleepFor(2.0); + + // --------------------------------------------- + // Queue the attack, then sustain for a while, then queue the + // release. + osc.frequency.set(750.0); + envelopePlayer.dataQueue.queue(envelope, 0, 2); // attack + synth.sleepFor(2.0); + envelopePlayer.dataQueue.queue(envelope, 2, 1); // release + synth.sleepFor(2.0); + + // --------------------------------------------- + // Queue the attack, then sustain for a while, then queue the + // release. But this time use the sustain loop points. + osc.frequency.set(950.0); + // For noteOn, we want to play frames 0 and 1 then stop before 2. + envelope.setSustainBegin(2); + envelope.setSustainEnd(2); + envelopePlayer.dataQueue.queueOn(envelope); // attack + synth.sleepFor(2.0); + envelopePlayer.dataQueue.queueOff(envelope); // release + synth.sleepFor(2.0); + + // --------------------------------------------- + // Queue the entire envelope to play 4 times (3 loops back). + osc.frequency.set(350.0); + envelopePlayer.dataQueue.queueLoop(envelope, 0, envelope.getNumFrames(), 3); + synth.sleepFor(5.0); + + // --------------------------------------------- + // Queue the entire envelope as a repeating loop. + // It will loop until something else is queued. + osc.frequency.set(450.0); + envelopePlayer.dataQueue.queueLoop(envelope, 0, envelope.getNumFrames()); + envelopePlayer.rate.set(3.0); + synth.sleepFor(5.0); + // Queue last frame to stop the looping. + envelopePlayer.dataQueue.queue(envelope, envelope.getNumFrames() - 1, 1); + synth.sleepFor(1.0); + + if (recorder != null) { + recorder.stop(); + recorder.close(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + try { + new PlaySegmentedEnvelope().test(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/com/jsyn/examples/PlaySegmentedEnvelopeCallback.java b/examples/src/main/java/com/jsyn/examples/PlaySegmentedEnvelopeCallback.java new file mode 100644 index 0000000..d8f4ff3 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlaySegmentedEnvelopeCallback.java @@ -0,0 +1,119 @@ +/* + * 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.SegmentedEnvelope; +import com.jsyn.ports.QueueDataCommand; +import com.jsyn.ports.QueueDataEvent; +import com.jsyn.ports.UnitDataQueueCallback; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.unitgen.VariableRateDataReader; +import com.jsyn.unitgen.VariableRateMonoReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Use a UnitDataQueueCallback to notify us of the envelope's progress. Modulate the amplitude of an + * oscillator using a segmented envelope. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PlaySegmentedEnvelopeCallback { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlaySegmentedEnvelopeCallback.class); + + private Synthesizer synth; + private UnitOscillator osc; + private LineOut lineOut; + private SegmentedEnvelope envelope; + private VariableRateDataReader envelopePlayer; + + private void test() { + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + // Add a tone generator. + synth.add(osc = new SawtoothOscillatorBL()); + // Add an envelope player. + synth.add(envelopePlayer = new VariableRateMonoReader()); + + // Create an envelope consisting of (duration,value) pairs. + double[] pairs = { + 0.1, 1.0, 0.2, 0.5, 0.6, 0.0 + }; + envelope = new SegmentedEnvelope(pairs); + + // Add an output mixer. + synth.add(lineOut = new LineOut()); + envelopePlayer.output.connect(osc.amplitude); + // Connect the oscillator to the output. + osc.output.connect(0, lineOut.input, 0); + osc.output.connect(0, lineOut.input, 1); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // We only need to start the LineOut. It will pull data from the other + // units. + lineOut.start(); + + try { + // Queue an envelope with callbacks. + QueueDataCommand command = envelopePlayer.dataQueue.createQueueDataCommand(envelope, 0, + envelope.getNumFrames()); + // Create an object to be called when the queued data is done. + TestQueueCallback callback = new TestQueueCallback(); + command.setCallback(callback); + command.setNumLoops(2); + envelopePlayer.rate.set(0.2); + synth.queueCommand(command); + synth.sleepFor(20.0); + + } catch (InterruptedException e) { + e.printStackTrace(); + } + // Stop everything. + synth.stop(); + } + + class TestQueueCallback implements UnitDataQueueCallback { + @Override + public void started(QueueDataEvent event) { + LOGGER.debug("CALLBACK: Envelope started."); + } + + @Override + public void looped(QueueDataEvent event) { + LOGGER.debug("CALLBACK: Envelope looped."); + } + + @Override + public void finished(QueueDataEvent event) { + LOGGER.debug("CALLBACK: Envelope finished."); + // Queue the envelope again at a faster rate. + // (If this hangs we may have hit a deadlock.) + envelopePlayer.rate.set(2.0); + envelopePlayer.dataQueue.queue(envelope); + } + } + + public static void main(String[] args) { + new PlaySegmentedEnvelopeCallback().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/PlaySequence.java b/examples/src/main/java/com/jsyn/examples/PlaySequence.java new file mode 100644 index 0000000..9d058b2 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlaySequence.java @@ -0,0 +1,85 @@ +/* + * Copyright 2009 Phil Burk, Mobileer Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.jsyn.examples; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.UnitOscillator; + +/** + * Use time stamps to change the frequency of an oscillator at precise times. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class PlaySequence { + Synthesizer synth; + UnitOscillator osc; + LineOut lineOut; + + private void test() { + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + + // Add a tone generator. + synth.add(osc = new SawtoothOscillatorBL()); + // Add an output mixer. + synth.add(lineOut = new LineOut()); + + // Connect the oscillator to the left and right audio output. + osc.output.connect(0, lineOut.input, 0); + osc.output.connect(0, lineOut.input, 1); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + + // Get synthesizer time in seconds. + double timeNow = synth.getCurrentTime(); + + // Advance to a near future time so we have a clean start. + double time = timeNow + 0.5; + double freq = 400.0; // hertz + osc.frequency.set(freq, time); + + // Schedule this to happen a bit later. + time += 0.5; + freq *= 1.5; // up a perfect fifth + osc.frequency.set(freq, time); + + time += 0.5; + freq *= 4.0 / 5.0; // down a major third + osc.frequency.set(freq, time); + + // Sleep while the sound is being generated in the background thread. + try { + synth.sleepUntil(time + 0.5); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + new PlaySequence().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/PlayTone.java b/examples/src/main/java/com/jsyn/examples/PlayTone.java new file mode 100644 index 0000000..e514dba --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlayTone.java @@ -0,0 +1,81 @@ +/* + * 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SineOscillator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play a tone using a JSyn oscillator. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PlayTone { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlayTone.class); + + private void test() { + + // Create a context for the synthesizer. + var synth = JSyn.createSynthesizer(); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + // Add a tone generator. + var oscillator = new SineOscillator(); + synth.add(oscillator); + // Add a stereo audio output unit. + var lineOut = new LineOut(); + synth.add(lineOut); + + // Connect the oscillator to both channels of the output. + oscillator.output.connect(0, lineOut.input, 0); + oscillator.output.connect(0, lineOut.input, 1); + + // Set the frequency and amplitude for the sine wave. + oscillator.frequency.set(345.0); + oscillator.amplitude.set(0.6); + + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + + LOGGER.debug("You should now be hearing a sine wave. ---------"); + + // Sleep while the sound is generated in the background. + try { + double time = synth.getCurrentTime(); + LOGGER.debug("time = " + time); + // Sleep for a few seconds. + synth.sleepUntil(time + 4.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + LOGGER.debug("Stop playing. -------------------"); + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + new PlayTone().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/RecordSineSweep.java b/examples/src/main/java/com/jsyn/examples/RecordSineSweep.java new file mode 100644 index 0000000..13161c6 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/RecordSineSweep.java @@ -0,0 +1,141 @@ +/* + * 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. + */ +/** + * Test recording to disk in non-real-time. + * Play several frequencies of a sine wave. + * Save data in a WAV file format. + * + * @author (C) 2010 Phil Burk + */ + +package com.jsyn.examples; + +import java.io.File; +import java.io.IOException; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.unitgen.ExponentialRamp; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.util.WaveRecorder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RecordSineSweep { + + private static final Logger LOGGER = LoggerFactory.getLogger(RecordSineSweep.class); + + final static double SONG_DURATION = 4.0; + private Synthesizer synth; + private UnitOscillator leftOsc; + private UnitOscillator rightOsc; + private ExponentialRamp sweeper; + private LineOut lineOut; + private WaveRecorder recorder; + private final static boolean useRecorder = true; + + private void test() throws IOException { + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + synth.setRealTime(false); + + if (useRecorder) { + File waveFile = new File("temp_recording.wav"); + // Default is stereo, 16 bits. + recorder = new WaveRecorder(synth, waveFile); + LOGGER.debug("Writing to WAV file " + waveFile.getAbsolutePath()); + } + // Add some tone generators. + synth.add(leftOsc = new SineOscillator()); + synth.add(rightOsc = new SawtoothOscillatorBL()); + + // Add a controller that will sweep up. + synth.add(sweeper = new ExponentialRamp()); + // Add an output unit. + synth.add(lineOut = new LineOut()); + + sweeper.current.set(50.0); + sweeper.input.set(1400.0); + sweeper.time.set(SONG_DURATION); + sweeper.output.connect(leftOsc.frequency); + sweeper.output.connect(rightOsc.frequency); + + // Connect the oscillator to the left and right audio output. + leftOsc.output.connect(0, lineOut.input, 0); + rightOsc.output.connect(0, lineOut.input, 1); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + if (useRecorder) { + leftOsc.output.connect(0, recorder.getInput(), 0); + rightOsc.output.connect(0, recorder.getInput(), 1); + // When we start the recorder it will pull data from the oscillator + // and sweeper. + recorder.start(); + } + + // We also need to start the LineOut if we want to hear it now. + lineOut.start(); + + // Get synthesizer time in seconds. + double timeNow = synth.getCurrentTime(); + + // Sleep while the sound is being generated in the background thread. + try { + synth.sleepUntil(timeNow + SONG_DURATION); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // Test stopping and restarting a recorder. This will cause a pop. + if (recorder != null) { + LOGGER.debug("Stop and restart recorder."); + recorder.stop(); + } + sweeper.input.set(100.0); + timeNow = synth.getCurrentTime(); + if (recorder != null) { + recorder.start(); + } + + // Sleep while the sound is being generated in the background thread. + try { + synth.sleepUntil(timeNow + SONG_DURATION); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (recorder != null) { + recorder.stop(); + recorder.close(); + } + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + try { + new RecordSineSweep().test(); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/SampleHoldNoteBlaster.java b/examples/src/main/java/com/jsyn/examples/SampleHoldNoteBlaster.java new file mode 100644 index 0000000..bc6b4d0 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/SampleHoldNoteBlaster.java @@ -0,0 +1,153 @@ +/* + * Copyright 2011 Phil Burk, Mobileer Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.jsyn.examples; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.unitgen.Circuit; +import com.jsyn.unitgen.EdgeDetector; +import com.jsyn.unitgen.EnvelopeDAHDSR; +import com.jsyn.unitgen.FilterLowPass; +import com.jsyn.unitgen.Latch; +import com.jsyn.unitgen.Multiply; +import com.jsyn.unitgen.PassThrough; +import com.jsyn.unitgen.PulseOscillator; +import com.jsyn.unitgen.SawtoothOscillatorDPW; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.unitgen.UnitSource; + +/** + * Classic osc-filter-envelope voice with a sample and hold. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class SampleHoldNoteBlaster extends Circuit implements UnitSource { + + public UnitInputPort frequency; + public UnitInputPort amplitude; + public UnitInputPort modRate; + public UnitInputPort modDepth; + private UnitInputPort cutoff; + private UnitInputPort resonance; + private UnitInputPort pulseRate; + private UnitInputPort sweepRate; + private UnitInputPort sweepDepth; + public UnitOutputPort output; + + private static SampleHoldNoteBlaster soundMaker; // singleton + + private UnitOscillator osc; + private UnitOscillator samplee; // for sample and hold + private PulseOscillator pulser; + private Latch latch; + private UnitOscillator lfo; + private FilterLowPass filter; + private PassThrough frequencyPin; + private Multiply modScaler; + private EnvelopeDAHDSR ampEnv; + private Multiply sweepScaler; + private EdgeDetector edgeDetector; + private UnitInputPort pulseWidth; + private UnitInputPort attack; + private UnitInputPort decay; + private UnitInputPort sustain; + private UnitInputPort release; + + public static SampleHoldNoteBlaster getInstance() { + if (soundMaker == null) { + soundMaker = new SampleHoldNoteBlaster(); + } + return soundMaker; + } + + public SampleHoldNoteBlaster() { + add(frequencyPin = new PassThrough()); + add(modScaler = new Multiply()); + add(sweepScaler = new Multiply()); + add(edgeDetector = new EdgeDetector()); + add(latch = new Latch()); + add(samplee = new SineOscillator()); + add(pulser = new PulseOscillator()); + add(lfo = new SineOscillator()); + add(osc = new SawtoothOscillatorDPW()); + add(filter = new FilterLowPass()); + // Use an envelope to control the amplitude. + add(ampEnv = new EnvelopeDAHDSR()); + + samplee.output.connect(latch.input); + pulser.output.connect(edgeDetector.input); + edgeDetector.output.connect(latch.gate); + latch.output.connect(osc.frequency); + + frequencyPin.output.connect(osc.frequency); + + frequencyPin.output.connect(modScaler.inputA); + modScaler.output.connect(lfo.amplitude); + + frequencyPin.output.connect(sweepScaler.inputA); + sweepScaler.output.connect(samplee.amplitude); + + lfo.output.connect(osc.frequency); + osc.output.connect(filter.input); + filter.output.connect(ampEnv.amplitude); + pulser.output.connect(ampEnv.input); + + // Setup ports --------------- + addPort(amplitude = osc.amplitude, "amplitude"); + amplitude.set(0.6); + + addPort(frequency = frequencyPin.input, "frequency"); + frequency.setup(50.0, 800.0, 2000.0); + + addPort(modRate = lfo.frequency, "modRate"); + modRate.setup(0.0, 12, 20.0); + + addPort(modDepth = modScaler.inputB, "modDepth"); + modDepth.setup(0.0, 0.0, 0.5); + + addPort(cutoff = filter.frequency, "cutoff"); + cutoff.setup(20.0, 2000.0, 5000.0); + addPort(resonance = filter.Q, "Q"); + + addPort(sweepDepth = sweepScaler.inputB, "sweepDepth"); + sweepDepth.setup(0.0, 0.6, 1.0); + addPort(sweepRate = samplee.frequency, "sweepRate"); + sweepRate.setup(0.2, 5.9271, 20.0); + + addPort(pulseRate = pulser.frequency, "pulseRate"); + pulseRate.setup(0.2, 7.0, 20.0); + addPort(pulseWidth = pulser.width, "pulseWidth"); + pulseWidth.setup(-0.9, 0.9, 0.9); + + addPort(attack = ampEnv.attack, "attack"); + attack.setup(0.001, 0.001, 2.0); + addPort(decay = ampEnv.decay, "decay"); + decay.setup(0.001, 0.26, 2.0); + addPort(sustain = ampEnv.sustain, "sustain"); + sustain.setup(0.000, 0.24, 1.0); + addPort(release = ampEnv.release, "release"); + release.setup(0.001, 0.2, 2.0); + + addPort(output = ampEnv.output); + } + + @Override + public UnitOutputPort getOutput() { + return output; + } +} diff --git a/examples/src/main/java/com/jsyn/examples/SawFaders.java b/examples/src/main/java/com/jsyn/examples/SawFaders.java new file mode 100644 index 0000000..f7ce555 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/SawFaders.java @@ -0,0 +1,103 @@ +/* + * 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.examples; + +import java.awt.GridLayout; + +import javax.swing.JApplet; +import javax.swing.JPanel; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.swing.ExponentialRangeModel; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.swing.PortControllerFactory; +import com.jsyn.swing.PortModelFactory; +import com.jsyn.swing.RotaryTextController; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.LinearRamp; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.UnitOscillator; + +/** + * Play a sawtooth using a JSyn oscillator and some knobs. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class SawFaders extends JApplet { + private Synthesizer synth; + private UnitOscillator osc; + private LinearRamp lag; + private LineOut lineOut; + + @Override + public void init() { + synth = JSyn.createSynthesizer(); + + // Add a tone generator. (band limited sawtooth) + synth.add(osc = new SawtoothOscillatorBL()); + // Add a lag to smooth out amplitude changes and avoid pops. + synth.add(lag = new LinearRamp()); + // Add an output mixer. + synth.add(lineOut = new LineOut()); + // Connect the oscillator to both left and right output. + osc.output.connect(0, lineOut.input, 0); + osc.output.connect(0, lineOut.input, 1); + + // Set the minimum, current and maximum values for the port. + lag.output.connect(osc.amplitude); + lag.input.setup(0.0, 0.5, 1.0); + lag.time.set(0.2); + + // Arrange the faders in a stack. + setLayout(new GridLayout(0, 1)); + + ExponentialRangeModel amplitudeModel = PortModelFactory.createExponentialModel(lag.input); + RotaryTextController knob = new RotaryTextController(amplitudeModel, 5); + JPanel knobPanel = new JPanel(); + knobPanel.add(knob); + add(knobPanel); + + osc.frequency.setup(50.0, 300.0, 10000.0); + add(PortControllerFactory.createExponentialPortSlider(osc.frequency)); + validate(); + } + + @Override + public void start() { + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + } + + @Override + public void stop() { + synth.stop(); + } + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + SawFaders applet = new SawFaders(); + JAppletFrame frame = new JAppletFrame("SawFaders", applet); + frame.setSize(440, 200); + frame.setVisible(true); + frame.test(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/SeeGoogleWave.java b/examples/src/main/java/com/jsyn/examples/SeeGoogleWave.java new file mode 100644 index 0000000..ff41e25 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/SeeGoogleWave.java @@ -0,0 +1,110 @@ +/* + * 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.examples; + +import java.awt.BorderLayout; +import java.awt.GridLayout; + +import javax.swing.JApplet; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.scope.AudioScope; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.swing.PortControllerFactory; +import com.jsyn.unitgen.LineOut; + +/** + * Generate the waveform shown on the Google home page on 2/22/12. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class SeeGoogleWave extends JApplet { + private Synthesizer synth; + private GoogleWaveOscillator googleWaveUnit; + private LineOut lineOut; + private AudioScope scope; + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + SeeGoogleWave applet = new SeeGoogleWave(); + JAppletFrame frame = new JAppletFrame("Google Wave", applet); + frame.setSize(640, 500); + frame.setVisible(true); + frame.test(); + frame.validate(); + } + + private void setupGUI() { + setLayout(new BorderLayout()); + + add(BorderLayout.NORTH, new JLabel("GoogleWave - elliptical segments")); + + scope = new AudioScope(synth); + scope.addProbe(googleWaveUnit.output); + scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); + scope.getView().setShowControls(false); + scope.start(); + add(BorderLayout.CENTER, scope.getView()); + + JPanel southPanel = new JPanel(); + southPanel.setLayout(new GridLayout(0, 1)); + add(BorderLayout.SOUTH, southPanel); + + southPanel.add(PortControllerFactory.createExponentialPortSlider(googleWaveUnit.frequency)); + southPanel.add(PortControllerFactory.createExponentialPortSlider(googleWaveUnit.variance)); + southPanel.add(PortControllerFactory.createExponentialPortSlider(googleWaveUnit.amplitude)); + + validate(); + } + + @Override + public void start() { + synth = JSyn.createSynthesizer(); + synth.add(googleWaveUnit = new GoogleWaveOscillator()); + googleWaveUnit.amplitude.setup(0.02, 0.5, 1.0); + googleWaveUnit.variance.setup(0.0, 0.0, 1.0); + googleWaveUnit.frequency.setup(40.0, 200.0, 1000.0); + + // Add an output so we can hear it. + synth.add(lineOut = new LineOut()); + + googleWaveUnit.output.connect(0, lineOut.input, 0); + googleWaveUnit.output.connect(0, lineOut.input, 1); + + setupGUI(); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // Start lineOut so it can pull data from other units. + lineOut.start(); + + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + + } + + @Override + public void stop() { + scope.stop(); + synth.stop(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/SeeOscillators.java b/examples/src/main/java/com/jsyn/examples/SeeOscillators.java new file mode 100644 index 0000000..8cdc3b8 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/SeeOscillators.java @@ -0,0 +1,219 @@ +/* + * 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.examples; + +import java.awt.BorderLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; + +import javax.swing.ButtonGroup; +import javax.swing.JApplet; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.scope.AudioScope; +import com.jsyn.scope.AudioScopeProbe; +import com.jsyn.swing.DoubleBoundedRangeSlider; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.swing.PortControllerFactory; +import com.jsyn.unitgen.ImpulseOscillator; +import com.jsyn.unitgen.ImpulseOscillatorBL; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.LinearRamp; +import com.jsyn.unitgen.MorphingOscillatorBL; +import com.jsyn.unitgen.Multiply; +import com.jsyn.unitgen.PulseOscillator; +import com.jsyn.unitgen.PulseOscillatorBL; +import com.jsyn.unitgen.RedNoise; +import com.jsyn.unitgen.SawtoothOscillator; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.SawtoothOscillatorDPW; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.SquareOscillator; +import com.jsyn.unitgen.SquareOscillatorBL; +import com.jsyn.unitgen.TriangleOscillator; +import com.jsyn.unitgen.UnitOscillator; + +/** + * Display each oscillator's waveform using the AudioScope. This is a re-implementation of the + * TJ_SeeOsc Applet from the old API. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class SeeOscillators extends JApplet { + private Synthesizer synth; + private ArrayList<UnitOscillator> oscillators = new ArrayList<UnitOscillator>(); + private LineOut lineOut; + private AudioScope scope; + private JPanel oscPanel; + private Multiply oscGain; + private ButtonGroup buttonGroup; + private LinearRamp freqRamp; + private LinearRamp widthRamp; + private LinearRamp shapeRamp; + private DoubleBoundedRangeSlider widthSlider; + private DoubleBoundedRangeSlider shapeSlider; + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + SeeOscillators applet = new SeeOscillators(); + JAppletFrame frame = new JAppletFrame("ShowWaves", applet); + frame.setSize(640, 500); + frame.setVisible(true); + frame.test(); + frame.validate(); + } + + private void setupGUI() { + setLayout(new BorderLayout()); + + add(BorderLayout.NORTH, new JLabel("Show Oscillators in an AudioScope")); + + scope = new AudioScope(synth); + AudioScopeProbe probe = scope.addProbe(oscGain.output); + probe.setAutoScaleEnabled(false); + probe.setVerticalScale(1.1); + scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); + // scope.getModel().getTriggerModel().getLevelModel().setDoubleValue( 0.0001 ); + // Turn off the gain and trigger control GUI. + scope.getView().setControlsVisible(false); + scope.start(); + add(BorderLayout.CENTER, scope.getView()); + + JPanel southPanel = new JPanel(); + southPanel.setLayout(new GridLayout(0, 1)); + add(BorderLayout.SOUTH, southPanel); + + oscPanel = new JPanel(); + oscPanel.setLayout(new GridLayout(2, 5)); + southPanel.add(oscPanel); + + southPanel.add(PortControllerFactory.createExponentialPortSlider(freqRamp.input)); + southPanel.add(PortControllerFactory.createExponentialPortSlider(oscGain.inputB)); + southPanel.add(widthSlider = PortControllerFactory.createPortSlider(widthRamp.input)); + widthSlider.setEnabled(false); + southPanel.add(shapeSlider = PortControllerFactory.createPortSlider(shapeRamp.input)); + shapeSlider.setEnabled(false); + + oscPanel.validate(); + validate(); + } + + @Override + public void start() { + synth = JSyn.createSynthesizer(); + + // Use a multiplier for gain control and so we can hook up to the scope + // from a single unit. + synth.add(oscGain = new Multiply()); + oscGain.inputB.setup(0.02, 0.5, 1.0); + oscGain.inputB.setName("Amplitude"); + + synth.add(freqRamp = new LinearRamp()); + freqRamp.input.setup(50.0, 300.0, 20000.0); + freqRamp.input.setName("Frequency"); + freqRamp.time.set(0.1); + + synth.add(widthRamp = new LinearRamp()); + widthRamp.input.setup(-1.0, 0.0, 1.0); + widthRamp.input.setName("Width"); + widthRamp.time.set(0.1); + + synth.add(shapeRamp = new LinearRamp()); + shapeRamp.input.setup(-1.0, 0.0, 1.0); + shapeRamp.input.setName("Shape"); + shapeRamp.time.set(0.1); + + // Add an output so we can hear the oscillators. + synth.add(lineOut = new LineOut()); + + oscGain.output.connect(0, lineOut.input, 0); + oscGain.output.connect(0, lineOut.input, 1); + + setupGUI(); + + buttonGroup = new ButtonGroup(); + + addOscillator(new SineOscillator(), "Sine"); + addOscillator(new TriangleOscillator(), "Triangle"); + addOscillator(new SawtoothOscillator(), "Sawtooth"); + addOscillator(new SawtoothOscillatorBL(), "SawBL"); + addOscillator(new SawtoothOscillatorDPW(), "SawDPW"); + addOscillator(new RedNoise(), "RedNoise"); + + addOscillator(new SquareOscillator(), "Square"); + addOscillator(new SquareOscillatorBL(), "SquareBL"); + addOscillator(new PulseOscillator(), "Pulse"); + addOscillator(new PulseOscillatorBL(), "PulseBL"); + addOscillator(new MorphingOscillatorBL(), "MorphBL"); + addOscillator(new ImpulseOscillator(), "Impulse"); + addOscillator(new ImpulseOscillatorBL(), "ImpulseBL"); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // Start lineOut so it can pull data from other units. + lineOut.start(); + + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + + } + + private void addOscillator(final UnitOscillator osc, String label) { + oscillators.add(osc); + synth.add(osc); + freqRamp.output.connect(osc.frequency); + if (osc instanceof PulseOscillatorBL) { + widthRamp.output.connect(((PulseOscillatorBL)osc).width); + } + if (osc instanceof PulseOscillator) { + widthRamp.output.connect(((PulseOscillator)osc).width); + } + if (osc instanceof MorphingOscillatorBL) { + shapeRamp.output.connect(((MorphingOscillatorBL)osc).shape); + } + osc.amplitude.set(1.0); + JRadioButton checkBox = new JRadioButton(label); + buttonGroup.add(checkBox); + checkBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + // Disconnect other oscillators. + oscGain.inputA.disconnectAll(0); + // Connect this one. + osc.output.connect(oscGain.inputA); + widthSlider.setEnabled(osc instanceof PulseOscillator + || osc instanceof PulseOscillatorBL); + shapeSlider.setEnabled(osc instanceof MorphingOscillatorBL); + } + }); + oscPanel.add(checkBox); + } + + @Override + public void stop() { + scope.stop(); + synth.stop(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/ShowWaves.java b/examples/src/main/java/com/jsyn/examples/ShowWaves.java new file mode 100644 index 0000000..8ad3c4a --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/ShowWaves.java @@ -0,0 +1,120 @@ +/* + * 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.examples; + +import java.awt.BorderLayout; +import java.util.ArrayList; + +import javax.swing.JApplet; +import javax.swing.JLabel; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.scope.AudioScope; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.unitgen.Add; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.TriangleOscillator; +import com.jsyn.unitgen.UnitOscillator; + +/** + * Display waveforms using the AudioScope. The frequency of the oscillators is modulated by an LFO. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class ShowWaves extends JApplet { + private Synthesizer synth; + private UnitOscillator lfo; + private Add adder; + private ArrayList<UnitOscillator> oscillators = new ArrayList<UnitOscillator>(); + private LineOut lineOut; + private AudioScope scope; + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + ShowWaves applet = new ShowWaves(); + JAppletFrame frame = new JAppletFrame("ShowWaves", applet); + frame.setSize(640, 300); + frame.setVisible(true); + frame.test(); + } + + private void setupGUI() { + setLayout(new BorderLayout()); + + add(BorderLayout.NORTH, new JLabel("ShowWaves in an AudioScope Mod001")); + + scope = new AudioScope(synth); + for (UnitOscillator osc : oscillators) { + scope.addProbe(osc.output); + } + scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); + scope.start(); + + // Turn on the gain and trigger control GUI. + scope.getView().setControlsVisible(true); + add(BorderLayout.CENTER, scope.getView()); + validate(); + } + + @Override + public void start() { + synth = JSyn.createSynthesizer(); + + // Add an LFO. + synth.add(lfo = new SineOscillator()); + synth.add(adder = new Add()); + + // Add an output so we can hear the oscillators. + synth.add(lineOut = new LineOut()); + + lfo.frequency.set(0.1); + lfo.amplitude.set(200.0); + adder.inputB.set(400.0); + lfo.output.connect(adder.inputA); + + oscillators.add(new SawtoothOscillatorBL()); + oscillators.add(new SineOscillator()); + oscillators.add(new TriangleOscillator()); + for (UnitOscillator osc : oscillators) { + synth.add(osc); + adder.output.connect(osc.frequency); + osc.output.connect(0, lineOut.input, 0); + osc.amplitude.set(0.2); + } + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // Start lineOut so it can pull data from other units. + lineOut.start(); + setupGUI(); + + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + + } + + @Override + public void stop() { + scope.stop(); + synth.stop(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/SwarmOfOscillators.java b/examples/src/main/java/com/jsyn/examples/SwarmOfOscillators.java new file mode 100644 index 0000000..9f7c19c --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/SwarmOfOscillators.java @@ -0,0 +1,146 @@ +/* + * 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.unitgen.Add; +import com.jsyn.unitgen.AsymptoticRamp; +import com.jsyn.unitgen.Circuit; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.Pan; +import com.jsyn.unitgen.SawtoothOscillatorDPW; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.unitgen.UnitSource; + +/** + * Make a bunch of oscillators that swarm around a moving frequency. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class SwarmOfOscillators { + private Synthesizer synth; + Follower[] followers; + SineOscillator lfo; + LineOut lineOut; + private Add tiePoint; + private static final int NUM_FOLLOWERS = 30; + + class Follower extends Circuit implements UnitSource { + UnitOscillator osc; + AsymptoticRamp lag; + Pan panner; + + Follower() { + // Add a tone generator. + add(osc = new SawtoothOscillatorDPW()); + osc.amplitude.set(0.03); + + // Use a lag to smoothly change frequency. + add(lag = new AsymptoticRamp()); + double hlife = 0.01 + (Math.random() * 0.9); + lag.halfLife.set(hlife); + + // Set left/right pan randomly between -1.0 and +1.0. + add(panner = new Pan()); + panner.pan.set((Math.random() * 2.0) - 1.0); + + // Track the frequency coming through the tiePoint. + tiePoint.output.connect(lag.input); + // Add the LFO offset. + lfo.output.connect(lag.input); + + lag.output.connect(osc.frequency); + + // Connect the oscillator to the left and right audio output. + osc.output.connect(panner.input); + } + + @Override + public UnitOutputPort getOutput() { + return panner.output; + } + } + + private void test() { + synth = JSyn.createSynthesizer(); + + // Add an output mixer. + synth.add(lineOut = new LineOut()); + + // Add a unit just to distribute the control frequency. + synth.add(tiePoint = new Add()); + synth.add(lfo = new SineOscillator()); + lfo.amplitude.set(40.0); + lfo.frequency.set(2.3); + + followers = new Follower[NUM_FOLLOWERS]; + for (int i = 0; i < followers.length; i++) { + Follower follower = new Follower(); + synth.add(follower); + + // Every follower can connect directly to the lineOut because all input ports are + // mixers. + follower.getOutput().connect(0, lineOut.input, 0); + follower.getOutput().connect(1, lineOut.input, 1); + + followers[i] = follower; + } + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + + // Get synthesizer time in seconds. + double timeNow = synth.getCurrentTime(); + + // Advance to a near future time so we have a clean start. + double duration = 0.9; + double time = timeNow + duration; + double freq = 400.0; // hertz + tiePoint.inputA.set(freq, time); + + // Randomly change the target frequency for the followers. + try { + for (int i = 0; i < 20; i++) { + // Schedule this to happen a bit later. + time += duration; + freq = 200.0 + (Math.random() * 500.0); + tiePoint.inputA.set(freq, time); + + // Sleep while the sound is being generated in the background + // thread. + synth.sleepUntil(time - 0.2); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + + System.out.format("CPU usage = %4.2f%c\n", synth.getUsage() * 100, '%'); + + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + new SwarmOfOscillators().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/UseMidiKeyboard.java b/examples/src/main/java/com/jsyn/examples/UseMidiKeyboard.java new file mode 100644 index 0000000..f88b877 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/UseMidiKeyboard.java @@ -0,0 +1,125 @@ +/* + * 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.examples; + +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; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Connect a USB MIDI Keyboard to the internal MIDI Synthesizer using JavaSound. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class UseMidiKeyboard { + + private static final Logger LOGGER = LoggerFactory.getLogger(UseMidiKeyboard.class); + private static final int NUM_CHANNELS = 16; + private static final int VOICES_PER_CHANNEL = 3; + + private Synthesizer synth; + private LineOut lineOut; + private MidiSynthesizer midiSynthesizer; + private VoiceDescription voiceDescription; + private MultiChannelSynthesizer multiSynth; + + public static void main(String[] args) { + UseMidiKeyboard app = new UseMidiKeyboard(); + try { + app.test(); + } catch (MidiUnavailableException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + // 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(); + midiSynthesizer.onReceive(bytes, 0, bytes.length); + } + } + + public int test() throws MidiUnavailableException, IOException, InterruptedException { + setupSynth(); + + 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); + LOGGER.debug("Play MIDI keyboard: " + keyboard.getDeviceInfo().getDescription()); + result = 0; + } else { + LOGGER.debug("Could not find a keyboard."); + } + return result; + } + + + private void setupSynth() { + synth = JSyn.createSynthesizer(); + + voiceDescription = DualOscillatorSynthVoice.getVoiceDescription(); +// voiceDescription = SubtractiveSynthVoice.getVoiceDescription(); + + multiSynth = new MultiChannelSynthesizer(); + final int startChannel = 0; + multiSynth.setup(synth, startChannel, NUM_CHANNELS, VOICES_PER_CHANNEL, voiceDescription); + midiSynthesizer = new MidiSynthesizer(multiSynth); + + // Create a LineOut for the entire synthesizer. + synth.add(lineOut = new LineOut()); + multiSynth.getOutput().connect(0,lineOut.input, 0); + multiSynth.getOutput().connect(1,lineOut.input, 1); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + lineOut.start(); + + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/WindCircuit.java b/examples/src/main/java/com/jsyn/examples/WindCircuit.java new file mode 100644 index 0000000..1e3623e --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/WindCircuit.java @@ -0,0 +1,90 @@ +/* + * Copyright 1997 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.examples; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.unitgen.Circuit; +import com.jsyn.unitgen.FilterStateVariable; +import com.jsyn.unitgen.MultiplyAdd; +import com.jsyn.unitgen.RedNoise; +import com.jsyn.unitgen.UnitSource; +import com.jsyn.unitgen.WhiteNoise; + +/** + * Wind Sound Create a wind-like sound by feeding white noise "shshshshsh" through a randomly + * varying state filter to make a "whooowhoosh" sound. The cuttoff frequency of the low pass filter + * is controlled by a RedNoise unit which creates a slowly varying random control signal. + * + * @author (C) 1997 Phil Burk, SoftSynth.com + */ + +public class WindCircuit extends Circuit implements UnitSource { + /* Declare units that will be part of the circuit. */ + WhiteNoise myNoise; + FilterStateVariable myFilter; + RedNoise myLFO; + MultiplyAdd myScalar; + + /* Declare ports. */ + public UnitInputPort noiseAmp; + public UnitInputPort modRate; + public UnitInputPort modDepth; + public UnitInputPort cutoff; + public UnitInputPort resonance; + public UnitInputPort amplitude; + public UnitOutputPort output; + + public WindCircuit() { + /* + * Create various unit generators and add them to circuit. + */ + add(myNoise = new WhiteNoise()); + add(myFilter = new FilterStateVariable()); + add(myLFO = new RedNoise()); + add(myScalar = new MultiplyAdd()); + + /* Make ports on internal units appear as ports on circuit. */ + /* Optionally give some circuit ports more meaningful names. */ + addPort(noiseAmp = myNoise.amplitude, "NoiseAmp"); + addPort(modRate = myLFO.frequency, "ModRate"); + addPort(modDepth = myScalar.inputB, "ModDepth"); + addPort(cutoff = myScalar.inputC, "Cutoff"); + addPort(resonance = myFilter.resonance); + addPort(amplitude = myFilter.amplitude); + addPort(output = myFilter.output); + + /* Connect SynthUnits to make control signal path. */ + myLFO.output.connect(myScalar.inputA); + myScalar.output.connect(myFilter.frequency); + /* Connect SynthUnits to make audio signal path. */ + myNoise.output.connect(myFilter.input); + + /* Set ports to useful values and ranges. */ + noiseAmp.setup(0.0, 0.3, 0.4); + modRate.setup(0.0, 1.0, 10.0); + modDepth.setup(0.0, 300.0, 1000.0); + cutoff.setup(0.0, 600.0, 1000.0); + resonance.setup(0.0, 0.066, 0.2); + amplitude.setup(0.0, 0.9, 0.999); + } + + @Override + public UnitOutputPort getOutput() { + return output; + } +} diff --git a/examples/src/main/resources/log4j.xml b/examples/src/main/resources/log4j.xml new file mode 100644 index 0000000..cc107b1 --- /dev/null +++ b/examples/src/main/resources/log4j.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="iso-8859-1"?> +<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd" > +<log4j:configuration debug="false"> + <appender name="default.console" class="org.apache.log4j.ConsoleAppender"> + <param name="target" value="System.out"/> + <layout class="org.apache.log4j.PatternLayout"> + <param name="ConversionPattern" value="[%d{HH:mm:ss}] [%t/%p]: %m%n"/> + </layout> + </appender> + + <root> + <priority value="INFO" /> + <appender-ref ref="default.console"/> + </root> +</log4j:configuration> |