Skip to content

Commit

Permalink
Contribution to jimm#8
Browse files Browse the repository at this point in the history
  • Loading branch information
Fukurokudzu committed Feb 24, 2023
1 parent 71a9b43 commit bc8896f
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 44 deletions.
98 changes: 54 additions & 44 deletions lib/midilib/sequence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class Sequence

UNNAMED = 'Unnamed Sequence'
DEFAULT_TEMPO = 120
BPM_ROUND = 3

NOTE_TO_LENGTH = {
'whole' => 4.0,
Expand Down Expand Up @@ -70,50 +71,6 @@ def beats_per_minute
event ? Tempo.mpq_to_bpm(event.tempo) : DEFAULT_TEMPO
end

def get_tempo_parts
tempo_parts = {}
Array(@tracks).each do |track|
track.events.map do |e|
tempo_parts[e.time_from_start] = Tempo.mpq_to_bpm(e.tempo) if e.is_a?(MIDI::Tempo)
end
end
tempo_parts
end

def avg_beats_per_minute
return DEFAULT_TEMPO if @tracks.nil? || @tracks.empty?

bpm_min = tempo_parts.min
bpm_max = tempo_parts.max
tempo_events ? Tempo.mpq_to_bpm(event.tempo) : DEFAULT_TEMPO
# parts_lenght(tempo_parts.keys, self.get_measures.last.end)
end

def beats_per_minute_now

end

# def avg_beats_per_minute
# return DEFAULT_TEMPO if @tracks.nil? || @tracks.empty?
# tempo_parts = {}
# Array(@tracks).each do |track|
# track.events.map do |e|
# tempo_parts[e.time_from_start] = Tempo.mpq_to_bpm(e.tempo) if e.is_a?(MIDI::Tempo)
# end
# end
# # tempo_events ? Tempo.mpq_to_bpm(event.tempo) : DEFAULT_TEMPO
# # parts_lenght(tempo_parts.keys, self.get_measures.last.end)
# end

# def parts_lenght(start_points, track_ends)
# parts_lenght = []
# start_points.each_with_index do |start_point, i|
# start_points[i+1].nil? ? part_end = track_ends : part_end = start_points[i+1]
# parts_lenght << part_end - start_point
# end
# parts_lenght
# end

alias bpm beats_per_minute
alias tempo beats_per_minute

Expand All @@ -124,6 +81,12 @@ def pulses_to_seconds(pulses)
(pulses.to_f / @ppqn.to_f / beats_per_minute) * 60.0
end

def pulses_to_seconds_current(pulses, offset)
unless beats_per_minute_current(offset).nil?
(pulses.to_f / @ppqn.to_f / beats_per_minute_current(offset)) * 60.0
end
end

# Given a note length name like "whole", "dotted quarter", or "8th
# triplet", return the length of that note in quarter notes as a delta
# time.
Expand Down Expand Up @@ -245,5 +208,52 @@ def get_measures
end
measures
end

# Returns array of minimum and maximum bpm within sequence
def beats_per_minute_min_max
return [DEFAULT_TEMPO] if @tracks.nil? || @tracks.empty?
tempo_parts = get_tempo_parts
return tempo_parts.values if tempo_parts.length == 1
[tempo_parts.values.min.round(BPM_ROUND), tempo_parts.values.max.round(BPM_ROUND)]
end

# Returns array with all tempos parts within sequence
def beats_per_minute_all
return [DEFAULT_TEMPO] if @tracks.nil? || @tracks.empty?
tempo_parts = get_tempo_parts
tempo_parts.values.map { |bpm| bpm.round(BPM_ROUND) }
end

# Returns bpm value for offset, nil if offset is out of range
def beats_per_minute_current(offset)
return DEFAULT_TEMPO if @tracks.nil? || @tracks.empty?
return nil if offset > self.get_measures.last.end || offset < 0
current_bpm = 0
tempo_parts = get_tempo_parts
tempo_parts.each_with_index do |part, i|
if !tempo_parts[i+1].nil?
current_bpm = part[1] if part[0] <= offset && tempo_parts[i+1][0] > offset
else
current_bpm = part[1] if part[0] <= offset
end
end
current_bpm.round(BPM_ROUND)
end

private

# Private method to split sequence into parts, if more then one bpm present in sequence
# Returns hash { start_time => bpm }
def get_tempo_parts
tempo_parts = {}
return tempo_parts[0] = DEFAULT_TEMPO if @tracks.nil? || @tracks.empty?
Array(@tracks).each do |track|
track.events.map do |e|
e.is_a?(MIDI::Tempo) ? tempo_parts[e.time_from_start] = Tempo.mpq_to_bpm(e.tempo) : tempo_parts[0] = DEFAULT_TEMPO
end
end
tempo_parts
end

end
end
26 changes: 26 additions & 0 deletions test/test_sequence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def setup
@seq.tracks << @track
3.times { @track.events << MIDI::NoteOn.new(0, 64, 64, 100) }
@track.recalc_times
@seq_bpm_diff = MIDI::Sequence.new
end

def test_basics
Expand All @@ -31,6 +32,12 @@ def test_pulses_to_seconds

# An eight note should take 0.25 seconds
assert_in_delta 0.25, @seq.pulses_to_seconds(480 / 2), 0.00001

# At a tempo of 120 BPM 480 pulses (one quarter note) should take 0.5 seconds
assert_in_delta 0.5, @seq.pulses_to_seconds_current(480, 1000), 0.00001

# Should retun nil if offset is out of range
assert_equal(nil, @seq.pulses_to_seconds_current(480, 1920))
end

def test_length_to_delta
Expand Down Expand Up @@ -77,4 +84,23 @@ def test_note_to_delta
assert_equal(480 / 16, @seq.note_to_delta('sixtyfourth'))
assert_equal(480 / 16, @seq.note_to_delta('64th'))
end

def test_beats_per_minute
# Using file with 2 different tempos whithin sequence (bpm change at 15600)
File.open('examples/ex2.mid', 'rb') do | file |
@seq_bpm_diff.read(file)
assert_equal(nil, @seq_bpm_diff.beats_per_minute_current(-1000))
assert_equal(120.0, @seq_bpm_diff.beats_per_minute_current(15599))
assert_equal(131.34, @seq_bpm_diff.beats_per_minute_current(15600))
assert_equal(131.34, @seq_bpm_diff.beats_per_minute_current(15601))
assert_equal([120.0, 131.34], @seq_bpm_diff.beats_per_minute_min_max)
assert_equal([120.0, 131.34], @seq_bpm_diff.beats_per_minute_all)
end

# Using regular testing sequence
assert_equal(120.0, @seq.beats_per_minute_current(1918))
assert_equal(nil, @seq.beats_per_minute_current(1920))
assert_equal([120.0], @seq.beats_per_minute_min_max)
assert_equal([120.0], @seq.beats_per_minute_all)
end
end

0 comments on commit bc8896f

Please sign in to comment.