midilib 2.0.2 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,12 +1,10 @@
1
- require 'midilib/io/seqreader'
2
- require 'midilib/io/seqwriter'
3
- require 'midilib/measure.rb'
1
+ require_relative 'io/seqreader'
2
+ require_relative 'io/seqwriter'
3
+ require_relative 'measure'
4
4
 
5
5
  module MIDI
6
-
7
- # A MIDI::Sequence contains MIDI::Track objects.
8
- class Sequence
9
-
6
+ # A MIDI::Sequence contains MIDI::Track objects.
7
+ class Sequence
10
8
  include Enumerable
11
9
 
12
10
  UNNAMED = 'Unnamed Sequence'
@@ -33,7 +31,7 @@ class Sequence
33
31
  # Pulses (i.e. clocks) Per Quarter Note resolution for the sequence
34
32
  attr_accessor :ppqn
35
33
  # The MIDI file format (0, 1, or 2)
36
- attr_accessor :format
34
+ attr_accessor :format
37
35
  attr_accessor :numer, :denom, :clocks, :qnotes
38
36
  # The class to use for reading MIDI from a stream. The default is
39
37
  # MIDI::IO::SeqReader. You can change this at any time.
@@ -43,43 +41,49 @@ class Sequence
43
41
  attr_accessor :writer_class
44
42
 
45
43
  def initialize
46
- @tracks = Array.new()
47
- @ppqn = 480
44
+ @tracks = []
45
+ @ppqn = 480
48
46
 
49
- # Time signature
50
- @numer = 4 # Numer + denom = 4/4 time default
51
- @denom = 2
52
- @clocks = 24 # Bug fix Nov 11, 2007 - this is not the same as ppqn!
53
- @qnotes = 8
47
+ # Time signature
48
+ @numer = 4 # Numer + denom = 4/4 time default
49
+ @denom = 2
50
+ @clocks = 24 # Bug fix Nov 11, 2007 - this is not the same as ppqn!
51
+ @qnotes = 8
54
52
 
55
- @reader_class = IO::SeqReader
56
- @writer_class = IO::SeqWriter
53
+ @reader_class = IO::SeqReader
54
+ @writer_class = IO::SeqWriter
57
55
  end
58
56
 
59
57
  # Sets the time signature.
60
58
  def time_signature(numer, denom, clocks, qnotes)
61
- @numer = numer
62
- @denom = denom
63
- @clocks = clocks
64
- @qnotes = qnotes
59
+ @numer = numer
60
+ @denom = denom
61
+ @clocks = clocks
62
+ @qnotes = qnotes
65
63
  end
66
64
 
67
65
  # Returns the song tempo in beats per minute.
68
66
  def beats_per_minute
69
- return DEFAULT_TEMPO if @tracks.nil? || @tracks.empty?
70
- event = @tracks.first.events.detect { | e |
71
- e.kind_of?(MIDI::Tempo)
72
- }
73
- return event ? (Tempo.mpq_to_bpm(event.tempo)) : DEFAULT_TEMPO
67
+ return DEFAULT_TEMPO if @tracks.nil? || @tracks.empty?
68
+
69
+ event = @tracks.first.events.detect { |e| e.is_a?(MIDI::Tempo) }
70
+ event ? Tempo.mpq_to_bpm(event.tempo) : DEFAULT_TEMPO
71
+ end
72
+ alias bpm beats_per_minute
73
+ alias tempo beats_per_minute
74
+
75
+ # Pulses (also called ticks) are the units of delta times and event
76
+ # time_from_start values. This method converts a number of pulses to a
77
+ # float value that is a time in seconds.
78
+ def pulses_to_seconds(pulses)
79
+ (pulses.to_f / @ppqn.to_f / beats_per_minute) * 60.0
74
80
  end
75
- alias_method :bpm, :beats_per_minute
76
- alias_method :tempo, :beats_per_minute
77
81
 
78
82
  # Given a note length name like "whole", "dotted quarter", or "8th
79
83
  # triplet", return the length of that note in quarter notes as a delta
