Only display chords that fit on the displayed piano

Only show the chords that we can fit on the piano.

TODO: Debug occasional instance where we render chords that do not fit. I am
unsure how to reproduce these states at the moment.
This commit is contained in:
William Carroll 2020-04-12 23:33:56 +01:00
parent 3ee1b1f670
commit 083763f7f2

View file

@ -1,5 +1,6 @@
module Theory exposing (..)
import Array exposing (Array)
import List.Extra
import Maybe.Extra
import Misc
@ -221,6 +222,14 @@ type Mode
| MinorMode
type alias NoteMetadata =
{ note : Note
, label : String
, noteClass : NoteClass
, natural : Bool
}
scaleDegree : Int -> Key -> NoteClass
scaleDegree which { noteClass } =
case noteClass of
@ -624,41 +633,7 @@ keyClass 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
note |> getNoteMetadata |> .noteClass
{-| Return a list of the notes that comprise a `chord`
@ -692,49 +667,14 @@ notesForKey { noteClass, mode } =
-}
isAccidental : Note -> Bool
isAccidental note =
case classifyNote note of
C ->
False
C_sharp ->
True
D ->
False
D_sharp ->
True
E ->
False
F ->
False
F_sharp ->
True
G ->
False
G_sharp ->
True
A ->
False
A_sharp ->
True
B ->
False
note |> isNatural |> not
{-| Return true if `note` is a white key.
-}
isNatural : Note -> Bool
isNatural note =
note |> isAccidental |> not
note |> getNoteMetadata |> .natural
{-| Return a list of all of the notes that we know about.
@ -742,92 +682,9 @@ Only return the notes within the range `start` and `end`.
-}
notesFromRange : Note -> Note -> List Note
notesFromRange start end =
[ 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
]
noteMetadata
|> Array.toList
|> List.map .note
|> List.Extra.dropWhile ((/=) start)
|> List.Extra.takeWhile ((/=) end)
@ -859,6 +716,415 @@ allChordTypes =
]
noteMetadata : Array NoteMetadata
noteMetadata =
Array.fromList
[ { note = A1, label = "A1", noteClass = A, natural = True }
, { note = A_sharp1, label = "A/B1", noteClass = A_sharp, natural = False }
, { note = B1, label = "B1", noteClass = B, natural = True }
, { note = C1, label = "C1", noteClass = C, natural = True }
, { note = C_sharp1, label = "C/D1", noteClass = C_sharp, natural = False }
, { note = D1, label = "D1", noteClass = D, natural = True }
, { note = D_sharp1, label = "D/E1", noteClass = D_sharp, natural = False }
, { note = E1, label = "E1", noteClass = E, natural = True }
, { note = F1, label = "F1", noteClass = F, natural = True }
, { note = F_sharp1, label = "F/G1", noteClass = F_sharp, natural = False }
, { note = G1, label = "G1", noteClass = G, natural = True }
, { note = G_sharp1, label = "G/A1", noteClass = G, natural = False }
, { note = A2, label = "A2", noteClass = A, natural = True }
, { note = A_sharp2, label = "A/B2", noteClass = A_sharp, natural = False }
, { note = B2, label = "B2", noteClass = B, natural = True }
, { note = C2, label = "C2", noteClass = C, natural = True }
, { note = C_sharp2, label = "C/D2", noteClass = C_sharp, natural = False }
, { note = D2, label = "D2", noteClass = D, natural = True }
, { note = D_sharp2, label = "D/E2", noteClass = D_sharp, natural = False }
, { note = E2, label = "E2", noteClass = E, natural = True }
, { note = F2, label = "F2", noteClass = F, natural = True }
, { note = F_sharp2, label = "F/G2", noteClass = F_sharp, natural = False }
, { note = G2, label = "G2", noteClass = G, natural = True }
, { note = G_sharp2, label = "G/A2", noteClass = G, natural = False }
, { note = A3, label = "A3", noteClass = A, natural = True }
, { note = A_sharp3, label = "A/B3", noteClass = A_sharp, natural = False }
, { note = B3, label = "B3", noteClass = B, natural = True }
, { note = C3, label = "C3", noteClass = C, natural = True }
, { note = C_sharp3, label = "C/D3", noteClass = C_sharp, natural = False }
, { note = D3, label = "D3", noteClass = D, natural = True }
, { note = D_sharp3, label = "D/E3", noteClass = D_sharp, natural = False }
, { note = E3, label = "E3", noteClass = E, natural = True }
, { note = F3, label = "F3", noteClass = F, natural = True }
, { note = F_sharp3, label = "F/G3", noteClass = F_sharp, natural = False }
, { note = G3, label = "G3", noteClass = G, natural = True }
, { note = G_sharp3, label = "G/A3", noteClass = G, natural = False }
, { note = A4, label = "A4", noteClass = A, natural = True }
, { note = A_sharp4, label = "A/B4", noteClass = A_sharp, natural = False }
, { note = B4, label = "B4", noteClass = B, natural = True }
, { note = C4, label = "C4", noteClass = C, natural = True }
, { note = C_sharp4, label = "C/D4", noteClass = C_sharp, natural = False }
, { note = D4, label = "D4", noteClass = D, natural = True }
, { note = D_sharp4, label = "D/E4", noteClass = D_sharp, natural = False }
, { note = E4, label = "E4", noteClass = E, natural = True }
, { note = F4, label = "F4", noteClass = F, natural = True }
, { note = F_sharp4, label = "F/G4", noteClass = F_sharp, natural = False }
, { note = G4, label = "G4", noteClass = G, natural = True }
, { note = G_sharp4, label = "G/A4", noteClass = G, natural = False }
, { note = A5, label = "A5", noteClass = A, natural = True }
, { note = A_sharp5, label = "A/B5", noteClass = A_sharp, natural = False }
, { note = B5, label = "B5", noteClass = B, natural = True }
, { note = C5, label = "C5", noteClass = C, natural = True }
, { note = C_sharp5, label = "C/D5", noteClass = C_sharp, natural = False }
, { note = D5, label = "D5", noteClass = D, natural = True }
, { note = D_sharp5, label = "D/E5", noteClass = D_sharp, natural = False }
, { note = E5, label = "E5", noteClass = E, natural = True }
, { note = F5, label = "F5", noteClass = F, natural = True }
, { note = F_sharp5, label = "F/G5", noteClass = F_sharp, natural = False }
, { note = G5, label = "G5", noteClass = G, natural = True }
, { note = G_sharp5, label = "G/A5", noteClass = G, natural = False }
, { note = A6, label = "A6", noteClass = A, natural = True }
, { note = A_sharp6, label = "A/B6", noteClass = A_sharp, natural = False }
, { note = B6, label = "B6", noteClass = B, natural = True }
, { note = C6, label = "C6", noteClass = C, natural = True }
, { note = C_sharp6, label = "C/D6", noteClass = C_sharp, natural = False }
, { note = D6, label = "D6", noteClass = D, natural = True }
, { note = D_sharp6, label = "D/E6", noteClass = D_sharp, natural = False }
, { note = E6, label = "E6", noteClass = E, natural = True }
, { note = F6, label = "F6", noteClass = F, natural = True }
, { note = F_sharp6, label = "F/G6", noteClass = F_sharp, natural = False }
, { note = G6, label = "G6", noteClass = G, natural = True }
, { note = G_sharp6, label = "G/A6", noteClass = G, natural = False }
, { note = A7, label = "A7", noteClass = A, natural = True }
, { note = A_sharp7, label = "A/B7", noteClass = A_sharp, natural = False }
, { note = B7, label = "B7", noteClass = B, natural = True }
, { note = C7, label = "C7", noteClass = C, natural = True }
, { note = C_sharp7, label = "C/D7", noteClass = C_sharp, natural = False }
, { note = D7, label = "D7", noteClass = D, natural = True }
, { note = D_sharp7, label = "D/E7", noteClass = D_sharp, natural = False }
, { note = E7, label = "E7", noteClass = E, natural = True }
, { note = F7, label = "F7", noteClass = F, natural = True }
, { note = F_sharp7, label = "F/G7", noteClass = F_sharp, natural = False }
, { note = G7, label = "G7", noteClass = G, natural = True }
, { note = G_sharp7, label = "G/A7", noteClass = G, natural = False }
, { note = C8, label = "C8", noteClass = C, natural = True }
]
{-| Mapping of note data to commonly needed metadata for that note.
-}
getNoteMetadata : Note -> NoteMetadata
getNoteMetadata note =
case Array.get (noteAsNumber note) noteMetadata of
Just metadata ->
metadata
-- This case should never hit, so we just return C1 to appease the
-- compiler.
Nothing ->
getNoteMetadata C1
{-| Return the numeric representation of `note` to ues when comparing two
notes.
-}
noteAsNumber : Note -> Int
noteAsNumber note =
case note of
C1 ->
0
C_sharp1 ->
1
D1 ->
2
D_sharp1 ->
3
E1 ->
4
F1 ->
5
F_sharp1 ->
6
G1 ->
7
G_sharp1 ->
8
A1 ->
9
A_sharp1 ->
10
B1 ->
11
C2 ->
12
C_sharp2 ->
13
D2 ->
14
D_sharp2 ->
15
E2 ->
16
F2 ->
17
F_sharp2 ->
18
G2 ->
19
G_sharp2 ->
20
A2 ->
21
A_sharp2 ->
22
B2 ->
23
C3 ->
24
C_sharp3 ->
25
D3 ->
26
D_sharp3 ->
27
E3 ->
28
F3 ->
29
F_sharp3 ->
30
G3 ->
31
G_sharp3 ->
32
A3 ->
33
A_sharp3 ->
34
B3 ->
35
C4 ->
36
C_sharp4 ->
37
D4 ->
38
D_sharp4 ->
39
E4 ->
40
F4 ->
41
F_sharp4 ->
42
G4 ->
43
G_sharp4 ->
44
A4 ->
45
A_sharp4 ->
46
B4 ->
47
C5 ->
48
C_sharp5 ->
49
D5 ->
50
D_sharp5 ->
51
E5 ->
52
F5 ->
53
F_sharp5 ->
54
G5 ->
55
G_sharp5 ->
56
A5 ->
57
A_sharp5 ->
58
B5 ->
59
C6 ->
60
C_sharp6 ->
61
D6 ->
62
D_sharp6 ->
63
E6 ->
64
F6 ->
65
F_sharp6 ->
66
G6 ->
67
G_sharp6 ->
68
A6 ->
69
A_sharp6 ->
70
B6 ->
71
C7 ->
72
C_sharp7 ->
73
D7 ->
74
D_sharp7 ->
75
E7 ->
76
F7 ->
77
F_sharp7 ->
78
G7 ->
79
G_sharp7 ->
80
A7 ->
81
A_sharp7 ->
82
B7 ->
83
C8 ->
84
{-| Return true if all of the notes that comprise `chord` can be played on a
piano whose keys begin at `start` and end at `end`.
-}
chordWithinRange : Note -> Note -> Chord -> Bool
chordWithinRange start end chord =
case notesForChord chord of
Just notes ->
let
nums =
List.map noteAsNumber notes
lo =
List.minimum nums |> Maybe.withDefault (noteAsNumber start)
hi =
List.maximum nums |> Maybe.withDefault (noteAsNumber end)
in
lo >= noteAsNumber start && hi < noteAsNumber end
Nothing ->
False
{-| Return a list of all of the chords that we know about.
-}
allNoteClasses : List NoteClass
allNoteClasses =
[ C
, C_sharp
, D
, D_sharp
, E
, F
, F_sharp
, G
, G_sharp
, A
, A_sharp
, B
]
{-| Return a list of all of the chords that we know about.
Only create chords from the range of notes delimited by the range `start` and
`end`.
@ -868,12 +1134,14 @@ allChords :
, end : Note
, inversions : List ChordInversion
, chordTypes : List ChordType
, noteClasses : List NoteClass
}
-> List Chord
allChords { start, end, inversions, chordTypes } =
allChords { start, end, inversions, chordTypes, noteClasses } =
let
notes =
notesFromRange start end
|> List.filter (\note -> List.member (classifyNote note) noteClasses)
in
notes
|> List.Extra.andThen
@ -892,267 +1160,14 @@ allChords { start, end, inversions, chordTypes } =
)
)
)
|> List.filter (chordWithinRange start end)
{-| Serialize a human-readable format of `note`.
-}
viewNote : Note -> String
viewNote note =
case note of
C1 ->
"C1"
C_sharp1 ->
"C/D1"
D1 ->
"D1"
D_sharp1 ->
"D/E1"
E1 ->
"E1"
F1 ->
"F1"
F_sharp1 ->
"F/G1"
G1 ->
"G1"
G_sharp1 ->
"G/A1"
A1 ->
"A1"
A_sharp1 ->
"A/B1"
B1 ->
"B1"
C2 ->
"C2"
C_sharp2 ->
"C/D2"
D2 ->
"D2"
D_sharp2 ->
"D/E2"
E2 ->
"E2"
F2 ->
"F2"
F_sharp2 ->
"F/G2"
G2 ->
"G2"
G_sharp2 ->
"G/A2"
A2 ->
"A2"
A_sharp2 ->
"A/B2"
B2 ->
"B2"
C3 ->
"C3"
C_sharp3 ->
"C/D3"
D3 ->
"D3"
D_sharp3 ->
"D/E3"
E3 ->
"E3"
F3 ->
"F3"
F_sharp3 ->
"F/G3"
G3 ->
"G3"
G_sharp3 ->
"G/A3"
A3 ->
"A3"
A_sharp3 ->
"A/B3"
B3 ->
"B3"
C4 ->
"C4"
C_sharp4 ->
"C/D4"
D4 ->
"D4"
D_sharp4 ->
"D/E4"
E4 ->
"E4"
F4 ->
"F4"
F_sharp4 ->
"F/G4"
G4 ->
"G4"
G_sharp4 ->
"G/A4"
A4 ->
"A4"
A_sharp4 ->
"A/B4"
B4 ->
"B4"
C5 ->
"C5"
C_sharp5 ->
"C/D5"
D5 ->
"D5"
D_sharp5 ->
"D/E5"
E5 ->
"E5"
F5 ->
"F5"
F_sharp5 ->
"F/G5"
G5 ->
"G5"
G_sharp5 ->
"G/A5"
A5 ->
"A5"
A_sharp5 ->
"A/B5"
B5 ->
"B5"
C6 ->
"C6"
C_sharp6 ->
"C/D6"
D6 ->
"D6"
D_sharp6 ->
"D/E6"
E6 ->
"E6"
F6 ->
"F6"
F_sharp6 ->
"F/G6"
G6 ->
"G6"
G_sharp6 ->
"G/A6"
A6 ->
"A6"
A_sharp6 ->
"A/B6"
B6 ->
"B6"
C7 ->
"C7"
C_sharp7 ->
"C/D7"
D7 ->
"D7"
D_sharp7 ->
"D/E7"
E7 ->
"E7"
F7 ->
"F7"
F_sharp7 ->
"F/G7"
G7 ->
"G7"
G_sharp7 ->
"G/A7"
A7 ->
"A7"
A_sharp7 ->
"A/B7"
B7 ->
"B7"
C8 ->
"C8"
note |> getNoteMetadata |> .label
inspectChord : Chord -> String