/**
 * Module containing all MIDI constants and functionality available in the {@link WebAudioAPI} library.
 * 
 * @module Midi
 */

/**
 * Object representing a mapping between a General MIDI command and its protocol value.
 * @constant {Object<string, number>}
 */
export const MidiCommand = {
   Unknown: 0x00, NoteOff: 0x80, NoteOn: 0x90, Aftertouch: 0xA0, ContinuousController: 0xB0,
   ProgramChange: 0xC0, ChannelPressure: 0xD0, PitchBend: 0xE0, SystemMessage: 0xF0
};

/**
 * Object representing a mapping between a General MIDI instrument and its patch number.
 * @constant {Object<string, number>}
 */
export const MidiInstrument = {
   'Grand Piano': 0x00, 'Bright Piano': 0x01, 'Electric Grand Piano': 0x02, 'Honky Tonk Piano': 0x03,
   'Electric Piano 1': 0x04, 'Electric Piano 2': 0x05, 'Harpsicord': 0x06, 'Clavinet': 0x07,
   'Celesta': 0x08, 'Glockenspiel': 0x09, 'Music Box': 0x0A, 'Vibraphone': 0x0B, 'Marimba': 0x0C,
   'Xylophone': 0x0D, 'Tubular Bell': 0x0E, 'Dulcimer': 0x0F, 'Hammond Organ': 0x10,
   'Percussive Organ': 0x11, 'Rock Organ': 0x12, 'Church Organ': 0x13, 'Reed Organ': 0x14,
   'Accordion': 0x15, 'Harmonica': 0x16, 'Tango Accordion': 0x17, 'Nylon Acoustic Guitar': 0x18,
   'Steel Acoustic Guitar': 0x19, 'Jazz Electric Guitar': 0x1A, 'Clean Electric Guitar': 0x1B,
   'Muted Electric Guitar': 0x1C, 'Overdriven Guitar': 0x1D, 'Distortion Guitar': 0x1E,
   'Guitar Harmonics': 0x1F, 'Acoustic Bass': 0x20, 'Fingered Electric Bass': 0x21,
   'Picked Electric Bass': 0x22, 'Fretless Bass': 0x23, 'Slap Bass 1': 0x24, 'Slap Bass 2': 0x25,
   'Synth Bass 1': 0x26, 'Synth Bass 2': 0x27, 'Violin': 0x28, 'Viola': 0x29, 'Cello': 0x2A,
   'Contrabass': 0x2B, 'Tremolo Strings': 0x2C, 'Pizzicato Strings': 0x2D, 'Harp': 0x2E,
   'Timpani': 0x2F, 'String Ensemble': 0x30, 'Slow Strings': 0x31, 'Synth Strings 1': 0x32,
   'Synth Strings 2': 0x33, 'Choir Aahs': 0x34, 'Choir Oohs': 0x35, 'Synth Choir': 0x36,
   'Orchestra Hit': 0x37, 'Trumpet': 0x38, 'Trombone': 0x39, 'Tuba': 0x3A, 'Muted Trumpet': 0x3B,
   'French Horn': 0x3C, 'Brass Ensemble': 0x3D, 'Synth Brass 1': 0x3E, 'Synth Brass 2': 0x3F,
   'Soprano Sax': 0x40, 'Alto Sax': 0x41, 'Tenor Sax': 0x42, 'Baritone Sax': 0x43, 'Oboe': 0x44,
   'English Horn': 0x45, 'Bassoon': 0x46, 'Clarinet': 0x47, 'Piccolo': 0x48, 'Flute': 0x49,
   'Recorder': 0x4A, 'Pan Flute': 0x4B, 'Bottle Blow': 0x4C, 'Shakuhachi': 0x4D, 'Whistle': 0x4E,
   'Ocarina': 0x4F, 'Synth Square Wave': 0x50, 'Synth Saw Wave': 0x51, 'Synth Calliope': 0x52,
   'Synth Chiff': 0x53, 'Synth Charang': 0x54, 'Synth Voice': 0x55, 'Synth Fifths Saw': 0x56,
   'Synth Brass and Lead': 0x57, 'Fantasia': 0x58, 'Warm Pad': 0x59, 'Polysynth': 0x5A,
   'Space Vox': 0x5B, 'Bowed Glass': 0x5C, 'Metal Pad': 0x5D, 'Halo Pad': 0x5E, 'Sweep Pad': 0x5F,
   'Ice Rain': 0x60, 'Soundtrack': 0x61, 'Crystal': 0x62, 'Atmosphere': 0x63, 'Brightness': 0x64,
   'Goblins': 0x65, 'Echo Drops': 0x66, 'Sci-fi': 0x67, 'Sitar': 0x68, 'Banjo': 0x69, 'Shamisen': 0x6A,
   'Koto': 0x6B, 'Kalimba': 0x6C, 'Bag Pipe': 0x6D, 'Fiddle': 0x6E, 'Shanai': 0x6F, 'Tinkle Bell': 0x70,
   'Agogo': 0x71, 'Steel Drums': 0x72, 'Woodblock': 0x73, 'Taiko Drum': 0x74, 'Melodic Tom': 0x75,
   'Synth Drum': 0x76, 'Reverse Cymbal': 0x77, 'Guitar Fret Noise': 0x78, 'Breath Noise': 0x79,
   'Seashore': 0x7A, 'Bird Tweet': 0x7B, 'Telephone Ring': 0x7C, 'Helicopter': 0x7D, 'Applause': 0x7E,
   'Gunshot': 0x7F
};