80
84
  # time.
81
85
  def note_to_delta(name)
82
- return length_to_delta(note_to_length(name))
86
+ length_to_delta(note_to_length(name))
83
87
  end
84
88
 
85
89
  # Given a note length name like "whole", "dotted quarter", or "8th
@@ -94,98 +98,107 @@ class Sequence
94
98
  def note_to_length(name)
95
99
  name.strip!
96
100
  name =~ /^(dotted)?(.*?)(triplet)?$/
97
- dotted, note_name, triplet = $1, $2, $3
101
+ dotted = Regexp.last_match(1)
102
+ note_name = Regexp.last_match(2)
103
+ triplet = Regexp.last_match(3)
98
104
  note_name.strip!
99
105
  mult = 1.0
100
106
  mult = 1.5 if dotted
101
107
  mult /= 3.0 if triplet
102
108
  len = NOTE_TO_LENGTH[note_name]
103
109
  raise "Sequence.note_to_length: \"#{note_name}\" not understood in \"#{name}\"" unless len
104
- return len * mult
110
+
111
+ len * mult
105
112
  end
106
113
 
107
114
  # Translates +length+ (a multiple of a quarter note) into a delta time.
108
115
  # For example, 1 is a quarter note, 1.0/32.0 is a 32nd note, 1.5 is a
109
116
  # dotted quarter, etc. Be aware when using division; 1/32 is zero due to
110
117
  # integer mathematics and rounding. Use floating-point numbers like 1.0
111
- # and 32.0. This method always returns an integer.
118
+ # and 32.0. This method always returns an integer by calling `.round` on
119
+ # the floating-point result.
112
120
  #
113
121
  # See also note_to_delta and note_to_length.
114
122
  def length_to_delta(length)
115
- return (@ppqn * length).to_i
123
+ (@ppqn * length).round
116
124
  end
117
125
 
118
126
  # Returns the name of the first track (track zero). If there are no
119
127
  # tracks, returns UNNAMED.
120
128
  def name
121
- return UNNAMED if @tracks.empty?
122
- return @tracks.first.name()
129
+ return UNNAMED if @tracks.empty?
130
+
131
+ @tracks.first.name
123
132
  end
124
133
 
125
134
  # Hands the name to the first track. Does nothing if there are no tracks.
126
135
  def name=(name)
127
- return if @tracks.empty?
128
- @tracks.first.name = name
136
+ return if @tracks.empty?
137
+
138
+ @tracks.first.name = name
129
139
  end
130
140
 
131
141
  # Reads a MIDI stream.
132
- def read(io, proc = nil) # :yields: track, num_tracks, index
133
- reader = @reader_class.new(self, block_given?() ? Proc.new() : proc)
134
- reader.read_from(io)
142
+ def read(io, &block) # :yields: track, num_tracks, index
143
+ reader = @reader_class.new(self, &block)
144
+ reader.read_from(io)
135
145
  end
136
146
 
137
- # Writes to a MIDI stream.
138
- def write(io, proc = nil) # :yields: track, num_tracks, index
139
- writer = @writer_class.new(self, block_given?() ? Proc.new() : proc)
140
- writer.write_to(io)
147
+ # Writes to a MIDI stream. +midi_format+ defaults to 1.
148
+ def write(io, midi_format = 1, &block) # :yields: track, num_tracks, index
149
+ writer = @writer_class.new(self, midi_format, &block)
150
+ writer.write_to(io)
141
151
  end
142
152
 
143
153
  # Iterates over the tracks.
144
- def each # :yields: track
145
- @tracks.each { | track | yield track }
154
+ def each(&block) # :yields: track
155
+ @tracks.each(&block)
146
156
  end
147
157
 
148
158
  # Returns a Measures object, which is an array container for all measures
149
159
  # in the sequence
