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 /src/main/java/com/jsyn/instruments | |
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 'src/main/java/com/jsyn/instruments')
6 files changed, 991 insertions, 0 deletions
diff --git a/src/main/java/com/jsyn/instruments/DrumWoodFM.java b/src/main/java/com/jsyn/instruments/DrumWoodFM.java new file mode 100644 index 0000000..ba6cd1b --- /dev/null +++ b/src/main/java/com/jsyn/instruments/DrumWoodFM.java @@ -0,0 +1,159 @@ +/* + * 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.instruments; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.unitgen.Add; +import com.jsyn.unitgen.Circuit; +import com.jsyn.unitgen.EnvelopeAttackDecay; +import com.jsyn.unitgen.Multiply; +import com.jsyn.unitgen.PassThrough; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.SineOscillatorPhaseModulated; +import com.jsyn.unitgen.UnitVoice; +import com.jsyn.util.VoiceDescription; +import com.softsynth.shared.time.TimeStamp; + +/** + * Drum instruments using 2 Operator FM. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class DrumWoodFM extends Circuit implements UnitVoice { + private static final int NUM_PRESETS = 3; + // Declare units and ports. + EnvelopeAttackDecay ampEnv; + SineOscillatorPhaseModulated carrierOsc; + EnvelopeAttackDecay modEnv; + SineOscillator modOsc; + PassThrough freqDistributor; + Add modSummer; + Multiply frequencyMultiplier; + + public UnitInputPort mcratio; + public UnitInputPort index; + public UnitInputPort modRange; + public UnitInputPort frequency; + + public DrumWoodFM() { + // Create unit generators. + add(carrierOsc = new SineOscillatorPhaseModulated()); + add(freqDistributor = new PassThrough()); + add(modSummer = new Add()); + add(ampEnv = new EnvelopeAttackDecay()); + add(modEnv = new EnvelopeAttackDecay()); + add(modOsc = new SineOscillator()); + add(frequencyMultiplier = new Multiply()); + + addPort(mcratio = frequencyMultiplier.inputB, "MCRatio"); + addPort(index = modSummer.inputA, "Index"); + addPort(modRange = modEnv.amplitude, "ModRange"); + addPort(frequency = freqDistributor.input, "Frequency"); + + ampEnv.export(this, "Amp"); + modEnv.export(this, "Mod"); + + freqDistributor.output.connect(carrierOsc.frequency); + freqDistributor.output.connect(frequencyMultiplier.inputA); + + carrierOsc.output.connect(ampEnv.amplitude); + modEnv.output.connect(modSummer.inputB); + modSummer.output.connect(modOsc.amplitude); + modOsc.output.connect(carrierOsc.modulation); + frequencyMultiplier.output.connect(modOsc.frequency); + + // Make the circuit turn off when the envelope finishes to reduce CPU load. + ampEnv.setupAutoDisable(this); + + usePreset(0); + } + + @Override + public void noteOff(TimeStamp timeStamp) { + } + + @Override + public void noteOn(double freq, double ampl, TimeStamp timeStamp) { + carrierOsc.amplitude.set(ampl, timeStamp); + ampEnv.input.trigger(timeStamp); + modEnv.input.trigger(timeStamp); + } + + @Override + public UnitOutputPort getOutput() { + return ampEnv.output; + } + + @Override + public void usePreset(int presetIndex) { + mcratio.setup(0.001, 0.6875, 20.0); + ampEnv.attack.setup(0.001, 0.005, 8.0); + modEnv.attack.setup(0.001, 0.005, 8.0); + + int n = presetIndex % NUM_PRESETS; + switch (n) { + case 0: + ampEnv.decay.setup(0.001, 0.293, 8.0); + modEnv.decay.setup(0.001, 0.07, 8.0); + frequency.setup(0.0, 349.0, 3000.0); + index.setup(0.001, 0.05, 10.0); + modRange.setup(0.001, 0.4, 10.0); + break; + case 1: + default: + ampEnv.decay.setup(0.001, 0.12, 8.0); + modEnv.decay.setup(0.001, 0.06, 8.0); + frequency.setup(0.0, 1400.0, 3000.0); + index.setup(0.001, 0.16, 10.0); + modRange.setup(0.001, 0.17, 10.0); + break; + } + } + + static class MyVoiceDescription extends VoiceDescription { + static String[] presetNames = { + "WoodBlockFM", "ClaveFM" + }; + static String[] tags = { + "electronic", "drum" + }; + + public MyVoiceDescription() { + super("DrumWoodFM", presetNames); + } + + @Override + public UnitVoice createUnitVoice() { + return new DrumWoodFM(); + } + + @Override + public String[] getTags(int presetIndex) { + return tags; + } + + @Override + public String getVoiceClassName() { + return DrumWoodFM.class.getName(); + } + } + + public static VoiceDescription getVoiceDescription() { + return new MyVoiceDescription(); + } +} diff --git a/src/main/java/com/jsyn/instruments/DualOscillatorSynthVoice.java b/src/main/java/com/jsyn/instruments/DualOscillatorSynthVoice.java new file mode 100644 index 0000000..c81041f --- /dev/null +++ b/src/main/java/com/jsyn/instruments/DualOscillatorSynthVoice.java @@ -0,0 +1,301 @@ +/* + * Copyright 2010 Phil Burk, Mobileer Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.jsyn.instruments; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.unitgen.Add; +import com.jsyn.unitgen.Circuit; +import com.jsyn.unitgen.EnvelopeDAHDSR; +import com.jsyn.unitgen.FilterFourPoles; +import com.jsyn.unitgen.MorphingOscillatorBL; +import com.jsyn.unitgen.Multiply; +import com.jsyn.unitgen.UnitVoice; +import com.jsyn.util.VoiceDescription; +import com.softsynth.math.AudioMath; +import com.softsynth.shared.time.TimeStamp; + +/** + * Synthesizer voice with two morphing oscillators and a four-pole resonant filter. + * Modulate the amplitude and filter using DAHDSR envelopes. + */ +public class DualOscillatorSynthVoice extends Circuit implements UnitVoice { + private Multiply frequencyMultiplier; + private Multiply amplitudeMultiplier; + private Multiply detuneScaler1; + private Multiply detuneScaler2; + private Multiply amplitudeBoost; + private MorphingOscillatorBL osc1; + private MorphingOscillatorBL osc2; + private FilterFourPoles filter; + private EnvelopeDAHDSR ampEnv; + private EnvelopeDAHDSR filterEnv; + private Add cutoffAdder; + + private static MyVoiceDescription voiceDescription; + + public UnitInputPort amplitude; + public UnitInputPort frequency; + /** + * This scales the frequency value. You can use this to modulate a group of instruments using a + * shared LFO and they will stay in tune. Set to 1.0 for no modulation. + */ + public UnitInputPort frequencyScaler; + public UnitInputPort oscShape1; + public UnitInputPort oscShape2; +// public UnitInputPort oscDetune1; +// public UnitInputPort oscDetune2; + public UnitInputPort cutoff; + public UnitInputPort filterEnvDepth; + public UnitInputPort Q; + + public DualOscillatorSynthVoice() { + add(frequencyMultiplier = new Multiply()); + add(amplitudeMultiplier = new Multiply()); + add(amplitudeBoost = new Multiply()); + add(detuneScaler1 = new Multiply()); + add(detuneScaler2 = new Multiply()); + // Add tone generators. + add(osc1 = new MorphingOscillatorBL()); + add(osc2 = new MorphingOscillatorBL()); + + // Use an envelope to control the amplitude. + add(ampEnv = new EnvelopeDAHDSR()); + + // Use an envelope to control the filter cutoff. + add(filterEnv = new EnvelopeDAHDSR()); + add(filter = new FilterFourPoles()); + add(cutoffAdder = new Add()); + + filterEnv.output.connect(cutoffAdder.inputA); + cutoffAdder.output.connect(filter.frequency); + frequencyMultiplier.output.connect(detuneScaler1.inputA); + frequencyMultiplier.output.connect(detuneScaler2.inputA); + detuneScaler1.output.connect(osc1.frequency); + detuneScaler2.output.connect(osc2.frequency); + osc1.output.connect(amplitudeMultiplier.inputA); // mix oscillators + osc2.output.connect(amplitudeMultiplier.inputA); + amplitudeMultiplier.output.connect(filter.input); + filter.output.connect(amplitudeBoost.inputA); + amplitudeBoost.output.connect(ampEnv.amplitude); + + addPort(amplitude = amplitudeMultiplier.inputB, PORT_NAME_AMPLITUDE); + addPort(frequency = frequencyMultiplier.inputA, PORT_NAME_FREQUENCY); + addPort(oscShape1 = osc1.shape, "OscShape1"); + addPort(oscShape2 = osc2.shape, "OscShape2"); +// addPort(oscDetune1 = osc1.shape, "OscDetune1"); +// addPort(oscDetune2 = osc2.shape, "OscDetune2"); + addPort(cutoff = cutoffAdder.inputB, PORT_NAME_CUTOFF); + addPortAlias(cutoff, PORT_NAME_TIMBRE); + addPort(Q = filter.Q); + addPort(frequencyScaler = frequencyMultiplier.inputB, PORT_NAME_FREQUENCY_SCALER); + addPort(filterEnvDepth = filterEnv.amplitude, "FilterEnvDepth"); + + filterEnv.export(this, "Filter"); + ampEnv.export(this, "Amp"); + + frequency.setup(osc1.frequency); + frequencyScaler.setup(0.2, 1.0, 4.0); + cutoff.setup(filter.frequency); + // Allow negative filter sweeps + filterEnvDepth.setup(-4000.0, 2000.0, 4000.0); + + // set amplitudes slightly different so that they never entirely cancel + osc1.amplitude.set(0.5); + osc2.amplitude.set(0.4); + // Make the circuit turn off when the envelope finishes to reduce CPU load. + ampEnv.setupAutoDisable(this); + // Add named port for mapping pressure. + amplitudeBoost.inputB.setup(1.0, 1.0, 4.0); + addPortAlias(amplitudeBoost.inputB, PORT_NAME_PRESSURE); + + usePreset(0); + } + + /** + * The first oscillator will be tuned UP by semitoneOffset/2. + * The second oscillator will be tuned DOWN by semitoneOffset/2. + * @param semitoneOffset + */ + private void setDetunePitch(double semitoneOffset) { + double halfOffset = semitoneOffset * 0.5; + setDetunePitch1(halfOffset); + setDetunePitch2(-halfOffset); + } + + /** + * Set the detuning for osc1 in semitones. + * @param semitoneOffset + */ + private void setDetunePitch1(double semitoneOffset) { + double scale = AudioMath.semitonesToFrequencyScaler(semitoneOffset); + detuneScaler1.inputB.set(scale); + } + + /** + * Set the detuning for osc2 in semitones. + * @param semitoneOffset + */ + private void setDetunePitch2(double semitoneOffset) { + double scale = AudioMath.semitonesToFrequencyScaler(semitoneOffset); + detuneScaler2.inputB.set(scale); + } + + @Override + public void noteOff(TimeStamp timeStamp) { + ampEnv.input.off(timeStamp); + filterEnv.input.off(timeStamp); + } + + @Override + public void noteOn(double freq, double ampl, TimeStamp timeStamp) { + frequency.set(freq, timeStamp); + amplitude.set(ampl, timeStamp); + ampEnv.input.on(timeStamp); + filterEnv.input.on(timeStamp); + } + + @Override + public UnitOutputPort getOutput() { + return ampEnv.output; + } + + // Reset to basic voice. + public void reset() { + osc1.shape.set(0.0); + osc2.shape.set(0.0); + ampEnv.attack.set(0.005); + ampEnv.decay.set(0.2); + ampEnv.sustain.set(0.5); + ampEnv.release.set(1.0); + filterEnv.attack.set(0.01); + filterEnv.decay.set(0.6); + filterEnv.sustain.set(0.4); + filterEnv.release.set(1.0); + cutoff.set(500.0); + filterEnvDepth.set(3000.0); + filter.reset(); + filter.Q.set(3.9); + setDetunePitch(0.02); + } + + @Override + public void usePreset(int presetIndex) { + reset(); // start from known configuration + int n = presetIndex % presetNames.length; + switch (n) { + case 0: + break; + case 1: + ampEnv.attack.set(0.1); + ampEnv.decay.set(0.9); + ampEnv.sustain.set(0.1); + ampEnv.release.set(0.1); + cutoff.set(500.0); + filterEnvDepth.set(500.0); + filter.Q.set(3.0); + break; + case 2: + ampEnv.attack.set(0.1); + ampEnv.decay.set(0.3); + ampEnv.release.set(0.5); + cutoff.set(2000.0); + filterEnvDepth.set(500.0); + filter.Q.set(2.0); + break; + case 3: + osc1.shape.set(-0.9); + osc2.shape.set(-0.8); + ampEnv.attack.set(0.3); + ampEnv.decay.set(0.8); + ampEnv.release.set(0.2); + filterEnv.sustain.set(0.7); + cutoff.set(500.0); + filterEnvDepth.set(500.0); + filter.Q.set(3.0); + break; + case 4: + osc1.shape.set(1.0); + osc2.shape.set(0.0); + break; + case 5: + osc1.shape.set(1.0); + setDetunePitch1(0.0); + osc2.shape.set(0.9); + setDetunePitch1(7.0); + break; + case 6: + osc1.shape.set(0.6); + osc2.shape.set(-0.2); + setDetunePitch1(0.01); + ampEnv.attack.set(0.005); + ampEnv.decay.set(0.09); + ampEnv.sustain.set(0.0); + ampEnv.release.set(1.0); + filterEnv.attack.set(0.005); + filterEnv.decay.set(0.1); + filterEnv.sustain.set(0.4); + filterEnv.release.set(1.0); + cutoff.set(2000.0); + filterEnvDepth.set(5000.0); + filter.Q.set(7.02); + break; + default: + break; + } + } + + private static final String[] presetNames = { + "FastSaw", "SlowSaw", "BrightSaw", + "SoftSine", "SquareSaw", "SquareFifth", + "Blip" + }; + + static class MyVoiceDescription extends VoiceDescription { + String[] tags = { + "electronic", "filter", "analog", "subtractive" + }; + + public MyVoiceDescription() { + super(DualOscillatorSynthVoice.class.getName(), presetNames); + } + + @Override + public UnitVoice createUnitVoice() { + return new DualOscillatorSynthVoice(); + } + + @Override + public String[] getTags(int presetIndex) { + return tags; + } + + @Override + public String getVoiceClassName() { + return DualOscillatorSynthVoice.class.getName(); + } + } + + public static VoiceDescription getVoiceDescription() { + if (voiceDescription == null) { + voiceDescription = new MyVoiceDescription(); + } + return voiceDescription; + } + + +} diff --git a/src/main/java/com/jsyn/instruments/JSynInstrumentLibrary.java b/src/main/java/com/jsyn/instruments/JSynInstrumentLibrary.java new file mode 100644 index 0000000..9f111c3 --- /dev/null +++ b/src/main/java/com/jsyn/instruments/JSynInstrumentLibrary.java @@ -0,0 +1,48 @@ +/* + * 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.instruments; + +import com.jsyn.swing.InstrumentBrowser; +import com.jsyn.util.InstrumentLibrary; +import com.jsyn.util.VoiceDescription; + +/** + * Stock instruments provided with the JSyn distribution. + * + * @author Phil Burk (C) 2011 Mobileer Inc + * @see InstrumentBrowser + */ + +public class JSynInstrumentLibrary implements InstrumentLibrary { + static VoiceDescription[] descriptions = { + WaveShapingVoice.getVoiceDescription(), + SubtractiveSynthVoice.getVoiceDescription(), + DualOscillatorSynthVoice.getVoiceDescription(), + NoiseHit.getVoiceDescription(), + DrumWoodFM.getVoiceDescription() + }; + + @Override + public VoiceDescription[] getVoiceDescriptions() { + return descriptions; + } + + @Override + public String getName() { + return "JSynInstruments"; + } +} diff --git a/src/main/java/com/jsyn/instruments/NoiseHit.java b/src/main/java/com/jsyn/instruments/NoiseHit.java new file mode 100644 index 0000000..b8714fc --- /dev/null +++ b/src/main/java/com/jsyn/instruments/NoiseHit.java @@ -0,0 +1,114 @@ +/* + * 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.instruments; + +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.unitgen.Circuit; +import com.jsyn.unitgen.EnvelopeAttackDecay; +import com.jsyn.unitgen.PinkNoise; +import com.jsyn.unitgen.UnitVoice; +import com.jsyn.util.VoiceDescription; +import com.softsynth.shared.time.TimeStamp; + +/** + * Cheap synthetic cymbal sound. + */ +public class NoiseHit extends Circuit implements UnitVoice { + EnvelopeAttackDecay ampEnv; + PinkNoise noise; + private static final int NUM_PRESETS = 3; + + public NoiseHit() { + // Create unit generators. + add(noise = new PinkNoise()); + add(ampEnv = new EnvelopeAttackDecay()); + noise.output.connect(ampEnv.amplitude); + + ampEnv.export(this, "Amp"); + + // Make the circuit turn off when the envelope finishes to reduce CPU load. + ampEnv.setupAutoDisable(this); + + usePreset(0); + } + + @Override + public void noteOff(TimeStamp timeStamp) { + } + + @Override + public void noteOn(double freq, double ampl, TimeStamp timeStamp) { + noise.amplitude.set(ampl, timeStamp); + ampEnv.input.trigger(); + } + + @Override + public UnitOutputPort getOutput() { + return ampEnv.output; + } + + @Override + public void usePreset(int presetIndex) { + int n = presetIndex % NUM_PRESETS; + switch (n) { + case 0: + ampEnv.attack.set(0.001); + ampEnv.decay.set(0.1); + break; + case 1: + ampEnv.attack.set(0.03); + ampEnv.decay.set(1.4); + break; + default: + ampEnv.attack.set(0.9); + ampEnv.decay.set(0.3); + break; + } + } + + static class MyVoiceDescription extends VoiceDescription { + static String[] presetNames = { + "ShortNoiseHit", "LongNoiseHit", "SlowNoiseHit" + }; + static String[] tags = { + "electronic", "noise" + }; + + public MyVoiceDescription() { + super("NoiseHit", presetNames); + } + + @Override + public UnitVoice createUnitVoice() { + return new NoiseHit(); + } + + @Override + public String[] getTags(int presetIndex) { + return tags; + } + + @Override + public String getVoiceClassName() { + return NoiseHit.class.getName(); + } + } + + public static VoiceDescription getVoiceDescription() { + return new MyVoiceDescription(); + } +} diff --git a/src/main/java/com/jsyn/instruments/SubtractiveSynthVoice.java b/src/main/java/com/jsyn/instruments/SubtractiveSynthVoice.java new file mode 100644 index 0000000..5cfc4b9 --- /dev/null +++ b/src/main/java/com/jsyn/instruments/SubtractiveSynthVoice.java @@ -0,0 +1,182 @@ +/* + * Copyright 2010 Phil Burk, Mobileer Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.jsyn.instruments; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.unitgen.Add; +import com.jsyn.unitgen.Circuit; +import com.jsyn.unitgen.EnvelopeDAHDSR; +import com.jsyn.unitgen.FilterLowPass; +import com.jsyn.unitgen.Multiply; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.unitgen.UnitVoice; +import com.jsyn.util.VoiceDescription; +import com.softsynth.shared.time.TimeStamp; + +/** + * Typical synthesizer voice with one oscillator and a biquad resonant filter. Modulate the amplitude and + * filter using DAHDSR envelopes. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class SubtractiveSynthVoice extends Circuit implements UnitVoice { + private UnitOscillator osc; + private FilterLowPass filter; + private EnvelopeDAHDSR ampEnv; + private EnvelopeDAHDSR filterEnv; + private Add cutoffAdder; + private Multiply frequencyScaler; + + public UnitInputPort amplitude; + public UnitInputPort frequency; + /** + * This scales the frequency value. You can use this to modulate a group of instruments using a + * shared LFO and they will stay in tune. + */ + public UnitInputPort pitchModulation; + public UnitInputPort cutoff; + public UnitInputPort cutoffRange; + public UnitInputPort Q; + + public SubtractiveSynthVoice() { + add(frequencyScaler = new Multiply()); + // Add a tone generator. + add(osc = new SawtoothOscillatorBL()); + + // Use an envelope to control the amplitude. + add(ampEnv = new EnvelopeDAHDSR()); + + // Use an envelope to control the filter cutoff. + add(filterEnv = new EnvelopeDAHDSR()); + add(filter = new FilterLowPass()); + add(cutoffAdder = new Add()); + + filterEnv.output.connect(cutoffAdder.inputA); + cutoffAdder.output.connect(filter.frequency); + frequencyScaler.output.connect(osc.frequency); + osc.output.connect(filter.input); + filter.output.connect(ampEnv.amplitude); + + addPort(amplitude = osc.amplitude, "Amplitude"); + addPort(frequency = frequencyScaler.inputA, "Frequency"); + addPort(pitchModulation = frequencyScaler.inputB, "PitchMod"); + addPort(cutoff = cutoffAdder.inputB, "Cutoff"); + addPort(cutoffRange = filterEnv.amplitude, "CutoffRange"); + addPort(Q = filter.Q); + + ampEnv.export(this, "Amp"); + filterEnv.export(this, "Filter"); + + frequency.setup(osc.frequency); + pitchModulation.setup(0.2, 1.0, 4.0); + cutoff.setup(filter.frequency); + cutoffRange.setup(filter.frequency); + + // Make the circuit turn off when the envelope finishes to reduce CPU load. + ampEnv.setupAutoDisable(this); + + usePreset(0); + } + + @Override + public void noteOff(TimeStamp timeStamp) { + ampEnv.input.off(timeStamp); + filterEnv.input.off(timeStamp); + } + + @Override + public void noteOn(double freq, double ampl, TimeStamp timeStamp) { + frequency.set(freq, timeStamp); + amplitude.set(ampl, timeStamp); + + ampEnv.input.on(timeStamp); + filterEnv.input.on(timeStamp); + } + + @Override + public UnitOutputPort getOutput() { + return ampEnv.output; + } + + @Override + public void usePreset(int presetIndex) { + int n = presetIndex % presetNames.length; + switch (n) { + case 0: + ampEnv.attack.set(0.01); + ampEnv.decay.set(0.2); + ampEnv.release.set(1.0); + cutoff.set(500.0); + cutoffRange.set(500.0); + filter.Q.set(1.0); + break; + case 1: + ampEnv.attack.set(0.5); + ampEnv.decay.set(0.3); + ampEnv.release.set(0.2); + cutoff.set(500.0); + cutoffRange.set(500.0); + filter.Q.set(3.0); + break; + case 2: + default: + ampEnv.attack.set(0.1); + ampEnv.decay.set(0.3); + ampEnv.release.set(0.5); + cutoff.set(2000.0); + cutoffRange.set(500.0); + filter.Q.set(2.0); + break; + } + } + + static String[] presetNames = { + "FastSaw", "SlowSaw", "BrightSaw" + }; + + static class MyVoiceDescription extends VoiceDescription { + String[] tags = { + "electronic", "filter", "clean" + }; + + public MyVoiceDescription() { + super("SubtractiveSynth", presetNames); + } + + @Override + public UnitVoice createUnitVoice() { + return new SubtractiveSynthVoice(); + } + + @Override + public String[] getTags(int presetIndex) { + return tags; + } + + @Override + public String getVoiceClassName() { + return SubtractiveSynthVoice.class.getName(); + } + } + + public static VoiceDescription getVoiceDescription() { + return new MyVoiceDescription(); + } + +} diff --git a/src/main/java/com/jsyn/instruments/WaveShapingVoice.java b/src/main/java/com/jsyn/instruments/WaveShapingVoice.java new file mode 100644 index 0000000..5044f21 --- /dev/null +++ b/src/main/java/com/jsyn/instruments/WaveShapingVoice.java @@ -0,0 +1,187 @@ +/* + * 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.instruments; + +import com.jsyn.data.DoubleTable; +import com.jsyn.ports.UnitFunctionPort; +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.unitgen.Circuit; +import com.jsyn.unitgen.EnvelopeDAHDSR; +import com.jsyn.unitgen.FunctionEvaluator; +import com.jsyn.unitgen.Multiply; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.unitgen.UnitVoice; +import com.jsyn.util.VoiceDescription; +import com.softsynth.math.ChebyshevPolynomial; +import com.softsynth.math.PolynomialTableData; +import com.softsynth.shared.time.TimeStamp; + +/** + * Waveshaping oscillator with envelopes. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class WaveShapingVoice extends Circuit implements UnitVoice { + private static final long serialVersionUID = -2704222221111608377L; + private static final int NUM_PRESETS = 3; + private UnitOscillator osc; + private FunctionEvaluator waveShaper; + private EnvelopeDAHDSR ampEnv; + private EnvelopeDAHDSR rangeEnv; + private Multiply frequencyScaler; + + public UnitInputPort range; + public UnitInputPort frequency; + public UnitInputPort amplitude; + public UnitFunctionPort function; + public UnitInputPort pitchModulation; + + // default Chebyshev polynomial table to share. + private static DoubleTable chebyshevTable; + private final static int CHEBYSHEV_ORDER = 11; + + static { + // Make table with Chebyshev polynomial to share among voices + PolynomialTableData chebData = new PolynomialTableData( + ChebyshevPolynomial.T(CHEBYSHEV_ORDER), 1024); + chebyshevTable = new DoubleTable(chebData.getData()); + } + + public WaveShapingVoice() { + add(frequencyScaler = new Multiply()); + add(osc = new SineOscillator()); + add(waveShaper = new FunctionEvaluator()); + add(rangeEnv = new EnvelopeDAHDSR()); + add(ampEnv = new EnvelopeDAHDSR()); + + addPort(amplitude = ampEnv.amplitude); + addPort(range = osc.amplitude, "Range"); + addPort(function = waveShaper.function); + addPort(frequency = frequencyScaler.inputA, "Frequency"); + addPort(pitchModulation = frequencyScaler.inputB, "PitchMod"); + + ampEnv.export(this, "Amp"); + rangeEnv.export(this, "Range"); + + function.set(chebyshevTable); + + // Connect units. + osc.output.connect(rangeEnv.amplitude); + rangeEnv.output.connect(waveShaper.input); + ampEnv.output.connect(waveShaper.amplitude); + frequencyScaler.output.connect(osc.frequency); + + // Set reasonable defaults for the ports. + pitchModulation.setup(0.1, 1.0, 10.0); + range.setup(0.1, 0.8, 1.0); + frequency.setup(osc.frequency); + amplitude.setup(0.0, 0.5, 1.0); + + // Make the circuit turn off when the envelope finishes to reduce CPU load. + ampEnv.setupAutoDisable(this); + + usePreset(2); + } + + @Override + public UnitOutputPort getOutput() { + return waveShaper.output; + } + + @Override + public void noteOn(double freq, double amp, TimeStamp timeStamp) { + frequency.set(freq, timeStamp); + amplitude.set(amp, timeStamp); + ampEnv.input.on(timeStamp); + rangeEnv.input.on(timeStamp); + } + + @Override + public void noteOff(TimeStamp timeStamp) { + ampEnv.input.off(timeStamp); + rangeEnv.input.off(timeStamp); + } + + @Override + public void usePreset(int presetIndex) { + int n = presetIndex % NUM_PRESETS; + switch (n) { + case 0: + ampEnv.attack.set(0.01); + ampEnv.decay.set(0.2); + ampEnv.release.set(1.0); + rangeEnv.attack.set(0.01); + rangeEnv.decay.set(0.2); + rangeEnv.sustain.set(0.4); + rangeEnv.release.set(1.0); + break; + case 1: + ampEnv.attack.set(0.5); + ampEnv.decay.set(0.3); + ampEnv.release.set(0.2); + rangeEnv.attack.set(0.03); + rangeEnv.decay.set(0.2); + rangeEnv.sustain.set(0.5); + rangeEnv.release.set(1.0); + break; + default: + ampEnv.attack.set(0.1); + ampEnv.decay.set(0.3); + ampEnv.release.set(0.5); + rangeEnv.attack.set(0.01); + rangeEnv.decay.set(0.2); + rangeEnv.sustain.set(0.9); + rangeEnv.release.set(1.0); + break; + } + } + + static class MyVoiceDescription extends VoiceDescription { + static String[] presetNames = { + "FastChebyshev", "SlowChebyshev", "BrightChebyshev" + }; + static String[] tags = { + "electronic", "waveshaping", "clean" + }; + + public MyVoiceDescription() { + super("Waveshaping", presetNames); + } + + @Override + public UnitVoice createUnitVoice() { + return new WaveShapingVoice(); + } + + @Override + public String[] getTags(int presetIndex) { + return tags; + } + + @Override + public String getVoiceClassName() { + return WaveShapingVoice.class.getName(); + } + } + + public static VoiceDescription getVoiceDescription() { + return new MyVoiceDescription(); + } + +} |