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,153 +1,157 @@
1
1
  # Writes MIDI files.
2
2
 
3
- require 'midilib/event'
4
- require 'midilib/utils'
3
+ require_relative '../event'
4
+ require_relative '../utils'
5
5
 
6
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 += write_bytes(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 += write_bytes(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)
7
+ module IO
8
+ class SeqWriter
9
+ def initialize(seq, midi_format = 1, &block) # :yields: num_tracks, index
10
+ @seq = seq
11
+ @midi_format = midi_format || 1
12
+ @update_block = block
13
+ end
14
+
15
+ # Writes a MIDI format 1 file.
16
+ def write_to(io)
17
+ if @midi_format == 0
18
+ # merge tracks before writing
19
+ merged_seq = Sequence.new
20
+ merged_track = Track.new(merged_seq)
21
+ merged_seq.tracks << merged_track
22
+ @seq.each do |track|
23
+ merged_track.merge(track.events)
24
+ end
25
+ @seq = merged_seq # replace
26
+ end
27
+
28
+ @io = io
29
+ @bytes_written = 0
30
+ write_header
31
+ @update_block.call(nil, @seq.tracks.length, 0) if @update_block
32
+ @seq.tracks.each_with_index do |track, i|
33
+ write_track(track)
34
+ @update_block.call(track, @seq.tracks.length, i) if @update_block
35
+ end
36
+ end
37
+
38
+ def write_header
39
+ @io.print 'MThd'
40
+ write32(6)
41
+ write16(@midi_format) # Ignore sequence format; write as format 1 or 0, default 1
42
+ write16(@seq.tracks.length)
43
+ write16(@seq.ppqn)
44
+ end
45
+
46
+ def write_track(track)
47
+ @io.print 'MTrk'
48
+ track_size_file_pos = @io.tell
49
+ write32(0) # Dummy byte count; overwritten later
50
+ @bytes_written = 0 # Reset after previous write
51
+
52
+ write_instrument(track.instrument)
53
+
54
+ prev_status = 0
55
+ track.events.each do |event|
56
+ write_var_len(event.delta_time) unless event.is_a?(Realtime)
57
+
58
+ data = event.data_as_bytes
59
+ status = data[0] # status byte plus channel number, if any
60
+
61
+ # running status byte
62
+ status = possibly_munge_due_to_running_status_byte(data, prev_status)
63
+
64
+ @bytes_written += write_bytes(data)
65
+
66
+ prev_status = status
67
+ end
68
+
69
+ # Write track end event.
70
+ event = MetaEvent.new(META_TRACK_END)
71
+ write_var_len(0)
72
+ @bytes_written += write_bytes(event.data_as_bytes)
73
+
74
+ # Go back to beginning of track data and write number of bytes,
75
+ # then come back here to end of file.
76
+ @io.seek(track_size_file_pos)
77
+ write32(@bytes_written)
78
+ @io.seek(0, ::IO::SEEK_END)
79
+ end
80
+
81
+ # If we can use a running status byte, delete the status byte from
82
+ # the given data. Return the status to remember for next time as the
83
+ # running status byte for this event.
84
+ def possibly_munge_due_to_running_status_byte(data, prev_status)
85
+ status = data[0]
86
+ return status if status >= 0xf0 || prev_status >= 0xf0
87
+
88
+ chan = (status & 0x0f)
89
+ return status if chan != (prev_status & 0x0f)
90
+
91
+ status = (status & 0xf0)
92
+ prev_status = (prev_status & 0xf0)
93
+
94
+ # Both events are on the same channel. If the two status bytes are
95
+ # exactly the same, the rest is trivial. If it's note on/note off,
96
+ # we can combine those further.
97
+ if status == prev_status
98
+ data[0, 1] = [] # delete status byte from data
99
+ status + chan
100
+ elsif status == NOTE_OFF && data[2] == 64
101
+ # If we see a note off and the velocity is 64, we can store
102
+ # a note on with a velocity of 0. If the velocity isn't 64
103
+ # then storing a note on would be bad because the would be
104
+ # changed to 64 when reading the file back in.
105
+ data[2] = 0 # set vel to 0; do before possible shrinking
106
+ status = NOTE_ON + chan
107
+ if prev_status == NOTE_ON
108
+ data[0, 1] = [] # delete status byte
109
+ else
110
+ data[0] = status
111
+ end
112
+ status
113
+ else
114
+ # Can't compress data
115
+ status + chan
116
+ end
117
+ end
118
+
119
+ def write_instrument(instrument)
120
+ return if instrument.nil?
121
+
122
+ event = MetaEvent.new(META_INSTRUMENT, instrument)
123
+ write_var_len(0)
124
+ data = event.data_as_bytes
125
+ @bytes_written += write_bytes(data)
126
+ end
127
+
128
+ def write_var_len(val)
129
+ buffer = Utils.as_var_len(val)
130
+ @bytes_written += write_bytes(buffer)
131
+ end
132
+
133
+ def write16(val)
134
+ val = (-val | 0x8000) if val < 0
135
+
136
+ @io.putc((val >> 8) & 0xff)
137
+ @io.putc(val & 0xff)
138
+ @bytes_written += 2
139
+ end
140
+
141
+ def write32(val)
142
+ val = (-val | 0x80000000) if val < 0
143
+
144
+ @io.putc((val >> 24) & 0xff)
145
+ @io.putc((val >> 16) & 0xff)
146
+ @io.putc((val >> 8) & 0xff)
147
+ @io.putc(val & 0xff)
148
+ @bytes_written += 4
149
+ end
150
+
151
+ def write_bytes(bytes)
152
+ bytes.each { |b| @io.putc(b) }
153
+ bytes.length
154
+ end
75
155
  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,1] = [] # 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,1] = [] # 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 += write_bytes(data)