150
- def get_measures
151
- # Collect time sig events and scan for last event time
152
- time_sigs = []
153
- max_pos = 0
154
- @tracks.each { |t|
155
- t.each { |e|
156
- time_sigs << e if e.kind_of?(MIDI::TimeSig)
157
- max_pos = e.time_from_start if e.time_from_start > max_pos
158
- }
159
- }
160
- time_sigs.sort { |x,y| x.time_from_start <=> y.time_from_start }
161
-
162
- # Add a "fake" time sig event at the very last position of the sequence,
163
- # just to make sure the whole sequence is calculated.
164
- t = MIDI::TimeSig.new(4, 2, 24, 8, 0)
165
- t.time_from_start = max_pos
166
- time_sigs << t
167
-
168
- # Default to 4/4
169
- measure_length = @ppqn * 4
170
- oldnumer, olddenom, oldbeats = 4, 2, 24
171
-
172
- measures = MIDI::Measures.new(max_pos, @ppqn)
173
- curr_pos = 0
174
- curr_meas_no = 1
175
- time_sigs.each { |te|
176
- meas_count = (te.time_from_start - curr_pos) / measure_length
177
- meas_count += 1 if (te.time_from_start - curr_pos) % measure_length > 0
178
- 1.upto(meas_count) { |i|
179
- measures << MIDI::Measure.new(curr_meas_no,
180
- curr_pos, measure_length, oldnumer, olddenom, oldbeats)
181
- curr_meas_no += 1
182
- curr_pos += measure_length
183
- }
184
- oldnumer, olddenom, oldbeats = te.numerator, te.denominator, te.metronome_ticks
185
- measure_length = te.measure_duration(@ppqn)
186
- }
187
- measures
160
+ def get_measures
161
+ # Collect time sig events and scan for last event time
162
+ time_sigs = []
163
+ max_pos = 0
164
+ @tracks.each do |t|
165
+ t.each do |e|
166
+ time_sigs << e if e.is_a?(MIDI::TimeSig)
167
+ max_pos = e.time_from_start if e.time_from_start > max_pos
168
+ end
169
+ end
170
+ time_sigs.sort { |x, y| x.time_from_start <=> y.time_from_start }
171
+
172
+ # Add a "fake" time sig event at the very last position of the sequence,
173
+ # just to make sure the whole sequence is calculated.
174
+ t = MIDI::TimeSig.new(4, 2, 24, 8, 0)
175
+ t.time_from_start = max_pos
176
+ time_sigs << t
177
+
178
+ # Default to 4/4
179
+ measure_length = @ppqn * 4
180
+ oldnumer = 4
181
+ olddenom = 2
182
+ oldbeats = 24
183
+
184
+ measures = MIDI::Measures.new(max_pos, @ppqn)
185
+ curr_pos = 0
186
+ curr_meas_no = 1
187
+ time_sigs.each do |te|
188
+ meas_count = (te.time_from_start - curr_pos) / measure_length
189
+ meas_count += 1 if (te.time_from_start - curr_pos) % measure_length > 0
190
+ 1.upto(meas_count) do |i|
191
+ measures << MIDI::Measure.new(curr_meas_no, curr_pos, measure_length,
192
+ oldnumer, olddenom, oldbeats)
193
+ curr_meas_no += 1
194
+ curr_pos += measure_length
195
+ end
196
+ oldnumer = te.numerator
197
+ olddenom = te.denominator
198
+ oldbeats = te.metronome_ticks
199
+ measure_length = te.measure_duration(@ppqn)
200
+ end
201
+ measures
188
202
  end
189
-
190
- end
203
+ end
191
204
  end
data/lib/midilib/track.rb CHANGED
@@ -1,60 +1,17 @@
1
- require 'midilib/event'
1
+ require_relative 'event'
2
+ require_relative 'mergesort'
2
3
 
3
4
  module MIDI
