package { import de.popforge.audio.output.Audio; import de.popforge.audio.output.SoundFactory; import flash.events.Event; import flash.media.Sound; import flash.media.SoundChannel; import flash.utils.ByteArray; public class student_mma_16_Sequencer { public static var NUM_SLOTS:int = 31, BPM:int = 125; public var onInit:Function; private var buffers:Array; private var slots:Array; //containts the clips or null for empty slot, parallel to buffers private var launchQueue:Array; //contains clips that are waiting to be launched private var numReady:int = 0; private var numBarSamples:uint; //number of samples that fit in 1 bar private var samplesLeft:int; //number of samples left to next bar private var sync: Sound; //buffer sync (sync buffer playback) private var syncChannel: SoundChannel; private var syncBuf:student_mma_16_AudioBuffer; //the buffer used for bar syncing public function student_mma_16_Sequencer() { init(); } public function init():void { buffers = new Array(NUM_SLOTS); slots = new Array(NUM_SLOTS); launchQueue = new Array(NUM_SLOTS); for(var i:int = 0; i < NUM_SLOTS; i++) { buffers[i] = new student_mma_16_AudioBuffer(4, Audio.STEREO, Audio.BIT16, Audio.RATE44100); //buffers[i].onInit = onBufferInit; } syncBuf = buffers[0]; syncBuf.onComplete = syncBufComplete; numBarSamples = (Math.ceil(44100 * (60 / BPM)) * 4); samplesLeft = numBarSamples; //-- create silent bytes for sync sound var syncSamples: ByteArray = new ByteArray(); syncSamples.length = ( buffers[0].numSamples - 1 ) << 1; SoundFactory.fromByteArray( syncSamples, 1, Audio.BIT16, Audio.RATE44100, onGenerateSyncSound ); } private function onGenerateSyncSound( sound: Sound ): void { //silent sync sound created, start sync and buffers sync = sound; //$isInit = true; if( sync != null ) { syncChannel = sync.play( 0, 1 ); syncChannel.addEventListener( Event.SOUND_COMPLETE, onSyncComplete, false, 10 ); for(var i:int = 0; i < NUM_SLOTS; i++) { buffers[i].start(); } } if(onInit != null) onInit(); } private function onSyncComplete( event: Event ): void { if( syncChannel != null ) syncChannel.stop(); syncChannel = sync.play( 0, 1 ); syncChannel.addEventListener( Event.SOUND_COMPLETE, onSyncComplete, false, 10 ); for(var i:int = 0; i < NUM_SLOTS; i++) { buffers[i].onSyncComplete(); if(slots[i] != null && slots[i].onComplete != null) slots[i].onComplete(buffers[i]); } syncBufComplete(); } private function syncBufComplete():void { samplesLeft -= syncBuf.numSamples; //samplesLeft < 0 (already launched, do nothing) //samplesLeft >= syncBuf.numSamples (not nearing end of bar yet, do nothing) //samplesLeft < syncBuf.numSamples (next buffer will contain new bar: launch clips!) if(samplesLeft < syncBuf.numSamples) { //new bar in next buffer: launch clips in queue for each(var clip:student_mma_16_Clip in launchQueue) { clip.launch(samplesLeft); } launchQueue = new Array(NUM_SLOTS); // Alert.show("bar at next buf after " + samplesLeft + " samples"); samplesLeft += numBarSamples; } } /** * get a slot in the sequencer * @return slot number or 0 if no slots are available */ public function getSlot(clip:student_mma_16_Clip):int { for(var i:int = 1; i < NUM_SLOTS; i++) { if(slots[i] == null) { slots[i] = clip; launchQueue.push(clip); return i; } } return 0; } /** * free a slot when done using it */ public function freeSlot(slotNumber:int):void { slots[slotNumber] = null; buffers[slotNumber].onComplete = null; } public function changeSlotVolume(slotNumber:int, volume:Number):void { if(buffers[slotNumber] == null) return; buffers[slotNumber].changeVolume(volume); } } }