Music Workshop Guide

The Music Workshop is Crusty Studio's portable music editor. It is a four-lane tracker that writes songs against the generic CrustyBasic sound API rather than one machine's sound chip, so the same song can be built for every target Crusty Studio supports.

That portability is the point of it, and it is also its limit. The portable player carries notes, lanes, waveform shapes, and volumes; it does not reach into chip registers. When you want SID registers, filter design, or chip-model-specific composition, use the SID Music Workshop instead — see Help → SID Music Guide.

Before you begin

  1. Open any project. The Music Workshop is offered for every target.
  2. Choose Tools → Music Workshop.
  3. Create a new song or open an existing .crustymusic.json project from the Explorer.
  4. Save regularly. Save writes into the project's music/ folder.

A song is data plus a player. Saving the project stores the tune; Export player include writes the MUSIC_* routine your game includes and calls.

The editor at a glance

Step by step

1. Set the tempo and feel

Set BPM, Speed, and Rows per beat before entering notes. Speed is the number of ticks per row, so it changes how much time each row has; rows per beat changes where the beat markers fall and therefore how the grid reads rhythmically.

2. Build the instruments

Create one instrument per job rather than one per note: a bass, a lead, a chord or pulse part, and a noise hit is the usual starting set. Each instrument has:

Use the instrument's preview to audition the patch, switch the loop on, and tune it by ear before writing a part with it.

3. Enter the notes

Type a note into a cell and set its instrument number. Blank clears the cell, and OFF stops a held note. The effect column takes a three-character code from the list below.

4. Arrange the song

Add patterns, copy one as a starting point for the next, and put them in the song order. Short, reused patterns are easier to keep musical than one long block.

5. Check the mix

The Mix tab warns when the arrangement asks for more voices than the target has. A portable song can use four lanes, but targets with fewer channels need channel sharing or muting: a two-channel machine has to alternate or drop lanes. Plan which lanes the target can actually play before the arrangement grows.

6. Preview

Press play to hear the song through system audio. The transport shows a pause mark while a song plays and a play mark while it is held: pausing freezes the playhead on the row it has reached, resuming continues from there, and Stop ends playback and releases the mix. The preview is a software approximation, so treat it as a sketch, not as the target's sound: pulses are played as square waves, the noise lane previews as a sawtooth, ADSR is rendered as gain ramps, and the effect column is not heard at all. It is good enough to check the tune, the arrangement, and the instrument balance. An Amiga song imported from a module is previewed with its own samples and dynamics, because that is the one case where the instruments are the music.

The song is mixed in JavaScript and the finished mix is encoded to a WAV, which plays through an audio element — not streamed through Web Audio, because WebKitGTK's realtime Web Audio cannot be played through on Linux and a streaming preview arrives with dropouts. Mixing costs roughly a quarter of a second for a six-minute module, and the status line counts the percentage while it works. Editing anything invalidates the mix, so the next play pays for it again; replaying an unedited song is instant.

7. Save and export

Save writes <name>.crustymusic.json into music/. Export player include writes a .cbi into src/ — by default named after the song with a -player suffix — using the same include-name and overwrite rules as the other workshops. The generated player contains:

Include the file from your game source, call MUSIC_INIT during setup, and call MUSIC_TICK once per frame. On the Amiga the player paces itself from the machine's own 50 Hz clock, so the song's tempo does not depend on how often the display refreshes. Notes are emitted as NOTE_<n><octave> names and shapes as SOUND_SHAPE_TRIANGLE, SOUND_SHAPE_PULSE, SOUND_SHAPE_SAW, or SOUND_SHAPE_NOISE, which the target's CrustyBasic runtime resolves for its own hardware.

An Atari 7800 project exports a player of the same shape with the same entry points, but its tables carry a folded voice and a note kind instead of a shape, because that machine plays PLAY_NOTE rather than PLAY_NOTE_SHAPE. An Amiga project exports a Paula player whose events carry a voice and a level and whose notes are periods rather than shapes. See The Atari 7800 player and The Amiga player below.

What reaches the player matters. Only the note, the lane, the waveform shape, and the volume are exported per event. The instrument's ADSR, gate length, duty, arpeggio, vibrato, and tremolo shape the preview and record your intent, but they are not written into the player, and the effect column is not exported at all. If a sound depends on an envelope or an effect, either bake that into the arrangement or plan to reproduce it in code.

