Highlight root note of each chord
Refactor the Piano component to highlight the root note of each chord. If this makes things too easy, I can support this as a preference. Also: - Reduced the number of keys that the piano displays and increased the key thickness to reclaim the space - Preferred using Tailwind selectors instead of inline styling where applicable - Call List.reverse on the keys to ensure that the top-most note is a lower note than the bottom-most note TODO: - Support showing only the name of the chord and not just the notes that comprise that chord - Rewrite the function that generates the chords for a given range of notes - Consider supporting a dark mode
This commit is contained in:
		
							parent
							
								
									c6132ab1d6
								
							
						
					
					
						commit
						11b140b6ae
					
				
					 2 changed files with 70 additions and 37 deletions
				
			
		| 
						 | 
					@ -6,6 +6,7 @@ import Html.Attributes exposing (..)
 | 
				
			||||||
import Html.Events exposing (..)
 | 
					import Html.Events exposing (..)
 | 
				
			||||||
import List.Extra
 | 
					import List.Extra
 | 
				
			||||||
import Theory
 | 
					import Theory
 | 
				
			||||||
 | 
					import UI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{-| On mobile phones, the keyboard displays vertically.
 | 
					{-| On mobile phones, the keyboard displays vertically.
 | 
				
			||||||
| 
						 | 
					@ -17,17 +18,18 @@ type Direction
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type alias KeyMarkup a =
 | 
					type alias KeyMarkup a =
 | 
				
			||||||
    { offset : Int
 | 
					    { offset : Int
 | 
				
			||||||
 | 
					    , direction : Direction
 | 
				
			||||||
    , isHighlit : Bool
 | 
					    , isHighlit : Bool
 | 
				
			||||||
    , note : Theory.Note
 | 
					    , note : Theory.Note
 | 
				
			||||||
    , direction : Direction
 | 
					    , isRootNote : Bool
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    -> Html a
 | 
					    -> Html a
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type alias Props =
 | 
					type alias Props =
 | 
				
			||||||
    { highlight : List Theory.Note
 | 
					    { chord : Maybe Theory.Chord
 | 
				
			||||||
    , start : Theory.Note
 | 
					    , firstNote : Theory.Note
 | 
				
			||||||
    , end : Theory.Note
 | 
					    , lastNote : Theory.Note
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,8 +46,6 @@ naturalWidth : Direction -> Int
 | 
				
			||||||
naturalWidth direction =
 | 
					naturalWidth direction =
 | 
				
			||||||
    case direction of
 | 
					    case direction of
 | 
				
			||||||
        Vertical ->
 | 
					        Vertical ->
 | 
				
			||||||
            -- Right now, I'm designing this specifically for my Google Pixel 4
 | 
					 | 
				
			||||||
            -- phone, which has a screen width of 1080px.
 | 
					 | 
				
			||||||
            1080
 | 
					            1080
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Horizontal ->
 | 
					        Horizontal ->
 | 
				
			||||||
| 
						 | 
					@ -58,10 +58,7 @@ naturalHeight : Direction -> Int
 | 
				
			||||||
naturalHeight direction =
 | 
					naturalHeight direction =
 | 
				
			||||||
    case direction of
 | 
					    case direction of
 | 
				
			||||||
        Vertical ->
 | 
					        Vertical ->
 | 
				
			||||||
            -- Right now, I'm designing this specifically for my Google Pixel 4
 | 
					            130
 | 
				
			||||||
            -- phone, which has a screen height of 2280px. 2280 / 21
 | 
					 | 
				
			||||||
            -- (i.e. no. natural keys) ~= 108
 | 
					 | 
				
			||||||
            108
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Horizontal ->
 | 
					        Horizontal ->
 | 
				
			||||||
            250
 | 
					            250
 | 
				
			||||||
| 
						 | 
					@ -94,17 +91,28 @@ accidentalHeight direction =
 | 
				
			||||||
{-| Return the markup for either a white or a black key.
 | 
					{-| Return the markup for either a white or a black key.
 | 
				
			||||||
-}
 | 
					-}
 | 
				
			||||||
pianoKey : KeyMarkup a
 | 
					pianoKey : KeyMarkup a
 | 
				
			||||||
pianoKey { offset, isHighlit, note, direction } =
 | 
					pianoKey { offset, isHighlit, note, direction, isRootNote } =
 | 
				
			||||||
    let
 | 
					    let
 | 
				
			||||||
 | 
					        { natColor, accColor, hiColor, rootColor } =
 | 
				
			||||||
 | 
					            { natColor = "bg-white"
 | 
				
			||||||
 | 
					            , accColor = "bg-black"
 | 
				
			||||||
 | 
					            , hiColor = "bg-red-400"
 | 
				
			||||||
 | 
					            , rootColor = "bg-red-600"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sharedClasses =
 | 
					        sharedClasses =
 | 
				
			||||||
            [ "box-border" ]
 | 
					            [ "box-border"
 | 
				
			||||||
 | 
					            , "absolute"
 | 
				
			||||||
 | 
					            , "border"
 | 
				
			||||||
 | 
					            , "border-black"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        { keyWidth, keyHeight, keyColor, offsetEdge, extraClasses } =
 | 
					        { keyWidth, keyHeight, keyColor, offsetEdge, extraClasses } =
 | 
				
			||||||
            case ( Theory.keyClass note, direction ) of
 | 
					            case ( Theory.keyClass note, direction ) of
 | 
				
			||||||
                ( Theory.Natural, Vertical ) ->
 | 
					                ( Theory.Natural, Vertical ) ->
 | 
				
			||||||
                    { keyWidth = naturalWidth Vertical
 | 
					                    { keyWidth = naturalWidth Vertical
 | 
				
			||||||
                    , keyHeight = naturalHeight Vertical
 | 
					                    , keyHeight = naturalHeight Vertical
 | 
				
			||||||
                    , keyColor = "white"
 | 
					                    , keyColor = natColor
 | 
				
			||||||
                    , offsetEdge = "top"
 | 
					                    , offsetEdge = "top"
 | 
				
			||||||
                    , extraClasses = []
 | 
					                    , extraClasses = []
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
| 
						 | 
					@ -112,7 +120,7 @@ pianoKey { offset, isHighlit, note, direction } =
 | 
				
			||||||
                ( Theory.Natural, Horizontal ) ->
 | 
					                ( Theory.Natural, Horizontal ) ->
 | 
				
			||||||
                    { keyWidth = naturalWidth Horizontal
 | 
					                    { keyWidth = naturalWidth Horizontal
 | 
				
			||||||
                    , keyHeight = naturalHeight Horizontal
 | 
					                    , keyHeight = naturalHeight Horizontal
 | 
				
			||||||
                    , keyColor = "white"
 | 
					                    , keyColor = natColor
 | 
				
			||||||
                    , offsetEdge = "left"
 | 
					                    , offsetEdge = "left"
 | 
				
			||||||
                    , extraClasses = []
 | 
					                    , extraClasses = []
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
| 
						 | 
					@ -120,7 +128,7 @@ pianoKey { offset, isHighlit, note, direction } =
 | 
				
			||||||
                ( Theory.Accidental, Vertical ) ->
 | 
					                ( Theory.Accidental, Vertical ) ->
 | 
				
			||||||
                    { keyWidth = accidentalWidth Vertical
 | 
					                    { keyWidth = accidentalWidth Vertical
 | 
				
			||||||
                    , keyHeight = accidentalHeight Vertical
 | 
					                    , keyHeight = accidentalHeight Vertical
 | 
				
			||||||
                    , keyColor = "black"
 | 
					                    , keyColor = accColor
 | 
				
			||||||
                    , offsetEdge = "top"
 | 
					                    , offsetEdge = "top"
 | 
				
			||||||
                    , extraClasses = [ "z-10" ]
 | 
					                    , extraClasses = [ "z-10" ]
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
| 
						 | 
					@ -128,26 +136,25 @@ pianoKey { offset, isHighlit, note, direction } =
 | 
				
			||||||
                ( Theory.Accidental, Horizontal ) ->
 | 
					                ( Theory.Accidental, Horizontal ) ->
 | 
				
			||||||
                    { keyWidth = accidentalWidth Horizontal
 | 
					                    { keyWidth = accidentalWidth Horizontal
 | 
				
			||||||
                    , keyHeight = accidentalHeight Horizontal
 | 
					                    , keyHeight = accidentalHeight Horizontal
 | 
				
			||||||
                    , keyColor = "black"
 | 
					                    , keyColor = accColor
 | 
				
			||||||
                    , offsetEdge = "left"
 | 
					                    , offsetEdge = "left"
 | 
				
			||||||
                    , extraClasses = [ "z-10" ]
 | 
					                    , extraClasses = [ "z-10" ]
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
    in
 | 
					    in
 | 
				
			||||||
    div
 | 
					    div
 | 
				
			||||||
        [ style "background-color"
 | 
					        [ class
 | 
				
			||||||
            (if isHighlit then
 | 
					            (case ( isHighlit, isRootNote ) of
 | 
				
			||||||
                "red"
 | 
					                ( False, _ ) ->
 | 
				
			||||||
 | 
					 | 
				
			||||||
             else
 | 
					 | 
				
			||||||
                    keyColor
 | 
					                    keyColor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                ( True, True ) ->
 | 
				
			||||||
 | 
					                    rootColor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                ( True, False ) ->
 | 
				
			||||||
 | 
					                    hiColor
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        , style "border-top" "1px solid black"
 | 
					 | 
				
			||||||
        , style "border-bottom" "1px solid black"
 | 
					 | 
				
			||||||
        , style "border-left" "1px solid black"
 | 
					 | 
				
			||||||
        , style "border-right" "1px solid black"
 | 
					 | 
				
			||||||
        , style "width" (pixelate keyWidth)
 | 
					        , style "width" (pixelate keyWidth)
 | 
				
			||||||
        , style "height" (pixelate keyHeight)
 | 
					        , style "height" (pixelate keyHeight)
 | 
				
			||||||
        , style "position" "absolute"
 | 
					 | 
				
			||||||
        , style offsetEdge (String.fromInt offset ++ "px")
 | 
					        , style offsetEdge (String.fromInt offset ++ "px")
 | 
				
			||||||
        , class <| String.join " " (List.concat [ sharedClasses, extraClasses ])
 | 
					        , class <| String.join " " (List.concat [ sharedClasses, extraClasses ])
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
| 
						 | 
					@ -156,11 +163,18 @@ pianoKey { offset, isHighlit, note, direction } =
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{-| A section of the piano consisting of all twelve notes.
 | 
					{-| A section of the piano consisting of all twelve notes.
 | 
				
			||||||
-}
 | 
					-}
 | 
				
			||||||
keys : Direction -> Theory.Note -> Theory.Note -> List Theory.Note -> List (Html a)
 | 
					keys :
 | 
				
			||||||
keys direction start end highlight =
 | 
					    { direction : Direction
 | 
				
			||||||
 | 
					    , start : Theory.Note
 | 
				
			||||||
 | 
					    , end : Theory.Note
 | 
				
			||||||
 | 
					    , highlitNotes : List Theory.Note
 | 
				
			||||||
 | 
					    , rootNote : Maybe Theory.Note
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    -> List (Html a)
 | 
				
			||||||
 | 
					keys { direction, start, end, highlitNotes, rootNote } =
 | 
				
			||||||
    let
 | 
					    let
 | 
				
			||||||
        isHighlit note =
 | 
					        isHighlit note =
 | 
				
			||||||
            List.member note highlight
 | 
					            List.member note highlitNotes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        spacing prevOffset prev curr =
 | 
					        spacing prevOffset prev curr =
 | 
				
			||||||
            case ( Theory.keyClass prev, Theory.keyClass curr, direction ) of
 | 
					            case ( Theory.keyClass prev, Theory.keyClass curr, direction ) of
 | 
				
			||||||
| 
						 | 
					@ -190,6 +204,7 @@ keys direction start end highlight =
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ( _, _, notes ) =
 | 
					        ( _, _, notes ) =
 | 
				
			||||||
            Theory.notesFromRange start end
 | 
					            Theory.notesFromRange start end
 | 
				
			||||||
 | 
					                |> List.reverse
 | 
				
			||||||
                |> List.foldl
 | 
					                |> List.foldl
 | 
				
			||||||
                    (\curr ( prevOffset, prev, result ) ->
 | 
					                    (\curr ( prevOffset, prev, result ) ->
 | 
				
			||||||
                        case ( prevOffset, prev ) of
 | 
					                        case ( prevOffset, prev ) of
 | 
				
			||||||
| 
						 | 
					@ -198,9 +213,13 @@ keys direction start end highlight =
 | 
				
			||||||
                                , Just curr
 | 
					                                , Just curr
 | 
				
			||||||
                                , pianoKey
 | 
					                                , pianoKey
 | 
				
			||||||
                                    { offset = 0
 | 
					                                    { offset = 0
 | 
				
			||||||
                                    , isHighlit = List.member curr highlight
 | 
					                                    , isHighlit = List.member curr highlitNotes
 | 
				
			||||||
                                    , note = curr
 | 
					                                    , note = curr
 | 
				
			||||||
                                    , direction = direction
 | 
					                                    , direction = direction
 | 
				
			||||||
 | 
					                                    , isRootNote =
 | 
				
			||||||
 | 
					                                        rootNote
 | 
				
			||||||
 | 
					                                            |> Maybe.map (\x -> x == curr)
 | 
				
			||||||
 | 
					                                            |> Maybe.withDefault False
 | 
				
			||||||
                                    }
 | 
					                                    }
 | 
				
			||||||
                                    :: result
 | 
					                                    :: result
 | 
				
			||||||
                                )
 | 
					                                )
 | 
				
			||||||
| 
						 | 
					@ -214,9 +233,13 @@ keys direction start end highlight =
 | 
				
			||||||
                                , Just curr
 | 
					                                , Just curr
 | 
				
			||||||
                                , pianoKey
 | 
					                                , pianoKey
 | 
				
			||||||
                                    { offset = offset
 | 
					                                    { offset = offset
 | 
				
			||||||
                                    , isHighlit = List.member curr highlight
 | 
					                                    , isHighlit = List.member curr highlitNotes
 | 
				
			||||||
                                    , note = curr
 | 
					                                    , note = curr
 | 
				
			||||||
                                    , direction = direction
 | 
					                                    , direction = direction
 | 
				
			||||||
 | 
					                                    , isRootNote =
 | 
				
			||||||
 | 
					                                        rootNote
 | 
				
			||||||
 | 
					                                            |> Maybe.map (\x -> x == curr)
 | 
				
			||||||
 | 
					                                            |> Maybe.withDefault False
 | 
				
			||||||
                                    }
 | 
					                                    }
 | 
				
			||||||
                                    :: result
 | 
					                                    :: result
 | 
				
			||||||
                                )
 | 
					                                )
 | 
				
			||||||
| 
						 | 
					@ -227,12 +250,22 @@ keys direction start end highlight =
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                    ( Nothing, Nothing, [] )
 | 
					                    ( Nothing, Nothing, [] )
 | 
				
			||||||
    in
 | 
					    in
 | 
				
			||||||
    List.reverse notes
 | 
					    notes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{-| Return the HTML that renders a piano representation.
 | 
					{-| Return the HTML that renders a piano representation.
 | 
				
			||||||
-}
 | 
					-}
 | 
				
			||||||
render : Props -> Html a
 | 
					render : Props -> Html a
 | 
				
			||||||
render { highlight, start, end } =
 | 
					render { chord } =
 | 
				
			||||||
    div [ style "display" "flex" ]
 | 
					    div [ style "display" "flex" ]
 | 
				
			||||||
        (keys Vertical start end highlight |> List.reverse |> List.repeat 1 |> List.concat)
 | 
					        (keys
 | 
				
			||||||
 | 
					            { direction = Vertical
 | 
				
			||||||
 | 
					            , start = Theory.G3
 | 
				
			||||||
 | 
					            , end = Theory.C6
 | 
				
			||||||
 | 
					            , rootNote = chord |> Maybe.map .note
 | 
				
			||||||
 | 
					            , highlitNotes =
 | 
				
			||||||
 | 
					                chord
 | 
				
			||||||
 | 
					                    |> Maybe.andThen Theory.notesForChord
 | 
				
			||||||
 | 
					                    |> Maybe.withDefault []
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,8 +37,8 @@ render model =
 | 
				
			||||||
            , isVisible = model.isPaused
 | 
					            , isVisible = model.isPaused
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        , Piano.render
 | 
					        , Piano.render
 | 
				
			||||||
            { highlight = model.selectedChord |> Maybe.andThen Theory.notesForChord |> Maybe.withDefault []
 | 
					            { chord = model.selectedChord
 | 
				
			||||||
            , start = model.firstNote
 | 
					            , firstNote = model.firstNote
 | 
				
			||||||
            , end = model.lastNote
 | 
					            , lastNote = model.lastNote
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue