TakuikaNinja's Disk Drive

Files from a Famicom Disk System fan

View on GitHub
22 June 2026

Tambo, the Dumbo Sound Driver

by TakuikaNinja

Why I’ve been writing my own NES/FC sound driver.

The driver/replayer source and demo songs can be found here.

FamiTracker isn’t Game-Ready

Let’s not beat around the bush here. It’s fairly well-known in the homebrew scene that FamiTracker’s current sound driver is rather bloated and inefficient for use in game contexts (to the point where it is one reason behind a driver rewrite). With the exception of MML-driven sound drivers such as NSD.Lib and Pently, most homebrew sound drivers limit themselves to a particular subset of FT features (such as removing effect column support) and tack on a sound effect system.

Take GGSound and Sabre. These two primarily operate by using macros stored in FT instruments - these are essentially repeatable sequences of volume levels, duty cycle/noise mode values, and note/pitch offsets.

One thing I couldn’t help but notice with these sound drivers is that they often present software-driven sound design as the only choice. Volume envelopes? Process them in software. Arpeggios and pitch bends? Process them in software. Do this for up to 4 channels (note: FT doesn’t support instrument macros for DPCM), and the execution time adds up quickly. Use too many instruments with long macros, and the song size grows like crazy.

Because these sound drivers often provide basic means of converting sound data from FT text exports, they also generally lack support for optimisations such as finite loops (e.g. repeat a sequence of patterns X times) and note transposition out of the box.

I think it’s about time we explored other approaches instead of clinging to paradigms laid out by existing music tooling.

Hardware-driven Sound Design

So if software-driven sound design exists, then that implies the existence of hardware-driven sound design, right? What would that sound like? Well, I’ll just point to the early era of first-party titles for some examples:

Gyromite

Wrecking Crew

Gumshoe

The benefits of this style are as follows:

The limitations are as follows:

Of course, I can’t talk about limited sound drivers without bringing up the soundtrack from the ROM hack called Layla - The Iris Missions:

SupperTails66 did an excellent job wrangling a sound driver which (I think) only has basic software fade-out envelopes and arp macros.

Working on Tambo

I trialled this hardware-driven sound design in “APU Dance” using FamiTracker as a way of getting out of a chiptune hiatus:

Yeah, all those “arps” and pitch bends are done with the hardware sweeps.

The satisfaction I had after finishing that song motivated me to write my own sound driver tailored to this style of sound design.

It’s called Tambo (田んぼ, Japanese for rice field) because:

So what makes this a “dumbo” sound driver, then? Well, have a glance at the note data format:

apu_dance_pulse2_pattern0:
	.byte 2, $80, $81, C3, $07 << 3
	.byte 2, $16, $b1, AS3, $09 << 3
	.byte 2, $80, $81, C3, $07 << 3
	.byte 1, $16, $b1, AS3, $09 << 3
	.byte 1, $16, $b1, C4, $09 << 3
	.byte 2, $80, $81, C3, $07 << 3
	.byte 2, $16, $b1, C4, $09 << 3
	.byte 2, $80, $81, C3, $07 << 3
	.byte 2, $16, $b1, C4, $09 << 3

	.byte 2, $80, $81, C3, $07 << 3
	.byte 2, $16, $b1, GS3, $09 << 3
	.byte 2, $80, $81, C3, $07 << 3
	.byte 1, $16, $b1, GS3, $09 << 3
	.byte 1, $16, $b1, GS3, $09 << 3
	.byte 2, $80, $81, C3, $07 << 3
	.byte 2, $16, $b1, GS3, $09 << 3
	.byte 2, $80, $81, C3, $07 << 3
	.byte 2, $16, $c1, FS3, $09 << 3
	.byte 0

Each “row” is formatted as follows:

There are some minor differences between channels but the key thing here is that this format is barely above the level of a compressed register log - there’s no notion of instruments at all here. All the driver has to do to process a new note for each channel is: read 5 bytes, perform a note lookup if necessary, then write the register values to the APU. SFX follow a very similar format, with the only difference being that they can pick a channel to play on.

An obvious problem with this format is the data size - 5 bytes for every note adds up quickly. I implemented the following commands to help composers address this:

The way these commands are distinguished from regular pattern addresses is simple: commands use addresses lower than $8000 (start of PRG-ROM in most mappers) and use the low byte as an ID. Some commands use the high byte of the address as a parameter, while jumps use an additional address to specify the jump target. Here’s an example for the triangle channel which uses everything except “end song”:

apu_dance_triangle:
	.word apu_dance_blank_pattern
	
	.word CMD::SET_LOOP1 | (12 << 8)
apu_dance_triangle_A:
	.word apu_dance_triangle_pattern0
	.word apu_dance_triangle_pattern1
	.word CMD::LOOP_JUMP1, apu_dance_triangle_A
	
	.word apu_dance_triangle_pattern0
	.word apu_dance_triangle_pattern2
	
	.word CMD::TRANSPOSE | (4 << 8) ; +4 semitones
	.word CMD::SET_LOOP1 | (1 << 8)
apu_dance_triangle_B:
	.word CMD::TRANSPOSE | ((128 - 2) << 8) ; -2 semitones
	.word CMD::SET_LOOP2 | (3 << 8)
@inner:
	.word apu_dance_triangle_pattern0
	.word apu_dance_triangle_pattern1
	.word CMD::LOOP_JUMP2, @inner
	.word CMD::LOOP_JUMP1, apu_dance_triangle_B
	
	.word CMD::JUMP, apu_dance_triangle

The above lets me get away with only defining 3 unique patterns for the triangle channel. Nifty, isn’t it?

Tambo also has the following features:

Audio Examples

I’ve been posting some audio snippets on Mastodon:

Post by @TakuikaNinja@oldbytes.space
View on Mastodon
Post by @TakuikaNinja@oldbytes.space
View on Mastodon

(The above is a port of “Hi-Score Party”)

What’s Next

I’m planning on doing the following for Tambo:

I’ll obviously be doing other things alongside this long-term project and writing about them, so look forward to those.

tags: NES - Famicom - music - chiptune - devlog - 6502 - assembly - Tambo