package jmslexamples.jsyn; import java.applet.Applet; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Enumeration; import java.util.Vector; import com.softsynth.jmsl.*; import com.softsynth.jmsl.jsyn.JSynMusicDevice; import com.softsynth.jmsl.jsyn.SynthNoteInstrument; import com.softsynth.jmsl.util.ChebyshevTableData; import com.softsynth.jsyn.*; import com.softsynth.jsyn.circuits.WaveShapingOscillator; import com.softsynth.jsyn.util.BussedVoiceAllocator; import com.softsynth.jsyn.view102.SynthScope; /** * Demonstrates QueueCollection which plays Composable children as they are added, repeating the head * of the queue when only one child left * * @author Phil Burk, MODS by ND removing LineOut, Synth.start/stop and using JMSLMixerContainer and JSynMusicDevice instead 4/23/04 */ /* Allocate Chebyshev based WaveShapingOscillators */ class ChebyOscAllocator extends BussedVoiceAllocator { ChebyshevTableData chebData; SynthTable table; public ChebyOscAllocator(int maxVoices, int order, int numFrames) throws SynthException { super(maxVoices); chebData = new ChebyshevTableData(com.softsynth.jmsl.util.ChebyshevPolynomial.T(order), numFrames); table = new SynthTable(chebData.getData().length); table.write(chebData.getData()); } public SynthCircuit makeVoice() throws SynthException { SynthNote circ = new WaveShapingOscillator(table); circ.amplitude.set(1.0 / getMaxVoices()); return addVoiceToMix(circ); } } /** Display the Composables in a QueueCollection as a chain of boxes. */ class QueueCollectionDisplay extends Canvas { QueueCollection queue; public QueueCollectionDisplay(QueueCollection queue) { this.queue = queue; } public void paint(Graphics g) { int w = getBounds().width; int h = getBounds().height; Enumeration e = queue.elements(); int dx = 60; int dy = 20; for (int i = 0; e.hasMoreElements(); i++) { Composable comp = (Composable) e.nextElement(); if (comp == null) return; int x = i * dx; g.setColor(Color.cyan); g.drawRect(x, 0, dx - 1, dy - 1); g.setColor(Color.red); g.drawString(comp.getName(), x + (dx / 2), dy - 6); } } } /** Play shapes by adding them to a queue. * Provide buttons for regenerating reversing the sequence. * @author Phil Burk */ public class QueueCollectionDemo extends Applet implements Playable { ChebyOscAllocator allocator; JMSLMixerContainer mixer; QueueCollection queue; SynthScope scope; ShapeTweaker tweakers[]; Vector shapes; static final int NUM_SHAPES = 4; QueueCollectionDisplay queueDisplay; Panel bPanel; /* Can be run as either an application or as an applet. */ public static void main(String args[]) { QueueCollectionDemo applet = new QueueCollectionDemo(); AppletFrame frame = new AppletFrame("QueueDemo", applet); frame.setSize(600, 500); frame.show(); frame.test(); } /* * Setup synthesis. */ public void start() { shapes = new Vector(); setLayout(new GridLayout(0, 1)); bPanel = new Panel(); add(bPanel); bPanel.setLayout(new GridLayout(0, 1)); try { JMSL.clock.setAdvance(0.1); MusicDevice dev = JSynMusicDevice.instance(); dev.open(); // Create a voice allocator and connect it to a LineOut. allocator = new ChebyOscAllocator(8, 9, 1024); SynthNoteInstrument ins = new SynthNoteInstrument(); ins.setAllocator(allocator); mixer = new JMSLMixerContainer(); mixer.start(); mixer.addInstrument(ins); // unitOut.start(); // Translate linear note indices into a key. KeyTranslator key = new KeyTranslator(); key.useHarmonicMinorScale(KeyTranslator.KEY_G + (3 * 12)); // use G minor ((NoteInterpreter) (ins.getInterpreter())).setKey(key); // Show signal on a scope. scope = new SynthScope(); scope.createProbe(allocator.getOutput(), "Bus out", Color.yellow); // create shapes with 4 dimensions to hold melodies for (int i = 0; i < NUM_SHAPES; i++) { MusicShape shape = new MusicShape(4); shapes.addElement(shape); shape.setInstrument(ins); // can share one instrument shape.setName("S" + i); bPanel.add(new ShapeTweaker(shape)); } // Create a QueueCollection and add one initial shape. queue = new QueueCollection(); queue.add((MusicShape) shapes.firstElement()); queue.addRepeatPlayable(this); queue.setRepeats(Integer.MAX_VALUE); // play forever queue.launch(JMSL.now()); } catch (SynthException e) { SynthAlert.showError(this, e); } queueDisplay = new QueueCollectionDisplay(queue); bPanel.add(queueDisplay); add(scope); /* Synchronize Java display. */ getParent().validate(); getToolkit().sync(); System.out.println("Exiting start() --------------------------------"); } public void stop() { System.out.println("STOPPING --------------------------------"); queue.finish(); removeAll(); JMSL.closeMusicDevices(); } // Inner class that presents a GUI for tweaking and playing shapes. // Objects of this class will be able to access members of outer class. class ShapeTweaker extends Panel implements ActionListener { MusicShape shape; Button printButton; Button queueButton; Button queueClonedButton; Button randomButton; Button reverseButton; public ShapeTweaker(MusicShape shape) { this.shape = shape; add(new Label(shape.getName())); add(queueButton = new Button("Queue")); add(queueClonedButton = new Button("Queue Clone")); add(randomButton = new Button("Randomize")); add(reverseButton = new Button("Reverse")); add(printButton = new Button("Print")); makeAscendingSequence(); printButton.addActionListener(this); queueButton.addActionListener(this); queueClonedButton.addActionListener(this); randomButton.addActionListener(this); reverseButton.addActionListener(this); } /* Algorithmically generate an ascending melody. */ void makeAscendingSequence() { shape.removeAll(); int lastDurInt = 0; int base = JMSLRandom.choose(0, 20); int incr = JMSLRandom.choose(1, 3); int num = 4 * JMSLRandom.choose(1, 4); for (int i = 0; i < num; i++) { if (lastDurInt == 0) { // choose 1, 2 or 4 duration lastDurInt = 1 << JMSLRandom.choose(3); // don't make another zero } else { // choose 0, 1 or 2 duration int ir = JMSLRandom.choose(3); lastDurInt = (ir == 0) ? 0 : (1 << (ir - 1)); // possibly make zero } double dur = lastDurInt * 0.1; double hold = JMSLRandom.choose(1, 3) * 0.1; shape.add(dur, base + (i * incr), 20, hold); } } public void actionPerformed(ActionEvent evt) { Object source = evt.getSource(); if (source == printButton) { shape.print(); } else if (source == queueButton) { queue.add(shape); queueDisplay.repaint(); } else if (source == queueClonedButton) { /* Clone shape before queuing it so that we can modify original. */ MusicShape clonedShape = (MusicShape) shape.clone(); clonedShape.setName(clonedShape.getName() + "C"); queue.add(clonedShape); queueDisplay.repaint(); } else if (source == randomButton) { makeAscendingSequence(); } else if (source == reverseButton) { shape.reverse(0, shape.size() - 1, -1); } } } /** Called by QueueCollection after it plays each child. * @return time finished */ public double play(double repeatTime, Composable thing) { queueDisplay.repaint(); return repeatTime; } }