xi2

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

commit
4899d3cf474c0667c0405b60e13f16a4be74f898
parent
3a3ddbdabfb471b1ba7740d38b5952a6a7f93c4c
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2023-09-19 06:41
style: high level restructure of xi2

Diffstat

M xi2.py 193 ++++++++++++++++++++++++++++++++-----------------------------

1 files changed, 100 insertions, 93 deletions


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

@@ -1,61 +1,17 @@
   -1     1 """Parse xi2 code and convert to MIDI."""
   -1     2 
    1     3 import argparse
    2     4 import re
    3     5 
    4     6 import midi
    5     7 from iparser import IParser
    6     8 
    7    -1 """Parse xi2 code and convert to MIDI."""
    8    -1 
    9    -1 parser = argparse.ArgumentParser()
   10    -1 parser.add_argument('-t', '--tempo', help="tempo in bpm", default=120, type=int)
   11    -1 parser.add_argument('-o', '--offset', help="key offset", default=60, type=int)
   12    -1 parser.add_argument('infile')
   13    -1 parser.add_argument('outfile')
   14    -1 args = parser.parse_args()
   15     9 
   16    10 def length(data):
   17    11     data = re.sub('{[^}]*}', '', data)
   18    12     data = re.sub('\([^\)]*\)', '', data)
   19    13     return len(data.split(',')) - 1
   20    14 
   21    -1 f = open(args.infile)
   22    -1 lines = f.readlines()
   23    -1 f.close()
   24    -1 
   25    -1 ll = ''.join(lines)
   26    -1 # remove whitespace
   27    -1 ll = re.sub('[ \t]', '', ll)
   28    -1 # remove c++ style comments
   29    -1 # we have to escape linebreaks
   30    -1 ll = re.sub('\n', '\\\\n', ll)
   31    -1 ll = re.sub('/\*[^(\*)]*\*/', '', ll)
   32    -1 ll = re.sub('\\\\n', '\n', ll)
   33    -1 # remove c style comments
   34    -1 ll = re.sub('//[^\n]*\n', '', ll)
   35    -1 
   36    -1 # expand macros
   37    -1 ll = re.sub('\n', '\\\\n', ll)
   38    -1 while re.search('(\[[^\]]*\]):(.*)', ll):
   39    -1     match = re.search('(\[[^\]]*\]):(.*)', ll)
   40    -1     (key, after) = match.groups()
   41    -1     if after.startswith('<<'):
   42    -1         eol = after.split('\\n',1)[0][2:]
   43    -1         val = after.split(eol,2)[1]
   44    -1         ll = re.sub(re.escape("%s:<<%s%s%s" % (key, eol, val, eol)), '', ll)
   45    -1     else:
   46    -1         val = after.split('\\n',1)[0]
   47    -1         ll = re.sub(re.escape("%s:%s" % (key, val)), '', ll)
   48    -1     ll = re.sub(re.escape(key), val, ll)
   49    -1 ll = re.sub('\\\\n', '\n', ll)
   50    -1 
   51    -1 # remove trailing newlines
   52    -1 ll = re.sub('^\n*', '', ll)
   53    -1 # remove \n\n+
   54    -1 ll = re.sub('\n', '\\\\n', ll)
   55    -1 ll = re.sub('\\\\n(\\\\n)+', '\\\\n\\\\n', ll)
   56    -1 ll = re.sub('\\\\n', '\n', ll)
   57    -1 # remove newlines at the end
   58    -1 ll = re.sub('\n*$', '', ll)
   59    15 
   60    16 def parse(t):
   61    17     string = ''
