// Copyright (c) 1998 Pierre Lewis lew@nortelnetworks.com // The copyright must remain with any identifiable parts of this code. // You may not use any of this code for commercial purposes without // authorization. // This is adapted from the JavaTuner applet (that demonstrates // tunings and temperaments) and is intended to illustrate how // I've used the sun.audio classes. // User interface: key presses // 2-9: Play the corresponding major interval (eg. 3 for major third) // m: Play a milliwatt tone // As with any S/W generating sound, some care is called for: don't // run with your speakers at high volume until you know it works! // This is especially true if you start playing with the code. The // muLaw() routine in particular will go seriously non-linear if the // input argument exceeds 1.0 volts. // Overview: // - On startup, the applet pre-computes the waveforms for one cycle // (360 degrees) for both a sine wave and a wave with 5 harmonics. // This is done to speed up computations later. // - The applet then waits for user input. Here, it's a key press. // When the users asks for something to be played, freq[] is set to // the one or two frequencies requested. // - The sound thread discovers that freq[] is non-null, converts the // frequencies to degrees (of the cycle) per mu-law sample (at 8 kHz), // then computes a 6000-byte audio sample, and finally submits it to // the audio player. Computation is done by interpolating the wave // computed at initialization (faster than doing complex computations // at this stage). // - Beware: only 2 notes in this example (for more notes, see invocation // of the harmonics() method). Also, since we are using up to the // 5th harmonic, no freq[] value should exceed 800 Hz. To go higher, // one would simply set the higher harmonics to 0.0 in the call to // harmonics(). // There are still a few things not fully cleaned up (wrt AWT). // Also, this applet still uses the old JDK1.0 event model. // References on sun.audio: O'Reilly's Java AWT Reference, and, on the Web // http://www.cdt.luth.se/java/doc/sun/shared/Package-sun.audio.html // (and surely many others) import java.awt.*; import java.io.*; import java.applet.*; import sun.audio.*; // The Roule class contains the following methods: // // method thread purpose // // init AWT/browser initialization // harmonics AWT/browser pre-computes one cycle of waveform // paint AWT/browser paint the applet // start AWT/browser start the applet // stop AWT/browser stop the applet // destroy AWT/browser destroy the applet // muLaw soundThread convert volts to mu-law sample // doPlay soundThread prepare sample if something to play, start it // run soundThread soundThread's main loop // handleEvent AWT/browser user input and other AWT events // main main main in stand-alone mode public class Roule extends Applet implements Runnable { // Pre-computed cycle with harmonics and as pure sine wave double [] harmwave; // waveform with harmonics double [] sinewave; int lgt = 6000; // basic sample length (3/4 seconds) byte [] sample = null; // the mu-law samples AudioData playable = null; // Thread used to prepare the samples private Thread soundThread; // Communication area between AWT/browser and soundThread threads Object audioLock; private double freq[]; // Current state of audio device (playing or not) AudioDataStream curStream; public void init() { setLayout(null); // initialize some objects audioLock = new Object(); sample = new byte[lgt]; freq = null; // define one cycle of waveforms harmwave = new double[361]; sinewave = new double[361]; harmonics(2, .91, .83, .75, .67); // initialize the AudioData from sample (once is enough it seems) playable = new AudioData(sample); } // Initialize one cycle (to speed up computations later) void harmonics(int voices, double second, double third, double fourth, double fifth) { int k; double max = 0.0; for (k = 0; k < 361; ++k) { harmwave[k] = ( 1.0 * Math.sin (k * 2. * Math.PI / 360.) + second * Math.sin (k * 4. * Math.PI / 360.) + third * Math.sin (k * 6. * Math.PI / 360.) + fourth * Math.sin (k * 8. * Math.PI / 360.) + fifth * Math.sin (k * 10. * Math.PI / 360.) ); if (harmwave[k] > max) max = harmwave[k]; else if (-harmwave[k] > max) max = -harmwave[k]; // for the milliwatt tone (single note!) sinewave[k] = 0.707 * Math.sin (k * 2. * Math.PI / 360.); } // allow up to number_of_notes simultaneous notes with harmwave[] for (k = 0; k < 361; ++k) harmwave[k] *= ((0.707/voices)/max); // eg. 0.35 for 2 voices } public void paint(Graphics g) { g.drawString("Type 2-9 for interval", 15-6, 10+6); g.drawString("Type m for milliwatt tone", 15-6, 25+6); g.drawString("Version 1.0 ", 15-6, 40+6); super.paint(g); } public void start() { System.out.println("start()"); if (soundThread == null) { soundThread = new Thread(this); soundThread.start(); System.out.println("Sound thread started"); } } public void stop() { System.out.println("stop()"); if (curStream != null) AudioPlayer.player.stop(curStream); curStream = null; if (soundThread != null) { soundThread.stop(); System.out.println("Sound thread stopped"); soundThread = null; } } public void destroy() { System.out.println("dispose()"); // "destroy" is a strong word :-) if (soundThread != null) { soundThread.stop(); System.out.println("Sound thread stopped"); soundThread = null; } } // Converts a voltage in range -1.0 to 1.0 into a mu-law sample // (I won't explain this code: I would also have to explain mu-law! static byte muLaw(double volt) { byte tic; int biaslinear = (int)( ( (volt<0.0) ? -volt : volt ) * 8158. + .5 ) + 33; if (biaslinear < 0x0040) tic = (byte)((biaslinear >> 1) ^ 0x10); else if (biaslinear < 0x0080) tic = (byte)((biaslinear >> 2) ^ 0x00); else if (biaslinear < 0x0100) tic = (byte)((biaslinear >> 3) ^ 0x30); else if (biaslinear < 0x0200) tic = (byte)((biaslinear >> 4) ^ 0x20); else if (biaslinear < 0x0400) tic = (byte)((biaslinear >> 5) ^ 0x50); else if (biaslinear < 0x0800) tic = (byte)((biaslinear >> 6) ^ 0x40); else if (biaslinear < 0x1000) tic = (byte)((biaslinear >> 7) ^ 0x70); else // (biaslinear < 0x2000) tic = (byte)((biaslinear >> 8) ^ 0x60); // the added 0x10 is to cancel the 1 from biased linear input // else would have to do, eg. for last: // tic = (byte)(((biaslinear >> 8) & 0x0f) ^ 0x70); if (volt < 0) tic |= 0x80; tic ^= 0xff; return tic; } // This method, running under the sound thread, recomputes the mu-law // sample whenever there is something to play (freq != null). // This following may seem to be a bit round-about, but it was done // thus for performance reasons: limit computations as much as possible // in the main loop doing the preparation of the sample, and esp. avoid // there any complex functions. boolean doPlay() { double degreespersample []; synchronized (audioLock) { if (freq != null) { degreespersample = new double [freq.length]; for (int k = 0; k < freq.length; ++k) { degreespersample[k] = (360. / 8000.) * freq[k]; } freq = null; } else degreespersample = null; } Thread.yield(); if (degreespersample != null) { // prepare u-law and start int cnt = degreespersample.length; for (int k = 0; k < lgt; ++k) { double volt = 0.0; if (cnt > 1) // two or more notes { for (int j = 0; j < cnt; ++j) // for each note { double deg = degreespersample[j] * k; int degi = (int) deg; double degm = deg - (double)degi; // using the wave with 5 harmonics volt += harmwave[degi%360] + // linear interpolate degm * (harmwave[(degi%360)+1] - harmwave[degi%360]); } } else // must be milliwatt tone (single note) { double deg = degreespersample[0] * k; int degi = (int) deg; double degm = deg - (double)degi; volt += sinewave[degi%360] + // linear interpolate degm * (sinewave[(degi%360)+1] - sinewave[degi%360]); } sample[k] = muLaw(volt); } // Now start playing! AudioDataStream stream = new AudioDataStream(playable); AudioPlayer.player.start(stream); curStream = stream; degreespersample = null; return true; } return false; } // soundThread's main: check regularly if there is something to play // by calling doPlay(). public void run() { try { for (;;) { if (doPlay()) { Thread.sleep(800); // duration of a normal play if (curStream != null) AudioPlayer.player.stop(curStream); curStream = null; } else { Thread.sleep(50); // be responsive... } } } catch (Exception e) { System.out.println("Sound thread exception: " + e); } } // Handle user input. public boolean handleEvent(Event e) { switch (e.id) { case Event.WINDOW_DESTROY: System.out.println("Event.WINDOW_DESTROY"); if (soundThread != null) { soundThread.stop(); System.out.println("Sound thread stopped"); soundThread = null; } System.exit(0); case Event.KEY_PRESS: if (e.key == 'm') // milliwatt { synchronized (audioLock) { freq = new double[1]; freq[0] = 1004.; } } else if (e.key >= '2' && e.key <= '9') // interval { int cents; switch (e.key) { case '2': cents = 200; break; case '3': cents = 400; break; case '4': cents = 500; break; case '5': cents = 700; break; case '6': cents = 900; break; case '7': cents =1100; break; case '8': cents =1200; break; case '9': cents =1400; break; default: cents = 600; break; } synchronized (audioLock) { freq = new double[2]; freq[0] = 220.; freq[1] = 220. * Math.pow(2.0, cents/1200.); } } return true; default: // System.out.println("Event: " + e.id); return getParent().handleEvent(e); } } // Needed when running in stand-alone mode public static void main(String args[]) { Frame f = new Frame("Roule"); Roule accord = new Roule(); accord.init(); accord.start(); if (args.length != 0) try { //AudioPlayer.player.setPriority(Thread.MAX_PRIORITY); } catch (Exception e) { System.out.println("AudioPlayer.*.setPriority: " + e); } f.add("Center", accord); // same size as in applet tag -- why do I need to make bigger? f.resize(300+10, 90+30); f.show(); } } // [from JavaSound newsgroup] // ... We can generate an inputsteam from a bit array, from // a thread, or a piece of code. The thing to remember is the input data // should be in u-law (mu-law) format. The header is not important. Since // u-law standard is 8000Hz, you certainly can not play sound with higher // frequency. ... // // Computing while playback may degrade the sound quality. But the current // machine is fast enough to do some other stuff (GUI, DATA transform) while // play sound at 8000Hz. // // Look at http://www.cdt.luth.se/java/doc/sun/shared/sun.audio.AudioPlayer.html // for more information about the "sun.audio" package. // // Search information about u-law or ".au" or "sun/Next" at sun's web page // for the audio file data format information. // // [wen@server.samasher.com]