package jmslexamples.jsyn;
import java.awt.*;
import java.awt.event.*;
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.*;
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;
}
}