midilib 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. data/ChangeLog +87 -0
  2. data/Credits +8 -0
  3. data/README +361 -0
  4. data/Rakefile +68 -0
  5. data/TODO +25 -0
  6. data/examples/NoFences.mid +0 -0
  7. data/examples/from_scratch.mid +0 -0
  8. data/examples/from_scratch.rb +56 -0
  9. data/examples/reader2text.rb +220 -0
  10. data/examples/seq2text.rb +41 -0
  11. data/examples/strings.rb +34 -0
  12. data/examples/transpose.rb +75 -0
  13. data/html/classes/MIDI.html +738 -0
  14. data/html/classes/MIDI/ActiveSense.html +156 -0
  15. data/html/classes/MIDI/ActiveSense.src/M000136.html +18 -0
  16. data/html/classes/MIDI/ActiveSense.src/M000137.html +18 -0
  17. data/html/classes/MIDI/ChannelEvent.html +179 -0
  18. data/html/classes/MIDI/ChannelEvent.src/M000124.html +20 -0
  19. data/html/classes/MIDI/ChannelEvent.src/M000125.html +18 -0
  20. data/html/classes/MIDI/ChannelPressure.html +184 -0
  21. data/html/classes/MIDI/ChannelPressure.src/M000088.html +19 -0
  22. data/html/classes/MIDI/ChannelPressure.src/M000089.html +21 -0
  23. data/html/classes/MIDI/ChannelPressure.src/M000090.html +18 -0
  24. data/html/classes/MIDI/Clock.html +156 -0
  25. data/html/classes/MIDI/Clock.src/M000134.html +18 -0
  26. data/html/classes/MIDI/Clock.src/M000135.html +18 -0
  27. data/html/classes/MIDI/Continue.html +156 -0
  28. data/html/classes/MIDI/Continue.src/M000132.html +18 -0
  29. data/html/classes/MIDI/Continue.src/M000133.html +18 -0
  30. data/html/classes/MIDI/Controller.html +189 -0
  31. data/html/classes/MIDI/Controller.src/M000129.html +21 -0
  32. data/html/classes/MIDI/Controller.src/M000130.html +22 -0
  33. data/html/classes/MIDI/Controller.src/M000131.html +18 -0
  34. data/html/classes/MIDI/Event.html +424 -0
  35. data/html/classes/MIDI/Event.src/M000145.html +27 -0
  36. data/html/classes/MIDI/Event.src/M000146.html +18 -0
  37. data/html/classes/MIDI/Event.src/M000147.html +18 -0
  38. data/html/classes/MIDI/Event.src/M000148.html +18 -0
  39. data/html/classes/MIDI/Event.src/M000149.html +18 -0
  40. data/html/classes/MIDI/Event.src/M000150.html +18 -0
  41. data/html/classes/MIDI/Event.src/M000151.html +18 -0
  42. data/html/classes/MIDI/Event.src/M000152.html +18 -0
  43. data/html/classes/MIDI/Event.src/M000153.html +18 -0
  44. data/html/classes/MIDI/Event.src/M000154.html +22 -0
  45. data/html/classes/MIDI/Event.src/M000155.html +18 -0
  46. data/html/classes/MIDI/Event.src/M000156.html +18 -0
  47. data/html/classes/MIDI/Event.src/M000157.html +18 -0
  48. data/html/classes/MIDI/IO.html +121 -0
  49. data/html/classes/MIDI/IO/MIDIFile.html +925 -0
  50. data/html/classes/MIDI/IO/MIDIFile.src/M000028.html +22 -0
  51. data/html/classes/MIDI/IO/MIDIFile.src/M000029.html +24 -0
  52. data/html/classes/MIDI/IO/MIDIFile.src/M000030.html +19 -0
  53. data/html/classes/MIDI/IO/MIDIFile.src/M000031.html +19 -0
  54. data/html/classes/MIDI/IO/MIDIFile.src/M000032.html +17 -0
  55. data/html/classes/MIDI/IO/MIDIFile.src/M000033.html +17 -0
  56. data/html/classes/MIDI/IO/MIDIFile.src/M000034.html +17 -0
  57. data/html/classes/MIDI/IO/MIDIFile.src/M000035.html +17 -0
  58. data/html/classes/MIDI/IO/MIDIFile.src/M000036.html +17 -0
  59. data/html/classes/MIDI/IO/MIDIFile.src/M000037.html +17 -0
  60. data/html/classes/MIDI/IO/MIDIFile.src/M000038.html +17 -0
  61. data/html/classes/MIDI/IO/MIDIFile.src/M000039.html +17 -0
  62. data/html/classes/MIDI/IO/MIDIFile.src/M000040.html +17 -0
  63. data/html/classes/MIDI/IO/MIDIFile.src/M000041.html +17 -0
  64. data/html/classes/MIDI/IO/MIDIFile.src/M000042.html +17 -0
  65. data/html/classes/MIDI/IO/MIDIFile.src/M000043.html +17 -0
  66. data/html/classes/MIDI/IO/MIDIFile.src/M000044.html +17 -0
  67. data/html/classes/MIDI/IO/MIDIFile.src/M000045.html +17 -0
  68. data/html/classes/MIDI/IO/MIDIFile.src/M000046.html +17 -0
  69. data/html/classes/MIDI/IO/MIDIFile.src/M000047.html +17 -0
  70. data/html/classes/MIDI/IO/MIDIFile.src/M000048.html +17 -0
  71. data/html/classes/MIDI/IO/MIDIFile.src/M000049.html +17 -0
  72. data/html/classes/MIDI/IO/MIDIFile.src/M000050.html +17 -0
  73. data/html/classes/MIDI/IO/MIDIFile.src/M000051.html +17 -0
  74. data/html/classes/MIDI/IO/MIDIFile.src/M000052.html +17 -0
  75. data/html/classes/MIDI/IO/MIDIFile.src/M000053.html +43 -0
  76. data/html/classes/MIDI/IO/MIDIFile.src/M000054.html +34 -0
  77. data/html/classes/MIDI/IO/MIDIFile.src/M000055.html +96 -0
  78. data/html/classes/MIDI/IO/MIDIFile.src/M000056.html +18 -0
  79. data/html/classes/MIDI/IO/MIDIFile.src/M000057.html +48 -0
  80. data/html/classes/MIDI/IO/MIDIFile.src/M000058.html +42 -0
  81. data/html/classes/MIDI/IO/MIDIFile.src/M000059.html +19 -0
  82. data/html/classes/MIDI/IO/MIDIFile.src/M000060.html +19 -0
  83. data/html/classes/MIDI/IO/MIDIFile.src/M000061.html +20 -0
  84. data/html/classes/MIDI/IO/MIDIFile.src/M000062.html +21 -0
  85. data/html/classes/MIDI/IO/MIDIFile.src/M000063.html +31 -0
  86. data/html/classes/MIDI/IO/MIDIFile.src/M000064.html +20 -0
  87. data/html/classes/MIDI/IO/MIDIFile.src/M000065.html +22 -0
  88. data/html/classes/MIDI/IO/MIDIFile.src/M000066.html +30 -0
  89. data/html/classes/MIDI/IO/MIDIFile.src/M000067.html +18 -0
  90. data/html/classes/MIDI/IO/MIDIFile.src/M000068.html +20 -0
  91. data/html/classes/MIDI/IO/MIDIFile.src/M000069.html +18 -0
  92. data/html/classes/MIDI/IO/MIDIFile.src/M000070.html +18 -0
  93. data/html/classes/MIDI/IO/SeqReader.html +460 -0
  94. data/html/classes/MIDI/IO/SeqReader.src/M000001.html +22 -0
  95. data/html/classes/MIDI/IO/SeqReader.src/M000002.html +22 -0
  96. data/html/classes/MIDI/IO/SeqReader.src/M000003.html +21 -0
  97. data/html/classes/MIDI/IO/SeqReader.src/M000004.html +34 -0
  98. data/html/classes/MIDI/IO/SeqReader.src/M000005.html +26 -0
  99. data/html/classes/MIDI/IO/SeqReader.src/M000006.html +28 -0
  100. data/html/classes/MIDI/IO/SeqReader.src/M000007.html +21 -0
  101. data/html/classes/MIDI/IO/SeqReader.src/M000008.html +19 -0
  102. data/html/classes/MIDI/IO/SeqReader.src/M000009.html +19 -0
  103. data/html/classes/MIDI/IO/SeqReader.src/M000010.html +19 -0
  104. data/html/classes/MIDI/IO/SeqReader.src/M000011.html +19 -0
  105. data/html/classes/MIDI/IO/SeqReader.src/M000012.html +19 -0
  106. data/html/classes/MIDI/IO/SeqReader.src/M000013.html +18 -0
  107. data/html/classes/MIDI/IO/SeqReader.src/M000014.html +18 -0
  108. data/html/classes/MIDI/IO/SeqReader.src/M000015.html +27 -0
  109. data/html/classes/MIDI/IO/SeqReader.src/M000016.html +18 -0
  110. data/html/classes/MIDI/IO/SeqReader.src/M000017.html +18 -0
  111. data/html/classes/MIDI/IO/SeqReader.src/M000018.html +18 -0
  112. data/html/classes/MIDI/IO/SeqWriter.html +267 -0
  113. data/html/classes/MIDI/IO/SeqWriter.src/M000019.html +19 -0
  114. data/html/classes/MIDI/IO/SeqWriter.src/M000020.html +25 -0
  115. data/html/classes/MIDI/IO/SeqWriter.src/M000021.html +22 -0
  116. data/html/classes/MIDI/IO/SeqWriter.src/M000022.html +54 -0
  117. data/html/classes/MIDI/IO/SeqWriter.src/M000023.html +49 -0
  118. data/html/classes/MIDI/IO/SeqWriter.src/M000024.html +21 -0
  119. data/html/classes/MIDI/IO/SeqWriter.src/M000025.html +19 -0
  120. data/html/classes/MIDI/IO/SeqWriter.src/M000026.html +24 -0
  121. data/html/classes/MIDI/IO/SeqWriter.src/M000027.html +26 -0
  122. data/html/classes/MIDI/Marker.html +139 -0
  123. data/html/classes/MIDI/Marker.src/M000107.html +18 -0
  124. data/html/classes/MIDI/MetaEvent.html +189 -0
  125. data/html/classes/MIDI/MetaEvent.src/M000167.html +21 -0
  126. data/html/classes/MIDI/MetaEvent.src/M000168.html +23 -0
  127. data/html/classes/MIDI/MetaEvent.src/M000169.html +53 -0
  128. data/html/classes/MIDI/NoteEvent.html +233 -0
  129. data/html/classes/MIDI/NoteEvent.src/M000091.html +21 -0
  130. data/html/classes/MIDI/NoteEvent.src/M000092.html +20 -0
  131. data/html/classes/MIDI/NoteEvent.src/M000093.html +18 -0
  132. data/html/classes/MIDI/NoteEvent.src/M000094.html +22 -0
  133. data/html/classes/MIDI/NoteOffEvent.html +169 -0
  134. data/html/classes/MIDI/NoteOffEvent.src/M000086.html +19 -0
  135. data/html/classes/MIDI/NoteOffEvent.src/M000087.html +19 -0
  136. data/html/classes/MIDI/NoteOnEvent.html +169 -0
  137. data/html/classes/MIDI/NoteOnEvent.src/M000102.html +19 -0
  138. data/html/classes/MIDI/NoteOnEvent.src/M000103.html +19 -0
  139. data/html/classes/MIDI/PitchBend.html +184 -0
  140. data/html/classes/MIDI/PitchBend.src/M000104.html +19 -0
  141. data/html/classes/MIDI/PitchBend.src/M000105.html +22 -0
  142. data/html/classes/MIDI/PitchBend.src/M000106.html +18 -0
  143. data/html/classes/MIDI/PolyPressure.html +186 -0
  144. data/html/classes/MIDI/PolyPressure.src/M000170.html +18 -0
  145. data/html/classes/MIDI/PolyPressure.src/M000171.html +18 -0
  146. data/html/classes/MIDI/PolyPressure.src/M000172.html +18 -0
  147. data/html/classes/MIDI/PolyPressure.src/M000173.html +19 -0
  148. data/html/classes/MIDI/ProgramChange.html +184 -0
  149. data/html/classes/MIDI/ProgramChange.src/M000099.html +19 -0
  150. data/html/classes/MIDI/ProgramChange.src/M000100.html +21 -0
  151. data/html/classes/MIDI/ProgramChange.src/M000101.html +18 -0
  152. data/html/classes/MIDI/Realtime.html +171 -0
  153. data/html/classes/MIDI/Realtime.src/M000083.html +19 -0
  154. data/html/classes/MIDI/Realtime.src/M000084.html +20 -0
  155. data/html/classes/MIDI/Realtime.src/M000085.html +18 -0
  156. data/html/classes/MIDI/Sequence.html +469 -0
  157. data/html/classes/MIDI/Sequence.src/M000108.html +28 -0
  158. data/html/classes/MIDI/Sequence.src/M000109.html +21 -0
  159. data/html/classes/MIDI/Sequence.src/M000110.html +22 -0
  160. data/html/classes/MIDI/Sequence.src/M000113.html +18 -0
  161. data/html/classes/MIDI/Sequence.src/M000114.html +27 -0
  162. data/html/classes/MIDI/Sequence.src/M000115.html +18 -0
  163. data/html/classes/MIDI/Sequence.src/M000116.html +19 -0
  164. data/html/classes/MIDI/Sequence.src/M000117.html +19 -0
  165. data/html/classes/MIDI/Sequence.src/M000118.html +20 -0
  166. data/html/classes/MIDI/Sequence.src/M000119.html +19 -0
  167. data/html/classes/MIDI/Sequence.src/M000120.html +18 -0
  168. data/html/classes/MIDI/SongPointer.html +184 -0
  169. data/html/classes/MIDI/SongPointer.src/M000138.html +19 -0
  170. data/html/classes/MIDI/SongPointer.src/M000139.html +22 -0
  171. data/html/classes/MIDI/SongPointer.src/M000140.html +18 -0
  172. data/html/classes/MIDI/SongSelect.html +184 -0
  173. data/html/classes/MIDI/SongSelect.src/M000126.html +19 -0
  174. data/html/classes/MIDI/SongSelect.src/M000127.html +21 -0
  175. data/html/classes/MIDI/SongSelect.src/M000128.html +18 -0
  176. data/html/classes/MIDI/Start.html +156 -0
  177. data/html/classes/MIDI/Start.src/M000097.html +18 -0
  178. data/html/classes/MIDI/Start.src/M000098.html +18 -0
  179. data/html/classes/MIDI/Stop.html +156 -0
  180. data/html/classes/MIDI/Stop.src/M000095.html +18 -0
  181. data/html/classes/MIDI/Stop.src/M000096.html +18 -0
  182. data/html/classes/MIDI/SystemCommon.html +139 -0
  183. data/html/classes/MIDI/SystemCommon.src/M000144.html +19 -0
  184. data/html/classes/MIDI/SystemExclusive.html +184 -0
  185. data/html/classes/MIDI/SystemExclusive.src/M000141.html +19 -0
  186. data/html/classes/MIDI/SystemExclusive.src/M000142.html +23 -0
  187. data/html/classes/MIDI/SystemExclusive.src/M000143.html +18 -0
  188. data/html/classes/MIDI/SystemReset.html +156 -0
  189. data/html/classes/MIDI/SystemReset.src/M000081.html +18 -0
  190. data/html/classes/MIDI/SystemReset.src/M000082.html +18 -0
  191. data/html/classes/MIDI/Tempo.html +250 -0
  192. data/html/classes/MIDI/Tempo.src/M000158.html +18 -0
  193. data/html/classes/MIDI/Tempo.src/M000159.html +18 -0
  194. data/html/classes/MIDI/Tempo.src/M000160.html +18 -0
  195. data/html/classes/MIDI/Tempo.src/M000161.html +18 -0
  196. data/html/classes/MIDI/Tempo.src/M000162.html +18 -0
  197. data/html/classes/MIDI/Tempo.src/M000163.html +25 -0
  198. data/html/classes/MIDI/Tempo.src/M000164.html +18 -0
  199. data/html/classes/MIDI/Track.html +380 -0
  200. data/html/classes/MIDI/Track.src/M000071.html +23 -0
  201. data/html/classes/MIDI/Track.src/M000072.html +21 -0
  202. data/html/classes/MIDI/Track.src/M000073.html +26 -0
  203. data/html/classes/MIDI/Track.src/M000074.html +18 -0
  204. data/html/classes/MIDI/Track.src/M000075.html +22 -0
  205. data/html/classes/MIDI/Track.src/M000076.html +20 -0
  206. data/html/classes/MIDI/Track.src/M000077.html +22 -0
  207. data/html/classes/MIDI/Track.src/M000078.html +22 -0
  208. data/html/classes/MIDI/Track.src/M000079.html +18 -0
  209. data/html/classes/MIDI/Track.src/M000080.html +19 -0
  210. data/html/classes/MIDI/TuneRequest.html +171 -0
  211. data/html/classes/MIDI/TuneRequest.src/M000121.html +18 -0
  212. data/html/classes/MIDI/TuneRequest.src/M000122.html +20 -0
  213. data/html/classes/MIDI/TuneRequest.src/M000123.html +18 -0
  214. data/html/classes/MIDI/Utils.html +190 -0
  215. data/html/classes/MIDI/Utils.src/M000165.html +20 -0
  216. data/html/classes/MIDI/Utils.src/M000166.html +25 -0
  217. data/html/created.rid +1 -0
  218. data/html/files/README.html +599 -0
  219. data/html/files/TODO.html +142 -0
  220. data/html/files/lib/midilib/consts_rb.html +107 -0
  221. data/html/files/lib/midilib/event_rb.html +109 -0
  222. data/html/files/lib/midilib/info_rb.html +101 -0
  223. data/html/files/lib/midilib/io/midifile_rb.html +108 -0
  224. data/html/files/lib/midilib/io/seqreader_rb.html +110 -0
  225. data/html/files/lib/midilib/io/seqwriter_rb.html +115 -0
  226. data/html/files/lib/midilib/sequence_rb.html +109 -0
  227. data/html/files/lib/midilib/track_rb.html +108 -0
  228. data/html/files/lib/midilib/utils_rb.html +101 -0
  229. data/html/files/lib/midilib_rb.html +124 -0
  230. data/html/fr_class_index.html +59 -0
  231. data/html/fr_file_index.html +38 -0
  232. data/html/fr_method_index.html +199 -0
  233. data/html/index.html +24 -0
  234. data/html/rdoc-style.css +208 -0
  235. data/install.rb +57 -0
  236. data/lib/midilib.rb +16 -0
  237. data/lib/midilib/consts.rb +422 -0
  238. data/lib/midilib/event.rb +559 -0
  239. data/lib/midilib/info.rb +9 -0
  240. data/lib/midilib/io/midifile.rb +446 -0
  241. data/lib/midilib/io/seqreader.rb +198 -0
  242. data/lib/midilib/io/seqwriter.rb +151 -0
  243. data/lib/midilib/sequence.rb +144 -0
  244. data/lib/midilib/track.rb +115 -0
  245. data/lib/midilib/utils.rb +36 -0
  246. data/test/event_equality.rb +81 -0
  247. data/test/test_event.rb +116 -0
  248. data/test/test_io.rb +55 -0
  249. data/test/test_sequence.rb +68 -0
  250. data/test/test_track.rb +111 -0
  251. data/test/test_varlen.rb +40 -0
  252. metadata +330 -0
