/* * Created on Oct 8, 2012 by Nick * */ package jmslexamples.jsyn2.unitvoices; import com.jsyn.data.SegmentedEnvelope; import com.jsyn.ports.UnitInputPort; import com.jsyn.ports.UnitOutputPort; import com.jsyn.unitgen.*; import com.softsynth.shared.time.TimeStamp; public class FMVoice extends Circuit implements UnitVoice, UnitSource { FMPair fmPair; VariableRateMonoReader carAmpEnvReader; VariableRateMonoReader modAmpEnvReader; SegmentedEnvelope carAmpEnvData; SegmentedEnvelope modAmpEnvData; private double modIndex; private double modfreq; private double fcfmRatio; public UnitInputPort amplitude; public UnitInputPort frequency; public static final int PRESET_BRASS = 0; public static final int PRESET_WOODDRUM = 1; public static final int PRESET_BELL = 2; public static final int PRESET_CLARINET = 3; /** * Construct a default brass like FMVoice */ public FMVoice() { this(1, 1, 4); } /** * Construct a FMVoice that maintains numer/denom Fc:Fm ratio, with * specified Modulation index */ public FMVoice(double numerFc, double denomFm, double modIndex) { setTimbre(numerFc, denomFm, modIndex); add(fmPair = new FMPair()); add(carAmpEnvReader = new VariableRateMonoReader()); add(modAmpEnvReader = new VariableRateMonoReader()); carAmpEnvReader.output.connect(fmPair.caramplitude); modAmpEnvReader.output.connect(fmPair.modamplitude); /* Make ports on internal units appear as ports on circuit. */ addPort(amplitude = carAmpEnvReader.amplitude, "amplitude"); addPort(frequency = fmPair.carfrequency, "frequency"); amplitude.setup(0.0, 0.0, 1.0); frequency.setup(0.0, 440, 4186.0); // 4186 Hz = C8; highest pitch on // piano setDefaultEnvelopes(); } public void setTimbre(double numerFc, double denomFm, double modIndex) { fcfmRatio = numerFc / denomFm; this.modIndex = modIndex; } /** * @return the modIndex */ public double getModIndex() { return modIndex; } /** * @param modIndex * the modIndex to set */ public void setModIndex(double modIndex) { this.modIndex = modIndex; } /** * @return the fcfmRatio */ public double getFcfmRatio() { return fcfmRatio; } /** * @param fcfmRatio * the fcfmRatio to set */ public void setFcfmRatio(double fcfmRatio) { this.fcfmRatio = fcfmRatio; } /** * Specify envelopes to use and the sustain portion of each. Note that end * frame is one greater than the one you want it to finish before looping */ public void setEnvelopes(double[] carAmpData, int carSusBegin, int carSusEnd, double[] modAmpData, int modSusBegin, int modSusEnd) { carAmpEnvData = new SegmentedEnvelope(carAmpData); carAmpEnvData.setSustainBegin(carSusBegin); carAmpEnvData.setSustainEnd(carSusEnd); modAmpEnvData = new SegmentedEnvelope(modAmpData); modAmpEnvData.setSustainBegin(modSusBegin); modAmpEnvData.setSustainEnd(modSusEnd); } void setDefaultEnvelopes() { double[] carAmpData = { 0.1, 1.0, 0.3, 0.25, 0.2, 0.3, 0.5, 0.0 }; double[] modAmpData = { 0.5, 1.0, 0.25, 0.25, 0.2, 0.35, 0.25, 0.0 }; setEnvelopes(carAmpData, 1, 3, modAmpData, 1, 3); } public UnitOutputPort getOutput() { return fmPair.getOutput(); } public void noteOff(TimeStamp ts) { carAmpEnvReader.dataQueue.queueOff(carAmpEnvData, true, ts); modAmpEnvReader.dataQueue.queueOff(modAmpEnvData, false, ts); } public void noteOn(double frequency, double amplitude, TimeStamp ts) { this.amplitude.set(amplitude, ts); this.frequency.set(frequency, ts); // Calculate modulation frequency given the Fc:Fm ratio and the carrier // frequency */ modfreq = frequency / fcfmRatio; fmPair.modfrequency.set(modfreq, ts); // Calculate modulation amplitude double modamp = modfreq * modIndex; modAmpEnvReader.amplitude.set(modamp, ts); // now queue up the envelopes carAmpEnvReader.dataQueue.clear(ts); carAmpEnvReader.dataQueue.queueOn(carAmpEnvData, ts); modAmpEnvReader.dataQueue.clear(ts); modAmpEnvReader.dataQueue.queueOn(modAmpEnvData, ts); } /** * Wood Drum, Fc:Fm ratio 80:55, IMAX = 10, DUR 0.2 sec (Dodge/Jerse p. 113) */ public void woodDrumPreset() { setTimbre(80, 55, 25); double[] carAmpData = { 0.01, 1.0, 0.25, 0 }; double[] modAmpData = { 0.01, 1.0, 0.01, 0 }; setEnvelopes(carAmpData, -1, -1, modAmpData, -1, -1); } /** Bell, Fc:Fm ratio 5:7, IMAX = 10, DUR 15 sec (Dodge/Jerse p. 113) */ public void bellPreset() { setTimbre(5, 7, 10); double[] carAmpData = { 0.01, 1.0, 0.01, 0.9, 0.2, 0.8, 0.2, 0.6, 0.2, 0.5, 0.2, 0.45, 10, 0 }; double[] modAmpData = { 0.01, 1.0, 0.01, 0.9, 10, 0 }; setEnvelopes(carAmpData, -1, -1, modAmpData, -1, -1); } /** * Brass, Fc:Fm ratio 1:1, IMAX = 5, DUR variable so we use sustain loop * (Dodge/Jerse p. 113) */ public void brassPreset() { setTimbre(1, 1, 10); double[] ampData = { 0.05, 1.0, 0.10, 0.5, 0.4, 0.5, 0.05, 0.0 }; setEnvelopes(ampData, 1, 2, ampData, 1, 2); } /** * Clarinet, Fc:Fm ratio 3:2, IMIN = 2, IMAX = 4, DUR variable so we use * sustain loop (Dodge/Jerse p. 113) */ public void clarinetPreset() { setTimbre(3, 2, 4); double[] ampData = { 0.05, 1.0, 0.10, 0.5, 0.4, 0.5, 0.05, 0.0 }; setEnvelopes(ampData, 1, 2, ampData, 1, 2); } /** * Pass in FMVoice.PRESET_BELL, FMVoice.PRESET_CLARINET, * FMVoice.PRESET_BRASS, or FMVoice.PRESET_WOODDRUM * * Overridden from UnitVoice interface */ public void usePreset(int presetIndex) { switch (presetIndex) { case PRESET_BELL: bellPreset(); break; case PRESET_CLARINET: clarinetPreset(); break; case PRESET_BRASS: brassPreset(); break; case PRESET_WOODDRUM: woodDrumPreset(); break; } } public static void main(String args[]) { FMVoice voice = new FMVoice(); voice.usePreset(PRESET_WOODDRUM); // voice.usePreset(PRESET_BELL); // voice.usePreset(PRESET_CLARINET); // voice.usePreset(PRESET_BRASS); UnitSourceTester.test(voice); } }