Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 34 additions & 9 deletions src/Metronome/controls/BeatCounter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { ControlPanelItem } from './ControlPanelItem'

type BeatCounterProps = {
clock: Worker
beatsPerMeasure: number
beatsPerLoop: number
loopLengthSeconds: number
playing: boolean
}
export function BeatCounter(props: BeatCounterProps) {
const [currentTick, setCurrentTick] = useState(0)
Expand All @@ -24,17 +26,40 @@ export function BeatCounter(props: BeatCounterProps) {
return () => {
props.clock.removeEventListener('message', clockMessageHandler)
}
}, [props.clock, props.beatsPerMeasure])
}, [props.clock])

return (
<ControlPanelItem>
<span className="text-2xl pr-2">
{/* `+ 1` to convert "computer numbers" to "musician numbers" */}
{(currentTick % props.beatsPerMeasure) + 1}
</span>
<span className="text-l">
. {Math.floor(currentTick / props.beatsPerMeasure) + 1}
</span>
<svg
version="1.1"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
className="block h-10 w-10"
>
<circle
cx="50%"
cy="50%"
r={9}
fill="none"
className={`stroke-black stroke-2`}
style={{
animationName: 'circle-stroke-shrink-9-radius',
strokeDasharray: 56.55,
// this is "x2" because the animation is designed to shrink, and then regrow the stroke.
// see index.css for more details.
animationDuration: `${props.loopLengthSeconds * 2}s`,
animationTimingFunction: 'linear',
animationDelay: '0s',
animationIterationCount: 'infinite',
animationDirection: 'normal',
animationFillMode: 'none',
animationPlayState: props.playing ? 'running' : 'paused',
}}
/>
<text x="50%" y="50%" text-anchor="middle" dy=".3em" fontSize="0.65em">
{props.beatsPerLoop - currentTick}
</text>
</svg>
</ControlPanelItem>
)
}
4 changes: 3 additions & 1 deletion src/Metronome/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,9 @@ export const Metronome: React.FC<Props> = ({ clock }) => {
<div className="flex flex-col items-center">
<BeatCounter
clock={clock}
beatsPerMeasure={timeSignature.beatsPerMeasure}
beatsPerLoop={timeSignature.beatsPerMeasure * measuresPerLoop}
loopLengthSeconds={loopLengthSeconds}
playing={playing}
/>

<TimeSignatureControl
Expand Down
16 changes: 16 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,19 @@
@tailwind components;
@tailwind utilities;

/*
This is defined here instead of tailwind.config.js because the usage of this keyframe (in BeatCounter)
is programmatically adjusted and the Tailwind compiler can't generate the classes properly
*/
@keyframes circle-stroke-shrink-9-radius {
0% {
stroke-dashoffset: 0;
}

100% {
/* this is 2x the circumfrence for a circle with radius 9.
this looks nice because first the circle will shrink to 0% stroke,
then it will grow to 100%. It is smoother than flashing to a full stroke each loop. */
stroke-dashoffset: 113.1;
}
}