# -*- coding: ISO-8859-1 -*-

from MidiOutStream import MidiOutStream
from RawOutstreamFile import RawOutstreamFile

from constants import *
from DataTypeConverters import fromBytes, writeVar

class MidiOutFile(MidiOutStream):


    """
    MidiOutFile is an eventhandler that subclasses MidiOutStream.
    """


    def __init__(self, raw_out=''):

        self.raw_out = RawOutstreamFile(raw_out)
        MidiOutStream.__init__(self)
        
    
    def write(self):
        self.raw_out.write()


    def event_slice(self, slc):
        """
        Writes the slice of an event to the current track. Correctly 
        inserting a varlen timestamp too.
        """
        trk = self._current_track_buffer
        trk.writeVarLen(self.rel_time())
        trk.writeSlice(slc)
        
    
    #####################
    ## Midi events


    def note_on(self, channel=0, note=0x40, velocity=0x40):

        """
        channel: 0-15
        note, velocity: 0-127
        """
        slc = fromBytes([NOTE_ON + channel, note, velocity])
        self.event_slice(slc)


    def note_off(self, channel=0, note=0x40, velocity=0x40):

        """
        channel: 0-15
        note, velocity: 0-127
        """
        slc = fromBytes([NOTE_OFF + channel, note, velocity])
        self.event_slice(slc)


    def aftertouch(self, channel=0, note=0x40, velocity=0x40):

        """
        channel: 0-15
        note, velocity: 0-127
        """
        slc = fromBytes([AFTERTOUCH + channel, note, velocity])
        self.event_slice(slc)


    def continuous_controller(self, channel, controller, value):

        """
        channel: 0-15
        controller, value: 0-127
        """
        slc = fromBytes([CONTINUOUS_CONTROLLER + channel, controller, value])
        self.event_slice(slc)
        # These should probably be implemented
        # http://users.argonet.co.uk/users/lenny/midi/tech/spec.html#ctrlnums


    def patch_change(self, channel, patch):

        """
        channel: 0-15
        patch: 0-127
        """
        slc = fromBytes([PATCH_CHANGE + channel, patch])
        self.event_slice(slc)


    def channel_pressure(self, channel, pressure):

        """
        channel: 0-15
        pressure: 0-127
        """
        slc = fromBytes([CHANNEL_PRESSURE + channel, pressure])
        self.event_slice(slc)


    def pitch_bend(self, channel, value):

        """
        channel: 0-15
        value: 0-16383
        """
        msb = (value>>7) & 0xFF
        lsb = value & 0xFF
        slc = fromBytes([PITCH_BEND + channel, msb, lsb])
        self.event_slice(slc)




    #####################
    ## System Exclusive

