package jmslexamples.jsyn; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Enumeration; import com.didkovsky.portview.*; import com.softsynth.jmsl.*; import com.softsynth.jmsl.jsyn.*; import com.softsynth.jmsl.util.FrequencyToPitchTranslator; import com.softsynth.jmsl.view.PVLabelAdapter; import com.softsynth.jmsl.view.PVPanelAdapter; import com.softsynth.jsyn.AppletFrame; /** * Control the playback speed of a ParallelCollection of MusicJobs that play * JSyn Instruments using timeStretch
*
* Also shows how to manage data as frequencies and use * com.softsynth.jmsl.util.FrequencyToPitchTranslator to convert to pitch right * before handing double[] to Instrument which expect pitch * * @author Nick Didkovsky, 12/13/98, copyright (c) 1998, Nick Didkovsky */ public class Speedy extends java.applet.Applet implements ActionListener, PVScrollbarListener { private JMSLMixerContainer mixer; private PVButton startButton; private PVButton stopButton; private PVScrollbar speedyScrollbar; private PVLabel speedyLabel; private PVButton speedyButton; private int numJobs = 4; private SpeedyJob[] jobs = new SpeedyJob[numJobs]; private SpeedyCollection parCol; private double dur = 1.0; private int numRepeats = 1000; private void buildLayout() { setLayout(new BorderLayout(20, 20)); PVPanel p = new PVPanelAdapter(); p.setLayout(new GridLayout(0, 1)); speedyLabel = new PVLabelAdapter(durString()); speedyScrollbar = new PVScrollbar((int) dur * 1000, 10, 6000); speedyScrollbar.addPVScrollbarListener(this); // set the size of the scrollbar speedyScrollbar.setSize(320, 25); // set the increment that the value will jump when user clicks inside // scrollbar speedyScrollbar.setPageIncrement(100); p.add(speedyLabel.getComponent()); p.add(speedyScrollbar.getComponent()); add(BorderLayout.CENTER, p.getComponent()); p = new PVPanelAdapter(); p.add((startButton = JMSL.getViewFactory().createButton("Start")).getComponent()); p.add((stopButton = JMSL.getViewFactory().createButton("Stop")).getComponent()); p.add((speedyButton = JMSL.getViewFactory().createButton("Set Time Stretch")).getComponent()); p.add(new PVUsageDisplay().getComponent()); add(BorderLayout.SOUTH, p.getComponent()); add(BorderLayout.NORTH, mixer.getPanAmpControlPanel()); speedyButton.addActionListener(this); startButton.addActionListener(this); stopButton.addActionListener(this); } void buildHierarchy() { parCol = new SpeedyCollection(); for (int i = 0; i < numJobs; i++) { jobs[i] = new SpeedyJob(Math.pow(2, i) * 0.5); jobs[i].setRepeats((int) Math.pow(2, numJobs - i - 1)); parCol.add(jobs[i]); } parCol.setTimeStretch(dur); parCol.setRepeats(numRepeats); } String durString() { return "Time Stretch: " + dur; } private void buildMixer() { mixer = new JMSLMixerContainer(); mixer.start(); double[] pans = { 0, 0.33, 0.66, 1.0 }; for (int i = 0; i < parCol.size(); i++) { MusicJob j = (MusicJob) parCol.get(i); double pan = pans[i]; mixer.addInstrument(j.getInstrument(), pan, 0.4); } } public void start() { synchronized (JMSL.class) { JMSL.setIsApplet(true); JMSL.clock.setAdvance(0.1); JMSLRandom.randomize(); JSynMusicDevice.instance().open(); buildHierarchy(); buildMixer(); buildLayout(); /* Synchronize Java display. */ getParent().validate(); getToolkit().sync(); } } public void stop() { synchronized (JMSL.class) { removeAll(); parCol.finishAll(); try { parCol.waitForDone(); } catch (InterruptedException e) { e.printStackTrace(); } JMSL.closeMusicDevices(); } } void handleSpeedyChange() { dur = speedyScrollbar.getValue() / 1000.0; speedyLabel.setText(durString()); } void handleSpeedyButton() { parCol.setTimeStretch(dur); } void handleStart() { parCol.finishAll(); try { parCol.waitForDone(); } catch (InterruptedException e) { e.printStackTrace(); } parCol.launch(JMSL.now()); } void handleStop() { parCol.finishAll(); } public void actionPerformed(ActionEvent e) { Object source = e.getSource(); if (source == speedyButton) handleSpeedyButton(); if (source == startButton) handleStart(); if (source == stopButton) handleStop(); } /* Can be run as either an application or as an applet. */ public static void main(String args[]) { Speedy applet = new Speedy(); AppletFrame frame = new AppletFrame("Speedy", applet); frame.setSize(800, 400); frame.setVisible(true); frame.setLayout(new FlowLayout()); /* * Begin test after frame opened so that DirectSound will use Java * window. */ frame.test(); } /* * (non-Javadoc) * * @see com.didkovsky.portview.PVScrollbarListener#notifyScrollbarValueChanged(com.didkovsky.portview.PVScrollbar) */ public void notifyScrollbarValueChanged(PVScrollbar sb) { if (sb == speedyScrollbar) { handleSpeedyChange(); } } } /** * Starts all its childrens' instruments when it starts, closes them when it * stops */ class SpeedyCollection extends ParallelCollection { public double start(double time) { try { // System.out.println("Speedy Collection starts"); Enumeration e = elements(); while (e.hasMoreElements()) { ((SpeedyJob) e.nextElement()).getInstrument().open(time); } } catch (InterruptedException e) { } return time; } public double stop(double time) { try { // System.out.println("Speedy Collection stops"); Enumeration e = elements(); while (e.hasMoreElements()) { ((SpeedyJob) e.nextElement()).getInstrument().close(time); } } catch (InterruptedException e) { } return time; } } class SpeedyJob extends MusicJob { static int nameIndex = 0; private double frequency = 110.0; private double ratio = 3.0 / 2.0; private double[] data; // passed to Instrument public SpeedyJob(double initialDur) { this.setDataTranslator(new FrequencyToPitchTranslator()); setDuration(initialDur); data = new double[4]; setInstrument(new JSynInsFromClassName(1, jmslexamples.jsyn.SpeedySynthNote.class.getName())); getInstrument().setName("voice " + nameIndex++); } void newRatio() { double denom = (double) JMSLRandom.choose(5) + 1.0; double numer = denom + (double) JMSLRandom.choose(5) + 1.0; ratio = numer / denom; System.out.println(getName() + ", new ratio = " + ratio); } /** * choose random ratio, play frequency * ratio up, inverse ratio down, * change to new random ratio when you hit bottom */ public double repeat(double playTime) { double nextFreq = frequency * ratio; // candidate to test for bounds if (nextFreq > 900 || nextFreq < 80.0) ratio = 1.0 / ratio; frequency *= ratio; data[0] = getDuration(); data[1] = frequency; data[2] = 0.5; data[3] = getDuration(); // JMSL.printDoubleArray(data); double[] translatedData = this.getDataTranslator().translate(this, data); // JMSL.printDoubleArray(translatedData); return getInstrument().play(playTime, timeStretch(), translatedData); } public double start(double time) { newRatio(); return time; } }