@@ -0,0 +1,198 @@
1
+ require 'midilib/io/midifile'
2
+ require 'midilib/track'
3
+ require 'midilib/event'
4
+
5
+ module MIDI
6
+
7
+ module IO
8
+
9
+ # Reads MIDI files. As a subclass of MIDIFile, this class implements
10
+ # the callback methods for each MIDI event and use them to build Track
11
+ # and Event objects and give the tracks to a Sequence.
12
+ #
13
+ # We append new events to the end of a track's event list, bypassing a call
14
+ # to Track.#add. This means that we must call Track.recalc_times at the end
15
+ # of the track so it can update each event with its time from the track's
16
+ # start (see end_track below).
17
+ #
18
+ # META_TRACK_END events are not added to tracks. This way, we don't have to
19
+ # worry about making sure the last event is always a track end event. We
20
+ # rely on the SeqWriter to append a META_TRACK_END event to each track when
21
+ # it is output.
22
+
23
+ class SeqReader < MIDIFile
24
+
25
+ # The optional proc block is called once at the start of the file
26
+ # and again at the end of each track. There are three arguments
27
+ # to the block: the track, the track number (1 through _n_), and
28
+ # the total number of tracks.
29
+ def initialize(seq, proc = nil) # :yields: track, num_tracks, index
30
+ super()
31
+ @seq = seq
32
+ @track = nil
33
+ @chan_mask = 0
34
+ @update_block = block_given?() ? Proc.new() : proc
35
+ end
36
+
37
+ def header(format, ntrks, division)
38
+ @seq.format = format
39
+ @seq.ppqn = division
40
+
41
+ @ntrks = ntrks
42
+ @update_block.call(nil, @ntrks, 0) if @update_block
43
+ end
44
+
45
+ def start_track()
46
+ @track = Track.new(@seq)
47
+ @seq.tracks << @track
48
+
49
+ @pending = []
50
+ end
51
+
52
+ def end_track()
53
+ # Turn off any pending note on messages
54
+ @pending.each { | on | make_note_off(on, 64) }
55
+ @pending = nil
56
+
57
+ # Don't bother adding the META_TRACK_END event to the track.
58
+ # This way, we don't have to worry about making sure the
59
+ # last event is always a track end event.
60
+
61
+ # Let the track calculate event times from start of track. This is
62
+ # in lieu of calling Track.add for each event.
63
+ @track.recalc_times()
64
+
65
+ # Store bitmask of all channels used into track
66
+ @track.channels_used = @chan_mask
67
+
68
+ # call update block
69
+ @update_block.call(@track, @ntrks, @seq.tracks.length) if @update_block
70
+ end
71
+
72
+ def note_on(chan, note, vel)
73
+ if vel == 0
74
+ note_off(chan, note, 64)
75
+ return
76
+ end
77
+
78
+ on = NoteOnEvent.new(chan, note, vel, @curr_ticks)
79
+ @track.events << on
80
+ @pending << on
81
+ track_uses_channel(chan)
82
+ end
83
+
84
+ def note_off(chan, note, vel)
85
+ # Find note on, create note off, connect the two, and remove
86
+ # note on from pending list.
87
+ @pending.each_with_index { | on, i |
88
+ if on.note == note && on.channel == chan
89
+ make_note_off(on, vel)
90
+ @pending.delete_at(i)
91
+ return
92
+ end
93
+ }
94
+ $stderr.puts "note off with no earlier note on (ch #{chan}, note" +
95
+ " #{note}, vel #{vel})" if $DEBUG
96
+ end
97
+
98
+ def make_note_off(on, vel)
99
+ off = NoteOffEvent.new(on.channel, on.note, vel, @curr_ticks)
100
+ @track.events << off
101
+ on.off = off
102
+ off.on = on
103
+ end
104
+
105
+ def pressure(chan, note, press)
106
+ @track.events << PolyPressure.new(chan, note, press, @curr_ticks)
107
+ track_uses_channel(chan)
108
+ end
109
+
110
+ def controller(chan, control, value)
111
+ @track.events << Controller.new(chan, control, value, @curr_ticks)
112
+ track_uses_channel(chan)
113
+ end
114
+
115
+ def pitch_bend(chan, msb, lsb)
116
+ @track.events << PitchBend.new(chan, (msb << 8) + lsb, @curr_ticks)
117
+ track_uses_channel(chan)
118
+ end
119
+
120
+ def program(chan, program)
121
+ @track.events << ProgramChange.new(chan, program, @curr_ticks)
122
+ track_uses_channel(chan)
123
+ end
124
+
125
+ def chan_pressure(chan, press)
126
+ @track.events << ChannelPressure.new(chan, press, @curr_ticks)
127
+ track_uses_channel(chan)
128
+ end
129
+
130
+ def sysex(msg)
131
+ @track.events << SystemExclusive.new(msg, @curr_ticks)
132
+ end
133
+
134
+ def meta_misc(type, msg)
135
+ @track.events << MetaEvent.new(type, msg, @curr_ticks)
136
+ end
137
+
138
+ # --
139
+ # def sequencer_specific(type, msg)
140
+ # end
141
+
142
+ # def sequence_number(num)
143
+ # end
144
+ # ++
145
+
146
+ def text(type, msg)
147
+ case type
148
+ when META_SEQ_NAME
149
+ @track.events << MetaEvent.new(META_SEQ_NAME, msg, 0)
150
+ when META_INSTRUMENT
151
+ @track.instrument = msg
152
+ when META_MARKER
153
+ @track.events << Marker.new(msg, @curr_ticks)
154
+ else
155
+ $stderr.puts "text = #{msg}, type = #{type}" if $DEBUG
156
+ end
157
+ end
158
+
159
+ # --
160
+ # Don't bother adding the META_TRACK_END event to the track. This way,
161
+ # we don't have to worry about always making sure the last event is
162
+ # always a track end event. We just have to make sure to write one when
163
+ # the track is output back to a file.
164
+ # def eot()
165
+ # @track.events << MetaEvent.new(META_TRACK_END, nil, @curr_ticks)
166
+ # end
167
+ # ++
168
+
169
+ def time_signature(numer, denom, clocks, qnotes)
170
+ @seq.time_signature(numer, denom, clocks, qnotes)
171
+ end
172
+
173
+ # --
174
+ # def smpte(hour, min, sec, frame, fract)
175
+ # end
176
+ # ++
177
+
178
+ def tempo(microsecs)
179
+ @track.events << Tempo.new(microsecs, @curr_ticks)
180
+ end
181
+
182
+ # --
183
+ # def key_signature(sharpflat, is_minor)
184
+ # end
185
+
186
+ # def arbitrary(msg)
187
+ # end
188
+ # ++
189
+
190
+ # Return true if the current track uses the specified channel.
191
+ def track_uses_channel(chan)
192
+ @chan_mask = @chan_mask | (1 << chan)
193
+ end
194
+
195
+ end
196
+
197
+ end
198
+ end
@@ -0,0 +1,151 @@
1
+ # Writes MIDI files.
2
+
3
+ require 'midilib/event'
4
+ require 'midilib/utils'
5
+
6
+ module MIDI
7
+
8
+ module IO
9
+
10
+ class SeqWriter
11
+
12
+ def initialize(seq, proc = nil) # :yields: num_tracks, index
13
+ @seq = seq
14
+ @update_block = block_given?() ? Proc.new() : proc
15
+ end
16
+
17
+ # Writes a MIDI format 1 file.
18
+ def write_to(io)
19
+ @io = io
20
+ @bytes_written = 0
21
+ write_header()
22
+ @update_block.call(nil, @seq.tracks.length, 0) if @update_block
23
+ @seq.tracks.each_with_index { | track, i |
24
+ write_track(track)
25
+ @update_block.call(track, @seq.tracks.length, i) if @update_block
26
+ }
27
+ end
28
+
29
+ def write_header
30
+ @io.print 'MThd'
31
+ write32(6)
32
+ write16(1) # Ignore sequence format; write as format 1
33
+ write16(@seq.tracks.length)
34
+ write16(@seq.ppqn)
35
+ end
36
+
37
+ def write_track(track)
38
+ @io.print 'MTrk'
39
+ track_size_file_pos = @io.tell()
40
+ write32(0) # Dummy byte count; overwritten later
41
+ @bytes_written = 0 # Reset after previous write
42
+
43
+ write_instrument(track.instrument)
44
+
45
+ prev_event = nil
46
+ prev_status = 0
47
+ track.events.each { | event |
48
+ if !event.kind_of?(Realtime)
49
+ write_var_len(event.delta_time)
50
+ end
51
+
52
+ data = event.data_as_bytes()
53
+ status = data[0] # status byte plus channel number, if any
54
+
55
+ # running status byte
56
+ status = possibly_munge_due_to_running_status_byte(data,
57
+ prev_status)
58
+
59
+ @bytes_written += @io.write(data)
60
+
61
+ prev_event = event
62
+ prev_status = status
63
+ }
64
+
65
+ # Write track end event.
66
+ event = MetaEvent.new(META_TRACK_END)
67
+ write_var_len(0)
68
+ @bytes_written += @io.write(event.data_as_bytes())
69
+
70
+ # Go back to beginning of track data and write number of bytes,
71
+ # then come back here to end of file.
72
+ @io.seek(track_size_file_pos)
73
+ write32(@bytes_written)
74
+ @io.seek(0, ::IO::SEEK_END)
75
+ end
76
+
77
+ # If we can use a running status byte, delete the status byte from
78
+ # the given data. Return the status to remember for next time as the
79
+ # running status byte for this event.
80
+ def possibly_munge_due_to_running_status_byte(data, prev_status)
81
+ status = data[0]
82
+ return status if status >= 0xf0 || prev_status >= 0xf0
83
+
84
+ chan = (status & 0x0f)
85
+ return status if chan != (prev_status & 0x0f)
86
+
87
+ status = (status & 0xf0)
88
+ prev_status = (prev_status & 0xf0)
89
+
90
+ # Both events are on the same channel. If the two status bytes are
91
+ # exactly the same, the rest is trivial. If it's note on/note off,
92
+ # we can combine those further.
93
+ if status == prev_status
94
+ data[0] = '' # delete status byte from data
95
+ return status + chan
96
+ elsif status == NOTE_OFF && data[2] == 64
97
+ # If we see a note off and the velocity is 64, we can store
98
+ # a note on with a velocity of 0. If the velocity isn't 64
99
+ # then storing a note on would be bad because the would be
100
+ # changed to 64 when reading the file back in.
101
+ data[2] = 0 # set vel to 0; do before possible shrinking
102
+ status = NOTE_ON + chan
103
+ if prev_status == NOTE_ON
104
+ data[0] = '' # delete status byte
105
+ else
106
+ data[0] = status
107
+ end
108
+ return status
109
+ else
110
+ # Can't compress data
111
+ return status + chan
112
+ end
113
+ end
114
+
115
+ def write_instrument(instrument)
116
+ event = MetaEvent.new(META_INSTRUMENT, instrument)
117
+ write_var_len(0)
118
+ data = event.data_as_bytes()
119
+ @bytes_written += @io.write(data)
120
+ end
121
+
122
+ def write_var_len(val)
123
+ buffer = Utils.as_var_len(val)
124
+ @bytes_written += @io.write(buffer)
125
+ end
126
+
127
+ def write16(val)
128
+ val = (-val | 0x8000) if val < 0
129
+
130
+ buffer = ''
131
+ buffer << ((val >> 8) & 0xff)
132
+ buffer << (val & 0xff)
133
+
134
+ @bytes_written += @io.write(buffer)
135
+ end
136
+
137
+ def write32(val)
138
+ val = (-val | 0x80000000) if val < 0
139
+
140
+ buffer = ''
141
+ buffer << ((val >> 24) & 0xff)
142
+ buffer << ((val >> 16) & 0xff)
143
+ buffer << ((val >> 8) & 0xff)
144
+ buffer << (val & 0xff)
145
+
146
+ @bytes_written += @io.write(buffer)
147
+ end
148
+ end
149
+
150
+ end
151
+ end
@@ -0,0 +1,144 @@
1
+ require 'midilib/io/seqreader'
2
+ require 'midilib/io/seqwriter'
3
+
4
+ module MIDI
5
+
6
+ # A MIDI::Sequence contains MIDI::Track objects.
7
+ class Sequence
8
+
9
+ include Enumerable
10
+
11
+ UNNAMED = 'Unnamed Sequence'
12
+ DEFAULT_TEMPO = 120
13
+
14
+ NOTE_TO_LENGTH = {
15
+ 'whole' => 4.0,
16
+ 'half' => 2.0,
17
+ 'quarter' => 1.0,
18
+ 'eighth' => 0.5,
19
+ '8th' => 0.5,
20
+ 'sixteenth' => 0.25,
21
+ '16th' => 0.25,
22
+ 'thirty second' => 0.125,
23
+ 'thirtysecond' => 0.125,
24
+ '32nd' => 0.125,
25
+ 'sixty fourth' => 0.0625,
26
+ 'sixtyfourth' => 0.0625,
27
+ '64th' => 0.0625
28
+ }
29
+
30
+ attr_accessor :tracks, :ppqn, :format
31
+ attr_accessor :numer, :denom, :clocks, :qnotes
32
+ # The class to use for reading MIDI from a stream. The default is
33
+ # MIDI::IO::SeqReader. You can change this at any time.
34
+ attr_accessor :reader_class
35
+ # The class to use for writeing MIDI from a stream. The default is
36
+ # MIDI::IO::SeqWriter. You can change this at any time.
37
+ attr_accessor :writer_class
38
+
39
+ def initialize
40
+ @tracks = Array.new()
41
+ @ppqn = 480
42
+
43
+ # Time signature
44
+ @numer = 4 # Numer + denom = 4/4 time default
45
+ @denom = 2
46
+ @clocks = @ppqn
47
+ @qnotes = 8
48
+
49
+ @reader_class = IO::SeqReader
50
+ @writer_class = IO::SeqWriter
51
+ end
52
+
53
+ # Sets the time signature.
54
+ def time_signature(numer, denom, clocks, qnotes)
55
+ @numer = numer
56
+ @denom = denom
57
+ @clocks = clocks
58
+ @qnotes = qnotes
59
+ end
60
+
61
+ # Returns the song tempo in beats per minute.
62
+ def beats_per_minute
63
+ return DEFAULT_TEMPO if @tracks.nil? || @tracks.empty?
64
+ event = @tracks.first.events.detect { | e |
65
+ e.kind_of?(MIDI::Tempo)
66
+ }
67
+ return event ? (Tempo.mpq_to_bpm(event.tempo)) : DEFAULT_TEMPO
68
+ end
69
+ alias_method :bpm, :beats_per_minute
70
+ alias_method :tempo, :beats_per_minute
71
+
72
+ # Given a note length name like "whole", "dotted quarter", or "8th
73
+ # triplet", return the length of that note in quarter notes as a delta
74
+ # time.
75
+ def note_to_delta(name)
76
+ return length_to_delta(note_to_length(name))
77
+ end
78
+
79
+ # Given a note length name like "whole", "dotted quarter", or "8th
80
+ # triplet", return the length of that note in quarter notes as a
81
+ # floating-point number, suitable for use as an argument to
82
+ # length_to_delta.
83
+ #
84
+ # Legal names are any value in NOTE_TO_LENGTH, optionally prefixed by
85
+ # "dotted_" and/or suffixed by "_triplet". So, for example,
86
+ # "dotted_quarter_triplet" returns the length of a dotted quarter-note
87
+ # triplet and "32nd" returns 1/32.
88
+ def note_to_length(name)
89
+ name.strip!
90
+ name =~ /^(dotted)?(.*?)(triplet)?$/
91
+ dotted, note_name, triplet = $1, $2, $3
92
+ note_name.strip!
93
+ mult = 1.0
94
+ mult = 1.5 if dotted
95
+ mult /= 3.0 if triplet
96
+ len = NOTE_TO_LENGTH[note_name]
97
+ raise "Sequence.note_to_length: \"#{note_name}\" not understood in \"#{name}\"" unless len
98
+ return len * mult
99
+ end
100
+
101
+ # Translates +length+ (a multiple of a quarter note) into a delta time.
102
+ # For example, 1 is a quarter note, 1.0/32.0 is a 32nd note, 1.5 is a
103
+ # dotted quarter, etc. Be aware when using division; 1/32 is zero due to
104
+ # integer mathematics and rounding. Use floating-point numbers like 1.0
105
+ # and 32.0. This method always returns an integer.
106
+ #
107
+ # See also note_to_delta and note_to_length.
108
+ def length_to_delta(length)
109
+ return (@ppqn * length).to_i
110
+ end
111
+
112
+ # Returns the name of the first track (track zero). If there are no
113
+ # tracks, returns UNNAMED.
114
+ def name
115
+ return UNNAMED if @tracks.empty?
116
+ return @tracks.first.name()
117
+ end
118
+
119
+ # Hands the name to the first track. Does nothing if there are no tracks.
120
+ def name=(name)
121
+ return if @tracks.empty?
122
+ @tracks.first.name = name
123
+ end
124
+
125
+ # Reads a MIDI stream.
126
+ def read(io, proc = nil) # :yields: track, num_tracks, index
127
+ @tracks = Array.new()
128
+ reader = @reader_class.new(self, block_given?() ? Proc.new() : proc)
129
+ reader.read_from(io)
130
+ end
131
+
132
+ # Writes to a MIDI stream.
133
+ def write(io, proc = nil) # :yields: track, num_tracks, index
134
+ writer = @writer_class.new(self, block_given?() ? Proc.new() : proc)
135
+ writer.write_to(io)
136
+ end
137
+
138
+ # Iterates over the tracks.
139
+ def each # :yields: track
140
+ @tracks.each { | track | yield track }
141
+ end
142
+
143
+ end
144
+ end