#    def sysex_slice(sysex_type, data):
#        ""
#        sysex_len = writeVar(len(data)+1)
#        self.event_slice(SYSTEM_EXCLUSIVE + sysex_len + data + END_OFF_EXCLUSIVE)
#
    def system_exclusive(self, data):

        """
        data: list of values in range(128)
        """
        sysex_len = writeVar(len(data)+1)
        self.event_slice(chr(SYSTEM_EXCLUSIVE) + sysex_len + data + chr(END_OFF_EXCLUSIVE))


    #####################
    ## Common events

    def midi_time_code(self, msg_type, values):
        """
        msg_type: 0-7
        values: 0-15
        """
        value = (msg_type<<4) + values
        self.event_slice(fromBytes([MIDI_TIME_CODE, value]))


    def song_position_pointer(self, value):

        """
        value: 0-16383
        """
        lsb = (value & 0x7F)
        msb = (value >> 7) & 0x7F
        self.event_slice(fromBytes([SONG_POSITION_POINTER, lsb, msb]))


    def song_select(self, songNumber):

        """
        songNumber: 0-127
        """
        self.event_slice(fromBytes([SONG_SELECT, songNumber]))


    def tuning_request(self):

        """
        No values passed
        """
        self.event_slice(chr(TUNING_REQUEST))

            
    #########################
    # header does not really belong here. But anyhoo!!!
    
    def header(self, format=0, nTracks=1, division=96):

        """
        format: type of midi file in [0,1,2]
        nTracks: number of tracks. 1 track for type 0 file
        division: timing division ie. 96 ppq.
        
        """        
        raw = self.raw_out
        raw.writeSlice('MThd')
        bew = raw.writeBew
        bew(6, 4) # header size
        bew(format, 2)
        bew(nTracks, 2)
        bew(division, 2)


    def eof(self):

        """
        End of file. No more events to be processed.
        """
        # just write the file then.
        self.write()


    #####################
    ## meta events


    def meta_slice(self, meta_type, data_slice):
        "Writes a meta event"
        slc = fromBytes([META_EVENT, meta_type]) + \
                         writeVar(len(data_slice)) +  data_slice
        self.event_slice(slc)


    def meta_event(self, meta_type, data):
        """
        Handles any undefined meta events
        """
        self.meta_slice(meta_type, fromBytes(data))


    def start_of_track(self, n_track=0):
        """
        n_track: number of track
        """
        self._current_track_buffer = RawOutstreamFile()
        self.reset_time()
        self._current_track += 1


    def end_of_track(self):
        """
        Writes the track to the buffer.
        """
        raw = self.raw_out
        raw.writeSlice(TRACK_HEADER)
        track_data = self._current_track_buffer.getvalue()
        # wee need to know size of track data.
        eot_slice = writeVar(self.rel_time()) + fromBytes([META_EVENT, END_OF_TRACK, 0])
        raw.writeBew(len(track_data)+len(eot_slice), 4)
        # then write
        raw.writeSlice(track_data)
        raw.writeSlice(eot_slice)
        


    def sequence_number(self, value):

        """
        value: 0-65535
        """
        self.meta_slice(meta_type, writeBew(value, 2))


    def text(self, text):
        """
        Text event
        text: string
        """
        self.meta_slice(TEXT, text)


    def copyright(self, text):

        """
        Copyright notice
        text: string
        """
        self.meta_slice(COPYRIGHT, text)


    def sequence_name(self, text):
        """
        Sequence/track name
        text: string
        """
        self.meta_slice(SEQUENCE_NAME, text)


    def instrument_name(self, text):

        """
        text: string
        """
        self.meta_slice(INSTRUMENT_NAME, text)


    def lyric(self, text):

        """
        text: string
        """
        self.meta_slice(LYRIC, text)


    def marker(self, text):

        """
        text: string
        """
        self.meta_slice(MARKER, text)


    def cuepoint(self, text):

        """
        text: string
        """
        self.meta_slice(CUEPOINT, text)


    def midi_ch_prefix(self, channel):

        """
        channel: midi channel for subsequent data
        (deprecated in the spec)
        """
        self.meta_slice(MIDI_CH_PREFIX, chr(channel))


    def midi_port(self, value):

        """
        value: Midi port (deprecated in the spec)
        """
        self.meta_slice(MIDI_CH_PREFIX, chr(value))


    def tempo(self, value):

        """
        value: 0-2097151
        tempo in us/quarternote
        (to calculate value from bpm: int(60,000,000.00 / BPM))
        """
        hb, mb, lb = (value>>16 & 0xff), (value>>8 & 0xff), (value & 0xff)
        self.meta_slice(TEMPO, fromBytes([hb, mb, lb]))


    def smtp_offset(self, hour, minute, second, frame, framePart):

        """
        hour,
        minute,
        second: 3 bytes specifying the hour (0-23), minutes (0-59) and 
                seconds (0-59), respectively. The hour should be 
                encoded with the SMPTE format, just as it is in MIDI 
                Time Code.
        frame: A byte specifying the number of frames per second (one 
               of : 24, 25, 29, 30).
        framePart: A byte specifying the number of fractional frames, 
                   in 100ths of a frame (even in SMPTE-based tracks 
                   using a different frame subdivision, defined in the 
                   MThd chunk).
        """
        self.meta_slice(SMTP_OFFSET, fromBytes([hour, minute, second, frame, framePart]))



    def time_signature(self, nn, dd, cc, bb):

        """
        nn: Numerator of the signature as notated on sheet music
        dd: Denominator of the signature as notated on sheet music
            The denominator is a negative power of 2: 2 = quarter 
            note, 3 = eighth, etc.
        cc: The number of MIDI clocks in a metronome click
        bb: The number of notated 32nd notes in a MIDI quarter note 
            (24 MIDI clocks)        
        """
        self.meta_slice(TIME_SIGNATURE, fromBytes([nn, dd, cc, bb]))




    def key_signature(self, sf, mi):

        """
        sf: is a byte specifying the number of flats (-ve) or sharps 
            (+ve) that identifies the key signature (-7 = 7 flats, -1 
            = 1 flat, 0 = key of C, 1 = 1 sharp, etc).
        mi: is a byte specifying a major (0) or minor (1) key.
        """
        self.meta_slice(KEY_SIGNATURE, fromBytes([sf, mi]))



    def sequencer_specific(self, data):

        """
        data: The data as byte values
        """
        self.meta_slice(SEQUENCER_SPECIFIC, data)





#    #####################
#    ## realtime events

#    These are of no use in a midi file, so they are ignored!!!

#    def timing_clock(self):
#    def song_start(self):
#    def song_stop(self):
#    def song_continue(self):
#    def active_sensing(self):
#    def system_reset(self):



if __name__ == '__main__':

    out_file = 'test/midifiles/midiout.mid'
    midi = MidiOutFile(out_file)

#format: 0, nTracks: 1, division: 480
#----------------------------------
#
#Start - track #0
#sequence_name: Type 0
#tempo: 500000
#time_signature: 4 2 24 8
#note_on  - ch:00,  note:48,  vel:64 time:0
#note_off - ch:00,  note:48,  vel:40 time:480
#End of track
#
#End of file


    midi.header(0, 1, 480)
    
    midi.start_of_track()
    midi.sequence_name('Type 0')
    midi.tempo(750000)
    midi.time_signature(4, 2, 24, 8)
    ch = 0
    for i in range(127):
        midi.note_on(ch, i, 0x64)
        midi.update_time(96)
        midi.note_off(ch, i, 0x40)
        midi.update_time(0)
    
    midi.update_time(0)
    midi.end_of_track()
    
    midi.eof() # currently optional, should it do the write instead of write??


    midi.write()