Skip to content

Commit

Permalink
Stateless implementation of Scale
Browse files Browse the repository at this point in the history
  • Loading branch information
dpsanders committed Dec 26, 2023
1 parent ee85ece commit 641b2fe
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 51 deletions.
2 changes: 2 additions & 0 deletions src/MusicTheory.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export Interval, IntervalType, Major, Minor, Perfect, Augmented, Diminished,
export Major_2nd, Minor_2nd, Major_3rd, Minor_3rd, Perfect_4th, Perfect_5th,
Major_6th, Minor_6th, Major_7th, Minor_7th, Octave

export major_scale, natural_minor_scale, melodic_minor_scale, harmonic_minor_scale

include("notes.jl")
include("intervals.jl")
include("scales.jl")
Expand Down
22 changes: 16 additions & 6 deletions src/intervals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ interval_quality_semitones = Dict(
semitone(interval::Interval) =
interval_semitones[interval.distance] + interval_quality_semitones[interval.quality]

Base.:(<=)(n1::Pitch, n2::Pitch) = M.semitone(n1) <= M.semitone(n2)
Base.:(<=)(n1::Pitch, n2::Pitch) = semitone(n1) <= semitone(n2)



Expand All @@ -59,7 +59,7 @@ function interval(n1::Pitch, n2::Pitch)

total_note_distance = note_distance + octave_distance

semitone_distance = (M.semitone(n2) - M.semitone(n1)) % 12
semitone_distance = (semitone(n2) - semitone(n1)) % 12

base_interval_semitone = interval_semitones[total_note_distance %7]
alteration_distance = semitone_distance - base_interval_semitone
Expand Down Expand Up @@ -95,21 +95,31 @@ tone(interval::Interval) = interval.distance
semitone(interval::Interval) =
interval_semitones[interval.distance] + interval_quality_semitones[interval.quality]

function add_interval(p::Pitch, interval::Interval)
new_tone = tone(p) + tone(interval)
function add_interval(n::Note, interval::Interval)
new_tone = (tone(n) + tone(interval)) % 7

new_note_class = NoteClass(new_tone % 7)
new_note_class = NoteClass(new_tone)
new_octave = new_tone ÷ 7

new_semitone = semitone(p) + semitone(interval)
new_semitone = semitone(n) + semitone(interval)
new_note = find_accidental(new_semitone % 12, new_note_class)

return new_note
end


function add_interval(p::Pitch, interval::Interval)
new_tone = tone(p) + tone(interval)
new_octave = new_tone ÷ 7

new_note = add_interval(p.note, interval)
new_pitch = Pitch(new_note, new_octave)

return new_pitch
end

Base.:(+)(p::Pitch, interval::Interval) = add_interval(p, interval)
Base.:(+)(n::Note, interval::Interval) = add_interval(n, interval)

const Minor_2nd = Interval(1, Minor)
const Major_2nd = Interval(1, Major)
Expand Down
1 change: 1 addition & 0 deletions src/notes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ function find_accidental(which_semitone, noteclass)
return Note(noteclass, accidental)
end

Note(n::Note) = n

const middle_C = C4

Expand Down
90 changes: 45 additions & 45 deletions src/scales.jl
Original file line number Diff line number Diff line change
@@ -1,79 +1,79 @@


mutable struct Scale
current::Pitch
steps
end
struct Scale{T<:Union{Note, Pitch}}
tonic::T
steps::Dict{Note, Interval}

# state is a counter that loops through the steps vector
function next_state(s::Scale, state)
if state == length(s.steps)
state = 1
else
state += 1
end
function Scale(tonic::T, steps::Vector{Interval}) where {T}
steps_dict = Dict{Note, Interval}()

return state
end
current = tonic
for step in steps
steps_dict[Note(current)] = step
current += step
end

function next_value!(s::Scale, state)
s.current = s.current + s.steps[state]
return s.current
return new{T}(tonic, steps_dict)
end
end

Scale(state, steps) = Scale(state, steps, 1)
# Scale2(M.C4, major_scale)

Base.IteratorSize(::Type{Scale}) = Base.IsInfinite()
Base.IteratorEltype(::Type{Scale}) = Base.HasEltype()
Base.eltype(::Type{Scale}) = Pitch
Base.iterate(s::Scale{T}) where {T} = s.tonic

function iterate!(s::Scale, state)
next_value!(s, state)
state = next_state(s, state)
function Base.iterate(s::Scale{T}, p::T) where {T}
n = Note(p)

return state
end
!haskey(s.steps, n) && error("Note $n not in scale")

function Base.iterate(s::Scale, state=1)
current = s.current
state = iterate!(s, state)
step = s.steps[n]
new_pitch = p + step

return current, state
return p, new_pitch
end

Base.IteratorSize(::Type{Scale{T}}) where {T} = Base.IsInfinite()
Base.IteratorEltype(::Type{Scale{T}}) where {T} = Base.HasEltype()
Base.eltype(::Type{Scale{T}}) where {T} = T


major_scale = let M = Major_2nd, m = Minor_2nd
const major_scale = let M = Major_2nd, m = Minor_2nd
[M, M, m, M, M, M, m]
end

natural_minor_scale = let M = Major_2nd, m = Minor_2nd
[M, m, M, M, M, m, M]
const natural_minor_scale = let M = Major_2nd, m = Minor_2nd
[M, m, M, M, M, m, M]
end

melodic_minor_scale = let M = Major_2nd, m = Minor_2nd
[M, m, M, M, M, M, m]
const melodic_minor_scale = let M = Major_2nd, m = Minor_2nd
[M, m, M, M, M, M, m]
end

harmonic_minor_scale = let M = Major_2nd, m = Minor_2nd
[M, m, M, M, m, Interval(1, Augmented), m]
const harmonic_minor_scale = let M = Major_2nd, m = Minor_2nd
[M, m, M, M, m, Interval(1, Augmented), m]
end


s = Scale(MusicTheory.C4, major_scale)
# s = Scale(MusicTheory.D4, major_scale)


# popfirst!(s)

# collect(Iterators.take(s, 20))

# s2 = Base.Iterators.Stateful(Scale(MusicTheory.C4, harmonic_minor_scale))

# Base.Iterators.take(s2, 10) |> collect

s
# eltype(Scale)

s = Base.Iterators.Stateful(Scale(MusicTheory.C4, major_scale))

popfirst!(s)
# # @edit Base.Iterators.Stateful(Scale(MusicTheory.C4, major_scale))

Iterators.take(s, 10) |> collect

s2 = Base.Iterators.Stateful(Scale(MusicTheory.C4, harmonic_minor_scale))
# interval(C4, B4)

Base.Iterators.take(s2, 10) |> collect

eltype(Scale)


@edit Base.Iterators.Stateful(Scale(MusicTheory.C4, major_scale))
# iterate(s)

0 comments on commit 641b2fe

Please sign in to comment.