/** * If you play notes fast enough they become a tone. * * Play a sine wave modulated by an envelope. * Speed up the envelope until it is playing at audio rate. * Slow down the oscillator until it becomes an LFO amp modulator. * Use a LatchZeroCrossing to stop at the end of a sine wave cycle when we are finished. * * @author Phil Burk, (C) 2010 Mobileer Inc, All Rights Reserved */ package com.jsyn.examples; import java.io.File; import java.io.IOException; import com.jsyn.JSyn; import com.jsyn.Synthesizer; import com.jsyn.data.SegmentedEnvelope; import com.jsyn.unitgen.ExponentialRamp; import com.jsyn.unitgen.LatchZeroCrossing; import com.jsyn.unitgen.LineOut; import com.jsyn.unitgen.SineOscillator; import com.jsyn.unitgen.UnitOscillator; import com.jsyn.unitgen.VariableRateDataReader; import com.jsyn.unitgen.VariableRateMonoReader; import com.jsyn.util.WaveRecorder; /** * When notes speed up they can become a new tone. * <br> * Multiply an oscillator and an envelope. * Speed up the envelope until it becomes a tone. * Slow down the oscillator until it acts like an envelope. * Write the resulting audio to a WAV file. * * @author Phil Burk (C) 2011 Mobileer Inc * */ public class NotesToTone { private final static double SONG_AMPLITUDE = 0.7; private final static double INTRO_DURATION = 2.0; private final static double OUTRO_DURATION = 2.0; private final static double RAMP_DURATION = 20.0; private final static double LOW_FREQUENCY = 1.0; private final static double HIGH_FREQUENCY = 800.0; private final static boolean useRecorder = true; private WaveRecorder recorder; private Synthesizer synth; private ExponentialRamp envSweeper; private ExponentialRamp oscSweeper; private VariableRateDataReader envelopePlayer; private UnitOscillator osc; private LatchZeroCrossing latch; private LineOut lineOut; private SegmentedEnvelope envelope; private void play() throws IOException { synth = JSyn.createSynthesizer(); synth.setRealTime( true ); if( useRecorder ) { File waveFile = new File( "notes_to_tone.wav" ); // Default is stereo, 16 bits. recorder = new WaveRecorder( synth, waveFile, 1 ); System.out.println( "Writing to WAV file " + waveFile.getAbsolutePath() ); } createUnits(); connectUnits(); setupEnvelope(); osc.amplitude.set( SONG_AMPLITUDE ); // Ramp the rate of the envelope up until it becomes an audible tone. envSweeper.current.set( LOW_FREQUENCY ); envSweeper.input.set( LOW_FREQUENCY ); envSweeper.time.set( RAMP_DURATION ); // Ramp the rate of the oscillator down until it becomes an LFO. oscSweeper.current.set( HIGH_FREQUENCY ); oscSweeper.input.set( HIGH_FREQUENCY ); oscSweeper.time.set( RAMP_DURATION ); // Start synthesizer using default stereo output at 44100 Hz. synth.start(); // When we start the recorder it will pull data from the oscillator and // sweeper. if( recorder != null ) { recorder.start(); } // We also need to start the LineOut if we want to hear it now. lineOut.start(); // Get synthesizer time in seconds. double timeNow = synth.getCurrentTime(); // Schedule start of ramps. double songDuration = INTRO_DURATION + RAMP_DURATION + OUTRO_DURATION; envSweeper.input.set( HIGH_FREQUENCY, timeNow + INTRO_DURATION ); oscSweeper.input.set( LOW_FREQUENCY, timeNow + INTRO_DURATION ); // Arm zero crossing latch latch.gate.set( 0.0, timeNow + songDuration ); // Sleep while the sound is being generated in the background thread. try { synth.sleepUntil( timeNow + songDuration + 2.0 ); } catch( InterruptedException e ) { e.printStackTrace(); } if( recorder != null ) { recorder.stop(); recorder.close(); } // Stop everything. synth.stop(); } private void createUnits() { // Add a tone generators. synth.add( osc = new SineOscillator() ); // Add a controller that will sweep the envelope rate up. synth.add( envSweeper = new ExponentialRamp() ); // Add a controller that will sweep the oscillator down. synth.add( oscSweeper = new ExponentialRamp() ); synth.add( latch = new LatchZeroCrossing() ); // Add an output unit. synth.add( lineOut = new LineOut() ); // Add an envelope player. synth.add( envelopePlayer = new VariableRateMonoReader() ); } private void connectUnits() { oscSweeper.output.connect( osc.frequency ); osc.output.connect( latch.input ); // Latch when sine LFO crosses zero. latch.output.connect( envelopePlayer.amplitude ); envSweeper.output.connect( envelopePlayer.rate ); // Connect the envelope player to the audio output. envelopePlayer.output.connect( 0, lineOut.input, 0 ); // crossFade.output.connect( 0, lineOut.input, 1 ); if( recorder != null ) { envelopePlayer.output.connect( 0, recorder.getInput(), 0 ); // crossFade.output.connect( 0, recorder.getInput(), 1 ); } } private void setupEnvelope() { // Setup envelope. The envelope has a total duration of 1.0 seconds. // Values are (duration,target) pairs. double[] pairs = new double[5 * 2 * 2]; int i = 0; // duration, target for delay pairs[i++] = 0.15; pairs[i++] = 0.0; // duration, target for attack pairs[i++] = 0.05; pairs[i++] = 1.0; // duration, target for release pairs[i++] = 0.1; pairs[i++] = 0.6; // duration, target for sustain pairs[i++] = 0.1; pairs[i++] = 0.6; // duration, target for release pairs[i++] = 0.1; pairs[i++] = 0.0; // Create mirror image of this envelope. int halfLength = i; while( i < pairs.length ) { pairs[i] = pairs[i - halfLength]; i++; pairs[i] = pairs[i - halfLength] * -1.0; i++; } envelope = new SegmentedEnvelope( pairs ); envelopePlayer.dataQueue.queueLoop( envelope, 0, envelope.getNumFrames() ); } public static void main( String[] args ) { try { new NotesToTone().play(); } catch( IOException e ) { e.printStackTrace(); } } }