midilib 2.0.2 → 3.0.1

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.
@@ -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.