@@ -78,51 +34,102 @@ def parse(t):
   78    34             string += c
   79    35     return stack[0]
   80    36 
   81    -1 # join track parts from different sets
   82    -1 tracks = dict()
   83    -1 for s in ll.split('\n\n'):
   84    -1     if len(tracks) == 0:
   85    -1         l = 0
   86    -1     else:
   87    -1         l = max([len(t) for t in tracks])
   88    -1     for track in s.split('\n'):
   -1    37 
   -1    38 def parse_args():
   -1    39     parser = argparse.ArgumentParser()
   -1    40     parser.add_argument('-t', '--tempo', help='tempo in bpm', default=120, type=int)
   -1    41     parser.add_argument('-o', '--offset', help='key offset', default=60, type=int)
   -1    42     parser.add_argument('infile')
   -1    43     parser.add_argument('outfile')
   -1    44     return parser.parse_args()
   -1    45 
   -1    46 
   -1    47 if __name__ == '__main__':
   -1    48     args = parse_args()
   -1    49 
   -1    50     with open(args.infile) as fh:
   -1    51         lines = fh.readlines()
   -1    52 
   -1    53     ll = ''.join(lines)
   -1    54     # remove whitespace
   -1    55     ll = re.sub('[ \t]', '', ll)
   -1    56     # remove c++ style comments
   -1    57     # we have to escape linebreaks
   -1    58     ll = re.sub('\n', '\\\\n', ll)
   -1    59     ll = re.sub('/\*[^(\*)]*\*/', '', ll)
   -1    60     ll = re.sub('\\\\n', '\n', ll)
   -1    61     # remove c style comments
   -1    62     ll = re.sub('//[^\n]*\n', '', ll)
   -1    63 
   -1    64     # expand macros
   -1    65     ll = re.sub('\n', '\\\\n', ll)
   -1    66     while re.search('(\[[^\]]*\]):(.*)', ll):
   -1    67         match = re.search('(\[[^\]]*\]):(.*)', ll)
   -1    68         (key, after) = match.groups()
   -1    69         if after.startswith('<<'):
   -1    70             eol = after.split('\\n',1)[0][2:]
   -1    71             val = after.split(eol,2)[1]
   -1    72             ll = re.sub(re.escape("%s:<<%s%s%s" % (key, eol, val, eol)), '', ll)
   -1    73         else:
   -1    74             val = after.split('\\n',1)[0]
   -1    75             ll = re.sub(re.escape("%s:%s" % (key, val)), '', ll)
   -1    76         ll = re.sub(re.escape(key), val, ll)
   -1    77     ll = re.sub('\\\\n', '\n', ll)
   -1    78 
   -1    79     # remove trailing newlines
   -1    80     ll = re.sub('^\n*', '', ll)
   -1    81     # remove \n\n+
   -1    82     ll = re.sub('\n', '\\\\n', ll)
   -1    83     ll = re.sub('\\\\n(\\\\n)+', '\\\\n\\\\n', ll)
   -1    84     ll = re.sub('\\\\n', '\n', ll)
   -1    85     # remove newlines at the end
   -1    86     ll = re.sub('\n*$', '', ll)
   -1    87 
   -1    88     # join track parts from different sets
   -1    89     tracks = dict()
   -1    90     for s in ll.split('\n\n'):
   -1    91         if len(tracks) == 0:
   -1    92             l = 0
   -1    93         else:
   -1    94             l = max([len(t) for t in tracks])
   -1    95         for track in s.split('\n'):
   -1    96             try:
   -1    97                 (name, data) = track.split(':', 1)
   -1    98             except Exception as e:
   -1    99                 print(track)
   -1   100                 raise e
   -1   101             data = parse(data)
   -1   102             if name not in tracks:
   -1   103                 tracks[name] = [''] * l
   -1   104             tracks[name] += data
   -1   105         if len(tracks) == 0:
   -1   106             l = 0
   -1   107         else:
   -1   108             l = max([len(t) for t in tracks])
   -1   109         for name, data in tracks.items():
   -1   110             data += [''] * (l - len(data))
   -1   111 
   -1   112     # create first track with meta infos
   -1   113     midi_tracks = []
   -1   114     t0 = midi.Midi()
   -1   115     t0.set_tempo(args.tempo)
   -1   116     midi_tracks.append(t0)
   -1   117 
   -1   118     ch = 0
   -1   119     for name, track in tracks.items():
   -1   120         m = midi.Midi()
   -1   121         # write meta info
   -1   122         m.meta_event_str(0, 0x04, name)
   89   123         try:
   90    -1             (name, data) = track.split(':', 1)
   91    -1         except Exception as e:
   92    -1             print(track)
   93    -1             raise e
   94    -1         data = parse(data)
   95    -1         if name not in tracks:
   96    -1             tracks[name] = [''] * l
   97    -1         tracks[name] += data
   98    -1     if len(tracks) == 0:
   99    -1         l = 0
  100    -1     else:
  101    -1         l = max([len(t) for t in tracks])
  102    -1     for name, data in tracks.items():
  103    -1         data += [''] * (l - len(data))
  104    -1 
  105    -1 # create first track with meta infos
  106    -1 midi_tracks = []
  107    -1 t0 = midi.Midi()
  108    -1 t0.set_tempo(args.tempo)
  109    -1 midi_tracks.append(t0)
  110    -1 
  111    -1 ch = 0
  112    -1 for name, track in tracks.items():
  113    -1     m = midi.Midi()
  114    -1     # write meta info
  115    -1     m.meta_event_str(0, 0x04, name)
  116    -1     try:
  117    -1         prog = int(name)
  118    -1     except ValueError:
  119    -1         prog = 0
  120    -1     m.prog_ch(0, ch, prog)
  121    -1     # write data
  122    -1     IParser(m, track, ch=ch, offset=args.offset)
  123    -1     # write
  124    -1     midi_tracks.append(m)
  125    -1     ch += 1
  126    -1 
  127    -1 with open(args.outfile, 'wb') as fh:
  128    -1     midi.write_file(fh, midi_tracks)
   -1   124             prog = int(name)
   -1   125         except ValueError:
   -1   126             prog = 0
   -1   127         m.prog_ch(0, ch, prog)
   -1   128         # write data
   -1   129         IParser(m, track, ch=ch, offset=args.offset)
   -1   130         # write
   -1   131         midi_tracks.append(m)
   -1   132         ch += 1
   -1   133 
   -1   134     with open(args.outfile, 'wb') as fh:
   -1   135         midi.write_file(fh, midi_tracks)