shiv.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. # IV file viewer
  2. import xml.etree.ElementTree as ET
  3. import optparse
  4. import sys
  5. import math
  6. import gzip
  7. import bz2
  8. parser = optparse.OptionParser()
  9. parser.add_option('-n', '--number', dest='number', action='store_true', help='Show number of tracks')
  10. parser.add_option('-g', '--groups', dest='groups', action='store_true', help='Show group names')
  11. parser.add_option('-G', '--group', dest='group', action='append', help='Only compute for this group (may be specified multiple times)')
  12. parser.add_option('-N', '--notes', dest='notes', action='store_true', help='Show number of notes')
  13. parser.add_option('-M', '--notes-stream', dest='notes_stream', action='store_true', help='Show notes per stream')
  14. parser.add_option('-m', '--meta', dest='meta', action='store_true', help='Show meta track information')
  15. parser.add_option('--histogram', dest='histogram', action='store_true', help='Show a histogram distribution of pitches')
  16. parser.add_option('--histogram-tracks', dest='histogram_tracks', action='store_true', help='Show a histogram distribution of pitches per track')
  17. parser.add_option('--vel-hist', dest='vel_hist', action='store_true', help='Show a histogram distribution of velocities')
  18. parser.add_option('--vel-hist-tracks', dest='vel_hist_tracks', action='store_true', help='Show a histogram distributions of velocities per track')
  19. parser.add_option('-d', '--duration', dest='duration', action='store_true', help='Show the duration of the piece')
  20. parser.add_option('-D', '--duty-cycle', dest='duty_cycle', action='store_true', help='Show the duration of the notes within tracks, and as a percentage of the piece duration')
  21. parser.add_option('-H', '--height', dest='height', type='int', help='Height of histograms')
  22. parser.add_option('-C', '--no-color', dest='no_color', action='store_true', help='Don\'t use ANSI color escapes')
  23. parser.add_option('-x', '--aux', dest='aux', action='store_true', help='Show information about the auxiliary streams')
  24. parser.add_option('-a', '--almost-all', dest='almost_all', action='store_true', help='Show useful information')
  25. parser.add_option('-A', '--all', dest='all', action='store_true', help='Show everything')
  26. parser.add_option('-t', '--total', dest='total', action='store_true', help='Make cross-file totals')
  27. parser.set_defaults(height=20, group=[])
  28. options, args = parser.parse_args()
  29. if not any((
  30. options.number,
  31. options.groups,
  32. options.notes,
  33. options.notes_stream,
  34. options.histogram,
  35. options.vel_hist,
  36. options.duration,
  37. options.duty_cycle,
  38. options.aux,
  39. options.meta,
  40. options.histogram_tracks,
  41. options.vel_hist_tracks,
  42. )):
  43. print 'No computations specified! Assuming you meant --almost-all...'
  44. options.almost_all = True
  45. if options.almost_all or options.all:
  46. options.number = True
  47. options.groups = True
  48. options.notes = True
  49. options.notes_stream = True
  50. options.histogram = True
  51. options.vel_hist = True
  52. options.duration = True
  53. options.duty_cycle = True
  54. if options.all:
  55. options.aux = True
  56. options.meta = True
  57. options.histogram_tracks= True
  58. options.vel_hist_tracks = True
  59. if options.no_color:
  60. class COL:
  61. NONE=''
  62. RED=''
  63. GREEN=''
  64. BLUE=''
  65. YELLOW=''
  66. MAGENTA=''
  67. CYAN=''
  68. else:
  69. class COL:
  70. NONE='\x1b[0m'
  71. RED='\x1b[31m'
  72. GREEN='\x1b[32m'
  73. BLUE='\x1b[34m'
  74. YELLOW='\x1b[33m'
  75. MAGENTA='\x1b[35m'
  76. CYAN='\x1b[36m'
  77. def show_hist(values, height=None):
  78. if not values:
  79. print '{empty histogram}'
  80. return
  81. if height is None:
  82. height = options.height
  83. xs, ys = values.keys(), values.values()
  84. minx, maxx = min(xs), max(xs)
  85. miny, maxy = min(ys), max(ys)
  86. xv = range(int(math.floor(minx)), int(math.ceil(maxx + 1)))
  87. incs = max((maxy - miny) / height, 1)
  88. print COL.CYAN + '\t --' + '-' * len(xv) + COL.NONE
  89. for ub in range(maxy + incs, miny, -incs):
  90. print '{}{}\t | {}{}{}'.format(COL.CYAN, ub, COL.YELLOW, ''.join(['#' if values.get(x) > (ub - incs) else ' ' for x in xv]), COL.NONE)
  91. print COL.CYAN + '\t |-' + '-' * len(xv) + COL.NONE
  92. xvs = map(str, xv)
  93. for i in range(max(map(len, xvs))):
  94. print COL.CYAN + '\t ' + ''.join([s[i] if len(s) > i else ' ' for s in xvs]) + COL.NONE
  95. print
  96. xcs = map(str, [values.get(x, 0) for x in xv])
  97. for i in range(max(map(len, xcs))):
  98. print COL.YELLOW + '\t ' + ''.join([s[i] if len(s) > i else ' ' for s in xcs]) + COL.NONE
  99. print
  100. if options.total:
  101. tot_note_cnt = 0
  102. max_note_cnt = 0
  103. tot_pitches = {}
  104. tot_velocities = {}
  105. tot_dur = 0
  106. max_dur = 0
  107. tot_streams = 0
  108. max_streams = 0
  109. tot_notestreams = 0
  110. max_notestreams = 0
  111. tot_groups = {}
  112. for fname in args:
  113. print
  114. print 'File :', fname
  115. try:
  116. if fname.endswith('.ivz'):
  117. ivf = gzip.open(fname, 'rb')
  118. elif fname.endswith('.ivb'):
  119. ivf = bz2.BZ2File(fname, 'r')
  120. else:
  121. ivf = open(fname, 'rb')
  122. iv = ET.parse(ivf).getroot()
  123. except Exception:
  124. import traceback
  125. traceback.print_exc()
  126. print 'Bad file :', fname, ', skipping...'
  127. continue
  128. print '\t<computing...>'
  129. if options.meta:
  130. print 'Metatrack:',
  131. meta = iv.find('./meta')
  132. if len(meta):
  133. print 'exists'
  134. print '\tBPM track:',
  135. bpms = meta.find('./bpms')
  136. if len(bpms):
  137. print 'exists'
  138. for elem in bpms.iterfind('./bpm'):
  139. print '\t\tAt ticks {}, time {}: {} bpm'.format(elem.get('ticks'), elem.get('time'), elem.get('bpm'))
  140. if not (options.number or options.groups or options.notes or options.histogram or options.histogram_tracks or options.vel_hist or options.vel_hist_tracks or options.duration or options.duty_cycle or options.aux):
  141. continue
  142. streams = iv.findall('./streams/stream')
  143. notestreams = [s for s in streams if s.get('type') == 'ns']
  144. auxstreams = [s for s in streams if s.get('type') == 'aux']
  145. if options.group:
  146. print 'NOTE: Restricting results to groups', options.group, 'as requested'
  147. notestreams = [ns for ns in notestreams if ns.get('group', '<anonymous>') in options.group]
  148. if options.number:
  149. print 'Stream count:'
  150. print '\tNotestreams:', len(notestreams)
  151. print '\tTotal:', len(streams)
  152. if options.total:
  153. tot_streams += len(streams)
  154. max_streams = max(max_streams, len(streams))
  155. tot_notestreams += len(notestreams)
  156. max_notestreams = max(max_notestreams, len(notestreams))
  157. if not (options.groups or options.notes or options.histogram or options.histogram_tracks or options.vel_hist or options.vel_hist_tracks or options.duration or options.duty_cycle or options.aux):
  158. continue
  159. if options.groups:
  160. groups = {}
  161. for s in notestreams:
  162. group = s.get('group', '<anonymous>')
  163. groups[group] = groups.get(group, 0) + 1
  164. if options.total:
  165. tot_groups[group] = tot_groups.get(group, 0) + 1
  166. print 'Groups:'
  167. for name, cnt in groups.iteritems():
  168. print '\t{} ({} streams)'.format(name, cnt)
  169. if options.aux:
  170. import midi
  171. fr = midi.FileReader()
  172. fr.RunningStatus = None # XXX Hack
  173. print 'Aux stream data:'
  174. for aidx, astream in enumerate(auxstreams):
  175. evs = astream.findall('ev')
  176. failed = 0
  177. print '\tFrom stream {}, {} events:'.format(aidx, len(evs))
  178. for ev in evs:
  179. try:
  180. data = eval(ev.get('data'))
  181. mev = fr.parse_midi_event(iter(data))
  182. except AssertionError:
  183. failed += 1
  184. else:
  185. print '\t\tAt time {}: {}'.format(ev.get('time'), mev)
  186. print '\t\t(...and {} others which failed to parse)'.format(failed)
  187. if not (options.notes or options.notes_stream or options.histogram or options.histogram_tracks or options.vel_hist or options.vel_hist_tracks or options.duration or options.duty_cycle):
  188. continue
  189. if options.notes:
  190. note_cnt = 0
  191. if options.notes_stream:
  192. notes_stream = [0] * len(notestreams)
  193. if options.histogram:
  194. pitches = {}
  195. if options.histogram_tracks:
  196. pitch_tracks = [{} for i in notestreams]
  197. if options.vel_hist:
  198. velocities = {}
  199. if options.vel_hist_tracks:
  200. velocities_tracks = [{} for i in notestreams]
  201. if options.duration or options.duty_cycle:
  202. max_dur = 0
  203. if options.duty_cycle:
  204. cum_dur = [0.0] * len(notestreams)
  205. for sidx, stream in enumerate(notestreams):
  206. notes = stream.findall('note')
  207. for note in notes:
  208. pitch = float(note.get('pitch'))
  209. ampl = int(127 * float(note.get('ampl', float(note.get('vel', 127.0)) / 127.0)))
  210. time = float(note.get('time'))
  211. dur = float(note.get('dur'))
  212. if options.notes:
  213. note_cnt += 1
  214. if options.total:
  215. tot_note_cnt += 1
  216. if options.notes_stream:
  217. notes_stream[sidx] += 1
  218. if options.histogram:
  219. pitches[pitch] = pitches.get(pitch, 0) + 1
  220. if options.total:
  221. tot_pitches[pitch] = tot_pitches.get(pitch, 0) + 1
  222. if options.histogram_tracks:
  223. pitch_tracks[sidx][pitch] = pitch_tracks[sidx].get(pitch, 0) + 1
  224. if options.vel_hist:
  225. velocities[ampl] = velocities.get(ampl, 0) + 1
  226. if options.total:
  227. tot_velocities[ampl] = tot_velocities.get(ampl, 0) + 1
  228. if options.vel_hist_tracks:
  229. velocities_tracks[sidx][ampl] = velocities_tracks[sidx].get(ampl, 0) + 1
  230. if (options.duration or options.duty_cycle) and time + dur > max_dur:
  231. max_dur = time + dur
  232. if options.duty_cycle:
  233. cum_dur[sidx] += dur
  234. if options.notes and options.total:
  235. max_note_cnt = max(max_note_cnt, note_cnt)
  236. if options.histogram_tracks:
  237. for sidx, hist in enumerate(pitch_tracks):
  238. print 'Stream {} (group {}) pitch histogram:'.format(sidx, notestreams[sidx].get('group', '<anonymous>'))
  239. show_hist(hist)
  240. if options.vel_hist_tracks:
  241. for sidx, hist in enumerate(velocities_tracks):
  242. print 'Stream {} (group {}) velocity histogram:'.format(sidx, notestreams[sidx].get('group', '<anonymous>'))
  243. show_hist(hist)
  244. if options.notes_stream:
  245. for sidx, value in enumerate(notes_stream):
  246. print 'Stream {} (group {}) note count: {}'.format(sidx, notestreams[sidx].get('group', '<anonymous>'), value)
  247. if options.duty_cycle:
  248. for sidx, value in enumerate(cum_dur):
  249. print 'Stream {} (group {}) duty cycle: {}'.format(sidx, notestreams[sidx].get('group', '<anonymous>'), value / max_dur)
  250. if options.notes:
  251. print 'Total notes: {}'.format(note_cnt)
  252. if options.histogram:
  253. print 'Pitch histogram:'
  254. show_hist(pitches)
  255. if options.vel_hist:
  256. print 'Velocity histogram:'
  257. show_hist(velocities)
  258. if options.duration:
  259. print 'Playing duration: {}'.format(max_dur)
  260. if options.total:
  261. print 'Totals:'
  262. if options.number:
  263. print '\tTotal streams:', tot_streams
  264. print '\tMax streams:', max_streams
  265. print '\tTotal notestreams:', tot_notestreams
  266. print '\tMax notestreams:', max_notestreams
  267. print
  268. if options.notes:
  269. print '\tTotal notes:', tot_note_cnt
  270. print '\tMax notes:', max_note_cnt
  271. print
  272. if options.groups:
  273. print '\tGroups:'
  274. for grp, cnt in tot_groups.iteritems():
  275. print '\t\t', grp, ':', cnt
  276. print
  277. if options.histogram:
  278. print 'Overall pitch histogram:'
  279. show_hist(tot_pitches)
  280. if options.vel_hist:
  281. print 'Overall velocity histogram:'
  282. show_hist(tot_velocities)