From 3e30fa27c36e2120f77268bae8d009c0d14f5974 Mon Sep 17 00:00:00 2001 From: Datseris Date: Mon, 4 Dec 2017 23:46:12 +0100 Subject: [PATCH 1/9] add Notes --- src/MIDI.jl | 2 +- src/midifile.jl | 2 +- src/miditrack.jl | 6 +++--- src/note.jl | 7 +++++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/MIDI.jl b/src/MIDI.jl index a46f54b..4ccf033 100644 --- a/src/MIDI.jl +++ b/src/MIDI.jl @@ -3,11 +3,11 @@ A Julia library for reading and writing MIDI files. """ module MIDI +include("note.jl") include("trackevent.jl") include("midievent.jl") include("metaevent.jl") include("sysexevent.jl") -include("note.jl") include("miditrack.jl") include("midifile.jl") include("constants.jl") diff --git a/src/midifile.jl b/src/midifile.jl index e1474b4..644b8d0 100644 --- a/src/midifile.jl +++ b/src/midifile.jl @@ -27,7 +27,7 @@ end """ readMIDIfile(filename::AbstractString) -Read a file into a MIDIFile data type. +Read a file into a `MIDIFile` data type. """ function readMIDIfile(filename::AbstractString) if length(filename) < 4 || filename[end-3:end] != ".mid" diff --git a/src/miditrack.jl b/src/miditrack.jl index 552b44f..858dd43 100644 --- a/src/miditrack.jl +++ b/src/miditrack.jl @@ -142,11 +142,11 @@ function addnote(track::MIDITrack, note::Note) end """ - addnotes(track::MIDITrack, notes::Vector{Note}) + addnotes(track::MIDITrack, notes::Notes) Add given `notes` to given `track`, internally doing all translations from absolute time to relative time. """ -function addnotes(track::MIDITrack, notes::Array{Note, 1}) +function addnotes(track::MIDITrack, notes::Notes) for note in notes addnote(track, note) end @@ -161,7 +161,7 @@ the `Note` datatype provided by this Package. Ordering is done based on position There are special cases where NOTEOFF is actually encoded as NOTEON with 0 velocity. `getnotes` takes care of this. -Returns: `Vector{Note}`. +Returns: `Notes` (which is `Vector{Note}`). """ function getnotes(track::MIDITrack) notes = Note[] diff --git a/src/note.jl b/src/note.jl index bef7e86..f9f38f4 100644 --- a/src/note.jl +++ b/src/note.jl @@ -1,8 +1,9 @@ -export Note +export Note, Notes """ Note <: Any -Data structure describing a "music note". +Data structure describing a "music note". The alias `Notes = Vector{Note}` is also +provided. ## Fields: * `value::UInt8` : Pitch, starting from C0 = 0, adding one per semitone (middle-C is 60). * `duration::UInt` : Duration in ticks. @@ -27,6 +28,8 @@ type Note end end +Notes = Vector{Note} + import Base.+, Base.-, Base.== +(n::Note, i::Integer) = Note(n.value + i, n.duration, n.position, n.channel, n.velocity) From 81fe5b8840d5d878aa7aa00443524b7621d35f2d Mon Sep 17 00:00:00 2001 From: Datseris Date: Mon, 4 Dec 2017 23:47:21 +0100 Subject: [PATCH 2/9] add BPM and tick_in_ms --- src/midifile.jl | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/midifile.jl b/src/midifile.jl index 644b8d0..aa5043a 100644 --- a/src/midifile.jl +++ b/src/midifile.jl @@ -79,3 +79,39 @@ function writeMIDIfile(filename::AbstractString, data::MIDIFile) close(f) end + + +""" + BPM(midi) +Given a `MIDIFile`, find and return the BPM where it was exported. +""" +function BPM(t::MIDI.MIDIFile) + # META-event list: + tlist = [x for x in t.tracks[1].events] + tttttt = Vector{UInt32} + # Find the one that corresponds to Set-Time: + for i in 1:length(tlist) + if typeof(tlist[i]) == MIDI.MetaEvent + y = tlist[i] + if y.metatype == 0x51 + tttttt = y.data + end + end + end + # Get the microsecond number from this tt-tt-tt + unshift!(tttttt , 0x00) + u = ntoh(reinterpret(UInt32, tttttt)[1]) + μs = Int64(u) + # BPM: + BPM = round(Int64, 60000000/μs) +end + +""" + tick_in_ms(midi) -> ms::Float64 +Given a `MIDIFile`, return how many miliseconds is one tick. +""" +function tick_in_ms(midi::MIDI.MIDIFile) + tpq = midi.timedivision + BPM = BPM(midi) + tick_ms = (1000*60)/(BPM*tpq) +end From d816354bbccaca3e6958f9c0750498231f50f4ea Mon Sep 17 00:00:00 2001 From: Datseris Date: Mon, 4 Dec 2017 23:52:08 +0100 Subject: [PATCH 3/9] optional argument and rename: ms_per_tick --- src/midifile.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/midifile.jl b/src/midifile.jl index aa5043a..6557a8d 100644 --- a/src/midifile.jl +++ b/src/midifile.jl @@ -83,7 +83,7 @@ end """ BPM(midi) -Given a `MIDIFile`, find and return the BPM where it was exported. +Return the BPM where the given `MIDIFile` was exported at. """ function BPM(t::MIDI.MIDIFile) # META-event list: @@ -103,15 +103,15 @@ function BPM(t::MIDI.MIDIFile) u = ntoh(reinterpret(UInt32, tttttt)[1]) μs = Int64(u) # BPM: - BPM = round(Int64, 60000000/μs) + BPM = round(Int, 60000000/μs) end """ - tick_in_ms(midi) -> ms::Float64 -Given a `MIDIFile`, return how many miliseconds is one tick. + ms_per_tick(midi, bpm::Integer = BPM(midi)) -> ms::Float64 +Given a `MIDIFile`, return how many miliseconds is one tick, based +on the `bpm`. By default the `bpm` is the BPM the midi file was exported at. """ -function tick_in_ms(midi::MIDI.MIDIFile) +function ms_per_tick(midi::MIDI.MIDIFile, bpm::Int = BPM(MIDI)) tpq = midi.timedivision - BPM = BPM(midi) - tick_ms = (1000*60)/(BPM*tpq) + tick_ms = (1000*60)/(bpm*tpq) end From 92ade956887d31bb745d4e00ee742ba489c434cb Mon Sep 17 00:00:00 2001 From: Datseris Date: Mon, 4 Dec 2017 23:55:14 +0100 Subject: [PATCH 4/9] export the functions! --- src/midifile.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/midifile.jl b/src/midifile.jl index 6557a8d..ce923b5 100644 --- a/src/midifile.jl +++ b/src/midifile.jl @@ -1,4 +1,5 @@ export MIDIFile, readMIDIfile, writeMIDIfile +export BPM, ms_per_tick """ MIDIFile <: Any From 4311fc65141f5d5c2c436e232b1e38bac9ba09c0 Mon Sep 17 00:00:00 2001 From: Datseris Date: Tue, 5 Dec 2017 00:00:34 +0100 Subject: [PATCH 5/9] update readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 7928a68..6593e8e 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,8 @@ MIDIfile = readMIDIfile("test.mid") writeMIDIfile("filename.mid", MIDIfile) ``` +Two functions that may also be of use are: `BPM(midi)` and `ms_per_tick(midi)`. + Creating a new file with arbitrary notes ---------------------------------------- @@ -138,6 +140,8 @@ end Value is a number indicating pitch class & octave (middle-C is 60). Position is an absolute time (in ticks) within the track. Please note that velocity cannot be higher than 127 (0x7F). Integers can be added to, or subtracted from notes to change the pitch, and notes can be directly compared with ==. Constants exist for the different pitch values at octave 0. MIDI.C, MIDI.Cs, MIDI.Db, etc. Enharmonic note constants exist as well (MIDI.Fb). Just add 12*n to the note to transpose to octave n. +In addition, the alias `Notes = Vector{Note}` is also exported for ease-of-use. + ``` type MIDIEvent <: TrackEvent dT::Int From 286b87cae1e67d60b463d591a6d2f6b4d4df63de Mon Sep 17 00:00:00 2001 From: Datseris Date: Tue, 5 Dec 2017 00:02:12 +0100 Subject: [PATCH 6/9] added constructor for empty Notes vector using Notes() calls Note[] --- README.md | 2 +- src/note.jl | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6593e8e..6352ff3 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ G = MIDI.Note(67, 96, 96, 0) inc = 96 file = MIDI.MIDIFile() track = MIDI.MIDITrack() -notes = MIDI.Note[] +notes = MIDI.Notes() i = 0 for v in values(MIDI.GM) # GM is a map of all the general MIDI instrument names and their codes push!(notes, C) diff --git a/src/note.jl b/src/note.jl index f9f38f4..ec9b079 100644 --- a/src/note.jl +++ b/src/note.jl @@ -29,6 +29,7 @@ type Note end Notes = Vector{Note} +Notes() = Note[] import Base.+, Base.-, Base.== From 59d54019b2b32f90a8bb9e0d1f1b33c7daafb908 Mon Sep 17 00:00:00 2001 From: Datseris Date: Tue, 5 Dec 2017 00:04:10 +0100 Subject: [PATCH 7/9] luls defining Notes() is not necessary. It works by default! --- src/note.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/note.jl b/src/note.jl index ec9b079..f9f38f4 100644 --- a/src/note.jl +++ b/src/note.jl @@ -29,7 +29,6 @@ type Note end Notes = Vector{Note} -Notes() = Note[] import Base.+, Base.-, Base.== From 90c6ab8f7e244558e0da6900392fb23797f7114f Mon Sep 17 00:00:00 2001 From: Datseris Date: Tue, 5 Dec 2017 00:07:05 +0100 Subject: [PATCH 8/9] removed MIDI. from README I removed `MIDI.` from all exported names and added `using MIDI` at the start of the example. This is more natural, less verbose, and more in line with how other packages operate. --- README.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 6352ff3..24d6391 100644 --- a/README.md +++ b/README.md @@ -63,14 +63,15 @@ Creating a new file with arbitrary notes ``` # Arguments are pitch (MIDI note number), duration (in ticks), position in track (in ticks), channel (0-15) and velocity (0-127) -C = MIDI.Note(60, 96, 0, 0) -E = MIDI.Note(64, 96, 48, 0) -G = MIDI.Note(67, 96, 96, 0) +using MIDI +C = Note(60, 96, 0, 0) +E = Note(64, 96, 48, 0) +G = Note(67, 96, 96, 0) inc = 96 -file = MIDI.MIDIFile() -track = MIDI.MIDITrack() -notes = MIDI.Notes() +file = MIDIFile() +track = MIDITrack() +notes = Notes() i = 0 for v in values(MIDI.GM) # GM is a map of all the general MIDI instrument names and their codes push!(notes, C) @@ -81,15 +82,15 @@ for v in values(MIDI.GM) # GM is a map of all the general MIDI instrument names C.position += inc E.position += inc G.position += inc - C = MIDI.Note(60, 96, C.position+inc, 0) - E = MIDI.Note(64, 96, E.position+inc, 0) - G = MIDI.Note(67, 96, G.position+inc, 0) + C = Note(60, 96, C.position+inc, 0) + E = Note(64, 96, E.position+inc, 0) + G = Note(67, 96, G.position+inc, 0) i += 1 end -MIDI.addnotes(track, notes) +addnotes(track, notes) push!(file.tracks, track) -MIDI.writeMIDIfile("test_out.mid", file) +writeMIDIfile("test_out.mid", file) ``` Data structures and functions you should know From aa20407a6237dcde3999d0814fb49e5d7ef8222c Mon Sep 17 00:00:00 2001 From: Datseris Date: Tue, 5 Dec 2017 09:23:46 +0100 Subject: [PATCH 9/9] added explanation for tttttt --- src/midifile.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/midifile.jl b/src/midifile.jl index ce923b5..c75d3e7 100644 --- a/src/midifile.jl +++ b/src/midifile.jl @@ -91,6 +91,10 @@ function BPM(t::MIDI.MIDIFile) tlist = [x for x in t.tracks[1].events] tttttt = Vector{UInt32} # Find the one that corresponds to Set-Time: + # The event tttttt corresponds to the command + # FF 51 03 tttttt Set Tempo (in microseconds per MIDI quarter-note) + # See here (page 8): + # http://www.cs.cmu.edu/~music/cmsip/readings/Standard-MIDI-file-format-updated.pdf for i in 1:length(tlist) if typeof(tlist[i]) == MIDI.MetaEvent y = tlist[i] @@ -99,7 +103,7 @@ function BPM(t::MIDI.MIDIFile) end end end - # Get the microsecond number from this tt-tt-tt + # Get the microsecond number from tttttt unshift!(tttttt , 0x00) u = ntoh(reinterpret(UInt32, tttttt)[1]) μs = Int64(u)