120
- end
121
-
122
- def write_var_len(val)
123
- buffer = Utils.as_var_len(val)
124
- @bytes_written += write_bytes(buffer)
125
- end
126
-
127
- def write16(val)
128
- val = (-val | 0x8000) if val < 0
129
-
130
- buffer = []
131
- @io.putc((val >> 8) & 0xff)
132
- @io.putc(val & 0xff)
133
- @bytes_written += 2
134
- end
135
-
136
- def write32(val)
137
- val = (-val | 0x80000000) if val < 0
138
-
139
- @io.putc((val >> 24) & 0xff)
140
- @io.putc((val >> 16) & 0xff)
141
- @io.putc((val >> 8) & 0xff)
142
- @io.putc(val & 0xff)
143
- @bytes_written += 4
144
- end
145
-
146
- def write_bytes(bytes)
147
- bytes.each { |b| @io.putc(b) }
148
- bytes.length
149
- end
150
- end
151
-
152
- end
156
+ end
153
157
  end
@@ -1,80 +1,78 @@
1
- require 'midilib/consts'
2
-
3
- module MIDI
4
-
5
- # The Measure class contains information about a measure from the sequence.
6
- # The measure data is based on the time signature information from the sequence
7
- # and is not stored in the sequence itself
8
- class Measure
9
- # The numerator (top digit) for the measure's time signature
10
- attr_reader :numerator
11
- # The denominator for the measure's time signature
12
- attr_reader :denominator
13
- # Start clock tick for the measure
14
- attr_reader :start
15
- # End clock tick for the measure (inclusive)
16
- attr_reader :end
17
- # The measure number (1-based)
18
- attr_reader :measure_number
19
- # The metronome tick for the measure
20
- attr_reader :metronome_ticks
21
-
22
- # Constructor
23
- def initialize(meas_no, start_time, duration, numer, denom, met_ticks)
24
- @measure_number = meas_no
25
- @start = start_time
26
- @end = start_time + duration - 1
27
- @numerator = numer
28
- @denominator = denom
29
- @metronome_ticks = met_ticks
30
- end
31
-
32
- # Returns a detailed string with information about the measure
33
- def to_s
34
- t = "#{@numerator}/#{2**@denominator}"
35
- m = @metronome_ticks.to_f / 24
36
- "measure #{@measure_number} #{@start}-#{@end} #{t} #{m} qs metronome"
37
- end
38
-
39
- # Returns +true+ if the event is in the measure
40
- def contains_event?(e)
41
- (e.time_from_start >= @start) && (e.time_from_start <= @end)
42
- end
43
- end
44
-
45
- # A specialized container for MIDI::Measure objects, which can be use to map
46
- # event times to measure numbers. Please note that this object has to be remade
47
- # when events are deleted/added in the sequence.
48
- class Measures < Array
49
- # The highest event time in the sequence (at the time when the
50
- # object was created)
51
- attr_reader :max_time
52
-
53
- # The ppqd from the sequence
54
- attr_reader :ppqd
55
-
56
- # Constructor
57
- def initialize(max_time, ppqd)
58
- super(0)
59
- @max_time = max_time
60
- @ppqd = ppqd
61
- end
62
-
63
- # Returns the MIDI::Measure object where the event is located.
64
- # Returns +nil+ if the event isn't found in the container (should
65
- # never happen if the MIDI::Measures object is up to date).
66
- def measure_for_event(e)
67
- detect { | m | m.contains_event?(e) }
68
- end
69
-
70
- # Returns the event's time as a formatted MBT string (Measure:Beat:Ticks)
71
- # as found in MIDI sequencers.
72
- def to_mbt(e)
73
- m = measure_for_event(e)
74
- b = (e.time_from_start.to_f - m.start.to_f) / @ppqd
75
- b *= 24 / m.metronome_ticks
76
- sprintf("%d:%02d:%03d", m.measure_number, b.to_i + 1, (b - b.to_i) * @ppqd)
77
- end
78
- end
79
-
80
- end
1
+ require_relative 'consts'
2
+
3
+ module MIDI
4
+ # The Measure class contains information about a measure from the sequence.
5
+ # The measure data is based on the time signature information from the sequence
6
+ # and is not stored in the sequence itself
7
+ class Measure
8
+ # The numerator (top digit) for the measure's time signature
9
+ attr_reader :numerator
10
+ # The denominator for the measure's time signature
11
+ attr_reader :denominator
12
+ # Start clock tick for the measure
13
+ attr_reader :start
14
+ # End clock tick for the measure (inclusive)
15
+ attr_reader :end
16
+ # The measure number (1-based)
17
+ attr_reader :measure_number
18
+ # The metronome tick for the measure
19
+ attr_reader :metronome_ticks
20
+
21
+ # Constructor
22
+ def initialize(meas_no, start_time, duration, numer, denom, met_ticks)
23
+ @measure_number = meas_no
24
+ @start = start_time
25
+ @end = start_time + duration - 1
26
+ @numerator = numer
27
+ @denominator = denom
28
+ @metronome_ticks = met_ticks
29
+ end
30
+
31
+ # Returns a detailed string with information about the measure
32
+ def to_s
33
+ t = "#{@numerator}/#{2**@denominator}"
34
+ m = @metronome_ticks.to_f / 24
35
+ "measure #{@measure_number} #{@start}-#{@end} #{t} #{m} qs metronome"
36
+ end
37
+
38
+ # Returns +true+ if the event is in the measure
39
+ def contains_event?(e)
40
+ (e.time_from_start >= @start) && (e.time_from_start <= @end)
41
+ end
42
+ end
43
+
44
+ # A specialized container for MIDI::Measure objects, which can be use to map
45
+ # event times to measure numbers. Please note that this object has to be remade
46
+ # when events are deleted/added in the sequence.
47
+ class Measures < Array
48
+ # The highest event time in the sequence (at the time when the
49
+ # object was created)
50
+ attr_reader :max_time
51
+
52
+ # The ppqd from the sequence
53
+ attr_reader :ppqd
54
+
55
+ # Constructor
56
+ def initialize(max_time, ppqd)
57
+ super(0)
58
+ @max_time = max_time
59
+ @ppqd = ppqd
60
+ end
61
+
62
+ # Returns the MIDI::Measure object where the event is located.
63
+ # Returns +nil+ if the event isn't found in the container (should
64
+ # never happen if the MIDI::Measures object is up to date).
65
+ def measure_for_event(e)
66
+ detect { |m| m.contains_event?(e) }
67
+ end
68
+
69
+ # Returns the event's time as a formatted MBT string (Measure:Beat:Ticks)
70
+ # as found in MIDI sequencers.
71
+ def to_mbt(e)
72
+ m = measure_for_event(e)
73
+ b = (e.time_from_start.to_f - m.start.to_f) / @ppqd
74
+ b *= 24 / m.metronome_ticks
75
+ format('%d:%02d:%03d', m.measure_number, b.to_i + 1, (b - b.to_i) * @ppqd)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,39 @@
1
+ # This code was originally taken from
2
+ # http://github.com/adamjmurray/cosy/blob/master/lib/cosy/helper/midi_file_renderer_helper.rb
3
+ # with permission from Adam Murray, who originally suggested this fix.
4
+ # See http://wiki.github.com/adamjmurray/cosy/midilib-notes for details.
5
+
6
+ # A stable sorting algorithm that maintains the relative order of equal
7
+ # elements.
8
+ #
9
+ # This code used to be in a new subclass of Array, but that started causing
10
+ # problems in Ruby 3.0, apparently due to the return type of the `[]`
11
+ # operator which was the parent Array class.
12
+ #
13
+ # This code borrowed from 'Moser' http://codesnippets.joyent.com/posts/show/1699
14
+ def mergesort(arr, &cmp)
15
+ cmp = ->(a, b) { a <=> b } if cmp.nil?
16
+ if arr.size <= 1
17
+ arr.dup
18
+ else
19
+ halves = mergesort_split(arr).map { |half| mergesort(half, &cmp) }
20
+ mergesort_merge(*halves, &cmp)
21
+ end
22
+ end
23
+
24
+ def mergesort_split(arr)
25
+ n = (arr.length / 2).floor - 1
26
+ [arr[0..n], arr[n + 1..-1]]
27
+ end
28
+
29
+ def mergesort_merge(first, second, &predicate)
30
+ result = []
31
+ until first.empty? || second.empty?
32
+ result << if predicate.call(first.first, second.first) <= 0
33
+ first.shift
34
+ else
35
+ second.shift
36
+ end
37
+ end
38
+ result.concat(first).concat(second)
39
+ end