4
-
5
- # This is taken from
6
- # http://github.com/adamjmurray/cosy/blob/master/lib/cosy/helper/midi_file_renderer_helper.rb
7
- # with permission from Adam Murray, who originally suggested this fix.
8
- # See http://wiki.github.com/adamjmurray/cosy/midilib-notes for details.
9
- # First we need to add some API infrastructure:
10
- class MIDI::Array < ::Array
11
- # This code borrowed from 'Moser' http://codesnippets.joyent.com/posts/show/1699
12
-
13
- # A stable sorting algorithm that maintains the relative order of equal elements
14
- def mergesort(&cmp)
15
- if cmp == nil
16
- cmp = lambda { |a, b| a <=> b }
17
- end
18
- if size <= 1
19
- self.dup
20
- else
21
- halves = split.map{ |half|
22
- half.mergesort(&cmp)
23
- }
24
- merge(*halves, &cmp)
25
- end
26
- end
27
-
28
- protected
29
- def split
30
- n = (length / 2).floor - 1
31
- [self[0..n], self[n+1..-1]]
32
- end
33
-
34
- def merge(first, second, &predicate)
35
- result = []
36
- until first.empty? || second.empty?
37
- if predicate.call(first.first, second.first) <= 0
38
- result << first.shift
39
- else
40
- result << second.shift
41
- end
42
- end
43
- result.concat(first).concat(second)
44
- end
45
- end
46
-
47
- # A Track is a list of events.
48
- #
49
- # When you modify the +events+ array, make sure to call recalc_times so
50
- # each Event gets its +time_from_start+ recalculated.
51
- #
52
- # A Track also holds a bitmask that specifies the channels used by the track.
53
- # This bitmask is set when the track is read from the MIDI file by an
54
- # IO::SeqReader but is _not_ kept up to date by any other methods.
55
-
56
- class Track
57
-
5
+ # A Track is a list of events.
6
+ #
7
+ # When you modify the +events+ array, make sure to call recalc_times so
8
+ # each Event gets its +time_from_start+ recalculated.
9
+ #
10
+ # A Track also holds a bitmask that specifies the channels used by the track.
11
+ # This bitmask is set when the track is read from the MIDI file by an
12
+ # IO::SeqReader but is _not_ kept up to date by any other methods.
13
+
14
+ class Track
58
15
  include Enumerable
59
16
 
60
17
  UNNAMED = 'Unnamed'
@@ -63,62 +20,59 @@ class Track
63
20
  attr_reader :sequence
64
21
 
65
22
  def initialize(sequence)
66
- @sequence = sequence
67
- @events = Array.new()
23
+ @sequence = sequence
24
+ @events = []
68
25
 
69
- # Bitmask of all channels used. Set when track is read in from
70
- # a MIDI file.
71
- @channels_used = 0
26
+ # Bitmask of all channels used. Set when track is read in from
27
+ # a MIDI file.
28
+ @channels_used = 0
29
+ @instrument = nil
72
30
  end
73
31
 
74
32
  # Return track name. If there is no name, return UNNAMED.
75
33
  def name
76
- event = @events.detect { | e |
77
- e.kind_of?(MetaEvent) && e.meta_type == META_SEQ_NAME
78
- }
79
- return event ? event.data_as_str : UNNAMED
34
+ event = @events.detect { |e| e.is_a?(MetaEvent) && e.meta_type == META_SEQ_NAME }
35
+ event ? event.data_as_str : UNNAMED
80
36
  end
81
37
 
82
38
  # Set track name. Replaces or creates a name meta-event.
83
39
  def name=(name)
84
- event = @events.detect { | e |
85
- e.kind_of?(MetaEvent) && e.meta_type == META_SEQ_NAME
86
- }
87
- if event
88
- event.data = name
89
- else
90
- event = MetaEvent.new(META_SEQ_NAME, name, 0)
91
- @events[0, 0] = event
92
- end
40
+ event = @events.detect { |e| e.is_a?(MetaEvent) && e.meta_type == META_SEQ_NAME }
41
+ if event
42
+ event.data = name
43
+ else
44
+ event = MetaEvent.new(META_SEQ_NAME, name, 0)
45
+ @events[0, 0] = event
46
+ end
93
47
  end
94
48
 
95
49
  def instrument
96
- MetaEvent.bytes_as_str(@instrument)
50
+ MetaEvent.bytes_as_str(@instrument)
97
51
  end
98
52
 
99
53
  def instrument=(str_or_bytes)
