Major components:
useWorkout
, based onuseReducer
. Itsdispatch
andstate
are exposed to all components viaWorkoutContext
.useClock
, a monotonically increasing clock. Can be paused, can be set to a specific timestamp.useBeepPlayer
; plays beeps according to schedule and current time.
Need to write
We have a functional useBeepPlayer
and useClock
. Example of using them together is in useBeepPlayer.stories.tsx
.
For now, accepting those as "good enough".
TODO:
- Refactor to using
useClock
, instead of the timer insideuseWorkout
- Challenge: will need to track current workout item, and relative time to show the Timer.
- As long as clock goes forward-only, we have no jitter
- We need to ensure to trigger all timed events ->
- This can be ensured by discretization of time OR by tracking intervals between previous and next tick.
- Providing discrete time-steps reliably is hard -> prone to drift.
- Discrete clock = continuous clock, where all discrete events between two ticks of continuous time are fired.
- How to cross into React hooks boundary, and ensure all these ticks are fired? Rendering cycles could be skipped -> not guaranteed we'll see all times.
- We'll need current and previous values from
useClock
. Previous and current value should be the same after callingsetTime(...)
, such that the interval of events to trigger is effectively empty.
Pivoted approach to beeps; Timestamps of beeps are now calculated in model.ts
directly from workout.
To support playing these beeps, we'll have a global workout clock with signature:
const { paused, setPaused, time, setTime } = useClock();
And a beep player, perhaps like this:
useBeepPlayer(time, paused, scheduledBeeps);
On start of workout, setTime()
will be called with a negative value to allow the initial countdown and playback of beeps
before time=0
.
- Workout start:
- Countdown timer: on start, begin with a 10s countdown.
- Add countdown state to WorkoutState
- Add phase to WorkoutState (initialized, counting down, working out)
- Add paused? to WorkoutState
- Add play/pause buttons to timer screen
- Visualize countdown on timer screen
- Start executing the workout plan
- Countdown timer: on start, begin with a 10s countdown.
- Options: - WebAudio API: control over precise timing of beeps - Playing audio files: file + offset information
- Features:
- Play "countdown" when transitioning into HANG (either from initial countdown or from PAUSE)
- Play "ending" at end of a hang
- Pause playback, when workout paused
- On continuation, resume with next beep.
- Play "countdown" when transitioning into HANG (either from initial countdown or from PAUSE)
clear()
orstop()
or something similar to clear all scheduled beepsscheduleStartBeep(finalStartBeepAfterTime)
(afterTime = seconds relative from now)scheduleEndBeep(lastBeepEndsAfterTime)
(afterTime = seconds relative from now)- Internally, the audio subsystem would have schedule for each of the beep sequences.
- It needs to gracefully handle possible negative start time of a beep by not playing it.
- Between reps, we have very little time. Distinguish short start beeps and long start beeps?
useBeeps({
paused: boolean,
playStartBeepsAfter: number, // calculated from workoutState.secondsRemaining and if next workout item is a HANG
playEndBeepsAfter: number, // calculated from workoutState.secondsRemaining, and if current workout item is a HANG
});
It's likely there will be jitter in the timestamps (for both API variants).
When exactly to call start
and end
on the oscillator?
Perhaps, the system could work in discrete ticks (maybe each 10ms?).
If the beep start/stop falls within the tick, we execute it.
When paused
goes from false
to true
, we pause all oscillators.
API:
useBeeps({
paused: boolean,
playStartBeepsAfter: number, // calculated from workoutState.secondsRemaining and if next workout item is a HANG
playEndBeepsAfter: number, // calculated from workoutState.secondsRemaining, and if current workout item is a HANG
});
- When paused becomes true, stop playing audio.
- When we get within 10ms of a scheduled start of file, play it
Audio files required:
- beep 880Hz
- longer beep 1760Hz
- two short 1760Hz beeps
Shortcomings were found;
For example, when playing the start beeps, we correctly transition from positive to negative numbers for []After
parameters
for the two beeps, that come before starting the workout. When we transition across that point, the remaining start beep doesn't play.
We have similar issues around positions of other beeps.
Candidate solution:
- don't let
[]After
offset go to null - use imperative API, with triggers on certain props transitions in
TimerPage
.