about summary refs log tree commit diff
diff options
context:
space:
mode:
authorWilliam Carroll <wpcarro@gmail.com>2020-04-11T22·11+0100
committerWilliam Carroll <wpcarro@gmail.com>2020-04-11T22·11+0100
commit730aecc076fd21c48884201f59c9b1fce8c7a23a (patch)
tree3601dd6d816b13e01def59eaa299d4c7c70b77e3
parent808e6ee4847a92cb763f8776149e97575d9c1961 (diff)
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.
-rw-r--r--website/sandbox/chord-drill-sergeant/src/Main.elm158
-rw-r--r--website/sandbox/chord-drill-sergeant/src/Piano.elm40
-rw-r--r--website/sandbox/chord-drill-sergeant/src/Theory.elm209
3 files changed, 283 insertions, 124 deletions
diff --git a/website/sandbox/chord-drill-sergeant/src/Main.elm b/website/sandbox/chord-drill-sergeant/src/Main.elm
index 8a606767bb69..ded6b81e4e80 100644
--- a/website/sandbox/chord-drill-sergeant/src/Main.elm
+++ b/website/sandbox/chord-drill-sergeant/src/Main.elm
@@ -38,9 +38,29 @@ bpmToMilliseconds target =
   let msPerMinute = 1000 * 60
   in round (toFloat msPerMinute / toFloat target)
 
+inspectChord : Theory.Chord -> String
+inspectChord {note, chordType, chordPosition} =
+  viewNote 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")
+
 viewChord : Theory.Chord -> String
 viewChord {note, chordType, chordPosition} =
-  viewNote note ++ " " ++
+  viewNoteClass (Theory.classifyNote note) ++ " " ++
   (case chordType of
      Theory.Major -> "major"
      Theory.Major7 -> "major 7th"
@@ -58,26 +78,116 @@ viewChord {note, chordType, chordPosition} =
      Theory.Third -> "3rd position"
      Theory.Fourth -> "4th position")
 
-{-| Serialize a human-readable format of `note` -}
+{-| 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♯/D♭1"
+    Theory.D1       -> "D1"
+    Theory.D_sharp1 -> "D♯/E♭1"
+    Theory.E1       -> "E1"
+    Theory.F1       -> "F1"
+    Theory.F_sharp1 -> "F♯/G♭1"
+    Theory.G1       -> "G1"
+    Theory.G_sharp1 -> "G♯/A♭1"
+    Theory.A1       -> "A1"
+    Theory.A_sharp1 -> "A♯/B♭1"
+    Theory.B1       -> "B1"
+    Theory.C2       -> "C2"
+    Theory.C_sharp2 -> "C♯/D♭2"
+    Theory.D2       -> "D2"
+    Theory.D_sharp2 -> "D♯/E♭2"
+    Theory.E2       -> "E2"
+    Theory.F2       -> "F2"
+    Theory.F_sharp2 -> "F♯/G♭2"
+    Theory.G2       -> "G2"
+    Theory.G_sharp2 -> "G♯/A♭2"
+    Theory.A2       -> "A2"
+    Theory.A_sharp2 -> "A♯/B♭2"
+    Theory.B2       -> "B2"
+    Theory.C3       -> "C3"
+    Theory.C_sharp3 -> "C♯/D♭3"
+    Theory.D3       -> "D3"
+    Theory.D_sharp3 -> "D♯/E♭3"
+    Theory.E3       -> "E3"
+    Theory.F3       -> "F3"
+    Theory.F_sharp3 -> "F♯/G♭3"
+    Theory.G3       -> "G3"
+    Theory.G_sharp3 -> "G♯/A♭3"
+    Theory.A3       -> "A3"
+    Theory.A_sharp3 -> "A♯/B♭3"
+    Theory.B3       -> "B3"
+    Theory.C4       -> "C4"
+    Theory.C_sharp4 -> "C♯/D♭4"
+    Theory.D4       -> "D4"
+    Theory.D_sharp4 -> "D♯/E♭4"
+    Theory.E4       -> "E4"
+    Theory.F4       -> "F4"
+    Theory.F_sharp4 -> "F♯/G♭4"
+    Theory.G4       -> "G4"
+    Theory.G_sharp4 -> "G♯/A♭4"
+    Theory.A4       -> "A4"
+    Theory.A_sharp4 -> "A♯/B♭4"
+    Theory.B4       -> "B4"
+    Theory.C5       -> "C5"
+    Theory.C_sharp5 -> "C♯/D♭5"
+    Theory.D5       -> "D5"
+    Theory.D_sharp5 -> "D♯/E♭5"
+    Theory.E5       -> "E5"
+    Theory.F5       -> "F5"
+    Theory.F_sharp5 -> "F♯/G♭5"
+    Theory.G5       -> "G5"
+    Theory.G_sharp5 -> "G♯/A♭5"
+    Theory.A5       -> "A5"
+    Theory.A_sharp5 -> "A♯/B♭5"
+    Theory.B5       -> "B5"
+    Theory.C6       -> "C6"
+    Theory.C_sharp6 -> "C♯/D♭6"
+    Theory.D6       -> "D6"
+    Theory.D_sharp6 -> "D♯/E♭6"
+    Theory.E6       -> "E6"
+    Theory.F6       -> "F6"
+    Theory.F_sharp6 -> "F♯/G♭6"
+    Theory.G6       -> "G6"
+    Theory.G_sharp6 -> "G♯/A♭6"
+    Theory.A6       -> "A6"
+    Theory.A_sharp6 -> "A♯/B♭6"
+    Theory.B6       -> "B6"
+    Theory.C7       -> "C7"
+    Theory.C_sharp7 -> "C♯/D♭7"
+    Theory.D7       -> "D7"
+    Theory.D_sharp7 -> "D♯/E♭7"
+    Theory.E7       -> "E7"
+    Theory.F7       -> "F7"
+    Theory.F_sharp7 -> "F♯/G♭7"
+    Theory.G7       -> "G7"
+    Theory.G_sharp7 -> "G♯/A♭7"
+    Theory.A7       -> "A7"
+    Theory.A_sharp7 -> "A♯/B♭7"
+    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 =
diff --git a/website/sandbox/chord-drill-sergeant/src/Piano.elm b/website/sandbox/chord-drill-sergeant/src/Piano.elm
index 0533492123fc..048208c1f556 100644
--- a/website/sandbox/chord-drill-sergeant/src/Piano.elm
+++ b/website/sandbox/chord-drill-sergeant/src/Piano.elm
@@ -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} =
diff --git a/website/sandbox/chord-drill-sergeant/src/Theory.elm b/website/sandbox/chord-drill-sergeant/src/Theory.elm
index 4a275fa79e1f..9e84aeac4c4e 100644
--- a/website/sandbox/chord-drill-sergeant/src/Theory.elm
+++ b/website/sandbox/chord-drill-sergeant/src/Theory.elm
@@ -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