237 lines
5.3 KiB
Python
237 lines
5.3 KiB
Python
#!/usr/bin/python3
|
|
|
|
import sys, os, subprocess, mido
|
|
|
|
def getarg(pos, default):
|
|
if len(sys.argv) > pos:
|
|
return sys.argv[pos]
|
|
return default
|
|
|
|
class Encoder:
|
|
def __init__(self, fp=None, fp_log=sys.stderr):
|
|
self.fp = fp if fp == None or fp.mode == "wb" else os.fdopen(fp.fileno(), 'wb')
|
|
self.fp_log = fp_log
|
|
|
|
self.sampling = 16000
|
|
self.signed = True
|
|
self.verbose = False
|
|
self.bits = 16
|
|
self.msbfirst = True
|
|
|
|
def write(self, amplitude):
|
|
if self.fp == None:
|
|
aplay = subprocess.Popen([
|
|
"aplay",
|
|
"-r",
|
|
str(self.sampling),
|
|
"-f",
|
|
("S" if self.signed else "U") + str(self.bits) + "_" + ("LE" if self.msbfirst else "BE")
|
|
], stdin=subprocess.PIPE, stderr=self.fp_log)
|
|
self.fp = aplay.stdin
|
|
|
|
smax = 2**(self.bits-self.signed)-1
|
|
if self.signed:
|
|
v = int(amplitude*smax)
|
|
w = v
|
|
s = v < 0
|
|
v = abs(v)
|
|
if s:
|
|
v = smax-v
|
|
v |= s << (self.bits-1)
|
|
else:
|
|
v = int((amplitude*smax+smax)/2)
|
|
w = v
|
|
|
|
if self.msbfirst:
|
|
i = 0
|
|
while i < self.bits:
|
|
if self.fp.write(bytes([(v>>i)&255])) > 1:
|
|
self.fp_log.write(str(v))
|
|
if self.verbose:
|
|
self.fp_log.write(bin((v>>i)&255)[2:].zfill(8))
|
|
i += 8
|
|
else:
|
|
i = self.bits
|
|
while i > 0:
|
|
i -= 8
|
|
self.fp.write(bytes([(v>>i)&255]))
|
|
if self.verbose:
|
|
self.fp_log.write(bin((v>>i)&255)[2:].zfill(8))
|
|
|
|
if self.verbose:
|
|
self.fp_log.write(" "+"0"*(self.bits-len(bin(v))+2)+bin(v)[2:]+" "+str(v)+" "+str(w)+"\n")
|
|
|
|
class Event:
|
|
def __init__(self, type):
|
|
self.type = type
|
|
|
|
class Wait(Event):
|
|
def __init__(self, value):
|
|
Event.__init__(self, Wait)
|
|
self.value = value
|
|
|
|
def dict(self):
|
|
return {
|
|
"type": "wait",
|
|
"number": self.value,
|
|
}
|
|
|
|
class End(Event):
|
|
def __init__(self):
|
|
Event.__init__(self, End)
|
|
|
|
def dict(self):
|
|
return {
|
|
"type": "end"
|
|
}
|
|
|
|
class Note(Event):
|
|
def __init__(self, number, velocity, channel=0, track=0):
|
|
Event.__init__(self, Note)
|
|
self.number = number
|
|
self.velocity = velocity
|
|
self.channel = channel
|
|
self.track = track
|
|
|
|
def dict(self):
|
|
return {
|
|
"type": "note",
|
|
"number": self.number,
|
|
"velocity": self.velocity,
|
|
"channel": self.channel
|
|
}
|
|
|
|
def midi_events():
|
|
if len(sys.argv) < 2:
|
|
raise ValueError("Il faut donner le nom du fichier: python3 "+sys.argv[0]+" fichier.mid")
|
|
return read_midi(sys.argv[1], float(getarg(2, 80)))
|
|
|
|
_midi = None
|
|
_midi_active = {}
|
|
_midi_wait = 0
|
|
_midi_tt = 0
|
|
_midi_loop = True
|
|
def midi2():
|
|
global _midi, _midi_active, _midi_wait, _midi_tt, _midi_loop
|
|
if _midi == None:
|
|
_midi = midi_events()
|
|
|
|
if _midi_loop:
|
|
if _midi_wait == 0:
|
|
while True:
|
|
event = _midi[_midi_tt]
|
|
_midi_tt += 1
|
|
if event.type == Note:
|
|
if event.velocity == 0:
|
|
if (event.number, event.track) in _midi_active:
|
|
_midi_active.pop((event.number, event.track))
|
|
else:
|
|
_midi_active[(event.number, event.track)] = event.velocity
|
|
elif event.type == Wait:
|
|
_midi_wait = int(event.value*8000)
|
|
break
|
|
elif event.type == End:
|
|
_midi_loop = False
|
|
break
|
|
else:
|
|
_midi_wait -= 1
|
|
|
|
return [[idx[0], idx[1], _midi_active[idx]] for idx in _midi_active]
|
|
|
|
"""
|
|
Lit le fichier MIDI donné en argument de la commande.
|
|
Retourne, pour chaque échantillon, la liste des notes sous la forme [piste, note, amplitude].
|
|
"""
|
|
class Midi:
|
|
def __init__(self, sampling=16000):
|
|
self.sampling = sampling
|
|
|
|
def __iter__(self):
|
|
self.active = {}
|
|
self.wait = 0
|
|
self.tt = 0
|
|
self.loop = True
|
|
self.events = midi_events()
|
|
return self
|
|
|
|
def __next__(self):
|
|
if self.loop:
|
|
if self.wait == 0:
|
|
while True:
|
|
event = self.events[self.tt]
|
|
self.tt += 1
|
|
if event.type == Note:
|
|
if event.velocity == 0:
|
|
if (event.track, event.number) in self.active:
|
|
self.active.pop((event.track, event.number))
|
|
else:
|
|
sys.stderr.write("Piste="+str(event.track)+" Note="+str(event.number)+" Force="+str(event.velocity)+"\n")
|
|
self.active[(event.track, event.number)] = event.velocity
|
|
elif event.type == Wait:
|
|
self.wait = int(event.value*self.sampling/2)
|
|
break
|
|
elif event.type == End:
|
|
self.loop = False
|
|
break
|
|
else:
|
|
self.wait -= 1
|
|
|
|
return [[idx[0], idx[1], self.active[idx]] for idx in self.active]
|
|
raise StopIteration
|
|
|
|
def read_midi(filename, tempo):
|
|
mid = mido.MidiFile(filename)
|
|
result = []
|
|
tracks = [0 for i in mid.tracks]
|
|
tempo = [tempo for i in mid.tracks]
|
|
wait = [None for i in mid.tracks]
|
|
end = [False for i in mid.tracks]
|
|
t = 0
|
|
loop = True
|
|
while loop:
|
|
loop = False
|
|
|
|
# Find the first events
|
|
first = []
|
|
firstt = None
|
|
for i, track in enumerate(mid.tracks):
|
|
if end[i]:
|
|
continue
|
|
if wait[i] == None:
|
|
wait[i] = track[tracks[i]].time/tempo[i]/4
|
|
|
|
if firstt == None or wait[i] < firstt:
|
|
firstt = wait[i]
|
|
first = [i]
|
|
elif wait[i] == firstt:
|
|
first.append(i)
|
|
|
|
if firstt > 0:
|
|
result.append(Wait(firstt))
|
|
|
|
# Play the first events
|
|
for i, track in enumerate(mid.tracks):
|
|
if end[i]:
|
|
continue
|
|
loop = True
|
|
msg = track[tracks[i]]
|
|
if i in first:
|
|
# Play event
|
|
if msg.type == "set_tempo":
|
|
tempo[i] = 6e7/msg.tempo
|
|
elif msg.type == "note_on":
|
|
result.append(Note(msg.note-50, msg.velocity/100, msg.channel, i))
|
|
elif msg.type == "end_of_track":
|
|
result.append(End())
|
|
end[i] = True
|
|
|
|
# Find next event
|
|
tracks[i] += 1
|
|
wait[i] = None
|
|
else:
|
|
wait[i] -= firstt
|
|
|
|
if not (False in end):
|
|
loop = False
|
|
|
|
return result
|