Skip to content

Commit

Permalink
Merge pull request #82 from JoelHobson/Notes
Browse files Browse the repository at this point in the history
Notes, BPM, ms_per_tick
  • Loading branch information
JoelHobson authored Dec 5, 2017
2 parents e84a6d9 + aa20407 commit e5a7850
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 18 deletions.
27 changes: 16 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,22 @@ 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
----------------------------------------

```
# 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.Note[]
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)
Expand All @@ -79,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
Expand Down Expand Up @@ -138,6 +141,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
Expand Down
2 changes: 1 addition & 1 deletion src/MIDI.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
43 changes: 42 additions & 1 deletion src/midifile.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export MIDIFile, readMIDIfile, writeMIDIfile
export BPM, ms_per_tick

"""
MIDIFile <: Any
Expand Down Expand Up @@ -27,7 +28,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"
Expand Down Expand Up @@ -79,3 +80,43 @@ function writeMIDIfile(filename::AbstractString, data::MIDIFile)

close(f)
end


"""
BPM(midi)
Return the BPM where the given `MIDIFile` was exported at.
"""
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:
# 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]
if y.metatype == 0x51
tttttt = y.data
end
end
end
# Get the microsecond number from tttttt
unshift!(tttttt , 0x00)
u = ntoh(reinterpret(UInt32, tttttt)[1])
μs = Int64(u)
# BPM:
BPM = round(Int, 60000000/μs)
end

"""
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 ms_per_tick(midi::MIDI.MIDIFile, bpm::Int = BPM(MIDI))
tpq = midi.timedivision
tick_ms = (1000*60)/(bpm*tpq)
end
6 changes: 3 additions & 3 deletions src/miditrack.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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[]
Expand Down
7 changes: 5 additions & 2 deletions src/note.jl
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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)
Expand Down

0 comments on commit e5a7850

Please sign in to comment.