xi2

a plain text language that compiles to MIDI
git clone https://git.ce9e.org/xi2.git

commit
bdda87bc720b6ca7dc59d3f1efd4e6fe039d1220
parent
6d83259c70a6c4edb05ea7d1b6978cd271394e00
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2023-09-18 21:37
refactor midi

Diffstat

M iparser.py 12 +++++-------
M midi.py 86 ++++++++++++++++++++++++-------------------------------------
M xi2.py 23 ++++++++++-------------

3 files changed, 48 insertions, 73 deletions


diff --git a/iparser.py b/iparser.py

@@ -1,5 +1,4 @@
    1    -1 from midi import Midi
    2    -1 from midi import MidiFile
   -1     1 import midi
    3     2 
    4     3 # The tricky part here is the time conversion.
    5     4 # and noteOff events
@@ -9,7 +8,7 @@ class IParser:
    9     8     """Convert intermediate code to MIDI bytecode."""
   10     9 
   11    10     def __init__(self, seq, ch=0, offset=60):
   12    -1         self.midi = Midi()
   -1    11         self.midi = midi.Midi()
   13    12         self.ch = ch
   14    13         self.offset = offset
   15    14         self.dt = 0
@@ -94,8 +93,7 @@ class IParser:
   94    93 
   95    94 if __name__ == '__main__':
   96    95     a = [(('0', '1'), '2'), '4', '5', '-', '', ['0', '4', '7'], '', '', '0', ['3', '-']]
   97    -1 
   98    -1     f = MidiFile()
   99    96     ip = IParser(a, 0, 60)
  100    -1     f.add_track(ip.midi)
  101    -1     f.write('test.mid')
   -1    97 
   -1    98     with open('test.mid', 'wb') as fh:
   -1    99         midi.write_file(fh, [ip.midi])

diff --git a/midi.py b/midi.py

@@ -1,44 +1,35 @@
    1     1 # http://www.sonicspot.com/guide/midifiles.html
    2     2 
   -1     3 import io
   -1     4 
    3     5 TIME_DEVISION = 0x00c0 # two bytes
    4     6 
    5     7 
    6     8 class Midi:
    7     9     """Create MIDI bytecode from MIDI events."""
    8    10 
    9    -1     def __init__(self):
   10    -1         self._buf = ''
   11    -1 
   12    -1     def __str__(self):
   13    -1         return self._buf.encode('hex')
   14    -1 
   15    -1     def __add__(self, other):
   16    -1         m = Midi()
   17    -1         m._buf = self._buf + other._buf
   18    -1         return m
   19    -1 
   20    -1     def write(self, name):
   21    -1         f = open(name, 'w')
   22    -1         f.write(self._buf)
   23    -1         f.close()
   -1    11     def __init__(self, fh=None):
   -1    12         if fh:
   -1    13             self.fh = fh
   -1    14         else:
   -1    15             self.fh = io.BytesIO()
   24    16 
   25    17     def write_fixed(self, value, size):
   26    -1         if type(value) == type(''):
   27    -1             self._buf += (' ' * size + value)[-size:]
   28    -1         else:
   29    -1             if size != 0:
   30    -1                 self.write_fixed(value / 0x100, size - 1)
   31    -1                 self._buf += chr(value % 0x100)
   -1    18         if isinstance(value, bytes):
   -1    19             self.fh.write((b' ' * size + value)[-size:])
   -1    20         elif size != 0:
   -1    21             assert isinstance(value, int)
   -1    22             self.write_fixed(value >> 8, size - 1)
   -1    23             self.fh.write(bytes([value & 0xff]))
   32    24 
   33    25     def write_variable(self, value, _rec=False):
   34    -1         # b = bin(a)[2:]; eval('0b'+''.join([b[k*8:(k+1)*8][1:] for k in range(int(len(b)/8))]))
   35    26         if value == 0 and _rec:
   36    27             return 0
   37    -1         self.write_variable(value / 0x80, True)
   -1    28         self.write_variable(value >> 7, True)
   38    29         if _rec:
   39    -1             self._buf += chr(value % 0x80 + 0x80)
   -1    30             self.fh.write(bytes([(value & 0x7f) | 0x80]))
   40    31         else:
   41    -1             self._buf += chr(value % 0x80)
   -1    32             self.fh.write(bytes([value & 0x7f]))
   42    33 
   43    34     def ch_event(self, dt, event, ch, p1, p2=-1):
   44    35         self.write_variable(int(dt * TIME_DEVISION))
@@ -54,7 +45,7 @@ class Midi:
   54    45         self.write_variable(length)
   55    46         self.write_fixed(data, length)
   56    47 
   57    -1     def sys_ex(self, dt, length, data):
   -1    48     def system_exclusive(self, dt, length, data):
   58    49         self.write_variable(int(dt * TIME_DEVISION))
   59    50         self.write_fixed(0xf0, 1)
   60    51         self.write_variable(length)
