diff options
author | Phil Burk <[email protected]> | 2023-04-10 11:12:50 -0700 |
---|---|---|
committer | GitHub <[email protected]> | 2023-04-10 11:12:50 -0700 |
commit | 90db5489c352bc038d6d22e336ac7eefac221ed7 (patch) | |
tree | 645dc5bfab661acff6f10921485c2752dc56ac4d /examples/src/main/java/com | |
parent | a46f8c93193fe8bb1eb7b93e55c85e6f46d5b108 (diff) |
Add PlateReverb, RoomReverb and MultiTapDelay units (#115)
PlateReverb is a simulation of a metal plate based on all-pass delays.
RoomReverb uses a MultiTapDelay for early reflections and a PlateReverb for diffusion.
Add a DSP package with utility classes used to build unit generators.
Add TuneReverb app with faders for experimenting and hearing reverb.
Add unit tests for SimpleDelay.
Co-authored-by: Phil Burk <[email protected]>
Diffstat (limited to 'examples/src/main/java/com')
4 files changed, 362 insertions, 8 deletions
diff --git a/examples/src/main/java/com/jsyn/examples/ChebyshevSong.java b/examples/src/main/java/com/jsyn/examples/ChebyshevSong.java index 2dbab88..d4b8d15 100644 --- a/examples/src/main/java/com/jsyn/examples/ChebyshevSong.java +++ b/examples/src/main/java/com/jsyn/examples/ChebyshevSong.java @@ -17,16 +17,19 @@ package com.jsyn.examples; import java.awt.BorderLayout; +import java.awt.GridLayout; import javax.swing.JApplet; +import javax.swing.JPanel; 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.unitgen.RoomReverb; +import com.jsyn.unitgen.PassThrough; import com.jsyn.util.PseudoRandom; import com.jsyn.util.VoiceAllocator; import com.softsynth.math.AudioMath; @@ -40,7 +43,8 @@ import com.softsynth.shared.time.TimeStamp; public class ChebyshevSong extends JApplet implements Runnable { private Synthesizer synth; - private Add mixer; + private PassThrough mixer; // use input as a summing node + private RoomReverb reverb; private LineOut lineOut; private AudioScope scope; private volatile boolean go = false; @@ -71,18 +75,19 @@ public class ChebyshevSong extends JApplet implements Runnable { synth = JSyn.createSynthesizer(); // Use a submix so we can show it on the scope. - synth.add(mixer = new Add()); + synth.add(mixer = new PassThrough()); synth.add(lineOut = new LineOut()); - - mixer.output.connect(0, lineOut.input, 0); - mixer.output.connect(0, lineOut.input, 1); + synth.add(reverb = new RoomReverb(1.0)); + mixer.output.connect(reverb.input); + mixer.output.connect(0, lineOut.input, 0); // dry + reverb.output.connect(0, lineOut.input, 1); // wet 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); + voice.getOutput().connect(mixer.input); voices[i] = voice; } allocator = new VoiceAllocator(voices); @@ -94,11 +99,16 @@ public class ChebyshevSong extends JApplet implements Runnable { // Use a scope to show the mixed output. scope = new AudioScope(synth); scope.addProbe(mixer.output); + scope.addProbe(reverb.output); scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); scope.getView().setControlsVisible(false); add(BorderLayout.CENTER, scope.getView()); scope.start(); + JPanel southPanel = new JPanel(); + southPanel.setLayout(new GridLayout(0, 1)); + add(BorderLayout.SOUTH, southPanel); + /* Synchronize Java display. */ getParent().validate(); getToolkit().sync(); @@ -107,7 +117,6 @@ public class ChebyshevSong extends JApplet implements Runnable { Thread thread = new Thread(this); go = true; thread.start(); - } @Override diff --git a/examples/src/main/java/com/jsyn/examples/InvestigateCordic.java b/examples/src/main/java/com/jsyn/examples/InvestigateCordic.java new file mode 100644 index 0000000..a1bcfb8 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/InvestigateCordic.java @@ -0,0 +1,100 @@ +/* + * Copyright 2023 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; + +/** + * Experiment with Cordic oscillators. + * Implement using float and double values internally. + */ +class CordicOscillatorFloat { + private float mCosPhi; + private float x; + private float y = -1.0f; + private float mSinPhi; + + CordicOscillatorFloat() { + setFrequency(441.0, 44100.0); + } + + public void setFrequency(double frequency, double sampleRate) { + double radians = frequency * Math.PI * 2.0 / sampleRate; + mCosPhi = (float) Math.cos(radians); + mSinPhi = (float) Math.sin(radians); + } + + public double generate() { + float x2 = x * mCosPhi - y * mSinPhi; + float y2 = y * mCosPhi + x * mSinPhi; + x = Math.min(x2, 1.0f); + y = y2; + return x; + } +} + + +class CordicOscillator { + private double mCosPhi; + private double x; + private double y = -1.0; + private double mSinPhi; + + CordicOscillator() { + setFrequency(441.0, 44100.0); + } + + public void setFrequency(double frequency, double sampleRate) { + double radians = frequency * Math.PI * 2.0 / sampleRate; + mCosPhi = Math.cos(radians); + mSinPhi = Math.sin(radians); + } + + public double generate() { + double x2 = x * mCosPhi - y * mSinPhi; + double y2 = y * mCosPhi + x * mSinPhi; + x = x2; // Math.min(x2, 1.0); + y = y2; + return x; + } +} + +public class InvestigateCordic +{ + public void test() { + CordicOscillator oscillator = new CordicOscillator(); + oscillator.setFrequency(1.0, 44100.0); + for (int i = 0; i < 100; i++) { + double x = oscillator.generate(); + System.out.println("x = " + x); + } + double peak = 0.0; + for (int n = 0; n < 200; n++) { + peak = 0.0; + for (int i = 0; i < 100000000; i++) { + double x = oscillator.generate(); + if (x > peak) { + peak = x; + } + } + System.out.println(n + ": peak = " + peak); + } + } + + public static void main(String[] args) { + new InvestigateCordic().test(); + System.exit(0); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/MeasurePlateReverb.java b/examples/src/main/java/com/jsyn/examples/MeasurePlateReverb.java new file mode 100644 index 0000000..db05251 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/MeasurePlateReverb.java @@ -0,0 +1,129 @@ +/* + * Copyright 2022 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.EnvelopeDAHDSR; +import com.jsyn.unitgen.ImpulseOscillator; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.PeakFollower; +import com.jsyn.unitgen.PinkNoise; +import com.jsyn.unitgen.PlateReverb; +import com.jsyn.unitgen.SawtoothOscillator; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.SquareOscillator; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.unitgen.WhiteNoise; +import com.softsynth.math.AudioMath; + +/** + * Measure the decay time of a PlateReverb tail. + */ +public class MeasurePlateReverb { + + private double measure(double size, double time, double damping) throws InterruptedException { + // Create a context for the synthesizer. + Synthesizer synth = JSyn.createSynthesizer(); + synth.setRealTime(false); + + // Add a signal source. + WhiteNoise source = new WhiteNoise(); + PlateReverb reverb = new PlateReverb(size); + PeakFollower peak = new PeakFollower(); + LineOut lineOut = new LineOut(); + + synth.add(source); + synth.add(peak); + synth.add(reverb); + synth.add(lineOut); + + source.amplitude.set(1.0); + peak.halfLife.set(0.01); + reverb.time.set(time); + reverb.damping.set(damping); + + source.output.connect(reverb.input); + reverb.output.connect(peak.input); + peak.output.connect(0, lineOut.input, 0); + reverb.output.connect(0, lineOut.input, 1); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + lineOut.start(); + + // Sleep while the sound is generated in the background. + double rt60 = 0.0; + final double REFERENCE_DB = -60.0; + final double TARGET_DB = -30.0; + synth.sleepFor(1.0); + double original = peak.output.getValue(); + source.amplitude.set(0.0); + double startTime = synth.getCurrentTime(); +// System.out.printf(" time, ratio, db\n"); + double db = 1.0; + double elapsed; + int count = 0; + do { + synth.sleepUntil(startTime + (count++ * 0.1)); + double level = peak.output.getValue(); + elapsed = synth.getCurrentTime() - startTime; + double ratio = level / original; + db = AudioMath.amplitudeToDecibels(ratio); +// System.out.printf(" %3.3f, %6.4f, %6.3f\n", +// elapsed, ratio, db); + } while (db > TARGET_DB && elapsed < 30.0); + if (elapsed >= 30.0) { + System.out.println("TIMEOUT!"); + } + // Time to reach reference; + rt60 = REFERENCE_DB * elapsed / db; + // Stop everything. + synth.stop(); + + return rt60; + } + +// private double estimateRT60(double size, double decay) { +// return size * (0.52 - (4.7 * Math.log(1.0001 - (decay * decay)))); +// } + + private void test() { + double damping = 0.0005; + for (double size = 0.2; size < 3.0; size *= 1.5) { + System.out.printf("\nsize = %5.2f\n", size); + System.out.printf("time, rt60\n"); + for (double time = 0.1; time < 30.0; time *= 1.2) { + double rt60 = 0.0; + try { + rt60 = measure(size, time, damping); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + // double estimate = estimateRT60(size, decay); + System.out.printf("%5.3f, %6.4f\n", + time, rt60); + } + } + } + + public static void main(String[] args) { + new MeasurePlateReverb().test(); + System.exit(0); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/TuneReverb.java b/examples/src/main/java/com/jsyn/examples/TuneReverb.java new file mode 100644 index 0000000..e38a2f7 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/TuneReverb.java @@ -0,0 +1,116 @@ +/* + * Copyright 2023 Phil Burk + * + * 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.swing.JAppletFrame; +import com.jsyn.swing.PortControllerFactory; +import com.jsyn.unitgen.EnvelopeDAHDSR; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.Pan; +import com.jsyn.unitgen.PinkNoise; +import com.jsyn.unitgen.RoomReverb; +import com.jsyn.unitgen.SawtoothOscillatorDPW; +import com.jsyn.unitgen.SquareOscillator; +import java.awt.GridLayout; +import javax.swing.JApplet; + +/** + * Play various sounds interactively through a reverb. + */ +public class TuneReverb extends JApplet { + private Synthesizer synth; + + private PinkNoise noise; + private SawtoothOscillatorDPW sawtooth; + // Use a square wave to trigger the envelope. + private SquareOscillator gatingOsc; + private EnvelopeDAHDSR dahdsr; + private Pan dryWet; + private RoomReverb reverb; + private LineOut lineOut; + + @Override + public void init() { + synth = JSyn.createSynthesizer(); + + synth.add(noise = new PinkNoise()); + synth.add(sawtooth = new SawtoothOscillatorDPW()); + synth.add(gatingOsc = new SquareOscillator()); + synth.add(dahdsr = new EnvelopeDAHDSR()); + synth.add(dryWet = new Pan()); + synth.add(reverb = new RoomReverb()); + synth.add(lineOut = new LineOut()); + + // Connect the oscillator to both channels of the output. + gatingOsc.output.connect(dahdsr.input); + gatingOsc.frequency.set(0.5); + dahdsr.attack.set(0.01); + dahdsr.decay.set(0.05); + dahdsr.sustain.set(0.00); + + noise.output.connect(dahdsr.amplitude); + sawtooth.output.connect(dahdsr.amplitude); + dahdsr.output.connect(dryWet.input); + dryWet.output.connect(1, reverb.input, 0); + dryWet.output.connect(0, lineOut.input, 0); + dryWet.output.connect(0, lineOut.input, 1); + reverb.output.connect(0, lineOut.input, 0); + reverb.output.connect(0, lineOut.input, 1); + + // Arrange the faders in a stack. + setLayout(new GridLayout(0, 1)); + + gatingOsc.frequency.setup(0.1, 0.5, 4.0); + add(PortControllerFactory.createExponentialPortSlider(sawtooth.frequency)); + add(PortControllerFactory.createExponentialPortSlider(sawtooth.amplitude)); + add(PortControllerFactory.createExponentialPortSlider(noise.amplitude)); + add(PortControllerFactory.createExponentialPortSlider(gatingOsc.frequency)); + add(PortControllerFactory.createPortSlider(dryWet.pan)); + add(PortControllerFactory.createExponentialPortSlider(reverb.preDelayMillis)); + add(PortControllerFactory.createExponentialPortSlider(reverb.multiTap)); + add(PortControllerFactory.createExponentialPortSlider(reverb.diffusion)); + add(PortControllerFactory.createExponentialPortSlider(reverb.time)); + add(PortControllerFactory.createExponentialPortSlider(reverb.damping)); + 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) { + TuneReverb applet = new TuneReverb(); + JAppletFrame frame = new JAppletFrame("Tune Reverb", applet); + frame.setSize(440, 600); + frame.setVisible(true); + frame.test(); + } + +} |