Skip to content

Instantly share code, notes, and snippets.

@jeremyfromearth
Created November 5, 2010 01:10
Show Gist options
  • Save jeremyfromearth/663498 to your computer and use it in GitHub Desktop.
Save jeremyfromearth/663498 to your computer and use it in GitHub Desktop.
Example for rendering visualizations based on notes and beats using dynamic sound features, no actual audio is produced
package makemachine.examples.audio.note_visualizer
{
import com.bit101.components.*;
import flash.display.*;
import flash.events.*;
import flash.media.*;
import flash.utils.ByteArray;
/**
* Example for rendering visualizations based on notes and beats,
* no audio is produced
* For details:
* http://labs.makemachine.net/2010/11/visualizing-notes-and-timing/
*
* @author Jeremy Brown
*/
[SWF( backgroundColor="0x222222", width="620", height="110", frameRate="60" )]
public class NoteVisualizerExample extends Sprite
{
public static const BUFFER_SIZE:int = 8192;
public static const SAMPLE_RATE:int = 44100;
public static const MILS_PER_SEC:int = 1000;
public static const WIDTH:int = 570;
protected var _outsound:Sound;
protected var _channel:SoundChannel;
protected var _playing:Boolean;
protected var _tempo:int;
protected var _quarterNotes:Vector.<Shape>;
protected var _measures:Vector.<Shape>;
protected var _eighthNotes:Vector.<Shape>;
protected var _gridContainer:Sprite;
protected var _timeClockLabel:Label;
protected var _musicClockLabel:Label;
protected var _button:PushButton;
protected var _slider:HUISlider
public function NoteVisualizerExample()
{
addEventListener( Event.ENTER_FRAME, validate );
}
/**
* Make sure the stage has been initalized
*/
protected function validate( event:Event ):void
{
if( !stage ) return;
if( stage.stageWidth == 0 ) return;
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
_outsound = new Sound();
createDisplay();
removeEventListener( Event.ENTER_FRAME, validate );
}
// ----------------------------------------------
//
// -- sound
//
// ----------------------------------------------
/**
* Toggles between play and stop
*/
protected function toggle( event:Event = null ):void
{
if( _playing ) {
stop();
}else {
play();
}
}
/**
* Stops playback, updates UI
*/
protected function stop():void
{
if( _channel && _playing )
{
_playing = false;
_button.label = 'Play';
_outsound.removeEventListener( SampleDataEvent.SAMPLE_DATA, onSampleData );
_channel.stop();
_channel = null;
removeEventListener( Event.ENTER_FRAME, onPlaybackEnterFrame );
}
}
/**
* Starts playback, updates UI
*/
protected function play():void
{
if( !_playing )
{
_playing = true;
_button.label = 'Stop';
addEventListener( Event.ENTER_FRAME, onPlaybackEnterFrame );
_outsound.addEventListener( SampleDataEvent.SAMPLE_DATA, onSampleData );
_channel = _outsound.play();
}
}
/**
* Fill the buffer with data, in this example we are just filling it with silence
*/
protected function onSampleData( event:SampleDataEvent ):void
{
var bytes:ByteArray = new ByteArray();
for( var i:int = 0; i < BUFFER_SIZE; i++ )
{
bytes.writeFloat( 0 );
}
event.data.writeBytes( bytes );
}
/**
* Updates the grid of squares to reflect the current 1/8, 1/4 and measure
* Updates the timecode clocks
* Not optimized
*/
protected function onPlaybackEnterFrame( event:Event ):void
{
if( _channel )
{
var samplesElapsed:int = ( _channel.position / 1000 ) * SAMPLE_RATE;
var eighthNoteLength:int = SAMPLE_RATE * .5 * 60 / _tempo;
var quarterNoteLength:int = eighthNoteLength * 2;
var measureLength:int = quarterNoteLength * 4;
var eighth:int = Math.floor( samplesElapsed / eighthNoteLength % 32 );
var quarter:int = Math.floor( ( samplesElapsed / quarterNoteLength % 16 ) );
var measure:int = Math.floor( ( samplesElapsed / measureLength % 4 ) );
var current:int;
var i:int = 0;
// -- current 1/8 note
for( i = 0; i < _eighthNotes.length; i++ )
{
if( i == eighth )
{
_eighthNotes[i].alpha = 1
} else {
_eighthNotes[i].alpha = .5;
}
}
// -- current 1/4 note
for( i = 0; i < _quarterNotes.length; i++ )
{
if( i == quarter )
{
_quarterNotes[i].alpha = 1;
} else {
_quarterNotes[i].alpha = .5;
}
}
// -- current measure
for( i = 0; i < _measures.length; i++ )
{
if( i == measure )
{
_measures[i].alpha = 1;
} else {
_measures[i].alpha = .5;
}
}
// -- clocks
_timeClockLabel.text = 'Real Time: ' +
toTimecode( String( Math.floor( _channel.position / 1000 / 60 ) ) ) + ':' +
toTimecode( String(Math.floor( _channel.position / 1000 % 60 ) ) ) + ':' +
toTimecode( String( Math.floor( _channel.position / 10 % 100 ) ) );
_musicClockLabel.text = 'Music Time: ' +
toTimecode( String( measure + 1 ) ) + ':' +
toTimecode( String( quarter + 1 ) ) + ':' +
toTimecode( String( eighth + 1 ) );
}
}
/**
* Update the value of the tempo
*/
protected function onTempoChange( event:Event ):void
{
_tempo = _slider.value;
}
// ----------------------------------------------
//
// -- display
//
// ----------------------------------------------
/**
* Creates rows of squares indicating 1/8, 1/4 and measures
* Also creates button and text fields
*/
protected function createDisplay():void
{
var i:int;
var w:Number;
var shape:Shape;
var padding:int = 2;
_gridContainer = new Sprite();
w = ( WIDTH / 32 ) - padding;
_eighthNotes = new Vector.<Shape>();
// -- eighth note squares
for( i = 0; i < 32; i++ )
{
shape = getRectangleShape( w, 0x00C6FF );
shape.x = ( i * w ) + ( i * padding );
shape.alpha = .5;
_eighthNotes.push( shape );
_gridContainer.addChild( shape );
}
// -- quarter note squares
w = ( WIDTH / 16 ) - padding;
_quarterNotes = new Vector.<Shape>();
for( i = 0; i < 16; i++ )
{
shape = getRectangleShape( w, 0x00C6FF );
shape.y = 22;
shape.x = ( i * w ) + ( i * padding );
shape.alpha = .5;
_quarterNotes.push( shape );
_gridContainer.addChild( shape );
}
// -- measure squares
w = ( WIDTH / 4 ) - padding;
_measures = new Vector.<Shape>();
for( i = 0; i < 4; i++ )
{
shape = getRectangleShape( w, 0x00C6FF );
shape.y = 44;
shape.x = ( i * w ) + ( i * padding );
shape.alpha = .5;
_measures.push( shape );
_gridContainer.addChild( shape );
}
new Label( this, 10, 10, '1/8' );
new Label( this, 10, 30, '1/4' );
new Label( this, 10, 50, '1/16' );
_gridContainer.x = 40;
_gridContainer.y = 10;
_timeClockLabel = new Label( this, 40, 80, 'Real Time: 00:00:00' );
_musicClockLabel = new Label( this, 150, 80, 'Music Time: 01:04:16' );
_slider = new HUISlider( this, 220, 80, 'Tempo', onTempoChange );
_slider.minimum = 40;
_slider.maximum = 320;
_slider.value = _tempo = 120;
_slider.x = 270;
_slider.y = 80;
_slider.width = 250;
_slider.labelPrecision = 0;
_button = new PushButton( this, 507, 80, 'Play', toggle );
_button.toggle = true;
addChild( _gridContainer );
}
/**
* Convenience method for creating a rectangle
*/
protected function getRectangleShape( w:int, color:uint ):Shape
{
var shape:Shape = new Shape();
var g:Graphics = shape.graphics;
g.beginFill( color );
g.drawRoundRect( 0, 0, w, 20, 3, 3 );
g.endFill();
addChild( shape );
return shape;
}
/**
* Make sure our timecode string has at least two digits
*/
protected function toTimecode( value:String ):String
{
if( value.length < 2 ) {
return '0' + value;
}
return value;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment