diff --git a/CHANGELOG.md b/CHANGELOG.md index 14eebc6..5160ff2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +# v2.6.0 +* New functions `metric_time`. +* New functions `duration_metric_time`. # v2.5.0 * Implement `Note(pitch_name::String; position = 0, velocity = 100, duration = 960, channel = 0)` * Implement `Notes(notes_string::String, tpq::Int = 960)` diff --git a/Project.toml b/Project.toml index 66b9502..616ad22 100644 --- a/Project.toml +++ b/Project.toml @@ -1,13 +1,15 @@ name = "MIDI" uuid = "f57c4921-e30c-5f49-b073-3f2f2ada663e" repo = "https://github.com/JuliaMusic/MIDI.jl.git" -version = "2.5.0" +version = "2.6.0" [deps] FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" +IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e" [compat] FileIO = "1" +IterTools = "1.8.0" julia = "1" [extras] diff --git a/src/MIDI.jl b/src/MIDI.jl index f914623..5c9941d 100644 --- a/src/MIDI.jl +++ b/src/MIDI.jl @@ -4,6 +4,7 @@ A Julia library for reading and writing MIDI files. module MIDI using Base.Unicode +using IterTools include("constants.jl") include("note.jl") @@ -34,6 +35,7 @@ export pitch_to_name, name_to_pitch, is_octave export pitch_to_hz, hz_to_pitch export TrackEvent, MetaEvent, MIDIEvent, SysexEvent export readvariablelength, writevariablelength +export metric_time,duration_metric_time """ testmidi() diff --git a/src/midifile.jl b/src/midifile.jl index 7e3e76b..06da296 100644 --- a/src/midifile.jl +++ b/src/midifile.jl @@ -167,7 +167,7 @@ end """ ms_per_tick(tpq, qpm) ms_per_tick(midi::MIDIFile) -Return how many miliseconds is one tick, based +Return how many milliseconds is one tick, based on the quarter notes per minute `qpm` and ticks per quarter note `tpq`. """ ms_per_tick(midi::MIDI.MIDIFile, qpm = qpm(midi)) = ms_per_tick(midi.tpq, qpm) @@ -175,3 +175,32 @@ ms_per_tick(tpq, qpm) = (1000*60)/(qpm*tpq) getnotes(midi::MIDIFile, trackno = midi.format == 0 ? 1 : 2) = getnotes(midi.tracks[trackno], midi.tpq) + +""" + metric_time(midi::MIDIFile,note::AbstractNote)::Float64 +Return how many milliseconds elapsed at `note` position. +Matric time calculations need `tpq` field of `MIDIFile`. +Apparently it only make sense if the `note` coming from `MIDIFile`, otherwise you can't get the correct result. +""" +function metric_time(midi::MIDIFile,note::AbstractNote)::Float64 + # get all tempo change event before note + tc_tuples = filter(x->x[1]<=note.position,tempochanges(midi)) + # how many ticks between two tempo changes event + tempo_ticks = map(x->x[2][1]-x[1][1],partition(tc_tuples,2,1)) + push!(tempo_ticks,note.position-last(tc_tuples)[1]) + return mapreduce(x -> ms_per_tick(midi.tpq, x[1][2]) * x[2], +, zip(tc_tuples,tempo_ticks)) +end + +""" + duration_metric_time(midi::MIDIFile,note::AbstractNote)::Float64 +Return `note` duration time in milliseconds. +Matric time calculations need `tpq` field of `MIDIFile`. +Apparently it only make sense if the `note` coming from `MIDIFile`, otherwise you can't get the correct result. +""" +function duration_metric_time(midi::MIDIFile,note::AbstractNote)::Float64 + tc_tuple = (0,0.0) + for tc in tempochanges(midi) + tc[1] <= note.position ? tc_tuple = tc : break + end + return ms_per_tick(midi.tpq,tc_tuple[2])*note.duration +end \ No newline at end of file diff --git a/test/miditrack.jl b/test/miditrack.jl index de3dcb5..fe56047 100644 --- a/test/miditrack.jl +++ b/test/miditrack.jl @@ -200,7 +200,7 @@ invalidtestvalues = [ onotes = getnotes(original_track) tnotes = getnotes(test_track) identical = true - for i = 1:length(onotes) + for i = eachindex(onotes) if onotes[i] != tnotes[i] identical = false end @@ -209,4 +209,17 @@ invalidtestvalues = [ @test identical end + + @testset "metric_time" begin + midifile = load("./tempo_change.mid") + notes = getnotes(midifile.tracks[1]) + + @test metric_time(midifile,last(notes)) == 8500.0 + @test metric_time(midifile,first(notes)) == 0.0 + @test metric_time(midifile,notes[5]) == 2500.0 + + @test round(duration_metric_time(midifile,notes[2])) == 474 + @test round(duration_metric_time(midifile,last(notes))) == 948 + @test round(duration_metric_time(midifile,first(notes))) == 474 + end end \ No newline at end of file diff --git a/test/tempo_change.mid b/test/tempo_change.mid new file mode 100644 index 0000000..eb32c86 Binary files /dev/null and b/test/tempo_change.mid differ