DEV Community

Leo Hunter
Leo Hunter

Posted on AI-assisted

Building Melody Creator: a Browser-Based Music Sketchpad

Melody Creator is a small browser-based music sketchpad, but the interesting part is not the UI alone. You can try the live tool at melodycreator.app. Under the surface, the app treats melody writing as a set of ordinary engineering problems: model the musical idea, keep generation deterministic, preview sound in the browser, export useful files, and make the result shareable without requiring an account.

The project is built with Next.js App Router, React, TypeScript, Tailwind CSS, and a few music-focused libraries such as tonal and midi-writer-js. The goal is not to generate a finished song. It is to help someone create a short musical idea, hear it quickly, and move it into a DAW as MIDI or audio.

The Core Abstraction: A Composition Model

The most useful decision was to avoid tying the musical state directly to the UI. A grid cell is great for editing, but export code should not need to know about buttons, selected cells, or React state. So Melody Creator uses a small composition model that can be produced from different sources and consumed by different renderers.

At a simplified level, the model looks like this:

type Composition = {
  settings: CompositionSettings;
  notes: CompositionNote[];
  chords: CompositionChord[];
};
Enter fullscreen mode Exit fullscreen mode

A note has a pitch, start step, duration, and velocity. A chord has a name, a list of pitches, a start step, and a duration. That gives the app one shared representation for browser playback, MIDI export, WAV rendering, previews, tests, and URL state.

This also keeps the tool flexible. A composition can come from manual grid editing, a preset, a seeded generator, or a decoded share link. Once the app has a Composition, the rest of the pipeline can work the same way.

Turning a Melody Grid Into Music

The editor starts with a grid because it is a familiar mental model: rows are pitches, columns are time. In the simple workspace, the app uses a 16-step grid. In the Pro workspace, the number of steps depends on bars and note duration.

The grid itself stays intentionally plain:

type MelodyGrid = {
  rows: number;
  steps: number;
  activeCells: MelodyCell[];
};
Enter fullscreen mode Exit fullscreen mode

The music theory enters when the app maps rows to pitch names. Instead of hard-coding C major forever, it uses tonal to get scale notes for keys such as C, D, Bb, or Ab and scales such as Major, Minor, Pentatonic Major, Pentatonic Minor, and Blues.

That means the same row and step structure can become a different musical phrase when the user changes key or scale. A cell might map to C4 in one context and G4 or Bb4 in another. The UI remains simple, while the music layer handles the translation.

Deterministic Generation Without an AI Model

One design choice I like in Melody Creator is that the current generator is not an AI API call. It is local, predictable pattern logic. That makes it easier to test, easier to explain, and faster to run in the browser.

The generator uses a seeded pseudo-random function, scale degrees, chord degrees, rests, arpeggio patterns, and octave spread. For example, the basic harmonic path follows a repeating degree pattern:

const CHORD_DEGREES = [0, 4, 5, 3];
Enter fullscreen mode Exit fullscreen mode

From there, each step can choose a phrase offset, optionally follow an arpeggio pattern, skip a note if a rest style asks for silence, and produce a pitch inside the selected scale. Because generation is seeded, the same settings produce the same melody. That is important for tests and for a creative tool where "try again" should feel controlled rather than random in a confusing way.

The Pro workspace adds a few more musical controls:

  • Rest styles such as sparse, syncopated, and call-and-response.
  • Arpeggio patterns such as up, down, and up-and-down.
  • Octave ranges, including a wide range that builds more rows.
  • Chord styles such as blocked chords, rolled chords, rolled-only chords, and broken chords.

These controls are small, but they make the generator feel less like a black box. The user is still shaping the musical behavior.

Browser Playback With SoundFont Samples

For playback, Melody Creator uses the Web Audio API and sampled instruments. The app loads SoundFont MP3 samples, caches decoded buffers, and finds the nearest sample for each requested pitch.

The pitch shifting is based on MIDI note distance:

playbackRate: 2 ** ((targetMidi - sampleMidi) / 12)
Enter fullscreen mode Exit fullscreen mode

That one line is doing a lot of practical work. Instead of loading a separate recording for every possible note, the app can load a set of nearby samples and adjust playback rate to reach the target pitch.

The playback layer also supports stacked melody notes and chords. Notes with the same start step are grouped, so a user can create simple harmonies rather than being limited to one note at a time. During live playback, the UI can highlight the current column and the sounding cells because playback still maps back to grid steps.

