Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clock #492

Merged
merged 43 commits into from
Feb 20, 2025
Merged

Clock #492

Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
599383b
empty clock package with test app
funwithtriangles Jan 23, 2025
b77ddb5
basic skeleton for clock test app
funwithtriangles Jan 23, 2025
8618ea6
clock test app with mod/sin/cos
funwithtriangles Jan 23, 2025
c183bca
cycling in time with beat
funwithtriangles Jan 23, 2025
0705c77
extra waveforms
funwithtriangles Jan 23, 2025
f5d28bb
tidy
funwithtriangles Jan 25, 2025
783ae35
tidy
funwithtriangles Jan 25, 2025
6bea326
tidy
funwithtriangles Jan 25, 2025
5619206
tidy
funwithtriangles Jan 25, 2025
daa9884
test app extras
funwithtriangles Jan 25, 2025
80f6448
naive sendTemoTap implementation
funwithtriangles Jan 25, 2025
4939879
better temp tap alg
funwithtriangles Jan 25, 2025
77e1c51
tidy
funwithtriangles Jan 25, 2025
3dc5dab
eslint fix
funwithtriangles Jan 26, 2025
0cc9a4b
tidy
funwithtriangles Jan 26, 2025
d846424
removed build script (was crashing osc)
funwithtriangles Jan 26, 2025
ea4edca
clock test app as own package
funwithtriangles Jan 26, 2025
8dbe2c3
tidy
funwithtriangles Jan 26, 2025
b48cbc7
ts fix
funwithtriangles Jan 26, 2025
e511446
lint config fix
funwithtriangles Jan 26, 2025
af1f0b2
nohoist config tweak
funwithtriangles Jan 26, 2025
c65b26c
midi clock control HTML
funwithtriangles Jan 26, 2025
6ae3aea
midiclockmock pulse
funwithtriangles Jan 26, 2025
00da418
dodgy mock clock pulses
funwithtriangles Jan 26, 2025
344643e
mock clock not good
funwithtriangles Jan 26, 2025
e4797b1
tidy
funwithtriangles Jan 26, 2025
eb2cace
trying to increase accuracy of mock clock
funwithtriangles Jan 26, 2025
200a6cd
startOnNextTimingPulse and other timing pulse related stuff
funwithtriangles Jan 30, 2025
066c6d0
circle pulse
funwithtriangles Jan 30, 2025
8073730
fixed buggy app refresh behaviour
funwithtriangles Jan 31, 2025
383558d
tweaks and MIDI continue handling
funwithtriangles Jan 31, 2025
fe9f38a
timing offset calc every pulse
funwithtriangles Jan 31, 2025
a56b331
continueOnNextTimingPulse
funwithtriangles Jan 31, 2025
0b1f46a
beatPulseComparisonDelta
funwithtriangles Jan 31, 2025
f809017
tidy
funwithtriangles Jan 31, 2025
e9dc8a4
smoothed values
funwithtriangles Jan 31, 2025
8a8a819
nicer smoothing of bpm
funwithtriangles Jan 31, 2025
7a20fdc
readmes
funwithtriangles Feb 20, 2025
fc05d40
fixed tapping bpm display
funwithtriangles Feb 20, 2025
687dccf
nice comments
funwithtriangles Feb 20, 2025
9cb147a
external clock testing tips
funwithtriangles Feb 20, 2025
a7f7172
tidy up for pro tools stuff
funwithtriangles Feb 20, 2025
2117ab4
tidy
funwithtriangles Feb 20, 2025
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
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -6,9 +6,14 @@ dist
out

.pnp.*
.yarn/*
**/.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions



.nx/cache
.nx/workspace-data
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -5,6 +5,10 @@
1. Run `yarn` at the top level to install all deps
2. Run `yarn dev` at the top level to start Hedron. This will watch for changes in both `@hedron/desktop` and `@hedron/engine`

## Working on isolated packages

If you're just working on the clock package, use `yarn dev:clock`. This will build the clock package and also start the `clock-test-app` package.

## Building for all platforms

