aboutsummaryrefslogtreecommitdiffstats
path: root/examples/src/main/java/com
diff options
context:
space:
mode:
authorPhil Burk <[email protected]>2023-04-10 11:12:50 -0700
committerGitHub <[email protected]>2023-04-10 11:12:50 -0700
commit90db5489c352bc038d6d22e336ac7eefac221ed7 (patch)
tree645dc5bfab661acff6f10921485c2752dc56ac4d /examples/src/main/java/com
parenta46f8c93193fe8bb1eb7b93e55c85e6f46d5b108 (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')
-rw-r--r--examples/src/main/java/com/jsyn/examples/ChebyshevSong.java25
-rw-r--r--examples/src/main/java/com/jsyn/examples/InvestigateCordic.java100
-rw-r--r--examples/src/main/java/com/jsyn/examples/MeasurePlateReverb.java129
-rw-r--r--examples/src/main/java/com/jsyn/examples/TuneReverb.java116
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();
+ }
+
+}