midilib 2.0.4 → 2.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.rdoc +9 -4
- data/Rakefile +2 -6
- data/examples/from_scratch.rb +3 -5
- data/examples/measures_mbt.rb +4 -4
- data/examples/print_program_changes.rb +9 -9
- data/examples/reader2text.rb +188 -188
- data/examples/seq2text.rb +17 -17
- data/examples/split.rb +19 -19
- data/examples/strings.rb +14 -14
- data/examples/transpose.rb +31 -31
- data/html/IO.html +65 -169
- data/html/MIDI.html +138 -256
- data/html/MIDI/ActiveSense.html +89 -178
- data/html/MIDI/ChannelEvent.html +95 -183
- data/html/MIDI/ChannelPressure.html +105 -190
- data/html/MIDI/Clock.html +89 -178
- data/html/MIDI/Continue.html +89 -178
- data/html/MIDI/Controller.html +107 -192
- data/html/MIDI/Event.html +138 -222
- data/html/MIDI/IO.html +45 -157
- data/html/MIDI/IO/MIDIFile.html +596 -568
- data/html/MIDI/IO/SeqReader.html +272 -314
- data/html/MIDI/IO/SeqWriter.html +229 -305
- data/html/MIDI/KeySig.html +129 -211
- data/html/MIDI/MIDI.html +45 -154
- data/html/MIDI/MIDI/MIDI.html +45 -154
- data/html/MIDI/MIDI/MIDI/Array.html +87 -185
- data/html/MIDI/Marker.html +71 -170
- data/html/MIDI/Measure.html +95 -190
- data/html/MIDI/Measures.html +103 -193
- data/html/MIDI/MetaEvent.html +180 -253
- data/html/MIDI/NoteEvent.html +118 -204
- data/html/MIDI/NoteOff.html +95 -183
- data/html/MIDI/NoteOn.html +95 -183
- data/html/MIDI/PitchBend.html +106 -191
- data/html/MIDI/PolyPressure.html +106 -189
- data/html/MIDI/ProgramChange.html +105 -190
- data/html/MIDI/Realtime.html +98 -184
- data/html/MIDI/Sequence.html +246 -311
- data/html/MIDI/SongPointer.html +106 -191
- data/html/MIDI/SongSelect.html +105 -190
- data/html/MIDI/Start.html +89 -178
- data/html/MIDI/Stop.html +89 -178
- data/html/MIDI/SystemCommon.html +71 -170
- data/html/MIDI/SystemExclusive.html +108 -193
- data/html/MIDI/SystemReset.html +89 -178
- data/html/MIDI/Tempo.html +135 -213
- data/html/MIDI/TimeSig.html +135 -214
- data/html/MIDI/Track.html +217 -291
- data/html/MIDI/TuneRequest.html +98 -184
- data/html/MIDI/Utils.html +89 -189
- data/html/README_rdoc.html +237 -257
- data/html/TODO_rdoc.html +64 -139
- data/html/created.rid +14 -14
- data/html/css/fonts.css +167 -0
- data/html/{rdoc.css → css/rdoc.css} +265 -218
- data/html/fonts/Lato-Light.ttf +0 -0
- data/html/fonts/Lato-LightItalic.ttf +0 -0
- data/html/fonts/Lato-Regular.ttf +0 -0
- data/html/fonts/Lato-RegularItalic.ttf +0 -0
- data/html/fonts/SourceCodePro-Bold.ttf +0 -0
- data/html/fonts/SourceCodePro-Regular.ttf +0 -0
- data/html/images/add.png +0 -0
- data/html/images/arrow_up.png +0 -0
- data/html/images/delete.png +0 -0
- data/html/images/tag_blue.png +0 -0
- data/html/index.html +187 -169
- data/html/js/darkfish.js +41 -33
- data/html/js/jquery.js +4 -18
- data/html/js/navigation.js.gz +0 -0
- data/html/js/search.js +20 -5
- data/html/js/search_index.js +1 -1
- data/html/js/search_index.js.gz +0 -0
- data/html/js/searcher.js.gz +0 -0
- data/html/table_of_contents.html +1111 -498
- data/install.rb +43 -32
- data/lib/midilib/consts.rb +407 -407
- data/lib/midilib/event.rb +295 -294
- data/lib/midilib/info.rb +5 -5
- data/lib/midilib/io/midifile.rb +266 -267
- data/lib/midilib/io/seqreader.rb +106 -106
- data/lib/midilib/io/seqwriter.rb +59 -60
- data/lib/midilib/measure.rb +69 -69
- data/lib/midilib/sequence.rb +68 -70
- data/lib/midilib/track.rb +96 -102
- data/lib/midilib/utils.rb +15 -15
- data/test/event_equality.rb +50 -50
- data/test/test_event.rb +120 -122
- data/test/test_io.rb +35 -48
- data/test/test_sequence.rb +60 -60
- data/test/test_track.rb +154 -154
- data/test/test_varlen.rb +23 -25
- metadata +65 -57
data/lib/midilib/io/seqreader.rb
CHANGED
@@ -4,54 +4,54 @@ require 'midilib/event'
|
|
4
4
|
|
5
5
|
module MIDI
|
6
6
|
|
7
|
-
module IO
|
8
|
-
|
9
|
-
# Reads MIDI files. As a subclass of MIDIFile, this class implements
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
# We append new events to the end of a track's event list, bypassing a
|
14
|
-
# to Track.#add. This means that we must call Track.recalc_times at
|
15
|
-
# of the track so it can update each event with its time from
|
16
|
-
# start (see end_track below).
|
17
|
-
#
|
18
|
-
# META_TRACK_END events are not added to tracks. This way, we don't have
|
19
|
-
# worry about making sure the last event is always a track end event.
|
20
|
-
# rely on the SeqWriter to append a META_TRACK_END event to each
|
21
|
-
# it is output.
|
22
|
-
|
23
|
-
class SeqReader < MIDIFile
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
7
|
+
module IO
|
8
|
+
|
9
|
+
# Reads MIDI files. As a subclass of MIDIFile, this class implements the
|
10
|
+
# callback methods for each MIDI event and use them to build Track and
|
11
|
+
# 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
|
14
|
+
# call to Track.#add. This means that we must call Track.recalc_times at
|
15
|
+
# the end of the track so it can update each event with its time from
|
16
|
+
# the track's start (see end_track below).
|
17
|
+
#
|
18
|
+
# META_TRACK_END events are not added to tracks. This way, we don't have
|
19
|
+
# to worry about making sure the last event is always a track end event.
|
20
|
+
# We rely on the SeqWriter to append a META_TRACK_END event to each
|
21
|
+
# track when it is output.
|
22
|
+
|
23
|
+
class SeqReader < MIDIFile
|
24
|
+
|
25
|
+
# The optional proc block is called once at the start of the file and
|
26
|
+
# again at the end of each track. There are three arguments to the
|
27
|
+
# block: the track, the track number (1 through _n_), and the total
|
28
|
+
# number of tracks.
|
29
|
+
def initialize(seq, proc = nil) # :yields: track, num_tracks, index
|
30
30
|
super()
|
31
31
|
@seq = seq
|
32
32
|
@track = nil
|
33
33
|
@chan_mask = 0
|
34
34
|
@update_block = block_given?() ? Proc.new() : proc
|
35
|
-
|
35
|
+
end
|
36
36
|
|
37
|
-
|
37
|
+
def header(format, ntrks, division)
|
38
38
|
@seq.format = format
|
39
39
|
@seq.ppqn = division
|
40
40
|
|
41
41
|
@ntrks = ntrks
|
42
42
|
@update_block.call(nil, @ntrks, 0) if @update_block
|
43
|
-
|
43
|
+
end
|
44
44
|
|
45
|
-
|
45
|
+
def start_track()
|
46
46
|
@track = Track.new(@seq)
|
47
47
|
@seq.tracks << @track
|
48
48
|
|
49
49
|
@pending = []
|
50
|
-
|
50
|
+
end
|
51
51
|
|
52
|
-
|
52
|
+
def end_track()
|
53
53
|
# Turn off any pending note on messages
|
54
|
-
@pending.each { |
|
54
|
+
@pending.each { |on| make_note_off(on, 64) }
|
55
55
|
@pending = nil
|
56
56
|
|
57
57
|
# Don't bother adding the META_TRACK_END event to the track.
|
@@ -67,136 +67,136 @@ class SeqReader < MIDIFile
|
|
67
67
|
|
68
68
|
# call update block
|
69
69
|
@update_block.call(@track, @ntrks, @seq.tracks.length) if @update_block
|
70
|
-
|
70
|
+
end
|
71
71
|
|
72
|
-
|
72
|
+
def note_on(chan, note, vel)
|
73
73
|
if vel == 0
|
74
|
-
|
75
|
-
|
74
|
+
note_off(chan, note, 64)
|
75
|
+
return
|
76
76
|
end
|
77
77
|
|
78
78
|
on = NoteOn.new(chan, note, vel, @curr_ticks)
|
79
79
|
@track.events << on
|
80
80
|
@pending << on
|
81
81
|
track_uses_channel(chan)
|
82
|
-
|
82
|
+
end
|
83
83
|
|
84
|
-
|
84
|
+
def note_off(chan, note, vel)
|
85
85
|
# Find note on, create note off, connect the two, and remove
|
86
86
|
# note on from pending list.
|
87
|
-
@pending.each_with_index
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
87
|
+
@pending.each_with_index do |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
|
+
end
|
94
94
|
$stderr.puts "note off with no earlier note on (ch #{chan}, note" +
|
95
|
-
|
96
|
-
|
95
|
+
" #{note}, vel #{vel})" if $DEBUG
|
96
|
+
end
|
97
97
|
|
98
|
-
|
98
|
+
def make_note_off(on, vel)
|
99
99
|
off = NoteOff.new(on.channel, on.note, vel, @curr_ticks)
|
100
100
|
@track.events << off
|
101
101
|
on.off = off
|
102
102
|
off.on = on
|
103
|
-
|
103
|
+
end
|
104
104
|
|
105
|
-
|
105
|
+
def pressure(chan, note, press)
|
106
106
|
@track.events << PolyPressure.new(chan, note, press, @curr_ticks)
|
107
107
|
track_uses_channel(chan)
|
108
|
-
|
108
|
+
end
|
109
109
|
|
110
|
-
|
110
|
+
def controller(chan, control, value)
|
111
111
|
@track.events << Controller.new(chan, control, value, @curr_ticks)
|
112
112
|
track_uses_channel(chan)
|
113
|
-
|
113
|
+
end
|
114
114
|
|
115
|
-
|
115
|
+
def pitch_bend(chan, lsb, msb)
|
116
116
|
@track.events << PitchBend.new(chan, (msb << 7) + lsb, @curr_ticks)
|
117
117
|
track_uses_channel(chan)
|
118
|
-
|
118
|
+
end
|
119
119
|
|
120
|
-
|
120
|
+
def program(chan, program)
|
121
121
|
@track.events << ProgramChange.new(chan, program, @curr_ticks)
|
122
122
|
track_uses_channel(chan)
|
123
|
-
|
123
|
+
end
|
124
124
|
|
125
|
-
|
125
|
+
def chan_pressure(chan, press)
|
126
126
|
@track.events << ChannelPressure.new(chan, press, @curr_ticks)
|
127
127
|
track_uses_channel(chan)
|
128
|
-
|
128
|
+
end
|
129
129
|
|
130
|
-
|
130
|
+
def sysex(msg)
|
131
131
|
@track.events << SystemExclusive.new(msg, @curr_ticks)
|
132
|
-
|
132
|
+
end
|
133
133
|
|
134
|
-
|
134
|
+
def meta_misc(type, msg)
|
135
135
|
@track.events << MetaEvent.new(type, msg, @curr_ticks)
|
136
|
-
|
136
|
+
end
|
137
137
|
|
138
|
-
# --
|
139
|
-
# def sequencer_specific(type, msg)
|
140
|
-
# end
|
138
|
+
# --
|
139
|
+
# def sequencer_specific(type, msg)
|
140
|
+
# end
|
141
141
|
|
142
|
-
# def sequence_number(num)
|
143
|
-
# end
|
144
|
-
# ++
|
142
|
+
# def sequence_number(num)
|
143
|
+
# end
|
144
|
+
# ++
|
145
145
|
|
146
|
-
|
146
|
+
def text(type, msg)
|
147
147
|
case type
|
148
148
|
when META_TEXT, META_LYRIC, META_CUE
|
149
|
-
|
149
|
+
@track.events << MetaEvent.new(type, msg, @curr_ticks)
|
150
150
|
when META_SEQ_NAME, META_COPYRIGHT
|
151
|
-
|
152
|
-
when META_INSTRUMENT
|
153
|
-
|
151
|
+
@track.events << MetaEvent.new(type, msg, 0)
|
152
|
+
when META_INSTRUMENT
|
153
|
+
@track.instrument = msg
|
154
154
|
when META_MARKER
|
155
|
-
|
155
|
+
@track.events << Marker.new(msg, @curr_ticks)
|
156
156
|
else
|
157
|
-
|
157
|
+
$stderr.puts "text = #{msg}, type = #{type}" if $DEBUG
|
158
158
|
end
|
159
|
-
|
160
|
-
|
161
|
-
# --
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
# def eot()
|
167
|
-
# @track.events << MetaEvent.new(META_TRACK_END, nil, @curr_ticks)
|
168
|
-
# end
|
169
|
-
# ++
|
170
|
-
|
171
|
-
|
159
|
+
end
|
160
|
+
|
161
|
+
# --
|
162
|
+
# Don't bother adding the META_TRACK_END event to the track. This way,
|
163
|
+
# we don't have to worry about always making sure the last event is
|
164
|
+
# always a track end event. We just have to make sure to write one when
|
165
|
+
# the track is output back to a file.
|
166
|
+
# def eot()
|
167
|
+
# @track.events << MetaEvent.new(META_TRACK_END, nil, @curr_ticks)
|
168
|
+
# end
|
169
|
+
# ++
|
170
|
+
|
171
|
+
def time_signature(numer, denom, clocks, qnotes)
|
172
172
|
@seq.time_signature(numer, denom, clocks, qnotes)
|
173
173
|
@track.events << TimeSig.new(numer, denom, clocks, qnotes, @curr_ticks)
|
174
|
-
|
174
|
+
end
|
175
175
|
|
176
|
-
# --
|
177
|
-
# def smpte(hour, min, sec, frame, fract)
|
178
|
-
# end
|
179
|
-
# ++
|
176
|
+
# --
|
177
|
+
# def smpte(hour, min, sec, frame, fract)
|
178
|
+
# end
|
179
|
+
# ++
|
180
180
|
|
181
|
-
|
181
|
+
def tempo(microsecs)
|
182
182
|
@track.events << Tempo.new(microsecs, @curr_ticks)
|
183
|
-
|
183
|
+
end
|
184
184
|
|
185
|
-
|
186
|
-
|
187
|
-
|
185
|
+
def key_signature(sharpflat, is_minor)
|
186
|
+
@track.events << KeySig.new(sharpflat, is_minor, @curr_ticks)
|
187
|
+
end
|
188
188
|
|
189
|
-
# --
|
190
|
-
# def arbitrary(msg)
|
191
|
-
# end
|
192
|
-
# ++
|
189
|
+
# --
|
190
|
+
# def arbitrary(msg)
|
191
|
+
# end
|
192
|
+
# ++
|
193
193
|
|
194
|
-
|
195
|
-
|
194
|
+
# Return true if the current track uses the specified channel.
|
195
|
+
def track_uses_channel(chan)
|
196
196
|
@chan_mask = @chan_mask | (1 << chan)
|
197
|
-
|
197
|
+
end
|
198
198
|
|
199
|
-
end
|
199
|
+
end
|
200
200
|
|
201
|
-
end
|
201
|
+
end
|
202
202
|
end
|
data/lib/midilib/io/seqwriter.rb
CHANGED
@@ -5,36 +5,36 @@ require 'midilib/utils'
|
|
5
5
|
|
6
6
|
module MIDI
|
7
7
|
|
8
|
-
module IO
|
8
|
+
module IO
|
9
9
|
|
10
|
-
class SeqWriter
|
10
|
+
class SeqWriter
|
11
11
|
|
12
|
-
|
12
|
+
def initialize(seq, proc = nil) # :yields: num_tracks, index
|
13
13
|
@seq = seq
|
14
14
|
@update_block = block_given?() ? Proc.new() : proc
|
15
|
-
|
15
|
+
end
|
16
16
|
|
17
|
-
|
18
|
-
|
17
|
+
# Writes a MIDI format 1 file.
|
18
|
+
def write_to(io)
|
19
19
|
@io = io
|
20
20
|
@bytes_written = 0
|
21
21
|
write_header()
|
22
22
|
@update_block.call(nil, @seq.tracks.length, 0) if @update_block
|
23
|
-
@seq.tracks.each_with_index
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
23
|
+
@seq.tracks.each_with_index do |track, i|
|
24
|
+
write_track(track)
|
25
|
+
@update_block.call(track, @seq.tracks.length, i) if @update_block
|
26
|
+
end
|
27
|
+
end
|
28
28
|
|
29
|
-
|
29
|
+
def write_header
|
30
30
|
@io.print 'MThd'
|
31
31
|
write32(6)
|
32
32
|
write16(1) # Ignore sequence format; write as format 1
|
33
33
|
write16(@seq.tracks.length)
|
34
34
|
write16(@seq.ppqn)
|
35
|
-
|
35
|
+
end
|
36
36
|
|
37
|
-
|
37
|
+
def write_track(track)
|
38
38
|
@io.print 'MTrk'
|
39
39
|
track_size_file_pos = @io.tell()
|
40
40
|
write32(0) # Dummy byte count; overwritten later
|
@@ -44,23 +44,22 @@ class SeqWriter
|
|
44
44
|
|
45
45
|
prev_event = nil
|
46
46
|
prev_status = 0
|
47
|
-
track.events.each
|
48
|
-
|
49
|
-
|
50
|
-
|
47
|
+
track.events.each do |event|
|
48
|
+
if !event.kind_of?(Realtime)
|
49
|
+
write_var_len(event.delta_time)
|
50
|
+
end
|
51
51
|
|
52
|
-
|
53
|
-
|
52
|
+
data = event.data_as_bytes()
|
53
|
+
status = data[0] # status byte plus channel number, if any
|
54
54
|
|
55
|
-
|
56
|
-
|
57
|
-
prev_status)
|
55
|
+
# running status byte
|
56
|
+
status = possibly_munge_due_to_running_status_byte(data, prev_status)
|
58
57
|
|
59
|
-
|
58
|
+
@bytes_written += write_bytes(data)
|
60
59
|
|
61
|
-
|
62
|
-
|
63
|
-
|
60
|
+
prev_event = event
|
61
|
+
prev_status = status
|
62
|
+
end
|
64
63
|
|
65
64
|
# Write track end event.
|
66
65
|
event = MetaEvent.new(META_TRACK_END)
|
@@ -72,12 +71,12 @@ class SeqWriter
|
|
72
71
|
@io.seek(track_size_file_pos)
|
73
72
|
write32(@bytes_written)
|
74
73
|
@io.seek(0, ::IO::SEEK_END)
|
75
|
-
|
74
|
+
end
|
76
75
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
76
|
+
# If we can use a running status byte, delete the status byte from
|
77
|
+
# the given data. Return the status to remember for next time as the
|
78
|
+
# running status byte for this event.
|
79
|
+
def possibly_munge_due_to_running_status_byte(data, prev_status)
|
81
80
|
status = data[0]
|
82
81
|
return status if status >= 0xf0 || prev_status >= 0xf0
|
83
82
|
|
@@ -91,49 +90,49 @@ class SeqWriter
|
|
91
90
|
# exactly the same, the rest is trivial. If it's note on/note off,
|
92
91
|
# we can combine those further.
|
93
92
|
if status == prev_status
|
94
|
-
|
95
|
-
|
93
|
+
data[0,1] = [] # delete status byte from data
|
94
|
+
return status + chan
|
96
95
|
elsif status == NOTE_OFF && data[2] == 64
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
96
|
+
# If we see a note off and the velocity is 64, we can store
|
97
|
+
# a note on with a velocity of 0. If the velocity isn't 64
|
98
|
+
# then storing a note on would be bad because the would be
|
99
|
+
# changed to 64 when reading the file back in.
|
100
|
+
data[2] = 0 # set vel to 0; do before possible shrinking
|
101
|
+
status = NOTE_ON + chan
|
102
|
+
if prev_status == NOTE_ON
|
103
|
+
data[0,1] = [] # delete status byte
|
104
|
+
else
|
105
|
+
data[0] = status
|
106
|
+
end
|
107
|
+
return status
|
109
108
|
else
|
110
|
-
|
111
|
-
|
109
|
+
# Can't compress data
|
110
|
+
return status + chan
|
112
111
|
end
|
113
|
-
|
112
|
+
end
|
114
113
|
|
115
|
-
|
114
|
+
def write_instrument(instrument)
|
116
115
|
event = MetaEvent.new(META_INSTRUMENT, instrument)
|
117
116
|
write_var_len(0)
|
118
117
|
data = event.data_as_bytes()
|
119
118
|
@bytes_written += write_bytes(data)
|
120
|
-
|
119
|
+
end
|
121
120
|
|
122
|
-
|
121
|
+
def write_var_len(val)
|
123
122
|
buffer = Utils.as_var_len(val)
|
124
123
|
@bytes_written += write_bytes(buffer)
|
125
|
-
|
124
|
+
end
|
126
125
|
|
127
|
-
|
126
|
+
def write16(val)
|
128
127
|
val = (-val | 0x8000) if val < 0
|
129
128
|
|
130
129
|
buffer = []
|
131
130
|
@io.putc((val >> 8) & 0xff)
|
132
131
|
@io.putc(val & 0xff)
|
133
132
|
@bytes_written += 2
|
134
|
-
|
133
|
+
end
|
135
134
|
|
136
|
-
|
135
|
+
def write32(val)
|
137
136
|
val = (-val | 0x80000000) if val < 0
|
138
137
|
|
139
138
|
@io.putc((val >> 24) & 0xff)
|
@@ -141,13 +140,13 @@ class SeqWriter
|
|
141
140
|
@io.putc((val >> 8) & 0xff)
|
142
141
|
@io.putc(val & 0xff)
|
143
142
|
@bytes_written += 4
|
144
|
-
|
143
|
+
end
|
145
144
|
|
146
|
-
|
145
|
+
def write_bytes(bytes)
|
147
146
|
bytes.each { |b| @io.putc(b) }
|
148
147
|
bytes.length
|
148
|
+
end
|
149
149
|
end
|
150
|
-
end
|
151
150
|
|
152
|
-
end
|
151
|
+
end
|
153
152
|
end
|