(function(){
  
  // The Music Constructor
  var Music = function( scale ){
    
    // Set the musical-scale and BPM
    this.scale = this.Scales[ scale ]; // I could save memory if I took this line out, but I wont, because I have some to spare.
    this.mspn = 100; // Mulliseoncds per note ( i mean milliseconds of course ;)
    this.bufferLength = 10; // Length of buffer ( this is approximate, i do not blame myseflf for this fact )
    this.bufferClock = 0; // Fill the bufferClock with bufferLength for each push to stream, when clock reaches mspn (milliseconds-ish per note), step() to new note

    this.totalRange  = this.range.high - this.range.low,
    
     // Sets the note/freq to start at
    this.currentNote = this.range.low;

    // The scale index pointer that moves up and down th scale array
    this.currentStep = 0;
    
    this.currentFreq = this.notes[ this.currentNote ];
    
    // The direction in which the Arp is stepping ( 1 is up -1 is down )
    this.direction = 1;

    // Fills the Arpeggio with notes
    this.arpStack = this.stackArp();

    // Create a private member varaible linking to this Music object
    var privateLink = this;
    
    // Set an interval to play the audio
    this.interval = setInterval( function(){ privateLink.play() }, this.buffer );
    
    this.audio = document.getElementById( 'audio' );
    
    // Make the lil' selecter doodad. I hate it when people say doodad. I am a hypocryte.
    var select = document.createElement( 'select');
    var options = [];     
    for( var i in this.Scales ){
      options[ i ] = document.createElement( 'option' );
      options[ i ].innerHTML = i;
      options[ i ].onclick = function(){      
        privateLink.scale = Music.prototype.Scales[ this.innerHTML ];
        privateLink.currentStep = 0;
        privateLink.arpStack = privateLink.stackArp();
      }
      select.appendChild( options[ i ] );
    }
    document.getElementById( 'drop' ).appendChild( select );
  }
  
  // Crumby Excuse for Visualization
  Music.prototype.draw = function(){
   if( this.canvas ){      
      this.context.fillStyle ='#444';
      this.context.fillRect( 0, 0, this.canvas.width, this.canvas.height );      
      this.context.fillStyle ='#FFF';      
      for( var i = 0; i < this.arpStack.length; i++ ){
        var x = this.canvas.width / ( this.arpStack.length - 1 ) * i;
        this.context.fillRect( x, 100, 1, 40 );
      }      
      var x = this.canvas.width / ( this.arpStack.length - 1 ) * this.currentStep;
      this.context.beginPath();
      this.context.arc( x, 120, 5, 0, Math.PI * 2, true );
      this.context.closePath();
      this.context.fill();
    }
  }
  
  // Plays Audio and step etc.
  Music.prototype.play = function(){
    this.sampleData = generateWave.call( this );
    writeData.call( this );    
    this.bufferCount() ? this.step() : 0 ;
    this.draw();
  }
  
  Music.prototype.bufferCount = function(){
    this.bufferClock += this.bufferLength;
    if( this.bufferClock >= this.mspn ){
      this.bufferClock = 0;
      return true;
    } else {
      return false;
    }
  }
  
  // Step through the arp stack switching direction when limits are hit
  Music.prototype.step = function(){
    var thisStep = this.currentStep += this.direction;
    if( this.arpStack[ thisStep ] === undefined ){
      this.direction = this.direction == 1 ? -1 : 1 ;
      var thisStep = this.currentStep += this.direction;
    }
    this.currentFreq = this.arpStack[ this.currentStep ];
  }
  
  // Build an arp array containing frequencies
  Music.prototype.stackArp  = function(){
    var arpStack    = [],
        rangeLow    = this.range.low,
        currentStep = 0,
        octShift    = 0;        
    for( var i = 0; i < this.totalRange; i++ ){      
      if( this.scale[ currentStep ] ){
        arpStack[ arpStack.length ] = this.notes[ rangeLow + octShift + currentStep ];
      };      
      currentStep ++;
      if( currentStep > this.scale.length ){
        octShift += 12;
        currentStep = 0;
      };
    }
    return arpStack;
  }

  Music.prototype.range     = { low: 48, high: 76 };

                              /*   */ //  C         C#        D         D#        E         F         F#        G         G#        A      A#        B
  Music.prototype.notes     = /* 0 */ [ 16.35,    17.32,    18.35,    19.45,    20.6,     21.83,    23.12,    24.5,     25.96,    27.5,  29.14,    30.87,
                              /* 1 */   32.7,     34.65,    36.71,    38.89,    41.2,     43.65,    46.25,    49,       51.91,    55,    58.27,    61.74,
                              /* 2 */   65.41,    69.3,     73.42,    77.78,    82.41,    87.31,    92.5,     98,       103.83,   110,   116.54,   123.47,
                              /* 3 */   130.81,   138.59,   146.83,   155.56,   164.81,   174.61,   185,      196,      207.65,   220,   233.08,   246.94,
                              /* 4 */   261.63,   277.18,   293.66,   311.13,   329.63,   349.23,   369.99,   392,      415.3,    440,   466.16,   493.88,
                              /* 5 */   523.25,   554.37,   587.33,   622.25,   659.26,   698.46,   739.99,   783.99,   830.61,   880,   932.33,   987.77,
                              /* 6 */   1046.5,   1108.73,  1174.66,  1244.51,  1318.51,  1396.91,  1479.98,  1567.98,  1661.22,  1760,  1864.66,  1975.53,
                              /* 7 */   2093,     2217.46,  2349.32,  2489.02,  2637.02,  2793.83,  2959.96,  3135.96,  3322.44,  3520,  3729.31,  3951.07,
                              /* 8 */   4186.01,  4434.92,  4698.64,  4978 ];

  Music.prototype.Scales    = { // comp. into strings and use as lit. obj.
            Chromatic           : [1,1,1,1,1,1,1,1,1,1,1,1],
            Spanish8Tone        : [1,1,0,1,1,1,1,0,1,0,1,0],
            Flamenco            : [1,1,0,1,1,1,0,1,1,0,1,0],
            Symmetrical         : [1,1,0,1,1,0,1,1,0,1,1,0],
            Diminished          : [1,0,1,1,0,1,1,0,1,1,0,1],
            WholeTone           : [1,0,1,0,1,0,1,0,1,0,1,0],
            Augmented           : [1,0,0,1,1,0,0,1,1,0,0,1],
            SemitoneX3          : [1,0,0,1,0,0,1,0,0,1,0,0],
            SemitoneX4          : [1,0,0,0,1,0,0,0,1,0,0,0],
            UltraLocrian        : [1,1,0,1,1,0,1,0,1,1,0,0],
            SuperLocrian        : [1,1,0,1,1,0,1,0,1,1,1,0],
            Indianish           : [1,1,0,1,1,0,0,1,1,0,1,0],
            Locrian             : [1,1,0,1,0,1,1,0,1,0,1,0],
            Phrygian            : [1,1,0,1,0,1,0,1,1,0,1,0],
            NeapolitanMinor     : [1,1,0,1,0,1,0,1,1,0,0,1],
            Javanese            : [1,1,0,1,0,1,0,1,0,1,1,0],
            NeapolitanMajor     : [1,1,0,1,0,1,0,1,0,1,0,1],
            TodiIndian          : [1,1,0,1,0,0,1,1,1,0,0,1],
            Persian             : [1,1,0,0,1,1,1,0,1,0,0,1],
            Oriental            : [1,1,0,0,1,1,1,0,0,1,1,0],
            MajPhrygianDom      : [1,1,0,0,1,1,0,1,1,0,1,0],
            DoubleHarmonic      : [1,1,0,0,1,1,0,1,1,0,0,1],
            MarvaIndian         : [1,1,0,0,1,0,1,1,0,1,0,1],
            Enigmatic           : [1,1,0,0,1,0,1,0,1,0,1,1],
            LocrianNatural2nd   : [1,0,1,1,0,1,1,0,1,0,1,0],
            MinorNatural        : [1,0,1,1,0,1,0,1,1,0,1,0],
            HarmonicMinor       : [1,0,1,1,0,1,0,1,1,0,0,1],
            Dorian              : [1,0,1,1,0,1,0,1,0,1,1,0],
            MelodicMinorAsc     : [1,0,1,1,0,1,0,1,0,1,0,1],
            HungarianGypsy      : [1,0,1,1,0,0,1,1,1,0,1,0],
            HungarianMinor      : [1,0,1,1,0,0,1,1,1,0,0,1],
            Anne                : [1,0,0,1,0,1,0,1,0,1,1,0],
            Romanian            : [1,0,1,1,0,0,1,1,0,1,1,0],
            MajLocrian          : [1,0,1,0,1,1,1,0,1,0,1,0],
            Hindu               : [1,0,1,0,1,1,0,1,1,0,1,0],
            Ethiopian1          : [1,0,1,0,1,1,0,1,1,0,0,1],
            Mixolydian          : [1,0,1,0,1,1,0,1,0,1,1,0],
            Major               : [1,0,1,0,1,1,0,1,0,1,0,1],
            MixolydianAug       : [1,0,1,0,1,1,0,0,1,1,1,0],
            HarmonicMajor       : [1,0,1,0,1,1,0,0,1,1,0,1],
            LydianMin           : [1,0,1,0,1,0,1,1,1,0,1,0],
            LydianDominant      : [1,0,1,0,1,0,1,1,0,1,1,0],
            Lydian              : [1,0,1,0,1,0,1,1,0,1,0,1],
            LydianAug           : [1,0,1,0,1,0,1,0,1,1,1,0],
            LeadingWholeTone    : [1,0,1,0,1,0,1,0,1,0,1,1],
            BluesyRnR           : [1,0,0,1,1,1,0,1,0,1,1,0],
            HungarianMajor      : [1,0,0,1,1,0,1,1,0,1,1,0],
            pB                  : [1,1,0,1,0,0,1,0,1,0,0,0],
            Balinese1           : [1,1,0,1,0,0,0,1,1,0,0,0],
            PelogBalinese       : [1,1,0,1,0,0,0,1,0,0,1,0],
            IwatoJapanese       : [1,1,0,0,0,1,1,0,0,0,1,0],
            Japanese1           : [1,1,0,0,0,1,0,1,1,0,0,0],
            HirajoshiJapanese   : [1,0,1,1,0,0,0,1,1,0,0,0],
            pD                  : [1,0,1,1,0,0,0,1,0,1,0,0],
            PentatonicMajor     : [1,0,1,0,1,0,0,1,0,1,0,0],
            Egyptian            : [1,0,1,0,0,1,0,1,0,0,1,0],
            PentatonicMinor     : [1,0,0,1,0,1,0,1,0,0,1,0],
            Chinese2            : [1,1,0,0,1,0,1,1,0,0,1,1]
  }  
  
  // Adjusted from function by dude at http://mavra.perilith.com/~luser/test3.html
  var generateWave = function(){
    var samples = 44100 / this.currentFreq;
    var sampledata = Array( Math.round( samples ) );
    for( var i = 0; i < sampledata.length; i++ ){
      sampledata[ i ] = Math.sin( 2 * Math.PI * ( i / sampledata.length ) ) / Math.tan(i);
    }
    return sampledata;
  }

  // Adjusted from function by dude at http://mavra.perilith.com/~luser/test3.html
  var writeData = function(){
    var n = Math.ceil( this.currentFreq / 100 );
    for( var i = 0; i < n; i++ ){
      this.audio.mozWriteAudio( this.sampleData.length, this.sampleData );
    }
  }
  
  // Gets the ball rolling
  var init = function(){
    var music = new Music( 'Major' );
   
    // Make a canvas for tracking the notes
    music.canvas = document.getElementById( 'disp' );
    music.context = music.canvas.getContext( '2d' );
  }
  
  // Starts everything after DOM has time to figure out what's going on
  document.addEventListener( 'DOMContentLoaded', function(){ init() }, false );

})();