1. Close any instance of Hedron
53 changes: 53 additions & 0 deletions nx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"targetDefaults": {
"dev": {
"dependsOn": []
},
"build": {
"cache": true,
"dependsOn": ["^build"],
"outputs": ["{projectRoot}/dist"]
},
"lint": {
"dependsOn": ["^build", "^lint"],
"cache": true
},
"preview": {
"dependsOn": []
},
"typecheck": {
"dependsOn": ["^build", "^typecheck"],
"cache": true
},
"typecheck:node": {
"dependsOn": []
},
"typecheck:web": {
"dependsOn": []
},
"start": {
"dependsOn": []
},
"build:unpack": {
"dependsOn": []
},
"build:win": {
"dependsOn": []
},
"build:mac": {
"dependsOn": []
},
"build:linux": {
"dependsOn": []
},
"dist": {
"dependsOn": []
},
"storybook": {
"dependsOn": []
},
"build-storybook": {
"dependsOn": []
}
}
}
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -6,12 +6,12 @@
"packages/*"
],
"nohoist": [
"**electron**",
"**vite**"
"**electron**"
]
},
"scripts": {
"dev": "lerna run dev --parallel",
"dev": "lerna run dev --parallel --scope @hedron/engine --scope @hedron/desktop",
"dev:clock": "lerna run dev --parallel --scope @hedron/clock --scope @hedron/clock-test-app",
"storybook": "lerna run storybook --scope @hedron/desktop",
"build:engine": "lerna run build --scope @hedron/engine",
"build": "lerna run build",
2 changes: 2 additions & 0 deletions packages/clock-test-app/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
dist
13 changes: 13 additions & 0 deletions packages/clock-test-app/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module.exports = {
extends: '../../.eslintrc.cjs',
settings: {
'import/resolver': {
typescript: {
project: ['./tsconfig.app.json', './tsconfig.node.json'],
},
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
},
},
}
24 changes: 24 additions & 0 deletions packages/clock-test-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Clock Test App

This is a test app for the Hedron Clock module. A good place to look for example usage of the clock module.

## Develop

`yarn run dev:clock`

Get both the test app and the clock module building (and watching) with this command, run from **the repo root**, not this package directory.

## Tips for testing with external clock

You can use Pro Tools to generate an external clock value. Using the Pro Tools project in this repo, you can even have the clock speed up and slow down to really put it through its paces! 😈

1. Download [Pro Tools Intro](https://www.avid.com/pro-tools/intro) (it's free and enough for testing needs)

- ⚠️ **WARNING** ⚠️ If you are using a new Mac with Apple Silicon (e.g. M1), **run Pro Tools with Rosetta enabled**.
It will appear to work fine without doing this, but you will likely have MIDI related issues causing the clock to do all sorts of weird stuff.

2. Make sure you have some way of routing internal MIDI. (e.g. "Audio MIDI Setup" on OSX)
3. Open `protools-example/midi-clock-test.ptx`
4. Run `yarn run dev:clock` from the root of the repo
5. Follow the URL in the terminal to open up the test app (e.g. http://localhost:5173/)
6. In Pro Tools, press play (spacebar). It will start a metronome sound and a clock signal, that will speed up and slow down. You can visually see if it all seems in sync in the Clock Test App.
12 changes: 12 additions & 0 deletions packages/clock-test-app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Hedron Clock Test App</title>
</head>
<body>
<main class="container" id="root"></main>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
26 changes: 26 additions & 0 deletions packages/clock-test-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@hedron/clock-test-app",
"version": "1.0.0-alpha.1",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
"preview": "vite preview"
},
"dependencies": {
"@hedron/clock": "^1.0.0-alpha.1",
"@picocss/pico": "^2.0.6",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^4.3.4",
"globals": "^15.14.0",
"typescript": "~5.6.2",
"vite": "^6.0.5"
}
}
6 changes: 6 additions & 0 deletions packages/clock-test-app/protools-example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Ignore everything:
*

# Except:
!.gitignore
!midi-clock-test.ptx
Binary file not shown.
156 changes: 156 additions & 0 deletions packages/clock-test-app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { FormEvent, useEffect, useRef } from 'react'
import { Clock } from '@hedron/clock'
import './custom.css'
import { MidiClockListener } from './MidiClockListener'

const DEFAULT_BPM = 120

// arena height - pip height (see custom.css)
const H = 300 - 4

const TAU = Math.PI * 2

const $text = (id: string, text: string | number) => {
document.querySelector<HTMLDivElement>(`#${id}`)!.textContent = text.toString()
}

const $y = (id: string, y: number) =>
(document.querySelector<HTMLDivElement>(`#${id}`)!.style.transform = `translateY(${y}px)`)

let shouldCirclePulse = true

function App() {
const clockRef = useRef<Clock>()

useEffect(() => {
clockRef.current = new Clock(DEFAULT_BPM)

let raf = 0

const midiClockListener = new MidiClockListener({
onPulse: clockRef.current.sendTimingPulse,
onStart: clockRef.current.startOnNextTimingPulse,
onStop: () => {
clockRef.current!.stop()
},
onContinue: () => {
clockRef.current!.continueOnNextTimingPulse()
},
})

const update = () => {
const d = Math.round(clockRef.current!.beatDelta * 1000) / 1000

if (clockRef.current!.beatCount === 1 && shouldCirclePulse) {
shouldCirclePulse = false
const circle = document.querySelector<HTMLDivElement>('.circle')!

circle.classList.add('white')
requestAnimationFrame(() => {
circle.classList.remove('white')
})
}
if (clockRef.current!.beatCount === 2) {
shouldCirclePulse = true
}

$text('delta', d)
$text('smoothedBpm', clockRef.current!.smoothedBpm)
$text('bpm', Math.round(clockRef.current!.bpm * 100) / 100)
$text('beat', clockRef.current!.beatCount)
$text('beatPulseOffset', Math.round(clockRef.current!.beatPulseOffset * 10000) / 10000)

$y('saw', (d * H) % H)
$y('sin', Math.sin(d * TAU) * H * 0.5 + H * 0.5)
$y('sinBar', Math.sin((d * TAU) / 4) * H * 0.5 + H * 0.5)
$y('square', Math.floor((d % 1) * 2) * H) // TODO: feels off...
$y('triangle', Math.abs((d % 1) * 2 - 1) * H)

raf = requestAnimationFrame(update)
}

raf = requestAnimationFrame(update)

return () => {
midiClockListener.clearMidiEventListeners()
cancelAnimationFrame(raf)
}
}, [])

const onBpmSubmit = (e: FormEvent) => {
e.preventDefault()
clockRef.current!.bpm = Number(document.querySelector<HTMLInputElement>('#bpmField')!.value)
}

const onStartClick = () => {
clockRef.current?.start()
}

const onStopClick = () => {
clockRef.current?.stop()
}

const onResetClick = () => {
clockRef.current?.reset()
}

const onTempoTapClick = () => {
clockRef.current?.sendTempoTap()
}

return (
<>
<section>
<div className="grid">
<button onClick={onStartClick}>start</button>
<button onClick={onStopClick}>stop</button>
<button onClick={onResetClick}>reset</button>
<button onClick={onTempoTapClick}>tap</button>
</div>
</section>
<section>
<div className="arena grid">
<div id="saw" className="pip"></div>
<div id="sin" className="pip"></div>
<div id="sinBar" className="pip"></div>
<div id="square" className="pip"></div>
<div id="triangle" className="pip"></div>
</div>
</section>

<section className="grid">
<div>
<div>Current Beat</div>
<h3 id="beat"></h3>
</div>
<div>
<div>Current BPM</div>
<h3 id="smoothedBpm"></h3>
</div>
<div>
<label>Set BPM</label>
<form className="grid" onSubmit={onBpmSubmit}>
<div>
<input id="bpmField" type="number"></input>
</div>
<div>
<button type="submit">submit</button>
</div>
</form>
</div>
</section>
<section className="grid">
<div className="circle"></div>
<code>
Delta: <span id="delta"></span>
<br />
BPM (unsmoothed): <span id="bpm"></span>
<br />
Pulse offset: <span id="beatPulseOffset"></span>
</code>
</section>
</>
)
}

export default App
Loading