shiv.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. # IV file viewer
  2. import xml.etree.ElementTree as ET
  3. import optparse
  4. import sys
  5. parser = optparse.OptionParser()
  6. parser.add_option('-n', '--number', dest='number', action='store_true', help='Show number of tracks')
  7. parser.add_option('-g', '--groups', dest='groups', action='store_true', help='Show group names')
  8. parser.add_option('-N', '--notes', dest='notes', action='store_true', help='Show number of notes')
  9. parser.add_option('-M', '--notes-stream', dest='notes_stream', action='store_true', help='Show notes per stream')
  10. parser.add_option('-m', '--meta', dest='meta', action='store_true', help='Show meta track information')
  11. parser.add_option('--histogram', dest='histogram', action='store_true', help='Show a histogram distribution of pitches')
  12. parser.add_option('--histogram-tracks', dest='histogram_tracks', action='store_true', help='Show a histogram distribution of pitches per track')
  13. parser.add_option('--vel-hist', dest='vel_hist', action='store_true', help='Show a histogram distribution of velocities')
  14. parser.add_option('--vel-hist-tracks', dest='vel_hist_tracks', action='store_true', help='Show a histogram distributions of velocities per track')
  15. parser.add_option('-d', '--duration', dest='duration', action='store_true', help='Show the duration of the piece')
  16. 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')
  17. parser.add_option('-H', '--height', dest='height', type='int', help='Height of histograms')
  18. parser.add_option('-C', '--no-color', dest='no_color', action='store_true', help='Don\'t use ANSI color escapes')
  19. parser.add_option('-x', '--aux', dest='aux', action='store_true', help='Show information about the auxiliary streams')
  20. parser.add_option('-a', '--almost-all', dest='almost_all', action='store_true', help='Show useful information')
  21. parser.add_option('-A', '--all', dest='all', action='store_true', help='Show everything')
  22. parser.set_defaults(height=20)
  23. options, args = parser.parse_args()
  24. if options.almost_all or options.all:
  25. options.number = True
  26. options.groups = True
  27. options.notes = True
  28. options.notes_stream = True
  29. options.histogram = True
  30. options.vel_hist = True
  31. options.duration = True
  32. options.duty_cycle = True
  33. if options.all:
  34. options.aux = True
  35. options.meta = True
  36. options.histogram_tracks= True
  37. options.vel_hist_tracks = True
  38. if options.no_color:
  39. class COL:
  40. NONE=''
  41. RED=''
  42. GREEN=''
  43. BLUE=''
  44. YELLOW=''
  45. MAGENTA=''
  46. CYAN=''
  47. else:
  48. class COL:
  49. NONE='\x1b[0m'
  50. RED='\x1b[31m'
  51. GREEN='\x1b[32m'
  52. BLUE='\x1b[34m'
  53. YELLOW='\x1b[33m'
  54. MAGENTA='\x1b[35m'
  55. CYAN='\x1b[36m'
  56. def show_hist(values, height=None):
  57. if not values:
  58. print '{empty histogram}'
  59. if height is None:
  60. height = options.height
  61. xs, ys = values.keys(), values.values()
  62. minx, maxx = min(xs), max(xs)
  63. miny, maxy = min(ys), max(ys)
  64. xv = range(minx, maxx + 1)
  65. incs = max((maxy - miny) / height, 1)
  66. print COL.CYAN + '\t --' + '-' * len(xv) + COL.NONE
  67. for ub in range(maxy + incs, miny, -incs):
  68. print '{}{}\t | {}{}{}'.format(COL.CYAN, ub, COL.YELLOW, ''.join(['#' if values.get(x) > (ub - incs) else ' ' for x in xv]), COL.NONE)
  69. print COL.CYAN + '\t |-' + '-' * len(xv) + COL.NONE
  70. xvs = map(str, xv)
  71. for i in range(max(map(len, xvs))):
  72. print COL.CYAN + '\t ' + ''.join([s[i] if len(s) > i else ' ' for s in xvs]) + COL.NONE
  73. print
  74. xcs = map(str, [values.get(x, 0) for x in xv])
  75. for i in range(max(map(len, xcs))):
  76. print COL.YELLOW + '\t ' + ''.join([s[i] if len(s) > i else ' ' for s in xcs]) + COL.NONE
  77. print
  78. for fname in args:
  79. try:
  80. iv = ET.parse(fname).getroot()
  81. except IOError:
  82. import traceback
  83. traceback.print_exc()
  84. print 'Bad file :', fname, ', skipping...'
  85. continue
  86. print
  87. print 'File :', fname
  88. print '\t<computing...>'
  89. if options.meta:
  90. print 'Metatrack:',
  91. meta = iv.find('./meta')
  92. if len(meta):
  93. print 'exists'
  94. print '\tBPM track:',
  95. bpms = meta.find('./bpms')
  96. if len(bpms):
  97. print 'exists'
  98. for elem in bpms.iterfind('./bpm'):
  99. print '\t\tAt ticks {}, time {}: {} bpm'.format(elem.get('ticks'), elem.get('time'), elem.get('bpm'))
  100. 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):
  101. continue
  102. streams = iv.findall('./streams/stream')
  103. notestreams = [s for s in streams if s.get('type') == 'ns']
  104. auxstreams = [s for s in streams if s.get('type') == 'aux']
  105. if options.number:
  106. print 'Stream count:'
  107. print '\tNotestreams:', len(notestreams)
  108. print '\tTotal:', len(streams)
  109. 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):
  110. continue
  111. if options.groups:
  112. groups = {}
  113. for s in notestreams:
  114. group = s.get('group', '<anonymous>')
  115. groups[group] = groups.get(group, 0) + 1
  116. print 'Groups:'
  117. for name, cnt in groups.iteritems():
  118. print '\t{} ({} streams)'.format(name, cnt)
  119. if options.aux:
  120. import midi
  121. fr = midi.FileReader()
  122. fr.RunningStatus = None # XXX Hack
  123. print 'Aux stream data:'
  124. for aidx, astream in enumerate(auxstreams):
  125. evs = astream.findall('ev')
  126. failed = 0
  127. print '\tFrom stream {}, {} events:'.format(aidx, len(evs))
  128. for ev in evs:
  129. try:
  130. data = eval(ev.get('data'))
  131. mev = fr.parse_midi_event(iter(data))
  132. except AssertionError:
  133. failed += 1
  134. else:
  135. print '\t\tAt time {}: {}'.format(ev.get('time'), mev)
  136. print '\t\t(...and {} others which failed to parse)'.format(failed)
  137. 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):
  138. continue
  139. if options.notes:
  140. note_cnt = 0
  141. if options.notes_stream:
  142. notes_stream = [0] * len(notestreams)
  143. if options.histogram:
  144. pitches = {}
  145. if options.histogram_tracks:
  146. pitch_tracks = [{} for i in notestreams]
  147. if options.vel_hist:
  148. velocities = {}
  149. if options.vel_hist_tracks:
  150. velocities_tracks = [{} for i in notestreams]
  151. if options.duration or options.duty_cycle:
  152. max_dur = 0
  153. if options.duty_cycle:
  154. cum_dur = [0.0] * len(notestreams)
  155. for sidx, stream in enumerate(notestreams):
  156. notes = stream.findall('note')
  157. for note in notes:
  158. pitch = int(note.get('pitch'))
  159. vel = int(note.get('vel'))
  160. time = float(note.get('time'))
  161. dur = float(note.get('dur'))
  162. if options.notes:
  163. note_cnt += 1
  164. if options.notes_stream:
  165. notes_stream[sidx] += 1
  166. if options.histogram:
  167. pitches[pitch] = pitches.get(pitch, 0) + 1
  168. if options.histogram_tracks:
  169. pitch_tracks[sidx][pitch] = pitch_tracks[sidx].get(pitch, 0) + 1
  170. if options.vel_hist:
  171. velocities[vel] = velocities.get(vel, 0) + 1
  172. if options.vel_hist_tracks:
  173. velocities_tracks[sidx][vel] = velocities_tracks[sidx].get(vel, 0) + 1
  174. if (options.duration or options.duty_cycle) and time + dur > max_dur:
  175. max_dur = time + dur
  176. if options.duty_cycle:
  177. cum_dur[sidx] += dur
  178. if options.histogram_tracks:
  179. for sidx, hist in enumerate(pitch_tracks):
  180. print 'Stream {} (group {}) pitch histogram:'.format(sidx, notestreams[sidx].get('group', '<anonymous>'))
  181. show_hist(hist)
  182. if options.vel_hist_tracks:
  183. for sidx, hist in enumerate(velocities_tracks):
  184. print 'Stream {} (group {}) velocity histogram:'.format(sidx, notestreams[sidx].get('group', '<anonymous>'))
  185. show_hist(hist)
  186. if options.notes_stream:
  187. for sidx, value in enumerate(notes_stream):
  188. print 'Stream {} (group {}) note count: {}'.format(sidx, notestreams[sidx].get('group', '<anonymous>'), value)
  189. if options.duty_cycle:
  190. for sidx, value in enumerate(cum_dur):
  191. print 'Stream {} (group {}) duty cycle: {}'.format(sidx, notestreams[sidx].get('group', '<anonymous>'), value / max_dur)
  192. if options.notes:
  193. print 'Total notes: {}'.format(note_cnt)
  194. if options.histogram:
  195. print 'Pitch histogram:'
  196. show_hist(pitches)
  197. if options.vel_hist:
  198. print 'Velocity histogram:'
  199. show_hist(velocities)
  200. if options.duration:
  201. print 'Playing duration: {}'.format(max_dur)