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.
- data/ChangeLog +87 -0
- data/Credits +8 -0
- data/README +361 -0
- data/Rakefile +68 -0
- data/TODO +25 -0
- data/examples/NoFences.mid +0 -0
- data/examples/from_scratch.mid +0 -0
- data/examples/from_scratch.rb +56 -0
- data/examples/reader2text.rb +220 -0
- data/examples/seq2text.rb +41 -0
- data/examples/strings.rb +34 -0
- data/examples/transpose.rb +75 -0
- data/html/classes/MIDI.html +738 -0
- data/html/classes/MIDI/ActiveSense.html +156 -0
- data/html/classes/MIDI/ActiveSense.src/M000136.html +18 -0
- data/html/classes/MIDI/ActiveSense.src/M000137.html +18 -0
- data/html/classes/MIDI/ChannelEvent.html +179 -0
- data/html/classes/MIDI/ChannelEvent.src/M000124.html +20 -0
- data/html/classes/MIDI/ChannelEvent.src/M000125.html +18 -0
- data/html/classes/MIDI/ChannelPressure.html +184 -0
- data/html/classes/MIDI/ChannelPressure.src/M000088.html +19 -0
- data/html/classes/MIDI/ChannelPressure.src/M000089.html +21 -0
- data/html/classes/MIDI/ChannelPressure.src/M000090.html +18 -0
- data/html/classes/MIDI/Clock.html +156 -0
- data/html/classes/MIDI/Clock.src/M000134.html +18 -0
- data/html/classes/MIDI/Clock.src/M000135.html +18 -0
- data/html/classes/MIDI/Continue.html +156 -0
- data/html/classes/MIDI/Continue.src/M000132.html +18 -0
- data/html/classes/MIDI/Continue.src/M000133.html +18 -0
- data/html/classes/MIDI/Controller.html +189 -0
- data/html/classes/MIDI/Controller.src/M000129.html +21 -0
- data/html/classes/MIDI/Controller.src/M000130.html +22 -0
- data/html/classes/MIDI/Controller.src/M000131.html +18 -0
- data/html/classes/MIDI/Event.html +424 -0
- data/html/classes/MIDI/Event.src/M000145.html +27 -0
- data/html/classes/MIDI/Event.src/M000146.html +18 -0
- data/html/classes/MIDI/Event.src/M000147.html +18 -0
- data/html/classes/MIDI/Event.src/M000148.html +18 -0
- data/html/classes/MIDI/Event.src/M000149.html +18 -0
- data/html/classes/MIDI/Event.src/M000150.html +18 -0
- data/html/classes/MIDI/Event.src/M000151.html +18 -0
- data/html/classes/MIDI/Event.src/M000152.html +18 -0
- data/html/classes/MIDI/Event.src/M000153.html +18 -0
- data/html/classes/MIDI/Event.src/M000154.html +22 -0
- data/html/classes/MIDI/Event.src/M000155.html +18 -0
- data/html/classes/MIDI/Event.src/M000156.html +18 -0
- data/html/classes/MIDI/Event.src/M000157.html +18 -0
- data/html/classes/MIDI/IO.html +121 -0
- data/html/classes/MIDI/IO/MIDIFile.html +925 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000028.html +22 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000029.html +24 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000030.html +19 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000031.html +19 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000032.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000033.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000034.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000035.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000036.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000037.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000038.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000039.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000040.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000041.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000042.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000043.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000044.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000045.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000046.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000047.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000048.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000049.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000050.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000051.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000052.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000053.html +43 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000054.html +34 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000055.html +96 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000056.html +18 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000057.html +48 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000058.html +42 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000059.html +19 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000060.html +19 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000061.html +20 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000062.html +21 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000063.html +31 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000064.html +20 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000065.html +22 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000066.html +30 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000067.html +18 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000068.html +20 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000069.html +18 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000070.html +18 -0
- data/html/classes/MIDI/IO/SeqReader.html +460 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000001.html +22 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000002.html +22 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000003.html +21 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000004.html +34 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000005.html +26 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000006.html +28 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000007.html +21 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000008.html +19 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000009.html +19 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000010.html +19 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000011.html +19 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000012.html +19 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000013.html +18 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000014.html +18 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000015.html +27 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000016.html +18 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000017.html +18 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000018.html +18 -0
- data/html/classes/MIDI/IO/SeqWriter.html +267 -0
- data/html/classes/MIDI/IO/SeqWriter.src/M000019.html +19 -0
- data/html/classes/MIDI/IO/SeqWriter.src/M000020.html +25 -0
- data/html/classes/MIDI/IO/SeqWriter.src/M000021.html +22 -0
- data/html/classes/MIDI/IO/SeqWriter.src/M000022.html +54 -0
- data/html/classes/MIDI/IO/SeqWriter.src/M000023.html +49 -0
- data/html/classes/MIDI/IO/SeqWriter.src/M000024.html +21 -0
- data/html/classes/MIDI/IO/SeqWriter.src/M000025.html +19 -0
- data/html/classes/MIDI/IO/SeqWriter.src/M000026.html +24 -0
- data/html/classes/MIDI/IO/SeqWriter.src/M000027.html +26 -0
- data/html/classes/MIDI/Marker.html +139 -0
- data/html/classes/MIDI/Marker.src/M000107.html +18 -0
- data/html/classes/MIDI/MetaEvent.html +189 -0
- data/html/classes/MIDI/MetaEvent.src/M000167.html +21 -0
- data/html/classes/MIDI/MetaEvent.src/M000168.html +23 -0
- data/html/classes/MIDI/MetaEvent.src/M000169.html +53 -0
- data/html/classes/MIDI/NoteEvent.html +233 -0
- data/html/classes/MIDI/NoteEvent.src/M000091.html +21 -0
- data/html/classes/MIDI/NoteEvent.src/M000092.html +20 -0
- data/html/classes/MIDI/NoteEvent.src/M000093.html +18 -0
- data/html/classes/MIDI/NoteEvent.src/M000094.html +22 -0
- data/html/classes/MIDI/NoteOffEvent.html +169 -0
- data/html/classes/MIDI/NoteOffEvent.src/M000086.html +19 -0
- data/html/classes/MIDI/NoteOffEvent.src/M000087.html +19 -0
- data/html/classes/MIDI/NoteOnEvent.html +169 -0
- data/html/classes/MIDI/NoteOnEvent.src/M000102.html +19 -0
- data/html/classes/MIDI/NoteOnEvent.src/M000103.html +19 -0
- data/html/classes/MIDI/PitchBend.html +184 -0
- data/html/classes/MIDI/PitchBend.src/M000104.html +19 -0
- data/html/classes/MIDI/PitchBend.src/M000105.html +22 -0
- data/html/classes/MIDI/PitchBend.src/M000106.html +18 -0
- data/html/classes/MIDI/PolyPressure.html +186 -0
- data/html/classes/MIDI/PolyPressure.src/M000170.html +18 -0
- data/html/classes/MIDI/PolyPressure.src/M000171.html +18 -0
- data/html/classes/MIDI/PolyPressure.src/M000172.html +18 -0
- data/html/classes/MIDI/PolyPressure.src/M000173.html +19 -0
- data/html/classes/MIDI/ProgramChange.html +184 -0
- data/html/classes/MIDI/ProgramChange.src/M000099.html +19 -0
- data/html/classes/MIDI/ProgramChange.src/M000100.html +21 -0
- data/html/classes/MIDI/ProgramChange.src/M000101.html +18 -0
- data/html/classes/MIDI/Realtime.html +171 -0
- data/html/classes/MIDI/Realtime.src/M000083.html +19 -0
- data/html/classes/MIDI/Realtime.src/M000084.html +20 -0
- data/html/classes/MIDI/Realtime.src/M000085.html +18 -0
- data/html/classes/MIDI/Sequence.html +469 -0
- data/html/classes/MIDI/Sequence.src/M000108.html +28 -0
- data/html/classes/MIDI/Sequence.src/M000109.html +21 -0
- data/html/classes/MIDI/Sequence.src/M000110.html +22 -0
- data/html/classes/MIDI/Sequence.src/M000113.html +18 -0
- data/html/classes/MIDI/Sequence.src/M000114.html +27 -0
- data/html/classes/MIDI/Sequence.src/M000115.html +18 -0
- data/html/classes/MIDI/Sequence.src/M000116.html +19 -0
- data/html/classes/MIDI/Sequence.src/M000117.html +19 -0
- data/html/classes/MIDI/Sequence.src/M000118.html +20 -0
- data/html/classes/MIDI/Sequence.src/M000119.html +19 -0
- data/html/classes/MIDI/Sequence.src/M000120.html +18 -0
- data/html/classes/MIDI/SongPointer.html +184 -0
- data/html/classes/MIDI/SongPointer.src/M000138.html +19 -0
- data/html/classes/MIDI/SongPointer.src/M000139.html +22 -0
- data/html/classes/MIDI/SongPointer.src/M000140.html +18 -0
- data/html/classes/MIDI/SongSelect.html +184 -0
- data/html/classes/MIDI/SongSelect.src/M000126.html +19 -0
- data/html/classes/MIDI/SongSelect.src/M000127.html +21 -0
- data/html/classes/MIDI/SongSelect.src/M000128.html +18 -0
- data/html/classes/MIDI/Start.html +156 -0
- data/html/classes/MIDI/Start.src/M000097.html +18 -0
- data/html/classes/MIDI/Start.src/M000098.html +18 -0
- data/html/classes/MIDI/Stop.html +156 -0
- data/html/classes/MIDI/Stop.src/M000095.html +18 -0
- data/html/classes/MIDI/Stop.src/M000096.html +18 -0
- data/html/classes/MIDI/SystemCommon.html +139 -0
- data/html/classes/MIDI/SystemCommon.src/M000144.html +19 -0
- data/html/classes/MIDI/SystemExclusive.html +184 -0
- data/html/classes/MIDI/SystemExclusive.src/M000141.html +19 -0
- data/html/classes/MIDI/SystemExclusive.src/M000142.html +23 -0
- data/html/classes/MIDI/SystemExclusive.src/M000143.html +18 -0
- data/html/classes/MIDI/SystemReset.html +156 -0
- data/html/classes/MIDI/SystemReset.src/M000081.html +18 -0
- data/html/classes/MIDI/SystemReset.src/M000082.html +18 -0
- data/html/classes/MIDI/Tempo.html +250 -0
- data/html/classes/MIDI/Tempo.src/M000158.html +18 -0
- data/html/classes/MIDI/Tempo.src/M000159.html +18 -0
- data/html/classes/MIDI/Tempo.src/M000160.html +18 -0
- data/html/classes/MIDI/Tempo.src/M000161.html +18 -0
- data/html/classes/MIDI/Tempo.src/M000162.html +18 -0
- data/html/classes/MIDI/Tempo.src/M000163.html +25 -0
- data/html/classes/MIDI/Tempo.src/M000164.html +18 -0
- data/html/classes/MIDI/Track.html +380 -0
- data/html/classes/MIDI/Track.src/M000071.html +23 -0
- data/html/classes/MIDI/Track.src/M000072.html +21 -0
- data/html/classes/MIDI/Track.src/M000073.html +26 -0
- data/html/classes/MIDI/Track.src/M000074.html +18 -0
- data/html/classes/MIDI/Track.src/M000075.html +22 -0
- data/html/classes/MIDI/Track.src/M000076.html +20 -0
- data/html/classes/MIDI/Track.src/M000077.html +22 -0
- data/html/classes/MIDI/Track.src/M000078.html +22 -0
- data/html/classes/MIDI/Track.src/M000079.html +18 -0
- data/html/classes/MIDI/Track.src/M000080.html +19 -0
- data/html/classes/MIDI/TuneRequest.html +171 -0
- data/html/classes/MIDI/TuneRequest.src/M000121.html +18 -0
- data/html/classes/MIDI/TuneRequest.src/M000122.html +20 -0
- data/html/classes/MIDI/TuneRequest.src/M000123.html +18 -0
- data/html/classes/MIDI/Utils.html +190 -0
- data/html/classes/MIDI/Utils.src/M000165.html +20 -0
- data/html/classes/MIDI/Utils.src/M000166.html +25 -0
- data/html/created.rid +1 -0
- data/html/files/README.html +599 -0
- data/html/files/TODO.html +142 -0
- data/html/files/lib/midilib/consts_rb.html +107 -0
- data/html/files/lib/midilib/event_rb.html +109 -0
- data/html/files/lib/midilib/info_rb.html +101 -0
- data/html/files/lib/midilib/io/midifile_rb.html +108 -0
- data/html/files/lib/midilib/io/seqreader_rb.html +110 -0
- data/html/files/lib/midilib/io/seqwriter_rb.html +115 -0
- data/html/files/lib/midilib/sequence_rb.html +109 -0
- data/html/files/lib/midilib/track_rb.html +108 -0
- data/html/files/lib/midilib/utils_rb.html +101 -0
- data/html/files/lib/midilib_rb.html +124 -0
- data/html/fr_class_index.html +59 -0
- data/html/fr_file_index.html +38 -0
- data/html/fr_method_index.html +199 -0
- data/html/index.html +24 -0
- data/html/rdoc-style.css +208 -0
- data/install.rb +57 -0
- data/lib/midilib.rb +16 -0
- data/lib/midilib/consts.rb +422 -0
- data/lib/midilib/event.rb +559 -0
- data/lib/midilib/info.rb +9 -0
- data/lib/midilib/io/midifile.rb +446 -0
- data/lib/midilib/io/seqreader.rb +198 -0
- data/lib/midilib/io/seqwriter.rb +151 -0
- data/lib/midilib/sequence.rb +144 -0
- data/lib/midilib/track.rb +115 -0
- data/lib/midilib/utils.rb +36 -0
- data/test/event_equality.rb +81 -0
- data/test/test_event.rb +116 -0
- data/test/test_io.rb +55 -0
- data/test/test_sequence.rb +68 -0
- data/test/test_track.rb +111 -0
- data/test/test_varlen.rb +40 -0
- 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
|