GrvMkr: An Online Grid Notation / Beat Making Tool
TLDR
Spent 3 days building the MVP for a tool my mum wanted. GrvMkr is a web based grid notation creation and playback tool.
TSMD (Too short, more detail)
During my 2024-2025 career break (the 3rd one 😅), I found myself in the mountains in the USA with a bit of time on my hands. There was a huge storm, with gusts up to 70mph, so i didn’t fancy going skiing.
My mum had recently requested a replacement for a tool she used called Mango Drum. Its an old piece of software (check out that ancient ui!) which allows you to create percussion grid notation, play the rhythms and share them too.
My mum Beth de Lange is a community musician and does a fair amount of drumming in schools and in the community and having sensibly upgraded to a mac could no longer use MangoDrum.
Improvements
Since mango drum is only available on windows, the main improvement i wanted to make was to make it available to all and for free.
Thats kinda it.
Tech stack
I’m not a web developer, although i have done a bit here and there. Internal tooling and basic static websites mainly.
At my last role at Carv i contributed to a new internal web tool that analysed your skiing day geographically. I found this really fun, partly because i got to play with maps, but partly because the framework i was using was quite nice. Svelte was recommended by the ‘web guy’ there along with DaisyUI (built on top of tailwind css) for the UI components.
I decided to use this same tech stack, because it seemed to work for me last time.
Its deployed to GitHub pages as a static site. Svelte can do client server sites, but i didn’t want or need that.
Its truly been a joy to work on something that is available in prod within 30 seconds of you pushing to master.
Challenges and downsides
Music Maths
I wouldn’t really call myself a musician anymore. I used to play the trumpet, and drum kit. But never really cared for the theory side of music. This really bit me when it came to figuring out how to implement time signatures.
Given a BPM, lets say 60 for simplicity, how do i calculate how long to wait until playing the next column of grid notation? Yes, i asked ChatGPT. I need the time in ms, so given 1000ms = 1 second
, 60,000ms = 1 minute
. Divide that by the BPM to get the number of ms per beat. At 60bpm thats 1,000ms. Even my stupid brain can work that out.
Now, i got a bit stuck on the next bit, not really understanding how time signatures worked. I assumed that in the 3/4 time signature, the 3 meant each beat is split into 3 notes, and the 4 meant there were 4 beats in the bar. Turns out thats not how it works 🙈 I spoke to mum and she put me right, after many stupid questions. Turns out the 3 means there’s 3 beats to the bar, and the 4 denotes the type of note, specifically the denominator in the fraction of the note. So 4 is a crotchet (1/4th note), 8 is a quaver (1/8th note, and not the crisp sadly)
State Management and Reactivity
I’m used to unidirectional state management patterns, where the UI fires an event, the view model or state store handles the event, mutates the state, and dispatches a new state change, which the UI reacts to a re-renders automatically.
I honestly didn’t put much thought into the state architecture of this app, given i thought i’d be relatively simple and small. I wanted to try and utilise the tools Svelte gave me to achieve everything without bringing in external dependencies.
This meant relying heavily on the $state()
and $derived()
runes.
In trying to extract code from the .svelte
ui files i needed to move the state out too. Turns out you can use svelte runes in normal .ts
files, as long as they’re actually .svelte.ts
files.
However, a Map
stored in $state(new Map())
is
only reactive to re-assignments, so updating a value within the map will not trigger any dependents of the state. I wish this pr was merged when i was looking into this. It explains it nicely. There’s more discussion here.
There is a SvelteMap
from the svelte reactivity package that are reactive to adding / removing from the map. However, they are not reactive to object values within the map changing.
To enable this, i needed to wrap the things i was storing in the map in $state()
- which felt odd to me personally.
let map = SvelteMap();
let entry = $state({someNowReactiveValue: 0});
m.set(1, entry);
This culminated in this commmit which removes the workaround i had used earlier which basically replaced the entire map with a new one.
Final thoughts
This was a fun little project to work on, and i have enjoyed diving back into some web dev after 4 years of mobile.
I’ll likely continue tinkering with this project off and on to keep things interesting and procrastinate writing UI tests for Location Alam (which is realistically why i started this project 🙈)
Comments