/** JMSL Tutorial. Super simple monophonic JMSL Instrument which uses a JSyn UnitVoice to make a sound. Demonstrates principles of good instrument design.

Only write a class from scratch like this if for some very convincing reason you cannot use JSynUnitVoiceInstrument!!! Good instrument design practice follows these principles: 1) Keep JSyn stuff out of the constructor 2) Specify MusicDevice and Mixer classname in constructor 3) implement AttributeBuildable and do all JSyn building in buildFromAttributes() 4) Define on() and off() and call these in play(), giving you the option of turning this Instrument on and off arbitrarily instead of being played event-style
The idea is that you should be able to make a new Instrument without JSyn actually running. Then you should be able to anonymously query this new object for its MusicDevice, and open() it without knowing what it is. Then you should be able to add this instrument to a JMSLMixerContainer, again without knowing the details of which Mixer implementation it uses. @author Nick Didkovsky, (c) 2000 Nick Didkovsky */ package jmsltutorial; import java.awt.Frame; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import com.softsynth.jmsl.DimensionNameSpaceAdapter; import com.softsynth.jmsl.InstrumentAdapter; import com.softsynth.jmsl.JMSL; import com.softsynth.jmsl.JMSLMixerContainer; import com.softsynth.jmsl.MusicDevice; import com.softsynth.jmsl.MusicShape; import com.softsynth.jmsl.jsyn2.JSynMusicDevice; import com.softsynth.jmsl.util.AttributeBuildable; import com.softsynth.shared.time.TimeStamp; /** * This Instrument owns a JSyn SineUnitVoice, and plays it with frequency and * amplitude */ public class SineInstrument extends InstrumentAdapter implements AttributeBuildable { SineUnitVoice unitVoice; /** * Constructor makes no references to JSyn units. Should be able to execute * without synth engine running */ public SineInstrument() { this.setMixerClassName(com.softsynth.jmsl.jsyn2.JSynMixer.class.getName()); this.setMusicDevice(com.softsynth.jmsl.jsyn2.JSynMusicDevice.instance()); buildDimensionNameSpace(); } /** * Constructing a DimensionNameSpace for your instrument is not required but is * highly advisable */ private void buildDimensionNameSpace() { DimensionNameSpaceAdapter dns = new DimensionNameSpaceAdapter(); dns.setDimensionName(0, "duration"); dns.setLimits(0, 0, 8); dns.setDefault(0, 1); dns.setDimensionName(1, "frequency"); dns.setLimits(1, 20, 1500); dns.setDefault(1, 440); dns.setDimensionName(2, "amplitude"); dns.setLimits(2, 0, 1); dns.setDefault(2, 0.4); dns.setDimensionName(3, "hold"); dns.setLimits(3, 0, 8); dns.setDefault(3, 0.8); // add extra dimensions here if your circuit has SynthInputs that you // want to control this.setDimensionNameSpace(dns); } /** * We don't want to do any JSyn allocation in the constructor. Put all JSyn * stuff in buildFromAttributes(), which is the AttributeBuildable interface. */ public void buildFromAttributes() throws Exception { unitVoice = new SineUnitVoice(); JSynMusicDevice.instance().add(unitVoice); } /** * Instrument interface. * * @return the output of the circuit */ public Object getOutput() { return unitVoice.getOutput(); } /** * Instrument interface. * * @return output(n) of the circuit . In this case, there is only one output * part */ public Object getOutput(int part) { return getOutput(); } /** * In this case return 1. Note this method will fail if synth not running since * it refers to a live JSyn object. You could hardcode the returned 1 if this * were a problem */ public int getNumOutputs() { return unitVoice.output.getNumParts(); } /** turn instrument on with specified data at specified time */ public Object on(double playTime, double timeStretch, double dar[]) { double frequency = dar[1]; double amplitude = dar[2]; TimeStamp timeStamp = new TimeStamp(JSynMusicDevice.instance().jmslTimeToJSynTime(playTime)); unitVoice.noteOn(frequency, amplitude, timeStamp); return unitVoice; } /** * turn instrument off at specified time. data[] is included in case you need to * look up an allocated voice for example (this instrument is monophonic) */ public Object off(double playTime, double timeStretch, double[] dar) { /* JMSL runs on a different clock than JSyn, so convert. */ TimeStamp timeStamp = new TimeStamp(JSynMusicDevice.instance().jmslTimeToJSynTime(playTime)); unitVoice.noteOff(timeStamp); return unitVoice; } /** * Specify sonic behavior in play() A MusicShape calls its Instrument's play() * method for each element, and waits until the time returned by play() before * proceeding to the next element. play() consists of the finer grained on() and * off() methods, in case your music needs to turn a sound on, update it, then * eventually turn it off (ie it does not fit into a sequence of events paradigm * and you do not want to play it with a MusicShape) * * @return updated playTime */ public double play(double playTime, double timeStretch, double dar[]) { double duration = dar[0]; double hold = dar[3]; double offTime = playTime + hold * timeStretch; on(playTime, timeStretch, dar); off(offTime, timeStretch, dar); return playTime + duration * timeStretch; } /** * optionally define update(). update() might get called while a sound is * sustaining. * * @return updated playTime */ public double update(double playTime, double timeStretch, double dar[]) { double duration = dar[0]; double frequency = dar[1]; double amplitude = dar[2]; TimeStamp timeStamp = new TimeStamp(JSynMusicDevice.instance().jmslTimeToJSynTime(playTime)); unitVoice.frequency.set(frequency, timeStamp); unitVoice.amplitude.set(amplitude, timeStamp); return playTime + duration * timeStretch; } /** * Test your instrument. Note that we never refer to any JSyn explicitly in this * method! If your instrument can follow this example, it's nicely designed. */ public static void main(String args[]) { Frame myFrame = new Frame("Close to quit"); myFrame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { // close any open MusicDevices JMSL.closeMusicDevices(); System.exit(0); } }); try { // create an instrument, anonymously get its MusicDevice and open it SineInstrument ins = new SineInstrument(); MusicDevice dev = ins.getMusicDevice(); System.out.println(dev); dev.open(); ins.buildFromAttributes(); // Add instrument to a mixer JMSLMixerContainer mixer = new JMSLMixerContainer(); mixer.start(); mixer.addInstrument(ins); myFrame.add(mixer.getPanAmpControlPanel()); // myFrame.setSize(mixer.getSuggestedFrameSize()); myFrame.pack(); myFrame.setVisible(true); // Make a MusicShape to play the instrument MusicShape shape = new MusicShape(ins.getDimensionNameSpace()); shape.add(2.0, 440, 0.62, 1.8); shape.add(1.0, 880, 0.62, 0.5); shape.add(1.0, 660, 0.62, 0.3); shape.add(1.0, 770, 0.62, 0.1); shape.setInstrument(ins); shape.setRepeats(1000); boolean testSustain = false; if (testSustain) { double[] dada = { 2.0, 1000, 0.4, 1.8 }; ins.on(JMSL.now() + 1, 1.0, dada); ins.off(JMSL.now() + 5, 1.0, dada); } else { shape.launch(JMSL.now()); } } catch (Exception e) { System.out.println("SineInstrument.main() trouble: " + e); } } }