Model data and sketch ideas for Chord Drill Sergeant
Initialize an Elm application to build a MVP for the Chord Drill Sergeant application. There isn't much to see at the moment. I'm just sketching ideas. More forthcoming...
This commit is contained in:
parent
c350222fcc
commit
b600f709b4
6 changed files with 294 additions and 0 deletions
1
website/sandbox/chord-drill-sergeant/.gitignore
vendored
Normal file
1
website/sandbox/chord-drill-sergeant/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/elm-stuff
|
|
@ -45,3 +45,13 @@ Here are some useful features of CDS:
|
||||||
- Chaos-mode: Feeling confident? Throw the classical notions of harmony to the
|
- 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
|
wayside and use CDS in "chaos-mode" where CDS samples randomly from the Circle
|
||||||
of Fifths.
|
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
|
||||||
|
```
|
||||||
|
|
24
website/sandbox/chord-drill-sergeant/elm.json
Normal file
24
website/sandbox/chord-drill-sergeant/elm.json
Normal file
|
@ -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": {}
|
||||||
|
}
|
||||||
|
}
|
7
website/sandbox/chord-drill-sergeant/shell.nix
Normal file
7
website/sandbox/chord-drill-sergeant/shell.nix
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
let
|
||||||
|
pkgs = import <nixpkgs> {};
|
||||||
|
in pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
elmPackages.elm
|
||||||
|
];
|
||||||
|
}
|
206
website/sandbox/chord-drill-sergeant/src/Main.elm
Normal file
206
website/sandbox/chord-drill-sergeant/src/Main.elm
Normal file
|
@ -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
|
||||||
|
]
|
46
website/sandbox/chord-drill-sergeant/src/Piano.elm
Normal file
46
website/sandbox/chord-drill-sergeant/src/Piano.elm
Normal file
|
@ -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)
|
Loading…
Reference in a new issue