import time
import sys
import functools
import operator
import array

from midi import MidiFile
import gameduino
import GDprep
import random

def perftime():
    return int((time.time() - t0) * 192)

def until(t):
    while perftime() < t:
        pass

def midifreq(m):
    return 440 * (2 ** ((m - 69) / 12.))

gd = gameduino.Gameduino(sys.argv[1], 115200)
gd.silence()

# commands
# wait
# silence <v>
# target <v> <freq> <amp>

class LivePlayer(object):

    def __init__(self, bps):
        self.t0 = time.time()
        self.bps = bps

    def perftime(self):
        return int((time.time() - self.t0) * self.bps)

    def wait(self, nt):
        while self.perftime() < nt:
            pass

    def target(self, v, freq, a):
        gd.voice(v, 0, f, a, a)

    def amp(self, v, a):
        gd.wr16(gameduino.VOICES + (4 * v) + 2, (a << 8) | a)

    def silence(self, v):
        self.amp(v, 0)

class Schedule(object):

    def __init__(self):
        self.bytes = []
        self.t = 0

    def wait(self, nt):
        if nt != self.t:
            d = nt - self.t
            assert d < 64
            self.bytes.append(0x00 | d)
            self.t = nt

    def silence(self, v):
        self.bytes.append(0x40 | v)

    def target(self, v, freq, a):
        self.bytes.append(0x80 | v)
        self.bytes.append(freq & 0xff)
        self.bytes.append((freq >> 8) & 0xff)
        self.bytes.append(a)

    def amp(self, v, a):
        self.bytes.append(0xc0 | v)
        self.bytes.append(a)

    def dump(self, hh, name):
        GDprep.dump(hh, name, array.array('B', self.bytes))

flute = GDprep.spectrum("americanflute.txt", cutoff = 6, volume = 90)
string = GDprep.spectrum("string.txt", cutoff = 3, volume = 10)
piano = GDprep.spectrum("piano.txt", cutoff = 3, volume = 30)
choir = GDprep.spectrum("choir2.txt", cutoff = 3, volume = 60)
sine = [(1, 10)]
bass = [(1, 8), (2,16)]

if 1:
    # 0 empty
    # 1 baseline (2) acoustic guitar
    # 2 melody (5) / pan flute
    # 3 harmony (6) / flute
    # 4 no idea / choir (hardly used)
    # 5 bass (4) / picked bass
    # 6 harmony (6) / string ensemble
    # 7 (6) / tango accordion
    # 8 empty
    # 9 empty
    # 10 triangle (1)

    m = MidiFile() 
    m.open("MONT.MID") 
    m.read() 
    m.close() 

    instruments = {1: bass, 2: flute, 3: piano, 4: bass, 5: bass, 6: sine, 7: sine, 10: sine}

    selected = [m.tracks[t] for t in (2,3)]
    selected = [m.tracks[t] for t in (1,2,3,4,5,6,7)]
else:
    # 1 
    m = MidiFile() 
    m.open("Maestoso.mid") 
    m.read() 
    m.close() 
    organ = list(enumerate([9,3,3,9,3,6], 1))
    instruments = {2:organ, 3:organ,
                   4:sine, 5:sine, 6:sine, 7:sine,
                   8:sine, 9:sine, 10:sine, 11:sine, 12:sine,
                   }
    selected = [m.tracks[t] for t in (2,)]
    selected = m.tracks

allevents = sum([t.events for t in selected], [])
allevents = sorted(allevents, key=lambda e: e.time)
p2v = {}
maxvoices = set([])

if 0:
    for e in allevents:
        e.time -= 10560
    allevents = [e for e in allevents if 0 <= e.time < 23040]

sched = LivePlayer(1 * 192)
sched = Schedule()
prevf = [-1] * 64
for e in allevents:
    if e.type == "NOTE_ON":
        sched.wait(e.time)
        # print e.type, e.channel, e.time, e.pitch, e.velocity
        used = functools.reduce(operator.__or__, p2v.values(), set([]))
        maxvoices |= used
        k = (e.channel,e.pitch)
        if e.velocity:
            instrument = instruments.get(e.channel, sine)
            if k in p2v:
                del p2v[k]
            available = set(range(64)) - used
            # voices = set(random.sample(available, len(instrument)))
            # voices = set(sorted(available)[:len(instrument)])
            # assert len(voices) == len(instrument)
            voices = set([])
            fundamental = instrument[0][0]
            for i in range(len(instrument)):
                f = int((instrument[i][0] / fundamental) * midifreq(e.pitch) * 4)
                a = (instrument[i][1] * e.velocity) / 100
                matches = [v for v in range(64) if v in available and prevf[v] == f]
                if matches:
                    v = matches[0]
                    sched.amp(v, a)
                else:
                    v = min(available)
                    v = random.choice(list(available))
                    sched.target(v, f, a)
                available.remove(v)
                voices.add(v)
                # prevf[v] = f
            p2v[k] = voices
            # print "voice", voice, "freq", midifreq(e.pitch), "amp", a, p2v
        else:
            if k in p2v:
                for v in p2v[k]:
                   sched.silence(v)
                del p2v[k]
            # sched.append(1)
gd.silence()
print "max voices", len(maxvoices)
print len(sched.bytes)
sched.dump(open("../mont.h", "w"), "mont")
