Skip to content

Commit

Permalink
Add Base.keys() and is_octave() (#156)
Browse files Browse the repository at this point in the history
* add Base.keys

* add is_octave

* is_octave doc

* add markdown link

* add is_octave test

* add base.eachindex, add find max test, wait reply

* add is_octave() for pitch

* finish find max pitch note doc and test

* change log for keys and eachindex

* Update src/note.jl

Co-authored-by: George Datseris <[email protected]>
  • Loading branch information
NeroBlackstone and Datseris authored Dec 11, 2022
1 parent ba0237d commit 4554c16
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 6 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.3.0
* New functions `is_octave`.
* Implement `Base.keys(::Notes)` and `Base.eachindex(::Notes)`.
# v2.2.0
* New functions `pitch_to_hz ` and `hz_to_pitch `
# v2.1.0
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "MIDI"
uuid = "f57c4921-e30c-5f49-b073-3f2f2ada663e"
repo = "https://github.com/JuliaMusic/MIDI.jl.git"
version = "2.2.0"
version = "2.3.0"

[deps]
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
Expand Down
2 changes: 1 addition & 1 deletion src/MIDI.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export BPM, bpm, qpm, time_signature, tempochanges, ms_per_tick
export getnotes, addnote!, addnotes!, addevent!, addevents!
export MIDITrack
export Note, Notes, AbstractNote, DrumNote
export pitch_to_name, name_to_pitch
export pitch_to_name, name_to_pitch, is_octave
export pitch_to_hz, hz_to_pitch
export TrackEvent, MetaEvent, MIDIEvent, SysexEvent
export readvariablelength, writevariablelength
Expand Down
22 changes: 18 additions & 4 deletions src/note.jl
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ A data structure describing a collection of music notes, bundled with the ticks
per quarter note (so that the notes can be attributed rhythmic value).
`Notes` can be iterated and accessed as the given `note_vector`.
This eliminates the need for custom iteration or search functions.
For example, to get the note of maximum pitch you can do:
```julia
max_pitch, index_max = findmax(n -> n.pitch, notes)
max_pitch_note = notes[index_max]
```
"""
struct Notes{N <: AbstractNote}
notes::Vector{N}
Expand Down Expand Up @@ -101,6 +107,8 @@ Base.firstindex(n::Notes) = firstindex(n.notes)
Base.getindex(n::Notes, i::Int) = n.notes[i]
Base.getindex(n::Notes, r) = Notes(n.notes[r], n.tpq)
Base.view(n::Notes, r) = view(n.notes, r)
Base.eachindex(n::Notes) = eachindex(n.notes)
Base.keys(n::Notes) = eachindex(n)

# Pushing
Base.push!(no::Notes{N}, n::N) where {N <: AbstractNote} = push!(no.notes, n)
Expand All @@ -112,6 +120,12 @@ end

Base.copy(notes::Notes) = Notes([copy(n) for n in notes], notes.tpq)

"""
is_octave(note1::Note, note2::Note)
Return true if two notes form an octave.
"""
is_octave(note1::Note,note2::Note)::Bool = abs(Int(note1.pitch)-Int(note2.pitch)) == 12
is_octave(pitch1::Integer, pitch2::Integer)::Bool = abs(Int(pitch1)-Int(pitch2)) == 12

#######################################################
# string name <-> midi pitch
Expand Down Expand Up @@ -156,8 +170,8 @@ Return the pitch value of the given note name, which can be of the form
We define E.g. `name_to_pitch("C4") === 60` (i.e. string
`"C4"`, representing the middle-C, corresponds to pitch `60`).
See http://newt.phys.unsw.edu.au/jw/notes.html
and https://en.wikipedia.org/wiki/C_(musical_note) .
See [http://newt.phys.unsw.edu.au/jw/notes.html](http://newt.phys.unsw.edu.au/jw/notes.html)
and [https://en.wikipedia.org/wiki/C_(musical_note)](https://en.wikipedia.org/wiki/C_(musical_note)) .
"""
function name_to_pitch(name)
pe = collect(Unicode.graphemes(name))
Expand All @@ -184,8 +198,8 @@ end
"""
pitch_to_hz(pitch::Integer, A4::Real = 440) -> hz::Real
Return the frequency value of the given midi note, optionally given the reference for middle A.
See https://en.wikipedia.org/wiki/Piano_key_frequencies
and https://librosa.org/doc/main/_modules/librosa/core/convert.html#midi_to_hz.
See [https://en.wikipedia.org/wiki/Piano_key_frequencies](https://en.wikipedia.org/wiki/Piano_key_frequencies)
and [https://librosa.org/doc/main/_modules/librosa/core/convert.html#midi_to_hz](https://librosa.org/doc/main/_modules/librosa/core/convert.html#midi_to_hz).
"""
function pitch_to_hz(pitch, A4 = 440)
return A4 * (2^ ((pitch-69) / 12))
Expand Down
22 changes: 22 additions & 0 deletions test/note.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,25 @@ end
notes[1].pitch = 1
@test n2[1].pitch 1
end

@testset "is octave" begin
n1 = Note(name_to_pitch("C4"),0)
n2 = Note(name_to_pitch("C5"),1)
n3 = Note(name_to_pitch("D5"),2)
@test is_octave(n1,n2)
@test !is_octave(n1,n3)

@test is_octave(n1.pitch, name_to_pitch("C5"))
@test !is_octave(n1.pitch, name_to_pitch("D5"))
end

@testset "find max and min" begin
n1 = Note(name_to_pitch("C4"),0)
n2 = Note(name_to_pitch("C5"),1)
n3 = Note(name_to_pitch("D5"),2)
notes = Notes([n1,n2,n3])
max_pitch, index_max = findmax(n -> n.pitch, notes)
@test notes[index_max].pitch == name_to_pitch("D5")
min_pitch, index_min = findmin(n -> n.pitch, notes)
@test notes[index_min].pitch == name_to_pitch("C4")
end

0 comments on commit 4554c16

Please sign in to comment.