xi2

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

commit
7f35c1c2c7b44e3ccff23dd6696db324f31c29eb
parent
ee4b38e8ede70d4d7cbcd46e3ea70f6c25e77980
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2023-09-19 14:13
refactor xi2

Diffstat

M xi2.py 86 +++++++++++++++++++++++++++++--------------------------------

1 files changed, 41 insertions, 45 deletions


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

@@ -7,7 +7,7 @@ import midi
    7     7 from renderer import render
    8     8 
    9     9 
   10    -1 def parse(t):
   -1    10 def parse_seq(t):
   11    11     string = ''
   12    12     stack = [[]]
   13    13     for c in t:
@@ -29,59 +29,60 @@ def parse(t):
   29    29     return stack[0]
   30    30 
   31    31 
   32    -1 def parse_args():
   33    -1     parser = argparse.ArgumentParser()
   34    -1     parser.add_argument('-t', '--tempo', help='tempo in bpm', default=120, type=int)
   35    -1     parser.add_argument('-o', '--offset', help='key offset', default=60, type=int)
   36    -1     parser.add_argument('infile')
   37    -1     parser.add_argument('outfile')
   38    -1     return parser.parse_args()
   39    -1 
   40    -1 
   41    -1 if __name__ == '__main__':
   42    -1     args = parse_args()
   43    -1 
   44    -1     with open(args.infile) as fh:
   45    -1         lines = fh.readlines()
   46    -1 
   47    -1     ll = ''.join(lines)
   48    -1     # remove whitespace
   49    -1     ll = re.sub('[ \t]', '', ll)
   50    -1     # remove comments
   51    -1     ll = re.sub('#[^\n]*', '', ll)
   52    -1 
   53    -1     # expand macros
   54    -1     ll = ll.replace('\n', r'\n')
   55    -1     while re.search(r'(\[[^\]]*\]):(.*)', ll):
   56    -1         match = re.search(r'(\[[^\]]*\]):(.*)', ll)
   -1    32 def expand_macros(s):
   -1    33     s = s.replace('\n', r'\n')
   -1    34     while re.search(r'(\[[^\]]*\]):(.*)', s):
   -1    35         match = re.search(r'(\[[^\]]*\]):(.*)', s)
   57    36         key, after = match.groups()
   58    37         if after.startswith(r'\n'):
   59    38             val = after.split('[end]', 1)[0]
   60    -1             ll = ll.replace('%s:%s[end]' % (key, val), '')
   -1    39             s = s.replace('%s:%s[end]' % (key, val), '')
   61    40         else:
   62    41             val = after.split(r'\n', 1)[0]
   63    -1             ll = ll.replace('%s:%s' % (key, val), '')
   64    -1         ll = ll.replace(key, val)
   65    -1     ll = ll.replace(r'\n', '\n')
   -1    42             s = s.replace('%s:%s' % (key, val), '')
   -1    43         s = s.replace(key, val)
   -1    44     return s.replace(r'\n', '\n')
   -1    45 
   66    46 
   67    -1     # trim newlines
   68    -1     ll = ll.strip('\n')
   69    -1     ll = re.sub('\n\n+', '\n\n', ll)
   -1    47 def parse(s):
   -1    48     s = re.sub('[ \t]', '', s)
   -1    49     s = re.sub('#[^\n]*', '', s)
   -1    50     s = expand_macros(s)
   -1    51     s = s.strip('\n')
   -1    52     s = re.sub('\n\n+', '\n\n', s)
   70    53 
   71    -1     # join tracks from different sections
   72    -1     tracks = dict()
   73    -1     for section in ll.split('\n\n'):
   -1    54     tracks = {}
   -1    55     for section in s.split('\n\n'):
   74    56         length = max([len(t) for t in tracks.values()], default=0)
   75    57         for track in section.split('\n'):
   76    58             try:
   77    59                 name, data = track.split(':', 1)
   78    -1             except Exception:
   -1    60             except ValueError:
   79    61                 print(track)
   80    62                 raise
   81    63             if name not in tracks:
   82    64                 tracks[name] = []
   83    65             tracks[name] += [''] * (length - len(tracks[name]))
   84    -1             tracks[name] += parse(data)
   -1    66             tracks[name] += parse_seq(data)
   -1    67 
   -1    68     return tracks
   -1    69 
   -1    70 
   -1    71 def parse_args():
   -1    72     parser = argparse.ArgumentParser()
   -1    73     parser.add_argument('-t', '--tempo', help='tempo in bpm', default=120, type=int)
   -1    74     parser.add_argument('-o', '--offset', help='key offset', default=60, type=int)
   -1    75     parser.add_argument('infile')
   -1    76     parser.add_argument('outfile')
   -1    77     return parser.parse_args()
   -1    78 
   -1    79 
   -1    80 if __name__ == '__main__':
   -1    81     args = parse_args()
   -1    82 
   -1    83     with open(args.infile) as fh:
   -1    84         s = fh.read()
   -1    85     tracks = parse(s)
   85    86 
   86    87     # create first track with meta infos
   87    88     midi_tracks = []
@@ -89,21 +90,16 @@ if __name__ == '__main__':
   89    90     t0.set_tempo(args.tempo)
   90    91     midi_tracks.append(t0)
   91    92 
   92    -1     ch = 0
   93    -1     for name, track in tracks.items():
   -1    93     for ch, name in enumerate(tracks):
   94    94         m = midi.Midi()
   95    -1         # write meta info
   96    95         m.meta_event_str(0, 0x04, name)
   97    96         try:
   98    97             prog = int(name)
   99    98         except ValueError:
  100    99             prog = 0
  101   100         m.prog_ch(0, ch, prog)
  102    -1         # write data
  103    -1         render(track, m, ch=ch, offset=args.offset)
  104    -1         # write
   -1   101         render(tracks[name], m, ch=ch, offset=args.offset)
  105   102         midi_tracks.append(m)
  106    -1         ch += 1
  107   103 
  108   104     with open(args.outfile, 'wb') as fh:
  109   105         midi.write_file(fh, midi_tracks)