Effects you can write in a row

Code Meaning
Axy Arpeggio between semitone offsets
Vxy Vibrato depth and speed
Uxx / Dxx Pitch slide up or down
Txx Tremolo amount and speed
Wxy Waveform change

The effect column is an authoring aid and a written record of intent in this workshop. It shapes the preview only in the sense that the instrument settings it refers to also shape the preview; the exported player does not read it.

Import

Source What happens
.crustymusic.json The native project format. Tempo, speed, rows per beat, and the pattern shape are restored.
MIDI (.mid, .midi) Notes are quantised to sixteenth notes and at most 64 patterns are built. Lossy: track identity is discarded, notes are sorted into lanes by pitch, every instrument is replaced by one of four fixed patches, notes are clamped to a playable range, and looping is switched off. Only the 64-pattern clip is reported.
FamiTracker text (.txt) 2A03 pulse, triangle, and noise material is converted into portable lanes. Duty, a short arpeggio, and a pitch macro survive; other effects are mapped or dropped, and DPCM and expansion-chip data are discarded. The workshop's status message lists what needed approximating.
FamiTracker module (.ftm) FamiTracker's own saved module, read directly rather than through an export. It keeps the same tracker-level data as the text export — notes, instruments, macros, order list, speed and tempo — and reports the same approximations, so a module can be imported without opening FamiTracker first. Files up to FamiTracker 0.4.6 are read; a module saved by a 0.5.0 beta or by Dn-FamiTracker is refused by name, because its format differs. A .fmt file is accepted too, since that is what the extension is often written as; the file's contents decide.
Sample file (.wav, .8svx, .svx) An Amiga instrument: the recording becomes a sample in the song's own bank plus the patch that plays it. PCM 8/16/24/32-bit and 32-bit float, mono or multi-channel; IFF 8SVX is the Amiga's own format. An 8-bit file is imported bit for bit; a wider one is converted to 8-bit and normalised if it is quiet. See below.
ProTracker module (.mod) A four-channel Amiga module, read directly — notes, order list, per-channel instruments, speed and tempo, and the samples themselves, which become the song's Amiga instruments so an exported player replays the module's own recordings through Paula's DMA. The effects that mean the same thing here (0xy arpeggio, 1xx/2xx portamento, 4xy vibrato, Fxx speed/tempo, EC0 note cut) come across; everything else is counted and named in the report. See below.

Songs are cut into patterns of the editor's length (32 rows) rather than FamiTracker's frame length, so a module whose frames are 64 rows long imports as a longer order of 32-row patterns. That is the same shape the MIDI import produces, and it is what the native project format can reload without trimming.

ProTracker modules

A .mod is the format the Amiga was invented for — the tracker, the four hardware channels and the samples are one design — and the Music Workshop has exactly four lanes because Paula has exactly four voices, so nothing has to be folded or shared on the way in. Import one with Import → ProTracker module, opening a .mod from the Explorer, or dropping one on the window.

Samples as instruments

An Amiga instrument is a recording: Paula has no oscillator, so it replays bytes. That is why a module brings its own samples, and it is also why a sound file on its own is a complete instrument here. Import → Sample as instrument adds one to the song on an Amiga project: the bytes become a sample in the same bank a module's samples go into, and a new patch points at it. Nothing else changes — the exported player, the preview and the mixer are the ones that already exist, so an imported drum plays in a song exactly like a module's drum does.

Two things decide how a sample behaves.

Its rate sets its pitch. A recording was sampled at some rate, and Paula fetches bytes at clock / period, so hearing it as it was recorded means writing the period that rate asks for. The tracker's convention is that middle C is period 428 — 8287 Hz — and an imported file keeps that relationship: it plays at its recorded speed on middle C and transposes from there, like any tracker instrument. The status line reports the rate it was imported at and the note above which it can no longer be transposed, because a fast recording has little room before Paula's fastest period is reached.

Paula replays at most 65,534 bytes in one pass — the length argument is a 16-bit value. A five-second 22 kHz sample is 110 KB and cannot be played as one instrument, so the import resamples it down until it fits and says so. If fitting it would mean throwing away most of the sound, the import refuses and says the sample needs trimming. For a game, short is normal: a hit is a fraction of a second, and a sustained sound is a short loop.