100
- @instrument = case str_or_bytes
101
- when String
102
- MetaEvent.str_as_bytes(str_or_bytes)
103
- else
104
- str_or_bytes
105
- end
54
+ @instrument = case str_or_bytes
55
+ when String
56
+ MetaEvent.str_as_bytes(str_or_bytes)
57
+ else
58
+ str_or_bytes
59
+ end
106
60
  end
107
61
 
108
62
  # Merges an array of events into our event list. After merging, the
109
63
  # events' time_from_start values are correct so you don't need to worry
110
64
  # about calling recalc_times.
111
65
  def merge(event_list)
112
- @events = merge_event_lists(@events, event_list)
66
+ @events = merge_event_lists(@events, event_list)
113
67
  end
114
68
 
115
69
  # Merges two event arrays together. Does not modify this track.
116
70
  def merge_event_lists(list1, list2)
117
- recalc_times(0, list1)
118
- recalc_times(0, list2)
119
- list = list1 + list2
120
- recalc_delta_from_times(0, list)
121
- return list
71
+ recalc_times(0, list1)
72
+ recalc_times(0, list2)
73
+ list = list1 + list2
74
+ recalc_delta_from_times(0, list)
75
+ list
122
76
  end
123
77
 
124
78
  # Quantize every event. length_or_note is either a length (1 = quarter,
@@ -128,48 +82,48 @@ class Track
128
82
  # Since each event's time_from_start is modified, we call
129
83
  # recalc_delta_from_times after each event quantizes itself.
130
84
  def quantize(length_or_note)
131
- delta = case length_or_note
132
- when String
133
- @sequence.note_to_delta(length_or_note)
134
- else
135
- @sequence.length_to_delta(length_or_note.to_i)
136
- end
137
- @events.each { | event | event.quantize_to(delta) }
138
- recalc_delta_from_times
85
+ delta = case length_or_note
86
+ when String
87
+ @sequence.note_to_delta(length_or_note)
88
+ else
89
+ @sequence.length_to_delta(length_or_note.to_i)
90
+ end
91
+ @events.each { |event| event.quantize_to(delta) }
92
+ recalc_delta_from_times
139
93
  end
140
94
 
141
95
  # Recalculate start times for all events in +list+ from starting_at to
142
96
  # end.
143
- def recalc_times(starting_at=0, list=@events)
144
- t = (starting_at == 0) ? 0 : list[starting_at - 1].time_from_start
145
- list[starting_at .. -1].each { | e |
146
- t += e.delta_time
147
- e.time_from_start = t
148
- }
97
+ def recalc_times(starting_at = 0, list = @events)
98
+ t = starting_at == 0 ? 0 : list[starting_at - 1].time_from_start
99
+ list[starting_at..-1].each do |e|
100
+ t += e.delta_time
101
+ e.time_from_start = t
102
+ end
149
103
  end
150
104
 
151
105
  # The opposite of recalc_times: recalculates delta_time for each event
152
106
  # from each event's time_from_start. This is useful, for example, when
153
107
  # merging two event lists. As a side-effect, elements from starting_at
154
108
  # are sorted by time_from_start.
155
- def recalc_delta_from_times(starting_at=0, list=@events)
156
- prev_time_from_start = 0
157
- # We need to sort the sublist. sublist.sort! does not do what we want.
158
- # We call mergesort instead of Array.sort because sort is not stable
159
- # (it can mix up the order of events that have the same start time).
160
- # See http://wiki.github.com/adamjmurray/cosy/midilib-notes for details.
161
- list[starting_at .. -1] = MIDI::Array.new(list[starting_at .. -1]).mergesort { | e1, e2 |
162
- e1.time_from_start <=> e2.time_from_start
163
- }
164
- list[starting_at .. -1].each { | e |
165
- e.delta_time = e.time_from_start - prev_time_from_start
166
- prev_time_from_start = e.time_from_start
167
- }
109
+ def recalc_delta_from_times(starting_at = 0, list = @events)
110
+ prev_time_from_start = 0
111
+ # We need to sort the sublist. sublist.sort! does not do what we want.
112
+ # We call mergesort instead of Array.sort because sort is not stable
113
+ # (it can mix up the order of events that have the same start time).
114
+ # See http://wiki.github.com/adamjmurray/cosy/midilib-notes for details.
115
+ list[starting_at..-1] = mergesort(list[starting_at..-1]) do |e1, e2|
116
+ e1.time_from_start <=> e2.time_from_start
117
+ end
118
+ list[starting_at..-1].each do |e|
119
+ e.delta_time = e.time_from_start - prev_time_from_start
120
+ prev_time_from_start = e.time_from_start
121
+ end
168
122
  end
