/* * Created on Jun 28, 2004 * */ package jmslexamples.jsyn; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import com.didkovsky.portview.PVButton; import com.didkovsky.portview.PVPanel; import com.softsynth.jmsl.*; import com.softsynth.jmsl.jsyn.JSynMusicDevice; import com.softsynth.jmsl.jsyn.SynthNoteAllPortsInstrument; import com.softsynth.jmsl.util.*; import com.softsynth.jmsl.view.PVPanelAdapter; /** * Demonstrates continuous control by JMSL over JSyn synth parameters. Key idea: * for each pitch, instrument.on() starts a sound, instrument.update() changes * its timbre and can be called frequently at control rates. instrument.off() * terminates sound * * Use MusicList stuffed with ControlElement's whose durations are very short. * They turn an instrument on, update its timbre, turn it off * * Uses Interpolators to generate smoothly changing data * * @author Nick Didkovsky, (c) 2004 Nick Didkovsky, All rights reserved, * didkovn@mail.rockefeller.edu * */ public class TimbralControlApplet extends java.applet.Applet { String synthNoteClassName = com.softsynth.jsyn.circuits.FilteredSawtoothBL.class.getName(); JMSLMixerContainer mixer; PVButton playButton; PVButton finishButton; ParallelCollection parallelCollection; SynthNoteAllPortsInstrument instrument; public void init() { JMSLRandom.randomize(); JMSL.setIsApplet(true); } public void start() { setLayout(new BorderLayout()); initMusicDevice(); instrument = new SynthNoteAllPortsInstrument(8, synthNoteClassName); buildMixer(); buildContinuousTimbralControl(); buildButtons(); } public void stop() { parallelCollection.finishAll(); JMSL.closeMusicDevices(); removeAll(); } private void initMusicDevice() { JSynMusicDevice.instance().open(); JMSL.clock.setAdvance(0.1); } private void buildMixer() { mixer = new JMSLMixerContainer(); mixer.start(); mixer.addInstrument(instrument, 0.5, 0.60); add(BorderLayout.NORTH, mixer.getPanAmpControlPanel()); } private void buildContinuousTimbralControl() { TimbralGestureMaker gestureMaker = new TimbralGestureMaker(); gestureMaker.setInstrument(instrument); parallelCollection = new ParallelCollection(); for (int i = 0; i < JMSLRandom.choose(3, 10); i++) { double pitch = JMSLRandom.choose(30.0, 60.0); double duration = JMSLRandom.choose(6.0, 20.0); gestureMaker.generateGesture(pitch, duration); MusicList ml = gestureMaker.getControllerList(); parallelCollection.add(ml); if (i != 0) ml.setStartDelay(JMSLRandom.choose(20.0)); } parallelCollection.setRepeats(Integer.MAX_VALUE); } private void buildButtons() { playButton = JMSL.getViewFactory().createButton("PLAY"); playButton.setBackground(Color.GREEN); playButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ev) { buildContinuousTimbralControl(); parallelCollection.launch(JMSL.now()); playButton.setEnabled(false); finishButton.setEnabled(true); } }); finishButton = JMSL.getViewFactory().createButton("FINISH"); finishButton.setBackground(Color.RED); finishButton.setEnabled(false); finishButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ev) { parallelCollection.finishAll(); playButton.setEnabled(true); finishButton.setEnabled(false); // shut off each instrument by playing last element in MusicList for (int i = 0; i < parallelCollection.size(); i++) { MusicList m = (MusicList) parallelCollection.get(i); m.getInstrument().off(JMSL.now(), 1, ((ControlElement) m.get(m.size() - 1)).getData()); } } }); PVPanel p = new PVPanelAdapter(); p.setLayout(new FlowLayout()); p.add(playButton.getComponent()); p.add(finishButton.getComponent()); add(BorderLayout.SOUTH, p.getComponent()); } } class TimbralGestureMaker { private double CONTROL_RATE = 0.01; private Interpolator[] interpolators; private Instrument instrument; private MusicList controllerList; public void setInstrument(Instrument instrument) { this.instrument = instrument; } private void initControllerList() { controllerList = new MusicList(); controllerList.setInstrument(instrument); } public MusicList getControllerList() { return controllerList; } public void generateGesture(double pitch, double seconds) { initControllerList(); DimensionNameSpace dns = instrument.getDimensionNameSpace(); double[] dar = generateFirstElement(pitch, seconds, dns); double[] lastDar = generateUpdates(pitch, seconds, dns, dar); controllerList.add(new ControlElement(ControlElement.OFF, lastDar)); } private double[] generateFirstElement(double pitch, double seconds, DimensionNameSpace dns) { double amp = JMSLRandom.choose(); double[] dar = makeElement(pitch, seconds, dns); controllerList.add(new ControlElement(ControlElement.ON, dar)); return dar; } private double[] generateUpdates(double pitch, double seconds, DimensionNameSpace dns, double[] dar) { int steps = (int) (seconds / CONTROL_RATE); int stepsThisStage = JMSLRandom.choose(1, 100); buildInterpolators(0, dar, stepsThisStage); for (int i = 0; i < steps; i++) { dar = new double[dns.dimension()]; dar[0] = CONTROL_RATE; dar[1] = pitch; dar[3] = CONTROL_RATE; // not used for (int dimension = 0; dimension < dns.dimension(); dimension++) { if (notDurPitchHold(dimension)) { Interpolator interpolator = interpolators[dimension]; double lowLimit = dns.getLowLimit(dimension); double highLimit = dns.getHighLimit(dimension); if (!Limits.within(interpolator.interp(i), lowLimit, highLimit)) { System.err.println(interpolator.interp(i) + " out of range of [" + lowLimit + " , " + highLimit + "]"); } dar[dimension] = Limits.clipTo(interpolator.interp(i), lowLimit, highLimit); } } controllerList.add(new ControlElement(ControlElement.UPDATE, dar)); if (0 == --stepsThisStage) { stepsThisStage = JMSLRandom.choose(1, 100); buildInterpolators(i, dar, stepsThisStage); } } return dar; } private double[] makeElement(double pitch, double seconds, DimensionNameSpace dns) { double dar[] = new double[dns.dimension()]; dar[0] = seconds; dar[1] = pitch; dar[3] = seconds; for (int dimension = 0; dimension < dns.dimension(); dimension++) { if (notDurPitchHold(dimension)) { double minValue = instrument.getDimensionNameSpace().getLowLimit(dimension); double maxValue = instrument.getDimensionNameSpace().getHighLimit(dimension); double value = JMSLRandom.choose(minValue, maxValue); dar[dimension] = value; } } return dar; } /** * exclude dimensions for duration, pitch, and hold time which are not * subject to continuous control */ private boolean notDurPitchHold(int dimension) { return dimension != 0 && dimension != 1 && dimension != 3; } private void buildInterpolators(int x1, double[] dar, int stepsThisStage) { DimensionNameSpace dns = instrument.getDimensionNameSpace(); interpolators = new Interpolator[dns.dimension()]; for (int dimension = 0; dimension < dns.dimension(); dimension++) { if (notDurPitchHold(dimension)) { double minValue = instrument.getDimensionNameSpace().getLowLimit(dimension); double maxValue = instrument.getDimensionNameSpace().getHighLimit(dimension); double endingValue = JMSLRandom.choose(minValue, maxValue); double startingValue = dar[dimension]; Interpolator interpolator; if (JMSLRandom.choose() < 0.50) { interpolator = new HalfCosineInterpolator(x1, startingValue, stepsThisStage + x1, endingValue); } else { interpolator = new LinearInterpolator(x1, startingValue, stepsThisStage + x1, endingValue); } interpolators[dimension] = interpolator; } } } } /** * This element can be added to a MusicList to turn its instrument on, off, or * update it. Generally useful */ class ControlElement implements InstrumentPlayable { private double[] dar; private int action; public static final int ON = 0; public static final int UPDATE = 1; public static final int OFF = 2; public ControlElement(int action, double[] dar) { this.dar = dar; this.action = action; } public double play(double playTime, Composable parent, Instrument ins) { double updatedTime = playTime; double timeStretch = parent.getTimeStretch(); switch (action) { case ON: ins.on(playTime, timeStretch, dar); break; case OFF: ins.off(playTime, timeStretch, dar); break; case UPDATE: updatedTime = ins.update(playTime, timeStretch, dar); break; } return updatedTime; } public double[] getData() { return dar; } }