about summary refs log blame commit diff
path: root/website/sandbox/chord-drill-sergeant/src/Piano.elm
blob: f539f95fc9f6501888a6fdb03d0696e2ff192e97 (plain) (tree)
1
2
3
4
5
6
7
8
9





                                    
                 

             

















                                                             
                        

                            
 


                                  
                  


              
 

                                   
                   

               
 


                                  
                     


                                      
 

                                   
                      























                                                      
          












































                                                            

                                                                          


























































                                                                                         
module Piano exposing (render)

import Browser
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import List.Extra
import Theory


type alias KeyMarkup a =
    { offset : Int
    , isHighlit : Bool
    , note : Theory.Note
    }
    -> Html a


type alias Props =
    { highlight : List Theory.Note
    , start : Theory.Note
    , end : Theory.Note
    }


{-| Convert an integer into its pixel representation for CSS.
-}
pixelate : Int -> String
pixelate x =
    String.fromInt x ++ "px"


{-| Pixel width of the white keys.
-}
naturalWidth : Int
naturalWidth =
    45


{-| Pixel height of the white keys.
-}
naturalHeight : Int
naturalHeight =
    250


{-| Pixel width of the black keys.
-}
accidentalWidth : Int
accidentalWidth =
    round (toFloat naturalWidth * 0.4)


{-| Pixel height of the black keys.
-}
accidentalHeight : Int
accidentalHeight =
    round (toFloat naturalHeight * 0.63)


{-| These are the white keys on most modern pianos.
-}
natural : KeyMarkup a
natural { offset, isHighlit, note } =
    div
        [ style "background-color"
            (if isHighlit then
                "red"

             else
                "white"
            )
        , style "border-right" "1px solid black"
        , style "border-top" "1px solid black"
        , style "border-bottom" "1px solid black"
        , style "width" (pixelate naturalWidth)
        , style "height" (pixelate naturalHeight)
        , style "position" "absolute"
        , style "left" (String.fromInt offset ++ "px")
        ]
        []


{-| These are the black keys on most modern pianos.
-}
accidental : KeyMarkup a
accidental { offset, isHighlit, note } =
    div
        [ style "background-color"
            (if isHighlit then
                "red"

             else
                "black"
            )
        , style "border-top" "1px solid black"
        , style "border-left" "1px solid black"
        , style "border-right" "1px solid black"
        , style "border-bottom" "1px solid black"
        , style "width" (pixelate accidentalWidth)
        , style "height" (pixelate accidentalHeight)
        , style "position" "absolute"
        , style "left" (String.fromInt offset ++ "px")
        , style "z-index" "1"
        ]
        []


makeKey : List Theory.Note -> Theory.Note -> (Int -> Html a)
makeKey highlight note =
    if Theory.isNatural note then
        \x ->
            natural
                { offset = x
                , isHighlit = List.member note highlight
                , note = note
                }

    else
        \x ->
            accidental
                { offset = x
                , isHighlit = List.member note highlight
                , note = note
                }


{-| 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 : Theory.Note -> Theory.Note -> List Theory.Note -> List (Html a)
octave start end highlight =
    let
        isHighlit note =
            List.member note highlight

        spacing prevOffset prev curr =
            case ( Theory.keyClass prev, Theory.keyClass curr ) of
                ( Theory.Natural, Theory.Accidental ) ->
                    -- idk this calculation yet
                    prevOffset + naturalWidth - round (toFloat accidentalWidth / 2)

                ( Theory.Accidental, Theory.Natural ) ->
                    -- accidentalWidth / 2
                    prevOffset + round (toFloat accidentalWidth / 2)

                ( Theory.Natural, Theory.Natural ) ->
                    -- naturalWidth
                    prevOffset + naturalWidth

                -- This pattern should never hit.
                _ ->
                    prevOffset

        ( _, _, notes ) =
            Theory.notesFromRange start end
                |> List.foldl
                    (\curr ( prevOffset, prev, result ) ->
                        case ( prevOffset, prev ) of
                            ( Nothing, Nothing ) ->
                                ( Just 0, Just curr, makeKey highlight curr 0 :: result )

                            ( Just po, Just p ) ->
                                let
                                    offset =
                                        spacing po p curr
                                in
                                ( Just offset
                                , Just curr
                                , makeKey highlight curr offset :: result
                                )

                            -- This pattern should never hit.
                            _ ->
                                ( Nothing, Nothing, [] )
                    )
                    ( Nothing, Nothing, [] )
    in
    List.reverse notes


{-| Return the HTML that renders a piano representation.
-}
render : Props -> Html a
render { highlight, start, end } =
    div [ style "display" "flex" ]
        (octave start end highlight |> List.reverse |> List.repeat 1 |> List.concat)