Exporting MIDI and WAV From the Same Composition

MIDI export and WAV export solve different problems, so Melody Creator supports both.

MIDI is the editable format. A user can download a .mid file and open it in a DAW to change the instrument, edit notes, quantize, arrange, or continue producing the idea.

WAV is the listening format. It is useful when the user wants to hear or share the current sound without opening music production software.

Both formats start from the same Composition. The MIDI exporter turns grouped note starts into midi-writer-js note events and preserves empty steps as waits. This matters because silence is part of the rhythm. If a note starts on step 3, the exported MIDI needs to represent the gap before it.

For WAV, the app renders audio offline. It builds timed render events from notes and chords, schedules the samples into an OfflineAudioContext, renders the result, and then encodes the final AudioBuffer as PCM WAV. The encoder writes the RIFF/WAVE headers manually, which keeps the export path explicit and testable.

The nice part is that the app does not need two separate musical systems. MIDI and WAV are two output adapters around one composition model.

Shareable State Without Accounts

One small feature with surprisingly important engineering details is sharing. Melody Creator can encode a melody state into a URL, so the recipient can open the same idea without a database record or user account.

The app does this with a versioned transfer state. For URL size, the encoded form uses short keys:

{
  v: 1,
  g: { r: rows, s: steps, c: cells },
  k: key,
  m: scale,
  t: tempo,
  h: includeChords
}
Enter fullscreen mode Exit fullscreen mode

That object is serialized as JSON and encoded with Base64URL. Decoding is deliberately defensive. The app validates row counts, step counts, scale names, note durations, booleans, chord degrees, instrument IDs, and individual cells before accepting the state.

Invalid state returns null instead of crashing the page. That is a small implementation detail, but it is the kind of detail that makes public sharing safer. Links can be modified, copied incorrectly, or generated by old versions of the app, so the decoder has to be skeptical.

Testing the Musical Edge Cases

The tests focus on places where music logic can quietly break:

  • The same seed and settings should generate the same composition.
  • Generated notes should stay inside the selected scale.
  • MIDI export should preserve rests as waits.
  • Notes on the same step should be grouped into one MIDI event.
  • WAV export should produce a valid RIFF/WAVE payload.
  • URL state should round-trip without losing settings.
  • Invalid URL state should be ignored safely.
  • Pro settings such as note duration, bars, humanize, swing, rests, arpeggios, and chord style should not corrupt the editable grid.

This is where TypeScript helps, but types are not enough. A type can say that a note has a start value. A test can prove that changing from eighth notes to sixteenth notes still produces the expected loop duration.

For a music tool, those details matter. The app can look fine while timing, rests, or exports are wrong. Tests make the invisible parts less fragile.

What I Learned Building a Music Tool for the Browser

The biggest lesson is that music apps become easier to build when the UI is not the source of truth. The grid is an editor. The composition is the product. Playback, MIDI, WAV, sharing, presets, and tests all become cleaner once they read from the same structure.

The second lesson is that deterministic logic can be a feature. Not every creative tool needs to start with an AI model. For a melody sketchpad, simple controls such as scale, chord progression, rest style, arpeggio contour, and octave range give users enough direction while keeping the result understandable.

The third lesson is that export support changes how useful a toy project feels. A browser melody grid is fun. A browser melody grid that can export MIDI and WAV becomes part of a real workflow.

FAQ

Is Melody Creator an AI music generator?

The current implementation does not call an AI model. It uses presets, manual grid editing, seeded generation, music theory rules, and browser audio.

Why use both MIDI and WAV export?

MIDI is better for editing in a DAW because it preserves notes and timing. WAV is better for quick listening and sharing because it contains rendered audio.

Why encode melody state in the URL?

URL state makes sharing lightweight. A melody can be opened on another device or sent to someone else without requiring login, server storage, or a saved project database.

What was the hardest technical part?

The tricky part is keeping one musical idea consistent across many surfaces: editable grid, generated composition, live playback, share links, MIDI export, WAV rendering, and tests.

Conclusion

Melody Creator is a browser-based music sketchpad, but the engineering pattern behind it applies to many creative tools. Keep the domain model separate from the UI, make generation deterministic when possible, validate shared state carefully, and treat export formats as adapters rather than separate products.

For this project, that meant a small TypeScript composition model became the center of the app. Everything else, from Web Audio playback to MIDI export, became easier to reason about.

Top comments (0)