Support BPM for tempo

Using BPM as the unit for tempo.

TODO: Consider a higher-fidelity way to calculate BPM, although I'm not sure
this is critical functionality; an interesting problem is just seducing me, and
this app would be better off resisting the temptation.
This commit is contained in:
William Carroll 2020-04-11 17:46:46 +01:00
parent e864074600
commit c24c9b7fb9
2 changed files with 50 additions and 10 deletions

View file

@ -10,6 +10,7 @@ import Time exposing (..)
import Piano import Piano
import Theory import Theory
import Tempo
type alias Model = type alias Model =
{ whitelistedChords : List Theory.Chord { whitelistedChords : List Theory.Chord
@ -24,9 +25,18 @@ type Msg = NextChord
| Pause | Pause
| IncreaseTempo | IncreaseTempo
| DecreaseTempo | DecreaseTempo
| SetTempo String
tempoStep : Int tempoStep : Int
tempoStep = 100 tempoStep = 5
{-| Return the number of milliseconds that elapse during an interval in a
`target` bpm.
-}
bpmToMilliseconds : Int -> Int
bpmToMilliseconds target =
let msPerMinute = 1000 * 60
in round (toFloat msPerMinute / toFloat target)
viewChord : Theory.Chord -> String viewChord : Theory.Chord -> String
viewChord {note, chordType, chordPosition} = viewChord {note, chordType, chordPosition} =
@ -78,7 +88,7 @@ init =
{ whitelistedChords = Theory.allChords { whitelistedChords = Theory.allChords
, selectedChord = cmajor , selectedChord = cmajor
, isPaused = True , isPaused = True
, tempo = 1000 , tempo = 60
} }
subscriptions : Model -> Sub Msg subscriptions : Model -> Sub Msg
@ -86,7 +96,7 @@ subscriptions {isPaused, tempo} =
if isPaused then if isPaused then
Sub.none Sub.none
else else
Time.every (toFloat tempo) (\_ -> NextChord) Time.every (tempo |> bpmToMilliseconds |> toFloat) (\_ -> NextChord)
{-| Now that we have state, we need a function to change the state. -} {-| Now that we have state, we need a function to change the state. -}
update : Msg -> Model -> (Model, Cmd Msg) update : Msg -> Model -> (Model, Cmd Msg)
@ -108,12 +118,19 @@ update msg model =
Pause -> ( { model | isPaused = True } Pause -> ( { model | isPaused = True }
, Cmd.none , Cmd.none
) )
IncreaseTempo -> ( { model | tempo = model.tempo - tempoStep } IncreaseTempo -> ( { model | tempo = model.tempo + tempoStep }
, Cmd.none , Cmd.none
) )
DecreaseTempo -> ( { model | tempo = model.tempo + tempoStep } DecreaseTempo -> ( { model | tempo = model.tempo - tempoStep }
, Cmd.none , Cmd.none
) )
SetTempo tempo -> ( { model |
tempo = case String.toInt tempo of
Just x -> x
Nothing -> model.tempo
}
, Cmd.none
)
playPause : Model -> Html Msg playPause : Model -> Html Msg
playPause {isPaused} = playPause {isPaused} =
@ -124,12 +141,13 @@ playPause {isPaused} =
view : Model -> Html Msg view : Model -> Html Msg
view model = view model =
div [] [ p [] [ text (viewChord model.selectedChord) ] div [] [ Tempo.render { tempo = model.tempo
, p [] [ text (String.fromInt model.tempo) ] , handleIncrease = IncreaseTempo
, button [ onClick NextChord ] [ text "Next Chord" ] , handleDecrease = DecreaseTempo
, button [ onClick IncreaseTempo ] [ text "Faster" ] , handleInput = SetTempo
, button [ onClick DecreaseTempo ] [ text "Slower" ] }
, playPause model , playPause model
, p [] [ text (viewChord model.selectedChord) ]
, Piano.render { highlight = Theory.notesForChord model.selectedChord } , Piano.render { highlight = Theory.notesForChord model.selectedChord }
] ]

View file

@ -0,0 +1,22 @@
module Tempo exposing (render)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
type alias Props msg =
{ tempo : Int
, handleIncrease : msg
, handleDecrease : msg
, handleInput : String -> msg
}
render : Props msg -> Html msg
render {tempo, handleIncrease, handleDecrease, handleInput} =
div [] [ p [] [ text ((String.fromInt tempo) ++ " BPM") ]
, button [ onClick handleDecrease ] [ text "Slower" ]
, input [ onInput handleInput
, placeholder "Set tempo..."
] []
, button [ onClick handleIncrease ] [ text "Faster" ]
]