001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030/** 031 * 032 */ 033package org.openimaj.demos.sandbox.audio; 034 035import java.util.List; 036 037import org.openimaj.audio.AudioFormat; 038import org.openimaj.audio.AudioMixer; 039import org.openimaj.audio.AudioPlayer; 040import org.openimaj.audio.Instrument; 041import org.openimaj.audio.generation.PolyphonicSynthesizer; 042import org.openimaj.audio.util.BasicMusicTimekeeper; 043import org.openimaj.audio.util.MusicUtils; 044import org.openimaj.time.Sequencer; 045import org.openimaj.time.Sequencer.SequencedAction; 046import org.openimaj.time.Sequencer.SequencerEvent; 047import org.openimaj.util.pair.IndependentPair; 048 049/** 050 * 051 * 052 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 053 * @created 12 Feb 2013 054 * @version $Author$, $Revision$, $Date$ 055 */ 056public class Tune 057{ 058 /** 059 * A single voice channel in the polyphonic synth. 060 * 061 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 062 * @created 12 Feb 2013 063 * @version $Author$, $Revision$, $Date$ 064 */ 065 private class SingleVoiceChannel 066 { 067 /** The notes being played */ 068 private List<IndependentPair<Integer, Double>> notes = null; 069 070 public SingleVoiceChannel( final Sequencer sequencer, 071 final Instrument instrument, final String notes, 072 final long millisPerNote ) 073 { 074 // Parse the notes into note numbers and lengths. 075 this.notes = MusicUtils.parseABCNotes( notes ); 076 System.out.println( this.notes ); 077 078 // Accumulate the note lengths so they become absolute position 079 // markers. We also multiply them by the number of ticks in each 080 // beat, so that we get an absolute tick position for the note 081 // in time. 082 double o = 0; 083 for( final IndependentPair<Integer,Double> note: this.notes ) 084 { 085 final double tmp = note.getSecondObject(); 086 note.setSecondObject( o * millisPerNote ); 087 o += tmp; 088 } 089 090 // Add the notes as sequencer events 091 int lastNote = 0; 092 for( final IndependentPair<Integer,Double> note: this.notes ) 093 { 094 final int ln = lastNote; 095 sequencer.addEvent( new SequencerEvent( 096 (long)note.secondObject().doubleValue(), 097 new SequencedAction() 098 { 099 @Override 100 public boolean performAction() 101 { 102 if( note.firstObject() != -1 ) 103 { 104 instrument.noteOff( ln ); 105 instrument.noteOn( note.firstObject(), 0.5f ); 106 } 107 else 108 instrument.noteOff( ln ); 109 110 return true; 111 } 112 } ) ); 113 lastNote = note.firstObject(); 114 } 115 } 116 } 117 118 /** The notes for each channel */ 119 private final String[] notes = { 120 "CD4z", //DEFG-", 121// "E4z", //FGAB-", 122// ",C4z", //,D,E,F,G-", 123// ",,C4z" //,,E2,,G-" 124 }; 125 126 /** Each of the characters in the note strings are this number of notes */ 127 private final double noteLength = 1/4d; 128 129 /** Tempo of the tune playing */ 130 private final float tempo = 120; 131 132 /** The timekeeper for all the music channels */ 133 private final BasicMusicTimekeeper timeKeeper = new BasicMusicTimekeeper(); 134 135 /** The sequencer to use */ 136 private final Sequencer sequencer; 137 138 /** 139 * Create a new tune. 140 */ 141 public Tune() 142 { 143 // Set the speed of the tune 144 this.timeKeeper.setBPM( this.tempo ); 145 146 // Number of milliseconds per music tick 147 final long millisPerTick = MusicUtils.millisPerBeat( 148 (float)this.timeKeeper.getBPM() ) / 149 this.timeKeeper.getTicksPerBeat() * 10; 150 151 System.out.println( millisPerTick ); 152 153 // Create the sequencer 154 this.sequencer = new Sequencer( this.timeKeeper, millisPerTick ); 155 156 // The mixer that will mix all the synths' outputs 157 final AudioMixer mixer = new AudioMixer( new AudioFormat( 16, 44.1, 1 ) ); 158 159 // Work out the number of milliseconds per note length 160 // Assumption is that a beat lands on a quarter note. 161 final long millisPerNote = (long)(MusicUtils.millisPerBeat( 162 (float)this.timeKeeper.getBPM() ) * 163 4d * this.noteLength); 164 165 // Create a new voice. 166 for( final String noteString: this.notes ) 167 { 168 // Create a synth (start with making no noise) 169 final PolyphonicSynthesizer synth = new PolyphonicSynthesizer( 5 ); 170 171 // Add it as a stream to the mixer 172 mixer.addStream( synth, 0.1f ); 173 174 // Create a voice channel to run the synth 175 new SingleVoiceChannel( this.sequencer, synth, noteString, 176 millisPerNote ); 177 } 178 179 final AudioPlayer ap = new AudioPlayer( mixer ); 180 new Thread( ap ).start(); 181 182 // Start the timekeeper - the "music" should start 183 new Thread( this.sequencer ).start(); 184 } 185 186 /** 187 * @param args 188 */ 189 public static void main( final String[] args ) 190 { 191 new Tune(); 192 } 193}