169
123
 
170
124
  # Iterate over events.
171
- def each # :yields: event
172
- @events.each { | event | yield event }
125
+ def each(&block) # :yields: event
126
+ @events.each(&block)
173
127
  end
174
128
 
175
129
  # Sort events by their time_from_start. After sorting,
@@ -179,7 +133,6 @@ class Track
179
133
  # Note: this method is redundant, since recalc_delta_from_times sorts
180
134
  # the events first. This method may go away in a future release, or at
181
135
  # least be aliased to recalc_delta_from_times.
182
- alias_method :sort, :recalc_delta_from_times
183
- end
184
-
136
+ alias sort recalc_delta_from_times
137
+ end
185
138
  end
data/lib/midilib/utils.rb CHANGED
@@ -1,18 +1,16 @@
1
1
  module MIDI
2
-
3
- # Utility methods.
4
- class Utils
5
-
2
+ # Utility methods.
3
+ class Utils
6
4
  # MIDI note names. NOTE_NAMES[0] is 'C', NOTE_NAMES[1] is 'C#', etc.
7
5
  NOTE_NAMES = [
8
- 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'
6
+ 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'
9
7
  ]
10
8
 
11
9
  # Given a MIDI note number, return the name and octave as a string.
12
- def Utils.note_to_s(num)
13
- note = num % 12
14
- octave = num / 12
15
- return "#{NOTE_NAMES[note]}#{octave - 1}"
10
+ def self.note_to_s(num)
11
+ note = num % 12
12
+ octave = num / 12
13
+ "#{NOTE_NAMES[note]}#{octave - 1}"
16
14
  end
17
15
 
18
16
  # Given an integer, returns it as a variable length array of bytes (the
@@ -21,16 +19,15 @@ class Utils
21
19
  # The converse operation--converting a var len into a number--requires
22
20
  # input from a stream of bytes. Therefore we don't supply it here. That is
23
21
  # a part of the MIDIFile class.
24
- def Utils.as_var_len(val)
25
- buffer = []
26
- buffer << (val & 0x7f)
27
- val = (val >> 7)
28
- while val > 0
29
- buffer << (0x80 + (val & 0x7f))
30
- val = (val >> 7)
31
- end
32
- return buffer.reverse!
22
+ def self.as_var_len(val)
23
+ buffer = []
24
+ buffer << (val & 0x7f)
25
+ val = (val >> 7)
26
+ while val > 0
27
+ buffer << (0x80 + (val & 0x7f))
28
+ val = (val >> 7)
29
+ end
30
+ buffer.reverse!
33
31
  end
34
-
35
- end
32
+ end
36
33
  end
data/lib/midilib.rb CHANGED
@@ -5,11 +5,11 @@
5
5
  #
6
6
  # See the README.rdoc file or http://midilib.rubyforge.org for details.
7
7
 
8
- require 'midilib/info'
9
- require 'midilib/sequence'
10
- require 'midilib/track'
11
- require 'midilib/io/seqreader'
12
- require 'midilib/io/seqwriter'
8
+ require_relative 'midilib/info'
9
+ require_relative 'midilib/sequence'
10
+ require_relative 'midilib/track'
11
+ require_relative 'midilib/io/seqreader'
12
+ require_relative 'midilib/io/seqwriter'
13
13
 
14
14
  # --
15
15
  # consts.rb, utils.rb, and event.rb are included by these files.