An imported sample arrives as a one-shot, because most files are hits: it plays once and the player cuts the voice, since Paula's DMA would otherwise repeat it for ever. Choose Loop the whole sample in the instrument panel to keep a sustained sound going. A loop runs from the start of the sample, attack included, so it suits a pad or a bass rather than a drum. The instrument panel shows an imported recording as what it is and will not let the waveform designer reshape it — a recording is not one cycle — but New waveform designs a shape alongside it and leaves the recording in the song.

A module dropped into a project is stored in music/ and stays in the Music group, so it can be opened again later — double-click it, and the song comes back into the Workshop. What opens is the converted song, not the module: it has no file of its own yet, so Save asks for a name, offers <song>.crustymusic.json, and writes a native project beside the module. The module itself is never modified, and both files stay in the project together. A dropped module replaces whatever is on screen; dropping the same file again reloads it. If a module cannot be read, the reason appears above the editor — the file you already had stays put, so a silent drop would look like nothing happened.

The signature at offset 1080 is what says which tracker wrote the module and how many channels it has, and this importer reads the ids the Amiga trackers used — M.K., M!K!, N.T., FLT4/FLT8, EXO4/EXO8, TDZ1–TDZ3, FA04/FA08, CD61/CD81, OCTA/OKTA, NSMS, WOW! — along with the nCHN and nnCN forms that carry their own channel count, so 4CHN, 9CHN and 16CN are all read. A module with fewer than four channels imports with the spare lanes empty, and one with more is reduced to its first four with the loss named.

What arrives:

Because the samples come too, an imported module sounds like the module. The Amiga player switches to its DMA form when a song carries samples:

What does not come across:

Every one of those is counted and named in the import report, so a result that is quieter than the original says which command made it so.

Two practical limits are worth knowing. Paula's DMA cannot fetch faster than a period of 124, and the top of the module's period table is faster than that, so those notes play at the highest rate the hardware will serve — the exported player counts them in its header rather than letting them be a surprise. And the whole bank is copied into chip RAM on top of the copy embedded in the program: an Amiga 500 has 512 KB, so a module with a lot of sample data wants trimming, and the import says so when the bank is large.

There is no ProTracker .mod export. The Workshop reads the format; it does not write it.

Across the platforms

Target What the Music Workshop gives you
Every target The same editor, the same four lanes, and the same portable player include.
NES Four voices available (two pulse, triangle, noise), so a four-lane arrangement usually fits.
C64, C128, Plus/4 Three SID voices, so four lanes need sharing or muting — the Mix tab warns about it. For register-level work use the SID Music Workshop instead.
CoCo One DAC voice. The portable player is the default because it is the one that currently plays a tune; a single-voice timed player is available but sounds wrong until the runtime's timed DAC path is fixed. See below.
Atari 7800 Two TIA voices. The portable player is not offered as the default here — it drives four voices and a shape-driven pitch that this machine cannot play — so the Workshop generates a dedicated two-voice TIA player instead. See below.
Amiga Four Paula DMA voices, so every lane gets one. A song carrying samples — an imported module — gets a player that replays them through the DMA; a song written by hand gets the PAULA.SOUND player, because a chip with a single square waveform has nothing for the portable shape calls to choose between. See below.
Other targets Whatever the target's runtime provides for the generic sound API. Studio does not know the channel count, so treat the Mix warning as advisory and check on the machine.

The Target API tab's preview selector changes the generated code for the CoCo, the Atari 7800 and the Amiga and the description card for every other choice. Atari 7800 · two-voice TIA player is offered only for an Atari 7800 project and is the default there; Amiga · four-voice Paula player is offered only for an Amiga project and is the default there; CoCo · single-voice player is offered only for a Color Computer project; Portable, NES, and C64 all generate the same portable include, and it is the target's CrustyBasic runtime that maps those calls onto real hardware. For a CoCo the default is the portable player. The export dialog has the same selector, so the file you write is the one you previewed.

The panel is headed with the version of the compiler Studio found, not a fixed number, and the CoCo player is gated on it: it needs CrustyBasic 1.5.0 or newer, because SOUND_TIMED_NOTE_SHAPE does not exist before that. On 1.4 the call compiles and does nothing, so an unguarded export would build and play silence — the panel says so, and the exported include carries the same warning. If Studio cannot read the compiler's version at all, it names the version as unknown and does not guess.

