diff options
author | William Carroll <wpcarro@gmail.com> | 2020-04-12T15·43+0100 |
---|---|---|
committer | William Carroll <wpcarro@gmail.com> | 2020-04-12T15·43+0100 |
commit | 24692ab46508d5e3348af19a6eae583a6d02e0e7 (patch) | |
tree | d4b3c36d303957f5d32213bccb83bd291b6c7a04 /website/sandbox/chord-drill-sergeant/src/Main.elm | |
parent | 730aecc076fd21c48884201f59c9b1fce8c7a23a (diff) |
Properly support chord inversions
While I did change a lot of functionality, I also ran `elm-format` across the codebase, which makes these changes a bit noisy. Here is the TL;DR: - Properly support chord inversions - Ensure that the piano styling changes dynamically when I change the variables like `naturalWidth` - Add start and end notes to define the size of the piano and which chords we create - Support elm-format and run it across entire project - Debug Misc.comesBefore - Introduce a ChordInspector and debugger TODO: Ensure that we only generate chords where all of the notes can be rendered on the displayed keys. TODO: Add preferences panel, so that I can do things like "Practice blues chords in C and E with chord substitutions."
Diffstat (limited to 'website/sandbox/chord-drill-sergeant/src/Main.elm')
-rw-r--r-- | website/sandbox/chord-drill-sergeant/src/Main.elm | 425 |
1 files changed, 187 insertions, 238 deletions
diff --git a/website/sandbox/chord-drill-sergeant/src/Main.elm b/website/sandbox/chord-drill-sergeant/src/Main.elm index ded6b81e4e80..5ccc4d303eeb 100644 --- a/website/sandbox/chord-drill-sergeant/src/Main.elm +++ b/website/sandbox/chord-drill-sergeant/src/Main.elm @@ -1,280 +1,229 @@ module Main exposing (main) import Browser +import ChordInspector import Html exposing (..) import Html.Attributes exposing (..) import Html.Events exposing (..) +import Piano import Random import Random.List +import Tempo +import Theory import Time exposing (..) -import Piano -import Theory -import Tempo type alias Model = - { whitelistedChords : List Theory.Chord - , selectedChord : Theory.Chord - , isPaused : Bool - , tempo : Int - } - -type Msg = NextChord - | NewChord Theory.Chord - | Play - | Pause - | IncreaseTempo - | DecreaseTempo - | SetTempo String + { whitelistedChords : List Theory.Chord + , selectedChord : Theory.Chord + , isPaused : Bool + , tempo : Int + , firstNote : Theory.Note + , lastNote : Theory.Note + , debug : + { enable : Bool + , inspectChord : Bool + } + } + + +type Msg + = NextChord + | NewChord Theory.Chord + | Play + | Pause + | IncreaseTempo + | DecreaseTempo + | SetTempo String + | ToggleInspectChord + tempoStep : Int -tempoStep = 5 +tempoStep = + 5 + {-| Return the number of milliseconds that elapse during an interval in a `target` bpm. -} bpmToMilliseconds : Int -> Int 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} = - 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.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_sharp -> "D♯/E♭" - Theory.E -> "E" - Theory.F -> "F" - Theory.F_sharp -> "F♯/G♭" - Theory.G -> "G" - Theory.G_sharp -> "G♯/A♭" - Theory.A -> "A" - Theory.A_sharp -> "A♯/B♭" - Theory.B -> "B" + let + msPerMinute = + 1000 * 60 + in + round (toFloat msPerMinute / toFloat target) + cmajor : Theory.Chord cmajor = - { note = Theory.C4 - , chordType = Theory.Major - , chordPosition = Theory.First - } + { note = Theory.C4 + , chordType = Theory.MajorDominant7 + , chordInversion = Theory.Root + } -{-| The initial state for the application. -} + +{-| The initial state for the application. +-} init : Model init = - { whitelistedChords = Theory.allChords - , selectedChord = cmajor - , isPaused = True - , tempo = 60 - } + let + ( firstNote, lastNote ) = + ( Theory.C3, Theory.C5 ) + in + { whitelistedChords = Theory.allChords firstNote lastNote + , selectedChord = cmajor + , isPaused = True + , tempo = 60 + , firstNote = firstNote + , lastNote = lastNote + , debug = + { enable = True + , inspectChord = True + } + } + subscriptions : Model -> Sub Msg -subscriptions {isPaused, tempo} = - if isPaused then - Sub.none - else - Time.every (tempo |> bpmToMilliseconds |> toFloat) (\_ -> NextChord) - -{-| Now that we have state, we need a function to change the state. -} -update : Msg -> Model -> (Model, Cmd Msg) +subscriptions { isPaused, tempo } = + if isPaused then + Sub.none + + else + Time.every (tempo |> bpmToMilliseconds |> toFloat) (\_ -> NextChord) + + +{-| Now that we have state, we need a function to change the state. +-} +update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = - case msg of - NewChord chord -> ( { model | selectedChord = chord } - , Cmd.none - ) - NextChord -> ( model - , Random.generate (\x -> - case x of - (Just chord, _) -> NewChord chord - (Nothing, _) -> NewChord cmajor) - (Random.List.choose model.whitelistedChords) - ) - Play -> ( { model | isPaused = False } + case msg of + NewChord chord -> + ( { model | selectedChord = chord } , Cmd.none ) - Pause -> ( { model | isPaused = True } - , Cmd.none - ) - IncreaseTempo -> ( { model | tempo = model.tempo + tempoStep } - , Cmd.none - ) - DecreaseTempo -> ( { model | tempo = model.tempo - tempoStep } - , Cmd.none - ) - SetTempo tempo -> ( { model | - tempo = case String.toInt tempo of - Just x -> x - Nothing -> model.tempo - } - , Cmd.none - ) + + NextChord -> + ( model + , Random.generate + (\x -> + case x of + ( Just chord, _ ) -> + NewChord chord + + ( Nothing, _ ) -> + NewChord cmajor + ) + (Random.List.choose model.whitelistedChords) + ) + + Play -> + ( { model | isPaused = False } + , Cmd.none + ) + + Pause -> + ( { model | isPaused = True } + , Cmd.none + ) + + IncreaseTempo -> + ( { model | tempo = model.tempo + tempoStep } + , Cmd.none + ) + + DecreaseTempo -> + ( { model | tempo = model.tempo - tempoStep } + , Cmd.none + ) + + ToggleInspectChord -> + ( { model + | debug = + { inspectChord = not model.debug.inspectChord + , enable = model.debug.enable + } + } + , Cmd.none + ) + + SetTempo tempo -> + ( { model + | tempo = + case String.toInt tempo of + Just x -> + x + + Nothing -> + model.tempo + } + , Cmd.none + ) + playPause : Model -> Html Msg -playPause {isPaused} = - if isPaused then - button [ onClick Play ] [ text "Play" ] - else - button [ onClick Pause ] [ text "Pause" ] +playPause { isPaused } = + if isPaused then + button [ onClick Play ] [ text "Play" ] + + else + button [ onClick Pause ] [ text "Pause" ] + + +debugger : Html Msg +debugger = + fieldset [] + [ label [] [ text "Inspect Chord" ] + , input [ type_ "checkbox", onClick ToggleInspectChord, checked init.debug.inspectChord ] [] + ] + view : Model -> Html Msg view model = - case Theory.notesForChord model.selectedChord of - Nothing -> - p [] [ text (""" + 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. -} + """ ++ Theory.inspectChord model.selectedChord) ] + + Just x -> + div [] + [ Tempo.render + { tempo = model.tempo + , handleIncrease = IncreaseTempo + , handleDecrease = DecreaseTempo + , handleInput = SetTempo + } + , playPause model + , if model.debug.enable then + debugger + + else + span [] [] + , if model.debug.inspectChord then + ChordInspector.render model.selectedChord + + else + span [] [] + , p [] [ text (Theory.viewChord model.selectedChord) ] + , Piano.render + { highlight = x + , start = model.firstNote + , end = model.lastNote + } + ] + + +{-| For now, I'm just dumping things onto the page to sketch ideas. +-} main = - Browser.element { init = \() -> (init, Cmd.none) - , subscriptions = subscriptions - , update = update - , view = view - } + Browser.element + { init = \() -> ( init, Cmd.none ) + , subscriptions = subscriptions + , update = update + , view = view + } |