/**
 * Returns a value representing the MIDI command in the specified `midiData`.
 * 
 * @param {number[]} midiData - Data array containing raw MIDI event data
 * @returns {number} MIDI command specified in the corresponding `midiData`
 * @see {@link module:Midi.MidiCommand MidiCommand}
 */
export function getMidiCommand(midiData) {
   return midiData[0] & 0xF0;
}

/**
 * Returns a string representing the MIDI command in the specified `midiData`.
 * 
 * The returned string can be used to index into the {@link module:Midi.MidiCommand MidiCommand}
 * map object.
 * 
 * @param {number[]} midiData - Data array containing raw MIDI event data
 * @returns {string} String representing the MIDI command in the specified `midiData`
 * @see {@link module:Midi.MidiCommand MidiCommand}
 */
export function getMidiCommandString(midiData) {
   const value = midiData[0] & 0xF0;
   const key = Object.keys(MidiCommand).find(key => MidiCommand[key] == value);
   return key ? key : 'Unknown';
}

/**
 * Returns the MIDI channel target for the command specified in `midiData`.
 * 
 * @param {number[]} midiData - Data array containing raw MIDI event data
 * @returns {number} MIDI channel target specified in `midiData`
 */
export function getMidiChannel(midiData) {
   return midiData[0] & 0x0F;
}

/**
 * Returns the MIDI note corresponding to the `NoteOn` or `NoteOff` command in the specified
 * `midiData` parameter.
 * 
 * @param {number[]} midiData - Data array containing raw MIDI event data
 * @returns {number} MIDI note corresponding to the relevant `midiData` command
 */
export function getMidiNote(midiData) {
   return midiData[1] & 0x7F;
}

/**
 * Returns the note velocity corresponding to the `NoteOn` or `NoteOff` command in the
 * specified `midiData` parameter. This velocity will be in the range from [0.0, 1.0].
 * 
 * @param {number[]} midiData - Data array containing raw MIDI event data
 * @returns {number} Note velocity for a MIDI note in the range [0.0, 1.0]
 */
export function getMidiVelocity(midiData) {
   return (midiData[2] & 0x7F) / 127.0;
}

/**
 * Returns the target program number for the MIDI channel specified in `midiData`.
 * 
 * @param {number[]} midiData - Data array containing raw MIDI event data
 * @returns {number} Target program number for the specified MIDI channel
 */
export function getMidiProgramChange(midiData) {
   return midiData[1] & 0x7F;
}

/**
 * Returns the target MIDI instrument patch value for the MIDI channel indicated in
 * the specified `midiData` parameter.
 * 
 * @param {number[]} midiData - Data array containing raw MIDI event data
 * @returns {number} Target MIDI instrument patch value for the specified MIDI channel
 * @see {@link module:Midi.MidiInstrument MidiInstrument}
 */
export function getMidiInstrumentChange(midiData) {
   return midiData[1] & 0x7F;
}

/**
 * Returns the target MIDI instrument for the MIDI channel indicated in the specified
 * `midiData` parameter.
 * 
 * The returned string can be used to index into the {@link module:Midi.MidiInstrument MidiInstrument}
 * map object.
 * 
 * @param {number[]} midiData - Data array containing raw MIDI event data
 * @returns {string} Target MIDI instrument name for the specified MIDI channel
 * @see {@link module:Midi.MidiInstrument MidiInstrument}
 */
export function getMidiInstrumentChangeString(midiData) {
   const patchNumber = midiData[1] & 0x7F;
   return Object.keys(MidiInstrument).find(key => MidiInstrument[key] == patchNumber);
}

/**
 * Returns a scaler value by which the fundamental frequency of the unbent MIDI note should be
 * multiplied in order to output to the correct frequency.
 * 
 * @param {number[]} midiData - Data array containing raw MIDI event data
 * @param {number} maxPitchBendNumSemitones - Absolute maximum number of semitones to which a note can be bent
 * @returns {number} Frequency scaler by which the unbent fundamental frequency should be multiplied
 */
export function getMidiPitchBend(midiData, maxPitchBendNumSemitones) {
   const value = ((((midiData[2] & 0x007F) << 7) | (midiData[1] & 0x007F)) - 8192) / 8192.0;
   return Math.pow(2.0, value * maxPitchBendNumSemitones / 12.0);
}