A song written here plays on any target that implements the API. Where a chip cannot do something the song asks for — a fourth voice on a three-voice machine, for example — the arrangement has to give something up.

The CoCo player

The CoCo has one DAC voice. Its portable PLAY_NOTE_SHAPE is a blocking ~18 ms software waveform, so four lanes firing on one row reach the speaker as four different pitches one after another. The CoCo · single-voice player export addresses that by playing one note per row and holding it with the non-blocking timed-sound API. It also needs CrustyBasic 1.5.0 or newer for SOUND_TIMED_NOTE_SHAPE; on 1.4 the call compiles and does nothing, so the export would build and play silence. The Target API tab checks the installed compiler and says so rather than letting that reach the machine.

It is not the default, and it is not yet the better-sounding choice. The CoCo runtime's timed DAC path does not produce the pitch the blocking path produces for the same note-table entry — the numbers disagree by a large factor, so a song exported with it sounds like one tone repeating instead of a tune. Until that is fixed in the runtime, the export dialog offers the portable player for a CoCo and the CoCo player is opt-in.

When the CoCo player is used it behaves as follows:

The include's header comment lists what each entry plays, as speed-tick offsets, so the thinning is auditable.

The Atari 7800 player

The 7800's sound is the TIA's: two voices, each a frequency divider, a distortion and a volume. It is the same chip as the Atari 2600's, and it has no envelope. The portable player cannot drive it, for two reasons that are worth knowing because both are silent failures rather than compile errors:

Atari 7800 · two-voice TIA player is the default for a 7800 project. It:

The include's header comment names the lane folding and how many notes a four-lane song had to drop. The Mix tab warns when the arrangement asks for more than two notes at once. A two-lane song — a lead and a bass — loses nothing.

The practical range is C4 to B6, and the tuning is as good as the chip allows: the divider is five bits wide, so in the C4–A#4 octave a note can be up to a quarter-tone away from equal temperament (G4 is the worst), and above B4 it is close. Keep a bass line in C4–B4 and a lead above B4 and the tune sits where TIA is most nearly in tune.

Volume

The 7800 player has a master level, MUSIC_VOLUME, from 0 (silent) to 15 (full), shared by both voices and 15 at start-up. MUSIC_SET_VOLUME v sets it; MUSIC_VOLUME_UP and MUSIC_VOLUME_DOWN step it by one. The level scales each event's own volume rather than replacing it, so a quiet bass stays quieter than the lead.

A change is written straight into the TIA volume registers (POKE to AUDV0/AUDV1), which leaves the frequency divider and distortion alone. That matters for two reasons: notes that are already sounding change level immediately instead of waiting for the voice's next event, and the note is not re-triggered, so sweeping the volume does not click the way calling PLAY_NOTE again would.

MUSIC_INIT deliberately leaves MUSIC_VOLUME alone, so restarting a song or moving to the next one keeps the listener's setting. Set it once at start-up, or from an options screen.

The Atari 7800 has no keyboard. Its input is two joysticks, and the emulator maps those to whatever keys its input setup uses, so a volume control binds to a joystick direction (or a console switch) rather than to a lettered key. The demo project binds up and down.

The Amiga player

Work in progress, and switched off by default. The Amiga side of this Workshop — the Paula instrument designer, the sample import and the player described below — is still being finished, so a shipped build does not offer it for an Amiga project. Turn on Amiga editors in Settings → Toolchain to use it. Creating, building and running an Amiga game is unaffected, and every other machine's side of the Workshop works as it always did.

Paula is not a synthesizer. It is four DMA channels, each replaying a buffer of signed 8-bit samples at a period the CPU writes by hand, with a separate 0-64 volume. crustyBASIC's Amiga target has two ways of driving that, and the Workshop picks between them by asking whether the song has samples:

The portable player is not the default for an Amiga, though it does build: on this machine every portable shape constant (SOUND_SHAPE_TONE, SOUND_SHAPE_PULSE, SOUND_SHAPE_TRIANGLE, SOUND_SHAPE_SAW, SOUND_SHAPE_NOISE) is the same SOUND_SHAPE_TONE, so choosing between them chooses nothing. Amiga · four-voice Paula player is the default, and it:

