diff options
-rw-r--r-- | website/sandbox/chord-drill-sergeant/.gitignore | 1 | ||||
-rw-r--r-- | website/sandbox/chord-drill-sergeant/README.md | 10 | ||||
-rw-r--r-- | website/sandbox/chord-drill-sergeant/elm.json | 24 | ||||
-rw-r--r-- | website/sandbox/chord-drill-sergeant/shell.nix | 7 | ||||
-rw-r--r-- | website/sandbox/chord-drill-sergeant/src/Main.elm | 206 | ||||
-rw-r--r-- | website/sandbox/chord-drill-sergeant/src/Piano.elm | 46 |
6 files changed, 294 insertions, 0 deletions
diff --git a/website/sandbox/chord-drill-sergeant/.gitignore b/website/sandbox/chord-drill-sergeant/.gitignore new file mode 100644 index 000000000000..8b0d053e4e35 --- /dev/null +++ b/website/sandbox/chord-drill-sergeant/.gitignore @@ -0,0 +1 @@ +/elm-stuff \ No newline at end of file diff --git a/website/sandbox/chord-drill-sergeant/README.md b/website/sandbox/chord-drill-sergeant/README.md index db471fa40143..0f9dd4d6bc6e 100644 --- a/website/sandbox/chord-drill-sergeant/README.md +++ b/website/sandbox/chord-drill-sergeant/README.md @@ -45,3 +45,13 @@ Here are some useful features of CDS: - Chaos-mode: Feeling confident? Throw the classical notions of harmony to the wayside and use CDS in "chaos-mode" where CDS samples randomly from the Circle of Fifths. + +## Developing + +If you're interested in contributing, the following will create an environment +in which you can develop: + +```shell +$ nix-shell +$ elm reactor +``` diff --git a/website/sandbox/chord-drill-sergeant/elm.json b/website/sandbox/chord-drill-sergeant/elm.json new file mode 100644 index 000000000000..dea3450db112 --- /dev/null +++ b/website/sandbox/chord-drill-sergeant/elm.json @@ -0,0 +1,24 @@ +{ + "type": "application", + "source-directories": [ + "src" + ], + "elm-version": "0.19.1", + "dependencies": { + "direct": { + "elm/browser": "1.0.2", + "elm/core": "1.0.5", + "elm/html": "1.0.0" + }, + "indirect": { + "elm/json": "1.1.3", + "elm/time": "1.0.0", + "elm/url": "1.0.0", + "elm/virtual-dom": "1.0.2" + } + }, + "test-dependencies": { + "direct": {}, + "indirect": {} + } +} diff --git a/website/sandbox/chord-drill-sergeant/shell.nix b/website/sandbox/chord-drill-sergeant/shell.nix new file mode 100644 index 000000000000..30061e65f9d5 --- /dev/null +++ b/website/sandbox/chord-drill-sergeant/shell.nix @@ -0,0 +1,7 @@ +let + pkgs = import <nixpkgs> {}; +in pkgs.mkShell { + buildInputs = with pkgs; [ + elmPackages.elm + ]; +} diff --git a/website/sandbox/chord-drill-sergeant/src/Main.elm b/website/sandbox/chord-drill-sergeant/src/Main.elm new file mode 100644 index 000000000000..4fdb30226baf --- /dev/null +++ b/website/sandbox/chord-drill-sergeant/src/Main.elm @@ -0,0 +1,206 @@ +module Main exposing (main) + +import Browser +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) + +import Piano + +{-| Notes are the individuals sounds that we use to create music. Think: "do re +mi fa so la ti do". + +Note: Technically a "C-sharp" is also a "D-flat", but I will model accidentals +(i.e. sharps and flats) as sharps and represent the ambiguity when I render the +underlying state of the application. + +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 + +{-| We create "scales" by enumerating the notes of a given key. These keys are +defined by the "tonic" note and the "mode". I thought about including Ionian, +Dorian, Phrygian, etc., but in the I would like to avoid over-abstracting this +early on, so I'm going to err on the side of overly concrete until I have a +better idea of the extent of this project. -} +type Mode = BluesMode + | MajorMode + | MinorMode + +{-| One can measure the difference between between notes using intervals. -} +type Interval = Half + | Whole + | MajorThird + | MinorThird + +{-| Songs are written in one or more keys, which define the notes and therefore +chords that harmonize with one another. -} +type Key = Key (Note, Mode) + +{-| A bundle of notes which are usually, but not necessarily harmonious. -} +type Chord = Chord (Note, ChordType, ChordPosition) + +{-| On a piano, a triad can be played three ways. As a rule-of-thumb, The number +of ways a pianist can play a chord is equal to the number of notes in the chord +itself. -} +type ChordPosition = First + | Second + | Third + | Fourth + +{-| Many possible chords exist. This type encodes the possibilities. I am +tempted to model these in a more "DRY" way, but I worry that this abstraction +may cause more problems than it solves. -} +type ChordType = Major + | Major7 + | MajorDominant7 + | Minor + | Minor7 + | MinorDominant7 + | Augmented + | Augmented7 + | Diminished + | Diminished7 + +{-| Encode whether you are traversing "up" or "down" intervals -} +type StepDirection = Up | Down + +{-| Return a list of steps to take away from the root note to return back to the +root note for a given mode. +-} +intervalsForMode : Mode -> List Interval +intervalsForMode mode = + case mode of + MajorMode -> [Whole, Whole, Half, Whole, Whole, Whole, Half] + MinorMode -> [Whole, Half, Whole, Whole, Half, Whole, Whole] + BluesMode -> [MinorThird, Whole, Half, Half, MinorThird] + +{-| Return a list of the intervals the comprise a chord -} +intervalsForChordType : ChordType -> List Interval +intervalsForChordType chordType = + case chordType of + Major -> [MajorThird, MinorThird] + Major7 -> [MajorThird, MinorThird, MajorThird] + MajorDominant7 -> [MajorThird, MinorThird, MajorThird, MinorThird] + Minor -> [MinorThird, MajorThird] + Minor7 -> [MinorThird, MajorThird, MajorThird] + MinorDominant7 -> [MinorThird, MajorThird, MajorThird, MinorThird] + Augmented -> [MajorThird, MajorThird] + Augmented7 -> [MajorThird, MajorThird, Whole] + Diminished -> [MinorThird, MinorThird] + Diminished7 -> [MinorThird, MinorThird, MinorThird] + +{-| Return the note in the direction, `dir`, away from `note` `s` intervals -} +step : StepDirection -> Interval -> Note -> 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 + +{-| Return the note that is one half step away from `note` in the direction, +`dir`. +-} +halfStep : StepDirection -> Note -> 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 + +{-| Returns a list of all of the notes up from a give `note` -} +applySteps : List Interval -> Note -> 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 + +{-| Return the scale for a given `key` -} +notesForKey : Key -> List Note +notesForKey key = + case key of + Key (note, mode) -> applySteps (intervalsForKeyMode mode) note + +{-| Return a list of the notes that comprise a `chord` -} +notesForChord : Chord -> List Note +notesForChord chord = + case chord of + -- TODO(wpcarro): Use the Position to rotate the chord n times + Chord (note, chordType, _) -> applySteps (intervalsForChordType chordType) note + +{-| Serialize a human-readable format of `note` -} +viewNote : Note -> String +viewNote note = + case note of + C -> "C" + C_sharp -> "C♯/D♭" + D -> "D" + D_sharp -> "D♯/E♭" + E -> "E" + F -> "F" + F_sharp -> "F♯/G♭" + G -> "G" + G_sharp -> "G♯/A♭" + A -> "A" + A_sharp -> "A♯/B♭" + B -> "B" + +{-| For now, I'm just dumping things onto the page to sketch ideas. -} +main = + let + key = Key (D, MinorMode) + chord = Chord (D, Major, First) + in + div [] [ ul [] (notesForKey key |> List.map (\n -> li [] [ text (viewNote n) ])) + , ul [] (notesForChord chord |> List.map (\n -> li [] [ text (viewNote n) ])) + , Piano.render + ] diff --git a/website/sandbox/chord-drill-sergeant/src/Piano.elm b/website/sandbox/chord-drill-sergeant/src/Piano.elm new file mode 100644 index 000000000000..7c44f4bf4f6f --- /dev/null +++ b/website/sandbox/chord-drill-sergeant/src/Piano.elm @@ -0,0 +1,46 @@ +module Piano exposing (render) + +import Browser +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) + +{-| These are the white keys on most modern pianos. -} +natural : Html a +natural = + li [ style "background-color" "white" + , style "height" "20px" + , style "border-top" "1px solid black" + ] [] + +{-| These are the black keys on most modern pianos. -} +accidental : Html a +accidental = + li [ style "background-color" "black" + , style "height" "10px" + , style "width" "66%" + ] [] + +{-| A section of the piano consisting of all twelve notes. The name octave +implies eight notes, which most scales (not the blues scale) honor. -} +octave : List (Html a) +octave = [ natural + , accidental + , natural + , accidental + , natural + , natural + , accidental + , natural + , accidental + , natural + , accidental + , natural + ] + +{-| Return the HTML that renders a piano representation. -} +render : Html a +render = + ul [ style "width" "100px" + , style "list-style" "none" + ] (octave |> List.repeat 3 |> List.concat) |