Support NoteClass

Remodel application to support the scientific pitch notation for notes. Instead
of supporting simply "C", support "C4". This change created cascading
changes. After refactoring for around an hour, I restored the app to a working
state. The current state is not desirable, but it compiles. More changes on the
way.
This commit is contained in:
William Carroll 2020-04-11 23:11:04 +01:00
parent 808e6ee484
commit 730aecc076
3 changed files with 284 additions and 125 deletions

View file

@ -38,8 +38,8 @@ bpmToMilliseconds target =
let msPerMinute = 1000 * 60
in round (toFloat msPerMinute / toFloat target)
viewChord : Theory.Chord -> String
viewChord {note, chordType, chordPosition} =
inspectChord : Theory.Chord -> String
inspectChord {note, chordType, chordPosition} =
viewNote note ++ " " ++
(case chordType of
Theory.Major -> "major"
@ -58,26 +58,136 @@ viewChord {note, chordType, chordPosition} =
Theory.Third -> "3rd position"
Theory.Fourth -> "4th position")
{-| Serialize a human-readable format of `note` -}
viewChord : Theory.Chord -> String
viewChord {note, chordType, chordPosition} =
viewNoteClass (Theory.classifyNote note) ++ " " ++
(case chordType of
Theory.Major -> "major"
Theory.Major7 -> "major 7th"
Theory.MajorDominant7 -> "major dominant 7th"
Theory.Minor -> "minor"
Theory.Minor7 -> "minor 7th"
Theory.MinorDominant7 -> "minor dominant 7th"
Theory.Augmented -> "augmented"
Theory.Augmented7 -> "augmented 7th"
Theory.Diminished -> "diminished"
Theory.Diminished7 -> "diminished 7th") ++ " " ++
(case chordPosition of
Theory.First -> "root position"
Theory.Second -> "2nd position"
Theory.Third -> "3rd position"
Theory.Fourth -> "4th position")
{-| Serialize a human-readable format of `note`. -}
viewNote : Theory.Note -> String
viewNote note =
case note of
Theory.C -> "C"
Theory.C1 -> "C1"
Theory.C_sharp1 -> "C/D1"
Theory.D1 -> "D1"
Theory.D_sharp1 -> "D/E1"
Theory.E1 -> "E1"
Theory.F1 -> "F1"
Theory.F_sharp1 -> "F/G1"
Theory.G1 -> "G1"
Theory.G_sharp1 -> "G/A1"
Theory.A1 -> "A1"
Theory.A_sharp1 -> "A/B1"
Theory.B1 -> "B1"
Theory.C2 -> "C2"
Theory.C_sharp2 -> "C/D2"
Theory.D2 -> "D2"
Theory.D_sharp2 -> "D/E2"
Theory.E2 -> "E2"
Theory.F2 -> "F2"
Theory.F_sharp2 -> "F/G2"
Theory.G2 -> "G2"
Theory.G_sharp2 -> "G/A2"
Theory.A2 -> "A2"
Theory.A_sharp2 -> "A/B2"
Theory.B2 -> "B2"
Theory.C3 -> "C3"
Theory.C_sharp3 -> "C/D3"
Theory.D3 -> "D3"
Theory.D_sharp3 -> "D/E3"
Theory.E3 -> "E3"
Theory.F3 -> "F3"
Theory.F_sharp3 -> "F/G3"
Theory.G3 -> "G3"
Theory.G_sharp3 -> "G/A3"
Theory.A3 -> "A3"
Theory.A_sharp3 -> "A/B3"
Theory.B3 -> "B3"
Theory.C4 -> "C4"
Theory.C_sharp4 -> "C/D4"
Theory.D4 -> "D4"
Theory.D_sharp4 -> "D/E4"
Theory.E4 -> "E4"
Theory.F4 -> "F4"
Theory.F_sharp4 -> "F/G4"
Theory.G4 -> "G4"
Theory.G_sharp4 -> "G/A4"
Theory.A4 -> "A4"
Theory.A_sharp4 -> "A/B4"
Theory.B4 -> "B4"
Theory.C5 -> "C5"
Theory.C_sharp5 -> "C/D5"
Theory.D5 -> "D5"
Theory.D_sharp5 -> "D/E5"
Theory.E5 -> "E5"
Theory.F5 -> "F5"
Theory.F_sharp5 -> "F/G5"
Theory.G5 -> "G5"
Theory.G_sharp5 -> "G/A5"
Theory.A5 -> "A5"
Theory.A_sharp5 -> "A/B5"
Theory.B5 -> "B5"
Theory.C6 -> "C6"
Theory.C_sharp6 -> "C/D6"
Theory.D6 -> "D6"
Theory.D_sharp6 -> "D/E6"
Theory.E6 -> "E6"
Theory.F6 -> "F6"
Theory.F_sharp6 -> "F/G6"
Theory.G6 -> "G6"
Theory.G_sharp6 -> "G/A6"
Theory.A6 -> "A6"
Theory.A_sharp6 -> "A/B6"
Theory.B6 -> "B6"
Theory.C7 -> "C7"
Theory.C_sharp7 -> "C/D7"
Theory.D7 -> "D7"
Theory.D_sharp7 -> "D/E7"
Theory.E7 -> "E7"
Theory.F7 -> "F7"
Theory.F_sharp7 -> "F/G7"
Theory.G7 -> "G7"
Theory.G_sharp7 -> "G/A7"
Theory.A7 -> "A7"
Theory.A_sharp7 -> "A/B7"
Theory.B7 -> "B7"
Theory.C8 -> "C8"
{-| Serialize a human-readable format of `noteClass`. -}
viewNoteClass : Theory.NoteClass -> String
viewNoteClass noteClass =
case noteClass of
Theory.C -> "C"
Theory.C_sharp -> "C/D"
Theory.D -> "D"
Theory.D -> "D"
Theory.D_sharp -> "D/E"
Theory.E -> "E"
Theory.F -> "F"
Theory.E -> "E"
Theory.F -> "F"
Theory.F_sharp -> "F/G"
Theory.G -> "G"
Theory.G -> "G"
Theory.G_sharp -> "G/A"
Theory.A -> "A"
Theory.A -> "A"
Theory.A_sharp -> "A/B"
Theory.B -> "B"
Theory.B -> "B"
cmajor : Theory.Chord
cmajor =
{ note = Theory.C
{ note = Theory.C4
, chordType = Theory.Major
, chordPosition = Theory.First
}
@ -141,15 +251,25 @@ playPause {isPaused} =
view : Model -> Html Msg
view model =
div [] [ Tempo.render { tempo = model.tempo
, handleIncrease = IncreaseTempo
, handleDecrease = DecreaseTempo
, handleInput = SetTempo
}
, playPause model
, p [] [ text (viewChord model.selectedChord) ]
, Piano.render { highlight = Theory.notesForChord model.selectedChord }
]
case Theory.notesForChord model.selectedChord of
Nothing ->
p [] [ text ("""
We cannot render the chord that you provided because the
notes that comprise the chord fall off either the upper
or lower end of the piano.
Chord:
""" ++ (inspectChord model.selectedChord)) ]
Just x ->
div [] [ Tempo.render { tempo = model.tempo
, handleIncrease = IncreaseTempo
, handleDecrease = DecreaseTempo
, handleInput = SetTempo
}
, playPause model
, p [] [ text (viewChord model.selectedChord) ]
, Piano.render { highlight = x }
]
{-| For now, I'm just dumping things onto the page to sketch ideas. -}
main =

View file

@ -62,36 +62,20 @@ octave highlight =
let
isHighlit note = List.member note highlight
in
[ natural 0 (isHighlit Theory.C)
, accidental 25 (isHighlit Theory.C_sharp)
, natural 40 (isHighlit Theory.D)
, accidental 65 (isHighlit Theory.D_sharp)
, natural 80 (isHighlit Theory.E)
, natural 120 (isHighlit Theory.F)
, accidental 145 (isHighlit Theory.F_sharp)
, natural 160 (isHighlit Theory.G)
, accidental 185 (isHighlit Theory.G_sharp)
, natural 200 (isHighlit Theory.A)
, accidental 225 (isHighlit Theory.A_sharp)
, natural 240 (isHighlit Theory.B)
[ natural 0 (isHighlit Theory.C4)
, accidental 25 (isHighlit Theory.C_sharp4)
, natural 40 (isHighlit Theory.D4)
, accidental 65 (isHighlit Theory.D_sharp4)
, natural 80 (isHighlit Theory.E4)
, natural 120 (isHighlit Theory.F4)
, accidental 145 (isHighlit Theory.F_sharp4)
, natural 160 (isHighlit Theory.G4)
, accidental 185 (isHighlit Theory.G_sharp4)
, natural 200 (isHighlit Theory.A4)
, accidental 225 (isHighlit Theory.A_sharp4)
, natural 240 (isHighlit Theory.B4)
]
indexForNote : Theory.Note -> Int
indexForNote note =
case note of
Theory.C -> 0
Theory.C_sharp -> 1
Theory.D -> 2
Theory.D_sharp -> 3
Theory.E -> 4
Theory.F -> 5
Theory.F_sharp -> 6
Theory.G -> 7
Theory.G_sharp -> 8
Theory.A -> 9
Theory.A_sharp -> 10
Theory.B -> 11
{-| Return the HTML that renders a piano representation. -}
render : { highlight : List Theory.Note } -> Html a
render {highlight} =

View file

@ -1,6 +1,7 @@
module Theory exposing (..)
import List.Extra
import Misc
{-| Notes are the individuals sounds that we use to create music. Think: "do re
mi fa so la ti do".
@ -13,18 +14,29 @@ Note: There are "notes" like A, B, D-flat, and then there are notes like "middle
C", also denoted in scientific pitch notation as C4. I'm unsure of what to call
each of these, and my application does not model scientific pitch notation yet,
so these non-scientific pitch denote values are "notes" for now. -}
type Note = C
| C_sharp
| D
| D_sharp
| E
| F
| F_sharp
| G
| G_sharp
| A
| A_sharp
| B
type Note = C1 | C_sharp1 | D1 | D_sharp1 | E1 | F1 | F_sharp1 | G1 | G_sharp1 | A1 | A_sharp1 | B1
| C2 | C_sharp2 | D2 | D_sharp2 | E2 | F2 | F_sharp2 | G2 | G_sharp2 | A2 | A_sharp2 | B2
| C3 | C_sharp3 | D3 | D_sharp3 | E3 | F3 | F_sharp3 | G3 | G_sharp3 | A3 | A_sharp3 | B3
| C4 | C_sharp4 | D4 | D_sharp4 | E4 | F4 | F_sharp4 | G4 | G_sharp4 | A4 | A_sharp4 | B4
| C5 | C_sharp5 | D5 | D_sharp5 | E5 | F5 | F_sharp5 | G5 | G_sharp5 | A5 | A_sharp5 | B5
| C6 | C_sharp6 | D6 | D_sharp6 | E6 | F6 | F_sharp6 | G6 | G_sharp6 | A6 | A_sharp6 | B6
| C7 | C_sharp7 | D7 | D_sharp7 | E7 | F7 | F_sharp7 | G7 | G_sharp7 | A7 | A_sharp7 | B7
| C8
{-| I alluded to this concept in the Note type's documentation. These are the
letters of notes. For instance C2, C3, C4 are all instances of C. -}
type NoteClass = C
| C_sharp
| D
| D_sharp
| E
| F
| F_sharp
| G
| G_sharp
| A
| A_sharp
| B
{-| Encode whether you are traversing "up" or "down" intervals -}
type StepDirection = Up | Down
@ -67,7 +79,7 @@ type ChordPosition = First
{-| Songs are written in one or more keys, which define the notes and therefore
chords that harmonize with one another. -}
type alias Key =
{ note : Note
{ noteClass : NoteClass
, mode : Mode
}
@ -80,48 +92,35 @@ type Mode = BluesMode
| MajorMode
| MinorMode
{-| Returns the Note in the cental octave of the piano for a given
NoteClass. For example, C4 -- or "middle C" -- for C. -}
noteInCentralOctave : NoteClass -> Note
noteInCentralOctave noteClass =
case noteClass of
C -> C4
C_sharp -> C_sharp4
D -> D4
D_sharp -> D_sharp4
E -> E4
F -> F4
F_sharp -> F_sharp4
G -> G4
G_sharp -> G_sharp4
A -> A4
A_sharp -> A_sharp4
B -> B4
{-| Return the note that is one half step away from `note` in the direction,
`dir`.
In the case of stepping up or down from the end of the piano, this returns a
Maybe.
-}
halfStep : StepDirection -> Note -> Note
halfStep : StepDirection -> Note -> Maybe Note
halfStep dir note =
case (dir, note) of
-- C
(Up, C) -> C_sharp
(Down, C) -> B
-- C#
(Up, C_sharp) -> D
(Down, C_sharp) -> C
-- D
(Up, D) -> D_sharp
(Down, D) -> C_sharp
-- D_sharp
(Up, D_sharp) -> E
(Down, D_sharp) -> D
-- E
(Up, E) -> F
(Down, E) -> D_sharp
-- F
(Up, F) -> F_sharp
(Down, F) -> E
-- F#
(Up, F_sharp) -> G
(Down, F_sharp) -> F
-- G
(Up, G) -> G_sharp
(Down, G) -> F_sharp
-- G#
(Up, G_sharp) -> A
(Down, G_sharp) -> A
-- A
(Up, A) -> A_sharp
(Down, A) -> G_sharp
-- A#
(Up, A_sharp) -> B
(Down, A_sharp) -> A
-- B
(Up, B) -> C
(Down, B) -> A_sharp
case dir of
Up -> Misc.comesAfter note allNotes
Down -> Misc.comesBefore note allNotes
{-| Return a list of steps to take away from the root note to return back to the
root note for a given mode.
-}
@ -148,49 +147,105 @@ intervalsForChordType chordType =
Diminished7 -> [MinorThird, MinorThird, MinorThird]
{-| Return the note in the direction, `dir`, away from `note` `s` intervals -}
step : StepDirection -> Interval -> Note -> Note
step : StepDirection -> Interval -> Note -> Maybe Note
step dir s note =
let
doHalfStep = halfStep dir
in
case s of
Half -> doHalfStep note
Whole -> doHalfStep note |> doHalfStep
MinorThird -> doHalfStep note |> doHalfStep |> doHalfStep
MajorThird -> doHalfStep note |> doHalfStep |> doHalfStep |> doHalfStep
Half ->
doHalfStep note
Whole ->
doHalfStep note
|> Maybe.andThen doHalfStep
MinorThird ->
doHalfStep note
|> Maybe.andThen doHalfStep
|> Maybe.andThen doHalfStep
MajorThird ->
doHalfStep note
|> Maybe.andThen doHalfStep
|> Maybe.andThen doHalfStep
|> Maybe.andThen doHalfStep
{-| Returns a list of all of the notes up from a give `note` -}
applySteps : List Interval -> Note -> List Note
{-| Returns a list of all of the notes up from a give `note`.
In the case where applying all of the steps would result in running off of the
edge of the piano, this function returns a Maybe. -}
applySteps : List Interval -> Note -> Maybe (List Note)
applySteps steps note =
case List.foldl (\s (prev, result) -> ((step Up s prev), (step Up s prev :: result))) (note, []) steps of
(_, result) -> List.reverse result
doApplySteps steps note [] |> Maybe.map List.reverse
doApplySteps : List Interval -> Note -> List Note -> Maybe (List Note)
doApplySteps steps note result =
case steps of
[] -> Just (note::result)
s::rest ->
case step Up s note of
Just x -> doApplySteps rest x (note::result)
Nothing -> Nothing
{-| Return the NoteClass for a given note. -}
classifyNote : Note -> NoteClass
classifyNote note =
if List.member note [C1, C2, C3, C4, C5, C6, C7, C8] then
C
else if List.member note [C_sharp1, C_sharp2, C_sharp3, C_sharp4, C_sharp5, C_sharp6, C_sharp7] then
C_sharp
else if List.member note [D1, D2, D3, D4, D5, D6, D7] then
D
else if List.member note [D_sharp1, D_sharp2, D_sharp3, D_sharp4, D_sharp5, D_sharp6, D_sharp7] then
D_sharp
else if List.member note [E1, E2, E3, E4, E5, E6, E7] then
E
else if List.member note [F1, F2, F3, F4, F5, F6, F7] then
F
else if List.member note [F_sharp1, F_sharp2, F_sharp3, F_sharp4, F_sharp5, F_sharp6, F_sharp7] then
F_sharp
else if List.member note [G1, G2, G3, G4, G5, G6, G7] then
G
else if List.member note [G_sharp1, G_sharp2, G_sharp3, G_sharp4, G_sharp5, G_sharp6, G_sharp7] then
G_sharp
else if List.member note [A1, A2, A3, A4, A5, A6, A7] then
A
else if List.member note [A_sharp1, A_sharp2, A_sharp3, A_sharp4, A_sharp5, A_sharp6, A_sharp7] then
A_sharp
else
B
{-| Return a list of the notes that comprise a `chord` -}
notesForChord : Chord -> List Note
notesForChord : Chord -> Maybe (List Note)
notesForChord {note, chordType} =
note :: applySteps (intervalsForChordType chordType) note
case applySteps (intervalsForChordType chordType) note of
Nothing -> Nothing
Just notes -> Just <| note::notes
{-| Return the scale for a given `key` -}
notesForKey : Key -> List Note
notesForKey {note, mode} =
applySteps (intervalsForMode mode) note
notesForKey {noteClass, mode} =
let origin = noteInCentralOctave noteClass
in case applySteps (intervalsForMode mode) origin of
-- We should never hit the Nothing case here.
Nothing -> []
Just scale -> scale
{-| Return a list of all of the notes that we know about. -}
allNotes : List Note
allNotes =
[ C1 , C_sharp1 , D1 , D_sharp1 , E1 , F1 , F_sharp1 , G1 , G_sharp1 , A1 , A_sharp1 , B1
, C2 , C_sharp2 , D2 , D_sharp2 , E2 , F2 , F_sharp2 , G2 , G_sharp2 , A2 , A_sharp2 , B2
, C3 , C_sharp3 , D3 , D_sharp3 , E3 , F3 , F_sharp3 , G3 , G_sharp3 , A3 , A_sharp3 , B3
, C4 , C_sharp4 , D4 , D_sharp4 , E4 , F4 , F_sharp4 , G4 , G_sharp4 , A4 , A_sharp4 , B4
, C5 , C_sharp5 , D5 , D_sharp5 , E5 , F5 , F_sharp5 , G5 , G_sharp5 , A5 , A_sharp5 , B5
, C6 , C_sharp6 , D6 , D_sharp6 , E6 , F6 , F_sharp6 , G6 , G_sharp6 , A6 , A_sharp6 , B6
, C7 , C_sharp7 , D7 , D_sharp7 , E7 , F7 , F_sharp7 , G7 , G_sharp7 , A7 , A_sharp7 , B7
, C8
]
{-| Return a list of all of the chords that we know about. -}
allChords : List Chord
allChords =
let notes = [ C
, C_sharp
, D
, D_sharp
, E
, F
, F_sharp
, G
, G_sharp
, A
, A_sharp
, B
]
let notes = allNotes
chordTypes = [ Major
, Major7
, MajorDominant7