What the players give up, deliberately: the instrument's gate, duty, arpeggio, vibrato and tremolo, and the effect column — which never reached the portable player either. Note, lane, level and (on the DMA player) the waveform and the amplitude envelope reach the machine, which is everything Paula has to be written into.

The playable range of the square-wave player is C2 to B6 (NOTE_C2 to NOTE_B6, MIDI 36-95), which is the range the compiler's note table covers; a note outside it would have no period, so keep parts inside it. A noise instrument plays as the chip's square wave at the written note rather than as noise: Paula has no noise source built in, which is why a drum in a module is a sample rather than a waveform choice.

The player keeps time from the machine's clock, not from the display. A module's speed counts rows in ticks, and a tick is 1/50 s — what every tracker module was written against. A display frame is not a tick: the vertical blank is 50 Hz on a PAL machine and 60 on an NTSC one, so an exported player that counted MUSIC_TICK calls would play an NTSC machine a fifth too fast. It does not: the Amiga players read the runtime's free-running 50 Hz clock and run however many ticks have actually passed, so the tempo is the one the song was written at on either machine, and a slow frame spends its ticks instead of losing them. Call MUSIC_TICK once per frame and the player does the rest; if you call it less often than the ticks pass it catches up in one go, so keep it in the frame loop.

The four voices are shared with the game's sound effects, and the two players do not coordinate. An Amiga sound effect from the Sound Editor plays on whichever voice its own asset names, on its own frame tick. A song's notes own all four voices, so if the song puts a note on that voice, one of them loses: a music note on that lane restarts the DMA and stomps the effect, and while a music envelope is still moving the music writes that voice's level every frame, which fights the effect's. A note that has reached its sustain level writes nothing, so it is the least bad case. The way it was always done — and the way to do it here — is to give the voice to one of them: leave the effect's voice out of the song (mute that lane, or write no notes on it and let a OFF-free lane rest), or reserve it by keeping the song on three lanes. Nothing in the export enforces this; it is a decision you make in the arrangement.

Exporting a sampled song writes two files. The player include names its bank with @INCLUDE_FILE, which reads a file next to the source, so the Workshop writes <song>-samples.bin into src/ beside the .cbi. They belong together: copy one without the other and the build fails to find the bank. Every other music export is still a single file.

A worked example ships with the application. demos/Amiga Music Demo is Beethoven's Ode to Joy arranged as a four-part hymn, one lane per Paula voice, with its generated player include and a program that boots and loops it. Its README explains the arrangement and why the tempo is what it is, and it is the quickest way to hear what this player does before writing your own.

Designing a Paula instrument

An instrument on the Amiga is a waveform, because Paula has no oscillator to choose one from — it replays whatever bytes its voice is pointed at. So the Workshop's Instrument tab grows a designer on an Amiga project: a Paula instrument picker, and, when the patch is playing a designed shape, a drawing panel.

A cycle is short — 16, 32 or 64 bytes — and the panel draws it one column per byte. Drag up and down across it to draw. That is the whole cycle: it repeats for as long as the note sounds, so it is a shape rather than a recording, and it never runs out.

The harmonic sliders are the other way in. Each one is the amplitude of one sine in an additive sum, so a shape can be built by ear. Finishing a drag reads the sliders back from what was drawn, so the two views of the same instrument never disagree:

Sliders Shape
1st only a sine
1st, 3rd, 5th… at 1/n a square, as near as eight harmonics get
every harmonic at 1/n, alternating in sign a saw
1st, 3rd, 5th… at 1/n² a triangle

Length sets the top of the range. A cycle of L bytes at period P plays at clock / (P × L), and Paula will not fetch faster than a period of 124, so a longer cycle is richer and lower:

Cycle Highest note
16 bytes ~A6
32 bytes ~A5
64 bytes ~A4

The panel names the ceiling for each length, and the exported player counts the notes that asked for a period below 124 and were held at the hardware's fastest, so a 64-byte cycle with a tune above A4 is reported rather than quietly played flat. 32 bytes is the default and covers a normal melody.