@@ -84,34 +75,23 @@ class Midi:
   84    75         self.ctrl_event(dt, ch, 0x07, int(vol * 0x7f))
   85    76 
   86    77 
   87    -1 class MidiFile(Midi):
   88    -1     def __init__(self):
   89    -1         Midi.__init__(self)
   90    -1         self._tracks = []
   91    -1 
   92    -1     def update(self):
   93    -1         self._buf = ''
   94    -1         self.write_fixed(0x4D546864, 4)  # chunk ID "MThd"
   95    -1         self.write_fixed(6, 4)  # chunk size
   96    -1         self.write_fixed(1, 2)  # format type
   97    -1         self.write_fixed(len(self._tracks), 2)  # numer of tracks
   98    -1         self.write_fixed(TIME_DEVISION, 2)  # time devision
   99    -1         for track in self._tracks:
  100    -1             self.write_fixed(0x4D54726B, 4)  # chunk ID "MTtr"
  101    -1             self.write_fixed(len(track._buf) + 4, 4)  # chunk size
  102    -1             self._buf += track._buf
  103    -1             self.meta_event(0, 0x2f, 0, '')  # end_track event
  104    -1 
  105    -1     def add_track(self, data, update=True):
  106    -1         self._tracks.append(data)
  107    -1         if update:
  108    -1             self.update()
   -1    78 def write_file(fh, tracks):
   -1    79     f = Midi(fh)
   -1    80     f.write_fixed(0x4D546864, 4)  # chunk ID "MThd"
   -1    81     f.write_fixed(6, 4)  # chunk size
   -1    82     f.write_fixed(1, 2)  # format type
   -1    83     f.write_fixed(len(tracks), 2)  # numer of tracks
   -1    84     f.write_fixed(TIME_DEVISION, 2)  # time devision
   -1    85     for track in tracks:
   -1    86         f.write_fixed(0x4D54726B, 4)  # chunk ID "MTtr"
   -1    87         buf = track.fh.getvalue()
   -1    88         f.write_fixed(len(buf) + 4, 4)  # chunk size
   -1    89         f.fh.write(buf)
   -1    90         f.meta_event(0, 0x2f, 0, b'')  # end_track event
  109    91 
  110    92 
  111    93 if __name__ == '__main__':
  112    94     # Example:
  113    -1     f = MidiFile()
  114    -1     f.add_track(Midi())
  115    95     t = Midi()
  116    96     t.set_vol(0, 1, 1)
  117    97     t.ctrl_event(0, 1, 0x00, 0)  # setting bank
@@ -121,6 +101,6 @@ if __name__ == '__main__':
  121   101     t.note_off(1, 1, 62)
  122   102     t.note_on(0, 1, 64)
  123   103     t.note_off(1, 1, 64)
  124    -1     f.add_track(t)
  125    -1     print(f)
  126    -1     f.write('test.mid')
   -1   104 
   -1   105     with open('test.mid', 'wb') as fh:
   -1   106         write_file(fh, [t])

diff --git a/xi2.py b/xi2.py

@@ -1,9 +1,8 @@
    1     1 import argparse
    2     2 import re
    3     3 
   -1     4 import midi
    4     5 from iparser import IParser
    5    -1 from midi import Midi
    6    -1 from midi import MidiFile
    7     6 
    8     7 """Parse xi2 code and convert to MIDI."""
    9     8 
@@ -103,17 +102,15 @@ for s in ll.split('\n\n'):
  103   102     for name, data in tracks.items():
  104   103         data += [''] * (l - len(data))
  105   104 
  106    -1 # create midi
  107    -1 mf = MidiFile()
  108    -1 
  109   105 # create first track with meta infos
  110    -1 m = Midi()
  111    -1 m.set_tempo(args.tempo)
  112    -1 mf.add_track(m)
   -1   106 midi_tracks = []
   -1   107 t0 = midi.Midi()
   -1   108 t0.set_tempo(args.tempo)
   -1   109 midi_tracks.append(t0)
  113   110 
  114   111 ch = 0
  115    -1 for name, track in tracks.iteritems():
  116    -1     m = Midi()
   -1   112 for name, track in tracks.items():
   -1   113     m = midi.Midi()
  117   114     # write meta info
  118   115     m.meta_event(0, 0x04, len(name), name)
  119   116     try:
@@ -125,8 +122,8 @@ for name, track in tracks.iteritems():
  125   122     ip = IParser(track, ch=ch, offset=args.offset)
  126   123     m += ip.midi
  127   124     # write
  128    -1     mf.add_track(m)
   -1   125     midi_tracks.append(m)
  129   126     ch += 1
  130   127 
  131    -1 # write to file
  132    -1 mf.write(args.outfile)
   -1   128 with open(args.outfile, 'wb') as fh:
   -1   129     midi.write_file(fh, midi_tracks)