Only the shape is the designer's business. The patch's Sustain control is the level the shape plays at, its Finetune shifts the tuning in eighths of a semitone, and unticking Sustain (loop) in the panel makes the cycle play once and stop — a blip for a hit rather than a note.

An imported module's sample is not editable here, on purpose. A recording is seconds of audio rather than one cycle, and truncating it to 32 bytes would destroy it. Choosing such an instrument shows what it is — its length, its loop, its level — and offers New waveform, which designs a shape alongside it. The recording stays in the song for the patch that came with it.

Designs live in the song, like everything else, so they survive save and re-open, play in the preview, and export with the sample bank. A patch with no sample is still playable: the exported player falls back to the compiler's two-byte square wave for that voice, and the list beside the tracker says so — it names the sample a patch plays instead of the portable waveform picker, which chooses between four constants that are all the same tone on this machine.

Amplitude envelopes

A waveform says what a note sounds like; an envelope says how it starts, holds and ends. Paula has no envelope generator, so the player does it in software — the voice's level register is rewritten every frame — and the panel under the waveform controls is where the shape is drawn: four levels and four rates, the same four-and-four Aegis Sonix put on screen.

A rate is in ticks, and a tick is 1/50 s, so one tick is 20 ms and the longest segment is 5.1 seconds. A rate of zero is not a slow ramp, it is be there now, which is what makes a percussive attack possible. Levels are on the register's own 0-64 scale. The graph above the sliders shows the shape, with the sustain point marked.

Sustain is where the note waits for its ending. With one set, the segments after it are the release: an OFF cell runs them and the player silences the voice when they finish, so a pad fades out instead of clicking. That is why every preset ends at level 0 — a release that ends at 40 is cut at 40. With None, the whole curve plays and its last level is held, which is what a pluck wants: it has already faded to nothing and needs no release.

Preset Shape
Organ Flat at full level — no envelope at all, and what every instrument was before this existed
Pluck Struck, then a long decay to silence over about 1.6 s
Perc Instant attack, gone in a fifth of a second
Swell Slow rise, held at the top, released over its last segment when the note ends
Fade One long fade to silence over 7.6 s

The envelope is a property of the instrument, like the waveform and the level, and it works with or without a sample: an envelope on a patch that has none shapes the chip's square wave. Designing one is also what switches a hand-written Amiga song from the square-wave player to the DMA player, because that is the player that can walk a level per tick — so an envelope is worth adding even to a song that uses no samples at all.

Two things are worth knowing about the correspondence with the machine. The preview gives every note one row and then releases it, where the machine holds a note until its lane's next event: a slow swell lasts longer on the Amiga than in the Workshop. And the portable A/D/S/R controls above the envelope panel are the other players' envelope; on the Amiga the envelope replaces them, and the machine never sees them.

Practical advice

Troubleshooting

The exported player sounds different from the preview

Expected. The preview applies instrument envelopes and ignores the effect column; the player carries only note, lane, shape, and volume. Re-check the part with the settings the player can actually reproduce, or move the movement into the arrangement.

The preview's noise lane sounds like a sawtooth

The portable preview has no noise generator, so it approximates the noise lane with a sawtooth. The exported player sends the noise shape to the target, which renders it properly.

A MIDI import lost my instruments and tracks

That is the documented behaviour of the MIDI import: it is a sketch importer. It keeps the notes and the rhythm, sorts them into lanes by pitch, and replaces every instrument. Expect to re-instrument the result.

The import reported that it dropped effects

The FamiTracker imports — both the text export and the saved module — convert what maps cleanly onto the portable lanes and report the rest. Read the status message: anything they could not map is listed, and some of it affects only one channel of the original.

A FamiTracker module will not import

Modules saved by FamiTracker 0.5.0 beta or by Dn-FamiTracker use a newer container than the one this importer reads, and the message says which of the two it is. Open the module in FamiTracker 0.4.6 and use File → Export Text, then import the .txt: that export has always carried the same tracker-level data. Expansion-chip and DPCM material is dropped from a module just as it is from the text export, and the status message says so rather than failing.

My song asks for more voices than the target has

The Mix tab warns when the arrangement needs more channels than are available. Disable a lane, share it between parts, or accept that a target with fewer channels cannot play every lane at once. On the CoCo the generated player already thins the arrangement to one note per row; enable a single lane when you want an exact transcription rather than the spread version.