Casio ROM Pack Analysis

Casio ROM Pack Analysis



This is a near-complete analysis and tear-down of the Casio ROM Pack code. By the end of the document, you should be able to create your own, fully playable Casio ROM files. All the examples in this document are either from RO-551 “World Songs” or RO-270 “Christmas”. RO-551 is by far the most common ROM pack so I expect it’s one many are familiar with. RO-270 is also fairly common, but has all kinds of advanced techniques and tricks, making it an excellent reference. I’ve tried to keep things generic, but you may see some of my PT-80 favoritism show through. That was my first Casio keyboard, I still have it and love it, and I did most of my testing on that keyboard before more recently acquiring others for expanded testing.

This is the “web” form of the document, which does away with the old school, fixed-width, ascii-format 80-column goodness of the text version. Alas, there will be no reading it by the phosphor glow of your 80’s Casio-contemporary IBM 5151 monitor.

There are some additional companion files to this document:

  • RO-551.bin
    The dumped ROM file, RO-551. This serves as the basis of most of the examples in this document and is referenced many times. Throw it in a Hex editor and have at it!
  • RO-551 Color Coded.rtf
    This is a color-coded map of the entire contents of RO-551, highlighting headers, indexes, songs, and tracks in different colors. Featured in the snazzy image for this very post.
  • RO-551 Analysis.txt – coming soon
    This is a top to bottom analysis of the entire RO-551 ROM pack. It won’t make a lot of sense until you’ve read this document (sorry for the zip, it doesn’t want to let me upload a .txt).
  • CasioROMDump.ino & CasioROMEmu.ino
    These are the Arduino source code to my hardware ROM dumper and emulator. Not really required reading, but the code comments can help to understand what’s going on at the hardware level.
  • Casio ROM Index Generator.xlsx
    A spreadsheet that lets you plug in byte addresses (in decimal) of songs and generates the ROM code for file and song headers.
  • Casio ROM Analysis.txt
    The older text form of this doc. Formatted for 80 columns, ’cause that’s how I roll. Note: this version is frozen, I won’t be updating it if I update this page (sorry for the zip, it doesn’t want to let me upload a .txt).
  • RO-551_PT-50.aac & RO-270_PT-50.aac
    Audio recordings of these two ROMs as played on my PT-50

Numbers & Bits

The ROM packs used on the Casio pianos have a 4-bit data bus and the data stored in them is addressed in 4-bit words rather than standard 8-bit bytes. This means any address pointers in code will actually be twice what you’d see in a file in a hex editor. Aside from this, though, the ROM may be thought of as representing 8-bit data. The pianos almost always read pairs of words as bytes and never a single word. See the comments in my ROM Emulator code for more details if you’re interested. This document will always refer to Byte addresses and will always translate any word addresses in code into Byte addresses (see example below), the same as you’d see for a ROM file in a hex editor.

I will use $ to indicate hex values when necessary, and also to indicate memory addresses. In the many code blocks and examples you’ll see in this document, the hex address (byte address in the ROM file) will be listed on the left as $xxxx. A good portion of the code does not directly correspond to a hexadecimal number, so I usually don’t bother.

Most numbers in the code are represented as one byte (8 bits), but address pointers, when present, are always assumed to be 16 bits, little-endian. Because it’s little-endian, you’ll need to swap the bytes to get the actual address–and then divide it by two to convert from a word address to a byte address.

Example: Address Pointer in header of RO-551

 $0000	A50000D02F000400 281C003400003406 00280E000415002E 1C0000540000100A

D02F = $2FD0
$2FD0 / 2 = $17E8 = 6120 decimal

The highlighted code is an address pointer--in this case to the file footer, 
located 24 bytes from the end of the 6 kB file.

Handy Hex-to-Dec converter. Pro-tip: if you’re calculating addresses, deleting 0 off the end of a binary number divides by 2.

Song & Track Indexes

A ROM consists of a header, several songs, some padding, and a footer. Each song has a header, two instrument tracks, and a chord/rhythm track (the songs themselves have no footers). The tracks each have brief openings and closings. The instrument tracks, Tracks 1 & 2, each play one note at a time, and the Rhythm Track (which I tend to call “Track R”) covers the percussion beats and chords.

All data in Tracks 1 & 2 is in three-byte blocks, which can include meta-data, commands, notes, and rests. All data in Track R is in 2-byte blocks, which can include commands, chords, and rests.

The file opens with a short header consisting of A5, a pointer to the footer, the song count, a pointer to the End of Data (the unused area of padding after the last song), and an index of pointers to all the songs in the ROM pack. After the song index, there’s a pointer to the End of Data + 3. These are all separated by NULL bytes, $00.

RO-551 File Header/Song Index

 $0000	A50000D02F000400 281C003400003406 00280E000415002E 1C0000540000100A
 $0020	020002940500FF34 06001000000E0000 1E42002E01006060 005001B4480C000F

A5 appears to be used to confirm presence of a ROM pack (this tracks with PT-50 behavior, which asks for only the first byte of ROM, then does not attempt to load anymore data from ROM if it does not receive A5). Why A5 and not something else? $A is binary 1010 and $5 is the bit-inverse of it, so my guess is it’s acting as a very primitive memory test. Other than that, I couldn’t say.

Table: RO-551 File Header Analysis

AddressROM DataPointer ValuePurpose
$0000A500 00Likely a test for presence of ROM
$0003D02F 00$17E8Footer (last 24 bytes of file)
$000604 004(Song Count)
$0008281C 00$0E14End of Data
$000B3400 00$001ASong #1
$000E3406 00$031ASong #2
$0011280E 00$0714Song #3
$00140415 00$0A82Song #4
$00172E1C 00$0E17End of Data + 3 (unknown purpose)

The four song addresses bolded above point to the headers for each song (see the highlights in the next code block)

Each song starts with a 16-byte header consisting of four address pointers: one to each of the three tracks in it, and one to the end of the song. The three tracks are:

  • Track 1: Melody. This is also what you play in melody-guide mode with the LEDs.
  • Track 2: Accompaniment. The harmony/obbligato/counter-melody track.
  • Track R: Rhythm. The chords and percussion patterns.

Track 1, Track 2, and Track R pointers are “tagged” with 00, 10, and 02, respectively. The end of song pointer is tagged with FF.

RO-551 Song Headers/Track Indexes

 $0000	A50000D02F000400 281C003400003406 00280E000415002E 1C0000540000100A
 $0020	020002940500FF34 06001000000E0000 1E42002E01006060 005001B4480C000F
 $0300	01301F00AF000130 50D15050101550D0 505150D1F112C09D F000005406001052
 $0320	080002740D00FF28 0E001000000E0000 1E43002E28006040 005001C04A18000F
 $0700	1A4825481A901F00 9F001A5D50F0F112 C0A5F00000480E00 1040100002541400
 $0720	FF0415001000000E 00001E43002E0100 6040005001304812 060F00004824004A
 $0A80	F000002415001088 170002541B00FF28 1C001000000E0000 1E44002E01006060

Table: RO-551 Track Index Analysis

Song 1

AddressROM DataPointer ValuePoints To
$001A00 5400 00$002ATrack 1
$001E10 0A02 00$0105Track 2
$002202 9405 00$02CATrack R
$0026FF 3406 00$031AEnd of Song

Song 2

AddressROM DataPointer ValuePoints To
$031A00 5406 00$032ATrack 1
$031E10 5208 00$0429Track 2
$032202 740D 00$06BATrack R
$0326FF 280E 00$0714End of Song

Song 3

AddressROM DataPointer ValuePoints To
$071400 480E 00$0724Track 1
$071810 4010 00$0820Track 2
$071C02 5414 00$0A2ATrack R
$0720FF 0415 00$0A82End of Song

Song 4

AddressROM DataPointer ValuePoints To
$0A8200 2415 00$0A92Track 1
$0A8610 8817 00$0BC4Track 2
$0A8A02 541B 00$0DAATrack R
$0A8EFF 281C 00$0E14End of Song

Programmer’s Note: Gaps in Code
The songs and tracks need not all follow immediately one after another–there can be gaps in the code between songs and in between tracks within a song. So long as the pointers are correct, you may leave space between tracks and songs to allow room to grow. This can be a handy way to avoid having to recalculate indexes when you’re editing a song (with one exception: if you modify the last rhythm track, you’ll need to adjust End of Data and EoD+3).

Track Headers & Metadata

The opening bytes of each track can reveal some basic stats for the song:

  • Track 1 (Red) opens with 100000, then three meta-data blocks
  • Track 2 (Orange) also opens with 100000 but then goes immediately into the music
  • Track R (Blue), covered later, opens with 1000 and contains Meter, Tempo, and more
RO-551 Song 1 Track Headers

 $0000	A50000D02F000400 281C003400003406 00280E000415002E 1C0000540000100A
 $0020	020002940500FF34 06001000000E0000 1E42002E01006060 005001B4480C000F
 $0040	0000510804510C00 530804530C005512 0056060058060656 0C00550804550C00
 $00E0	00511E06480C001F 00009F0000511E06 480C001F0000AF00 0051243060E00050
 $0100	8100F00000100000 6000005000545C06 005A06005806005A 0600560600580600
 $0120	5506005606005306 0055060051242460 8000603000508000 5001180F00005108
 $02A0	5106005506005306 004806004C060053 0600510303410600 450600480600510C
 $02C0	3060A000508100F0 00001000900080D1 C01D105D50512830 01300F0010182818
 $02E0	0148281801482818 0148281801602830 0130283001182818 8F0001301F009F00
 $0300	01301F00AF000130 50D15050101550D0 505150D1F112C09D F000005406001052

Table: RO-551 Song Stats

11722/4C majTrumpetPianoMarchUnterlanders Heimweh
21123/4A minFlutePianoWaltzGreensleeves
31203/4C majFluteClarinetWaltzDie Lorelei
4964/4C majTrumpetPianoRockOld Folks at Home

The code in the track headers contains most of the above information

Table: RO-551 Track Headers

1Track 1
Track 2
Track R
100000 0E0000 1E4200 2E0100
1000 9000 80D1 C01D 105D 5051
2/4 time, C Major

88 bpm, March
2Track 1
Track 2
Track R
100000 0E0000 1E4300 2E2800
1000 9000 80F0 C025 1045 5070
3/4 time, A minor (Dorian)

116 bpm, Waltz
3Track 1
Track 2
Track R
100000 0E0000 1E4300 2E0100
1000 9000 80F0 C027 1045 5070
3/4 time, C Major

124 bpm, Waltz
4Track 1
Track 2
Track R
100000 0E0000 1E4400 2E0100
1000 9000 8080 C021 105D 5000
4/4 time, C Major

100 bpm, Rock

The three meta-data blocks in Track 1 are 0Exxxx, 1Exxxx, and 2Exxxx. 0E is always empty–in the dozens of ROMs I’ve dumped, I’ve never seen it used. 1E is the meter/time signature. Lazy ROM writers will just make it 3/4 or 4/4, but the better ones will use 2/4, 2/2, or even 6/8 where appropriate. 2E is the Key signature. Some ROM writers ignore this and set them all to C maj, but the good ones will set it appropriately. The really good ones (the author of RO-270…) will even insert one mid-song if there’s a key change.

Table: Time Signatures


These are the meters I’ve seen but since this is only metadata, there’s no reason more interesting meters couldn’t be used.

Table: Key Signatures

CodeMajorCodeNatural MinorCodeDorian Minor
2E01C Maj2EA1A min2E21D dor
2E02Db Maj2EA2Bb min2E22Eb dor
2E03D Maj2EA3B min2E23E dor
2E04Eb Maj2EA4C min2E24F dor
2E05E Maj2EA5C# min2E25F# dor
2E06F Maj2EA6D min2E26G dor
2E07Gb Maj2EA7Eb min2E27Ab dor
2E08G Maj2EA8E min2E28A dor
2E09Ab Maj2EA9F min2E29Bb dor
2E0AA Maj2EAAF# min2E2AB dor
2E0BBb Maj2EABG min2E2BC dor
2E0CB Maj2EACG# min2E2CC# dor

The time signatures are straight forward, but the key signatures can be tricky. Major keys are denoted (pardon the pun!) 0x, and minor keys are denoted Ax or 2x where Ax indicates Natural Minor (Aeolian) and 2x indicates Dorian Minor. Usually, a song in Dorian minor would just have the Natural minor key and then use accidentals to call out the non-lowered 6th that distinguishes Dorian from Natural minor. Here in ROM, they’ve given it its own code. Song 2, Greensleeves, is in A minor (Dorian).

Music Nerd Alert! …These numbers make a certain amount of sense: E Natural Minor and A Dorian Minor are each based off of different degrees of the G Major scale (the 6th and 2nd, respectively), hence all three codes end in 8. I’ve placed them on the table in the same rows to highlight this. Bet you didn’t expect a music theory lesson in a ROM disassembly, did you…

The Rhythm track will be explained in more detail later, but for now, C0xx determines the tempo, and 50xx determines the rhythm (March, Waltz, etc.). Following the track headers are the notes and commands for Tracks 1 & 2 and the chords/rhythms/commands for Track R.

Notes & Repeats

You’ve survived this far and I still haven’t told you how to play a note, so now on to the good part! Notes are only found in Tracks 1 and 2, and they behave exactly the same in both. They are all in three-byte blocks of pitch, note duration, and silence duration (the silence duration is used for producing articulations shorter than the full value of the note–but it’s also a convenient way to introduce any needed rests).

The first byte is broken into octave and pitch. 41 is a C, 42 is the C# above it, 3C is the B below it, etc.

Table: Note Pitches

Note RangeCodePitch
Lower 3rd Octave *31 – 35C3 – E3
3rd Octave36 – 3CF3 – B3
4th Octave41 – 4CC4 – B4
5th Octave51 – 5CC5 – B5
6th Octave **61C6 **
Rest (16-bit)10xxxx
Rest (high-byte)2000xx

* Undocumented, use in Track 2 only.
** The octave higher instruments (Violin, Flute, Celsta) may distort C6; C6 also not available on PT-50

Durations are expressed in ticks, where 24 (decimal) ticks = 1 beat = 1 Quarter note. 24 is easily divisible by several multiples of 2 and 3, allowing for all sorts of duples and triples. An 8-bit byte can only count 255 ticks (just over 10 beats) so there’s no way to play a note longer than that (which is a real shame because who wouldn’t want to listen to 10+ beats of totally rad consumer-grade 80’s synth violin?).

There are also two dedicated rests and they allow for rest periods longer than 10 beats by invoking 16 bit numbers. 10xxxx is a 16-bit rest period (little endian, as usual). Because notes already have a built-in rest, you don’t see this used too often. 2000xx is similar, where xx is the high-half of a 16-bit number. You can think of 2000xx as combining its high duration with the low duration of the previous note’s silence duration to make a single, 16-bit silent duration. I’m honestly not sure why you’d want to use 2000xx over 10xxxx, but traditionally (that is, in the ROMs I’ve looked at), it’s almost always 2000xx that’s used in these cases where a long duration is desired. In the next section, we’ll see rests can come from one more place: Commands, which also have an optional rest duration built into them.

Table: Note Durations

$0888th Triplet$A8168(7)
$1016Quarter Triplet$D8216(9)
$1824Quarter$20 200001288(12)
$2436.Quarter$50 200001336(14)
$3048Half$80 200001384(16)
$4872.Half$40 200002576(24)
$6096Whole$00 200003768(32)

Since durations are actual hexadecimal numbes, I do use $ notation. Examples show standard durations up to 10 beats, then use 2000xx 16-bit “high-byte” rest for longer periods of silence.

Example: Notes & Rests

 411800		= C4, as legato quarter note (full value)
 410612		= C4, as staccato quarter note (16th play, .8th silent)

 411848		= C4 legato again, but with 3 beats rest after it
 411868 200001	= C4 legato again, but with 15 beats rest ($0168 = 15 beats)

 10A800		= 7 beat rest
 104002		= 24 beat rest
 100000		= 0 beat rest (Wait, what???  Well, maybe...)

The first example is a typical quarter note. The second example could alternatively be viewed as a legato 16th note followed by a dotted-8th rest. The third example demonstrates that the “silence” byte can be plenty longer than the note and is a convenient and efficient (if not intuitive) way to add a rest. The next example shows the extreme case, using a 2000xx block to extend the rest into 16 bits. Following are some examples of 10xxxx, and finally, we touch on one of the mysteries of the ROM code: I believe it’s no coincidence that the opening header of a track is actually a special case of 10xxxx. On the other hand, I can’t really prove anything about what exactly 100000 does (believe me, I’ve tried: see Appendix A), so I may be wrong.

Programmer’s Note: Only 32 Keys
Most of the PT- series of keyboards have 32 keys, spanning 2 1/2 octaves from F3 to C6. While many of the MT- series have more, ROMs should assume 32 keys or else they’d end up playing out-of-range notes on the smaller keyboards (for that matter, the PT-50 only has 31 keys, lacking C6). That said, it is possible for them to play beyond their key range, extending the range down from F3 to C3–but this should only be done in Track 2, where it won’t break the Melody Guide functionality.

Lastly, there are markers available for repeats and endings that are analogous to what you’d find in actual sheet music. They may be used in any of the tracks, including Rhythm (where they take on their 2-byte form). You’ll see them used often since they can also save a lot of space.

Table: Repeats

Code (Track 1 & 2 / Track R)Purpose
0F0000 / 0F00Begin Repeat
1F0000 / 1F00End Repeat
8F0000 / 8F001st Ending
9F0000 / 9F00, etc. *2nd Ending, etc. *

* Tested through BF0000 (4th ending)

RO-551 Song 1 Notes (Track 1 Red, Track 2 Orange)

 $0020	020002940500FF34 06001000000E0000 1E42002E01006060 005001B4480C000F
 $0040	0000510804510C00 530804530C005512 0056060058060656 0C00550804550C00
 $0060	530804530C00511E 06480C0051080451 0C00530804530C00 5512005606005806
 $0080	06560C0055080455 0C00530804530C00 511E065806005606 0055060655060655
 $00A0	0606560600550600 5306065306065306 0658060056060055 0606550606550606
 $00C0	5606005506005306 0653060653060658 0C00550C00510C00 530804530C008F00
 $00E0	00511E06480C001F 00009F0000511E06 480C001F0000AF00 0051243060E00050
 $0100	8100F00000100000 6000005000545C06 005A06005806005A 0600560600580600
 $0120	5506005606005306 0055060051242460 8000603000508000 5001180F00005108
 $0140	04510C0048080448 0C00510600530600 5506005106004806 004A06004C060048
 $0160	060045243C510804 510C00480804480C 0051060048060051 0600550600560600
 $0180	5306004C06004806 0045240C8F000010 0600480600510600 550600580C124806
 $01A0	004C060053060058 0C12480600510600 550600580C124806 004C060053060058
 $01C0	0C12480600510600 5506005306004806 004C060053060051 0303410600450600
 $01E0	480600510C2460B0 006040181F00009F 0000480303480303 480303480600510C
 $0200	0C48030348030348 0303480600530C0C 4803034803034803 03480600510C0C48
 $0220	0303480303480303 480600530C0C4803 0348060051060055 0600560600530600
 $0240	4C06004806004106 0045060048060045 0600510C2460C000 6020181F0000AF00
 $0260	004106003C060041 06003C0600410C0C 4806004706004806 00470600480C0C41
 $0280	06003C0600410600 3C0600410C0C4806 0047060048060047 0600480C12480600
 $02A0	5106005506005306 004806004C060053 0600510303410600 450600480600510C
 $02C0	3060A000508100F0 00001000900080D1 C01D105D50512830 01300F0010182818

You should now be able to analyze just about all of Tracks 1 & 2 above. Only the Commands (still in black above) have yet to be explained, and they’re coming up next.

Programmer’s Note: Infinite Repeats
0F00 xxxx 1F00 8F00 xxxx 1F00
This instructs the keyboard to play xxxx (whatever you want repeated), then play it again–the normal behavior for a repeat. But then things get interesting: ordinarily, a 1st Ending would not have an End Repeat before it. 1st Ending is an instruction to play what follows, then go back to the Begin Repeat. In this case, the Begin Repeat is at the top of the passage, and the whole thing continues in this loop, playing xxxx twice, then once more in the 1st Ending. RO-556 uses this to provide background rhythms that loop endlessly (that way you can keep rockin’ those sweet Digital Horn riffs without having to constantly restart the rhythm).

Programmer’s Note: Song Length
To maintain highest compatibility, songs should be limited to about 1900 bytes max. Greater than this, and some of the older RAM-reading keyboards will not be able to load the entire song (see Appendix A, keyboard-specific notes for RAM-reader keyboards). Optionally, you could put two versions of a song on the same ROM, offering an “extended cut” that most keyboards could still play.

Instrument Change Command

Instruments are enabled and disabled/cancelled using the 60xxxx command. Disable is just enable + $80. For example, 6010xx enables Harpsichord and 6090xx disables it. You’ll typically see a 60xxxx command right after the track header, and its compliment at the end of a track to disable it. You may also see them in the middle of a track to change instruments. There can be only one instrument per track enabled at a time, so if there is an instrument change in the middle of a track, you’ll see a pair of 60xxxx commands, the first cancelling the previous instrument, and the second enabling the next one. Instruments must be disabled at the end of the track.

Programmer’s Note: Cancelling/Closing Instruments
According to my testing (on my MT-800 and PT-80), nothing actually seems to go wrong if you don’t cancel your instruments. For example, 6000xx to enable piano, then 6070xx to change to Celesta without a 6080xx in between. But I’m sure it’s there for a good reason, so… don’t do that.

Like all data on Tracks 1 and 2, the 60xxxx command is three bytes long. The final byte is an optional rest duration, similar to notes. It can be 00, but it’s often a convenient place to put a rest–for example, during the count-in, where Tracks 1 & 2 will rest during the count-in taps.

Example: Instrument Enable & Disable

 604060			= enable Flute, rest 4 beats
 60C000 606040 200002	= disable Flute, enable Trumpet, rest 24 beats

Using this can save a few bytes if you planned on resting after an instrument change anyway. It also turns out to be necessary: instrument changes take a moment to take effect, so it’s best to have a buffer of rest on both sides of a change, otherwise you tend to hear a small blip where the new instrument takes effect before the note is done playing; or the old one is still in effect as the next note starts. The following code block corresponds to Track 2 from the previous code block, colored orange there. Now the instrument changes are highlighted:

RO-551 Song 1, Track 2 Instrument Changes

 $0100	8100F00000100000 6000005000545C06 005A06005806005A 0600560600580600
 $0120	5506005606005306 0055060051242460 8000603000508000 5001180F00005108
 $0140	04510C0048080448 0C00510600530600 5506005106004806 004A06004C060048
 $0160	060045243C510804 510C00480804480C 0051060048060051 0600550600560600
 $0180	5306004C06004806 0045240C8F000010 0600480600510600 550600580C124806
 $01A0	004C060053060058 0C12480600510600 550600580C124806 004C060053060058
 $01C0	0C12480600510600 5506005306004806 004C060053060051 0303410600450600
 $01E0	480600510C2460B0 006040181F00009F 0000480303480303 480303480600510C
 $0200	0C48030348030348 0303480600530C0C 4803034803034803 03480600510C0C48
 $0220	0303480303480303 480600530C0C4803 0348060051060055 0600560600530600
 $0240	4C06004806004106 0045060048060045 0600510C2460C000 6020181F0000AF00
 $0260	004106003C060041 06003C0600410C0C 4806004706004806 00470600480C0C41
 $0280	06003C0600410600 3C0600410C0C4806 0047060048060047 0600480C12480600
 $02A0	5106005506005306 004806004C060053 0600510303410600 450600480600510C
 $02C0	3060A000508100F0 00001000900080D1 C01D105D50512830 01300F0010182818

This code enables Piano at the beginning of the track, then disables Piano and enables Violin after the intro. Later it changes to Flute, then still later, to Organ. Finally, it disables Organ at the end of the song. If you’re familiar with this track, you may notice (depending on which keyboard model you have) that you never hear Violin or Organ–that’s due to a bug. See the table below and Appendix A.

Table: Instrument Enable & Disable

EnableDisableTrack 1 InstrumentTrack 2 Instrument
6070xx60F0xxCelestaCelesta (distorted)

Violin, Flute, and Celesta are all 8va instruments (1 octave higher). Track 2 is limited on most keyboards due to a bug where some instruments are not available and are instead duplicates of other instruments. The bug is present on all hardware families I’ve been able to test, though to different extents depending on the model. It’s possible the sample-based hardware is immune (PT-88 hardware family). I do not have one of these working yet so have not been able to test.

RO-551 Song 1, Track 2 50xxxx Commands

 $0100	8100F00000100000 6000005000545C06 005A06005806005A 0600560600580600
 $0120	5506005606005306 0055060051242460 8000603000508000 5001180F00005108
 $02A0	5106005506005306 004806004C060053 0600510303410600 450600480600510C
 $02C0	3060A000508100F0 00001000900080D1 C01D105D50512830 01300F0010182818

There’s another command I might as well mention here: the 50xxxx command. This command is very similar in syntax to the 60xxxx command, taking the same form (with the third byte as an optional rest duration), and needing to be canceled like 60xxxx. The thing about it is… I have no idea what it does. It is only ever 5000xx or 5001xx (canceled with 5080xx or 5081xx), seems to only appear right after an Instrument Change, and removing it seems to have no effect on the song. Further, RO-270, a ROM which uses just about every clever trick in the book, doesn’t use this command anywhere in it at all. For the purposes of analysis, it may be ignored aside from treating it as a rest if the last byte is non-zero.

Table: 50xxxx Command

5000xx5080xxDon’t Know
5001xx5081xxDifferent thing I don’t know

I originally thought it might enable Chorus, Sustain, or something like that–but I’ve tested that theory on my MT-800 and it doesn’t, so…? Because it’s not even used in some ROMs, my current theory is that it was for an optional (or perhaps planned but then canceled) function that many keyboards do not support.


On to the Rhythm Track! Rhythms in ROM code work similar to how they do in manual play mode: you can start and stop them, play chords over them, and do drum fills, but you can’t do individual drum beats or select the notes of chords. This keeps Track R very small since you don’t have to explicitly play each drum beat and each chord, but the downside is you’re confined to what’s built-in.

Everything in Track R is in two-byte blocks, unlike the three-byte blocks in the other two tracks. Commands do not have durations attached to them, but chords (see the next section) do.

RO-551 Song 1 Rhythm Track Header

 $02C0	3060A000508100F0 00001000900080D1 C01D105D50512830 01300F0010182818

A typical Rhythm Track will open with six commands. These commands kick off the track, play the count-in (the tap, tap, tap, tap before the actual song starts), set the tempo, and set the rhythm. We saw hints of this in the Track Headers section of this document.

1000 and 9000 start the track, 80xx initializes (but does not yet play) the count-in, and C0xx sets the Tempo. Note that a quarter note is always 24 ($18) ticks of the clock, just like in Tracks 1 & 2. The tempo determines how fast those clock ticks are.

10xx is technically a chord (well technically it’s a rest…) and will be explained in full later on. In this context, it plays the count-in, where xx is a duration. Count-ins are typically 3 ticks short of a full measure, hence we see 105D instead of what we might expect for four beats, 1060. Why that’s so will also be made clear later…

Programmer’s Note: Tempo Changes
I’ve tested it, and Tempo may also be changed mid-song even though I’ve never seen any ROM do it: simply cancel the previous tempo and activate the new one. Be aware, though, that if you use this in your songs, it will not work with the older keyboards with manual tempo controls (like the MT-800). This incompatibility may be why it’s not used.

Table: Tempo


Values above and below simply play the max/min listed. For simplicity, I’ve only shown every other value. Like many other commands we’ve seen, Tempo is canceled (at the end of the song) by issuing the same command but with $80 added to it.

RO-551 Song 1 Rhythm Commands

 $02C0	3060A000508100F0 00001000900080D1 C01D105D50512830 01300F0010182818
 $02E0	0148281801482818 0148281801602830 0130283001182818 8F0001301F009F00
 $0300	01301F00AF000130 50D15050101550D0 505150D1F112C09D F000005406001052

50xx activates a rhythm, with the above code using 5051 to activate March for the first song of RO-551. Similar to instrument change, Rhythms are deactivated/cancelled by the same command just $80 higher (March is deactivated with 50D1). Even for keyboards with less than 16 rhythm buttons, some or all of the others are still available to ROM. Names given below are from the PT-50 which has all 16.

Table: Rhythm Enable & Disable

Rock50005080Bossa Nova504050C0
Rock ‘n’ Roll50015081Beguine504150C1
Swing 2-Beat502050A0Slow Rock506050E0
Swing 4-Beat502150A1Ballad506150E1
Latin Rock503150B1Rock Waltz507150F1

Like the instrument change command, anything enabled needs to be disabled before the end of the track, and only one can be enabled at a time. Count-in, explained in depth in a later section, is an exception to this rule. Speaking of later sections, this section gives a false sense of simplicity to the rhythm track. It’s sufficient for understanding a well-behaved ROM like RO-551, but looking at a ROM like RO-270, we see how quickly the standards are defenestrated (oh yes, defenestrated indeed!…). But first, chords.


Chords appear only in Track R, but they work similar to Notes. They are two bytes: the first byte is broken into alteration and pitch (Bb major, Dmin7, etc.), and the second byte is the Duration. Duration works exactly as it does in Tracks 1 and 2 except chords only have a play duration (no silence duration). For long chords, you’ll see the familiar-looking 20xx block following it, making it into a 16-bit duration by adding a high byte.

Table: Chords

01xxC81xxC dim
11xxC min91xxC aug
31xxCm7B1xxC7 -5
51xxC6D1xxC7 -9
61xxCm7 -5 (half dim)E1xx/C
71xxCsus4F1xxNo chord (drums only)
10xxContinue previous chord20xxExtend to 16-bit duration

Note the similarity of 10xx and 20xx to the 3-byte forms used in Tracks 1 & 2 for rest…

Example: Chords

 0118		= C maj, one beat
 3118		= Cm7, one beat
 9C60		= B aug, 4 beats
 2880 2001	= G7, 16 beats

Another reason the rhythm track is a lot smaller than the other tracks is because it plays automatically unless a change is required. Once the rhythm is started and the chord defined, the keyboard continues playing that chord and rhythm (much like it would in manual play mode), only needing code for chord changes or rhythm changes, which is typically only once every 2 or 4 beats (or often much longer).

RO-551 Song 1 Chords

 $02C0	3060A000508100F0 00001000900080D1 C01D105D50512830 01300F0010182818
 $02E0	0148281801482818 0148281801602830 0130283001182818 8F0001301F009F00
 $0300	01301F00AF000130 50D15050101550D0 505150D1F112C09D F000005406001052

Like Rhythms, many of the less-capable keyboards have more chords available to them via ROM code than what they have buttons for. For example, the PT-80 has only buttons for Major, Minor, Dominant, and Minor-7th; the PT-50 doubles that, but there are several more undocumented, even beyond the PT-50’s more impressive assortment, that are all available exclusively to ROM packs. There are 16 chords total. Most of these are straight-forward, but a few call for special explanations:

The Ex chord is a bass-only chord (it plays only the root of the chord in bass position). It will override whatever the bass note would be–March, for instance, normally plays the root on beat 1 and the 5th on beat 3. If placed on beat 3, Ex will force the bass note to whatever x is.

The Fx chord is a “No Chord” chord and the rhythm simply plays on with no chord (similar to if you were to put the piano in manual play mode and start a rhythm without assigning a chord). This isn’t a drum fill since it’s just the rhythm doing whatever it would normally do. If the rhythm has been disabled, Fx will simply play silence for the Rhythm Track (independent of whatever Tracks 1 & 2 are doing). Fx is usually F1, but doesn’t really matter since it has no pitch. Some ROMs like to use whatever key the song is in for the root of Fx, for instance F8 in the key of G.

Finally, there’s the 10xx command, which simply plays the previous chord again (or no chord if there is no chord playing, as is the case when it’s used way up at the top of the track during the count-in). It’s similar in concept to the 10xxxx Rest used in Tracks 1 and 2 (okay, fine, it really is a Rest, but I can’t quite teach that until Advanced Rhythm, next section…). Also, I believe 1000 is simply a special case of this command, analogous to 100000 in Tracks 1 & 2, and is used to initialize a track (but also like 100000, I can’t really prove it). 20xx, like its Track 1/Track 2 cousin 2000xx, extends a duration out to 16-bits. It makes a bit more sense here in Track R since 10xx has no high-byte component.

Some of the more clever ROMs, like RO-270, will often play an 8th note Ex chord followed by a more standard chord in order to manipulate the bass note of the chord, effectively creating a chord inversion:

Example: D7/A Chord

 EA0C		= /A for 8th beat
 2324		= D7 for .Qtr

Together, these two chords are D7/A for 2 beats

Now that we’ve seen Rhythms and Chords, we can study the famous “Tango Ending.” The Tango rhythm has a strong, solitary hit on beat 1, perfect for ending a song. The trick is simple: change to Tango for the last beat of your song, then cancel the rhythm before a 2nd beat can play. The result is a nice, clean ending. Many songs end this way, though when a different rhythm is used to end a song like this, it always changes back to the original rhythm before closing the track. I’m not sure why–it’s as if the song just really wants to end with the rhythm it started with.

Example: RO-551 Song 1 "Tango Ending" Tear-down

$0300	01301F00AF000130 50D15050101550D0 505150D1F112C09D F000005406001052

 0130			= C maj for 2 beats (last two beats before end)
 50D1 5050		= Disable March, Enable Tango
 1015			= Continue (C maj) for just under one beat
 50D0			= Disable Tango
 5051 50D1		= Enable then disable March
 F112			= With no rhythm enabled, this plays silence for .8th

All that's left after this is the Tempo is canceled and the song ends.

Chords can also be played without rhythms. When this is done, they sound just like they do when you push the chord buttons in manual play mode. Note: to get them in proper time, you need to close the 3-tick gap that’s normally in a rhythm track and play them on the beat (see the next section).

Programmer’s Note: Three Part Harmony
Unfortunately, the Casio keyboards can only play two notes at a time in ROM (one in Track 1, one in Track 2). Or can they?… Well, sort of. With no rhythm enabled, you can use the Ex chord to play a third pitch, converting Track R into… Track 3! There are limitations, though. It only has one octave range, and you can’t choose the octave–you’re stuck with the octaves native to each chord. The other limitation is that it’s not actually in the bass range when played by itself (with no rhythm). This could be good or bad, depending on what you were hoping for. Still, it’s worth considering.

At this point, other than closing a track, you should have almost all you need to be able to write songs in ROM. First, though: how I learned to stop worrying and love the Phrase Counter.

Advanced Rhythm

Alright, a disclaimer: this section isn’t absolutely necessary in order to write ROMs or understand most of the rest of this document, but it does explain the timing oddities we’ve seen in Track R to this point, reveals the true nature of 10xx and 20xx, and also grants access to some advanced rhythm tricks. Skip it if you like, but come back to it if you want to be as cool as the person behind RO-270 (you know you want to be that cool…).

Rhythms are synchronized to what I’m going to call the Phrase Counter, which is constantly looping through 2 bars (this section of the doc will assume 8 beats, but the Waltz rhythms would be 6), even when a song isn’t playing. When you issue a 50xx command, you assign a rhythm to this 8-beat counter and it begins to play it. Assuming the setting is in place by the time the counter hits beat 1, your rhythm starts playing on beat 1. The tricky thing about rhythms, then, is that rhythms/chords must be assigned slightly before the beat. Nothing in Track R is done on the beat (well, there is one exception…). If you try to set a setting directly on the beat, you won’t hear the event that should be on that beat–though whatever follows will play fine and in-time.

While this sounds ugly, in practice it’s really not (not for simple songs, anyway). Shorting the count-in by 3 ticks introduces the required offset–and from that point on, everything following can use the standard durations we’re already familiar with and will fall right into place. You can ignore the offset up until it’s time to close the track. This finally explains why count-ins are 105D and not 1060.

When you start to think of Rhythms in this way, as just being a product of the Phrase Counter doing its routine, 10xx being a form of rest makes more sense. 10xx means “just let the Phrase Counter do its thing.” If it’s playing Cmin7, it’ll keep playing Cmin7. If the rhythm has been disabled, it’ll play silence. Unlike in Tracks 1 and 2, it makes a lot more sense to have both 10xx and 20xx commands. 10xx is not 16-bit and so can only rest for 10 beats or so. 20xx allows you to extend that rest back out to 16-bits. And in this case, “rest” can also mean chords and rhythm, so 20xx is quite common in Track R where it’s used to play a chord for several bars.

The following example looks at RO-551, Song 1, but note the syntax change: I no longer say “play G7” but rather “set G7”–the implication being the Phrase Counter routine will play G7 over the assigned rhythm once it gets there. Rhythm commands don’t play anything, they simply set settings and wait while the Phrase Counter plays on.

Example: RO-551 Song 1 Phrase Counter Management

 1000	Begin track
 9000	Reset the Phrase Counter
 80D1	Set Count-in
 C01D	Set tempo
 105D	Wait for count-in to play (3 ticks short of four beats)
 5051	Set Rhythm (March)
 2830	Set Chord (G7), wait 2 beats while it plays

As mentioned earlier, the Phrase Counter is always running–disabling a rhythm doesn’t stop the counter, it’s more like turning the volume down. Likewise, if you enable a rhythm part way through the counter’s cycle, the rhythm will pick up at whatever beat the counter is on. This system enables easy, clean transitions between rhythms (and count-ins and fills, as we’ll see) without having to worry about exact timings.

But what if you want to do fancy stuff? It’s possible to reset the phrase counter and the 9000 command does exactly this. Resetting the phrase counter opens up all sorts of opportunities for cleverness (and it may also break reality when used carelessly…). When issued, 9000 immediately sets the counter to beat 1–which explains its presence at the start of every Rhythm Track. Unfortunately, because it is immediate, if you’re cruising along at your typical 3-ticks-ahead-of-the-beat and you want to reset exactly on a beat (which is usually the case), you first must close the 3-tick gap, then issue your 9000 command directly on the beat (there’s that exception I mentioned!). Then, you have to re-introduce the gap. Let’s look at some examples for why anyone would want to do this:

RO-270, Song 1 extracts four straight snare hits out of Tango without playing the flourish that would normally be on beat 4 of Tango. How? By resetting the Phrase Counter after beat 1: beat 1 plays, then the counter is reset on beat 2, turning it into a new beat 1, then beats 1, 2, and 3 play. Technically, this creates a 5/4 bar in Track R, leaving it out of sync compared to the rest of the song–but it so happens the rest of the song plays out without rhythm, so it’s not a problem here.

Example: RO-270 Song 1 Four Snare Hits

 ...	(song is set-up earlier)
 5050	Set Rhythm (Tango).  Note this is 3 ticks ahead of beat 1.
 3B18	Bb, 1 beat.  Now we're 3 ticks ahead of beat 2.
 3103	Cm7, 3 ticks.  This brings us exactly to beat 2.
 9000	Reset: Beat 2 becomes beat 1
 1015	Continue Chord (Cm7), restore 3-tick gap.  Now 3 ticks ahead of beat 2.
 5218	Db6, 1 beat...
 2418	Eb7, 1 beat...
 50D0	Disable Rhythm (Tango).  Rhythm is disabled, beat 4 never plays.

Speaking of 5/4, here’s one of my own making: the vamp section from Take Five. I play swing for 3 beats, reset the counter, play swing for 2 beats, reset the counter again, and repeat. This is the “improved version,” by the way. Easier would be to just reset every 5 beats, but it doesn’t sound quite as good. The un-highlighted section is a standard Track R setup, almost identical to any track on RO-551.

Example: Creating 5/4 Time with Phrase Counter Reset

 1000	Begin track
 9000	Reset the Phrase Counter
 80A0	Set Count-in
 C02C	Set tempo
 105D	Wait for count-in to play (3 ticks short of four beats)
 5020	Set Rhythm (Swing)
 1403	Eb min 3 ticks to close the gap (splitting chord allows for repeat)
 0F00	Begin Repeat
  102D	Continue Eb min, play beats 1, 2
  E418	Play /Eb (otherwise you get Bb on bass here) for beat 3
  1B03	Bb min, play beat 4 and close the gap
  9000	Reset: beat 4 becomes beat 1
  102D	Continue Bb min, play beats 1 and 2
  1403	Eb min, play beat 3 and close the gap
  9000	Reset: beat 3 becomes beat 1
 1F00	End Repeat
 8F00	Infinite Repeat trick to make it an endless vamp...
  102D E418 1B03 9000 102D 1403 9000

Programmer’s Note: Chords With and Without Rhythm
When a Rhythm is assigned (which is the norm on RO-551, but not always the case for other ROMs), chords are played at appropriate times via the Phrase Counter’s routine. But when chords are played without rhythm (for example, the opening of RO-270, Song 1), they behave more like the notes in Tracks 1 & 2. They should be played on the beat, not 3-ticks before. If you play them 3-ticks before, they’ll come in early (albeit only a 32nd note early, but it is still noticeable). If you plan to alternate between chords with rhythm and chords without, be mindful of your 3-tick gap and close it/restore it as necessary. I suppose that makes a second exception to my “everything on the beat but for one exception” rule…

Count-In & Rhythm Fill Commands

RO-551 Song 1 Count-In

 $02C0	3060A000508100F0 00001000900080D1 C01D105D50512830 01300F0010182818

Count-in and Rhythm Fill may both be thought of as temporary modes layered onto a rhythm. The count-in is triggered by an 80xx command. It rewinds the phrase counter back a bar (unless you’re on a PT-50) and initializes a simple tap pattern to count off the beginning of a song. After the tempo is set, a 10xx command waits for the pattern to play back (this is typically where the 3-tick gap is introduced). Unlike other commands, 80xx is auto-cancelling (perhaps indicated by its activation being $80 higher than usual, closer to the disable form of a typical command), so following the 10xx you’ll usually see 50xx issued to start the rhythm.

If you’d like the rhythm to kick in a bit before the first bar starts, simply execute your 50xx earlier, allowing it to take over from the count-in. The maximum length of a count-in is one bar and if you wait longer than that, the count-in will automatically convert to its corresponding rhythm. That said, it’s bad form to rely on that (and probably breaks stuff), so do explicitly set your rhythm after count-in.

Finally, count-ins are actually optional (several of the songs on RO-270 don’t use one), though most songs start with one, and it’s helpful if you’re trying to play along using the Melody Guide feature.

Table: Count-in & Fill

RhythmCount-inFill EnableFill Disable
Rock ‘n’ Roll808160016081
Swing 2-Beat80A0602060A0
Swing 4-Beat80A1602160A1
Latin Rock80B1603160B1
Bossa Nova80C0604060C0
Slow Rock80E0606060E0
Rock Waltz80F1607160F1

Each of the rhythms also has a unique 1-bar drum fill. In manual play mode, these are available via a dedicated button, or (on the PT-80, for example) by pressing that rhythm’s button while it’s already playing. The behavior in this mode is to fill the remainder of the bar with a drum fill, then reset the Phrase Counter so the next bar is beat 1. If executed between beats 1 and 4, beat 5 becomes beat 1; if executed between beats 5 and 8, beat 1 follows as it normally would.

RO-270 Song 4 Rhythm Fill

 $0FC0	30EC1828183130EA 1826183B30E41824 1850A05050E90850 D0F1285020EB0860 
 $0FE0	201000102860A01F 009F000F00043021 3036308F00EB182B 181F009F0010302B 

ROM code is a bit more versatile. Partial bars may be filled, and the Phrase Counter is not touched (except for the PT-50, see Appendix A). Rhythm fills can be thought of as an alternate rhythm pattern, specially defined for each rhythm. Fill mode allows you to temporarily shift over to this alternate pattern in order to play a drum fill. To switch to Fill mode, issue the corresponding 60xx command, followed by 1000 (you don’t need to first disable the rhythm). Then play a 10xx over the duration and disable Fill mode when finished. Drum fills by their nature have no chords, so 10xx is the standard when doing a fill.

Lastly, fills may be done anywhere in a bar, but will auto-cancel at the end of the bar (that’s bar, not just the 8-beat phrase counter phrase). Despite the auto-cancelling behavior, you should still explicitly cancel it (would you really want that on your conscience?).

Example: RO-270 Song 4 Rhythm Fill

 EB08	/Bb for one 8th note triplet (on beat three of the bar)
 6020	Enable Fill mode (Swing)
 1000	Activate Fill
 1028	Play fill for five 8th note triplets (the remainder of the bar)	
 60A0	Cancel Fill mode

A special note for the PT-50 and possibly others: most keyboards are pretty forgiving about timing on Fill mode, but if you want to fill an entire bar (starting on the first beat), you must trigger it twice: once to make up the 3-tick difference, and then once again to play it across the next bar (where you then bring the timing back ahead of the phrase counter).

Example: Full Bar Rhythm Fill

 6070	Enable Fill mode (Waltz)
 1000	Activate Fill
 1003	Play for 3 ticks
 6070	Re-enable Fill mode after auto-cancel
 1000	Activate Fill
 1045	Play for 3 beats (minus 3 ticks spent earlier)
 60F0	Disable Fill mode

Leaving this out will work fine for most of the keyboards I’ve tested, but the PT-50 will ignore fills if you do this. Presumably it plays only the 3-ticks of the previous bar as a “fill” then auto-cancels in time for the bar you actually wanted the fill to be in. The writer of RO-556 did not do this, and indeed, the tracks with fills do not play them on the PT-50. This is only a concern for full-bar fills–if you’re only filling the last few beats of a bar, don’t worry about it.

RO-270 Song 1 Rhythm Track (partial)

 $04C0	0C00440C00490C00 4B0C00510C095403 00590C0C39187860 F000F00000100090 
 $04E0	00C026508010600F 0008309830583098 301F00F12D500060 0010001030608050 
 $0500	8010305000600010 0010306080508050 51E30390001015E4 18E518E41850D150 
 $0520	50E33C50D0500060 001000F124608050 8050510878EC0C98 0C0118EC0C250C1A 

Now one, final example (you’ll need to have read the Advanced Rhythm section for this to make any sense): a complete tear-down of the opening 8 bars of RO-270, Song 1.

Example: RO-270 Song 1 Rhythm Intro Tear-down

 1000	Begin track
 9000	Reset the Phrase Counter
 C026	Set Tempo
 5080	Disable Rock
 1060	Wait 4 beats (silent count-in)

Because there was no 80xx count-in, we're at beat 5 of the Phrase Counter.
The writer intentionally does not create the 3-tick gap because the song
opens with chords but no rhythm.  8 beats are played, then repeated:

 0F00	Begin Repeat
 0830	G, 2 beats
 9830	G aug, 2 beats
 5830	G6, 2 beats
 9830	G aug, 2 beats
 1F00	End Repeat

We are still, then, exactly on beat 5 of the Phrase Counter.  The next 
command generates the 3-tick gap in preparation for Rhythms to begin.  There
are two beats rest, then a Rock fill played over the following two beats, 
covering beats 5, 6, 7, and 8:

 F12D	No Chord, 3 ticks short of 2 beats: 3-tick gap is now present
 5000	Enable Rock
 6000	Enable Rock Fill
 1000	Activate Fill
 1030	Play Fill across beats 7 and 8
 6080	Disable Fill Mode
 5080	Disable Rock

And then the same: 2 beats rest, followed by 2 beats Rock fill, this time 
covering beats 1, 2, 3, and 4:

 1030	Rest (because there's no chord or rhythm) 2 beats
 5000	Enable Rock
 6000	Enable Rock Fill
 1000	Activate Fill
 1030	Play Fill across beats 3 and 4
 6080	Disable Fill Mode
 5080	Disable Rock

We're now at beat 5 again (though now with our 3-tick gap).  The rhythm is
changed to March, and the Phrase Counter reset to convert beat 5 to beat 1.
If this were not done, March's beat 7-8 turnaround would play awkwardly over
the bar:

 5051	Enable March
 E303	/D, close the 3-tick gap.  Exactly on beat 5 now
 9000	Reset Phrase Counter.  Beat 5 becomes beat 1
 1015	Continue Chord 1 beat, restore the 3-tick gap.
 E418	/Eb, 1 beat
 E518	/E, 1 beat
 E418	/Eb, 1 beat
 50D1	Disable March
 5050	Enable Tango

Now a switch to Tango for straight quarter-note hits on 1, 2, and 3, and then
the bar is finished with another Rock fill:

 E33C	/D, 2.5 beats (plays beats 1, 2, and 3 of Tango)
 50D0	Disable Tango
 5000	Enable Rock
 6000	Enable Rock Fill
 1000	Activate Fill
 F124	Play Fill across second half of beat 3, and all of beat 4
 6080	Disable Fill mode
 5080	Disable Rock
 5051	Enable March

After all of this, the song then proceeds in a more standard form, though of
course RO-270 still can't resist the occasional chord inversion...

 0878		G, 5 beats
 EC0C 980C	G aug/B, 1 beat
 0118		C, 1 beat
 EC0C 250C	E7/B, 1 beat
 ...		etc...

Track Closing

RO-551 Song 1 Track Closers

 $0000	A50000D02F000400 281C003400003406 00280E000415002E 1C0000540000100A
 $0020	020002940500FF34 06001000000E0000 1E42002E01006060 005001B4480C000F
 $00C0	5606005506005306 0653060653060658 0C00550C00510C00 530804530C008F00
 $00E0	00511E06480C001F 00009F0000511E06 480C001F0000AF00 0051243060E00050
 $0100	8100F00000100000 6000005000545C06 005A06005806005A 0600560600580600
 $0120	5506005606005306 0055060051242460 8000603000508000 5001180F00005108
 $02A0	5106005506005306 004806004C060053 0600510303410600 450600480600510C
 $02C0	3060A000508100F0 00001000900080D1 C01D105D50512830 01300F0010182818
 $02E0	0148281801482818 0148281801602830 0130283001182818 8F0001301F009F00
 $0300	01301F00AF000130 50D15050101550D0 505150D1F112C09D F000005406001052

Strictly speaking, tracks are closed with F00000 for Tracks 1 & 2, and F000 for Track R, and that’s it. The orange highlights are included as a reminder that Instruments (and the mysterious 50xxxx command), Rhythms, and the Tempo need to be disabled before ending a track. After that, F00000/F000 is all you need.

All three tracks should end at the same time, and traditionally there’s some silence (that way it doesn’t kick immediately into the next song if the user chose Demo/Play All). Don’t forget to recover the 3-tick gap in Track R to put it back in sync with the other two tracks. RO-551 isn’t the best example since it ends on the and-of-2 instead of a nice, square beat, but if you do the math, you should see all three tracks end exactly here.

The song structure as a whole has no closing/footer–the end of the rhythm track is considered to be the end of the song. The next song (or the End of Data if it’s the last song) immediately follows. If it’s the end of data, you’ll just see 00’s all the way to the footer.

File Footer

And that’s it! The entirety of a Casio ROM pack explained! Oh right, the file footer. Well, the footer, I note, has several 83’s in it. And those FF’s at the end, they probably mean something, too. And obviously the 00’s mean something. Unless of course they don’t…

RO-551 File Footer

 $17C0	0000000000000000 0000000000000000 0000000000000000 0000000000000000 
 $17E0	0000000000000000 238393064783AB02 63274B2747932B83 FFFFFFFF00000000

In ASCII, it spells #ƒ“Gƒ«c’K’G“+ƒ
In byte-swapped ASCII, it spells 289`t8º 6r´rt9²8
In Hylian, it translates to “IT’S A SECRET TO EVERYBODY”

So yes, I have no clue what the footer is. It’s the same in every file, so it’s not a checksum. From what I can tell, this code is never accessed during normal playback. I’ve zeroed it out, zeroed out the pointer to it–neither matters, the ROM runs fine. Maybe it was an abandoned boot-strap code for something? An author or compiler signature?

“But surely,” you say, “You wouldn’t end this document with a big ol’ question mark, would you?”

Oh yes, I would. And don’t call me Shirley.

Where to from here?

Despite not knowing what some of the syntax means, the formatting is now known enough that it should be possible to create homebrew ROM files for Casio pianos. If you’re reading this, chances are you know that I’ve created a hardware ROM emulator based on Arduino, allowing vintage keyboards to play back ROM files. So, together with this document, I hope to see a Casio homebrew community spring up! Who’s with me? Let’s show those ROM-hacker video game nerds how it’s done!

~ Ra_226

Appendix A: Bugs & Misc Notes

  • My Collection (i.e., what I can test on): MT-800, PT-50, PT-80, MT-28, DH-800, and PT-480.
    • The PT-480 does not yet work with the ROM emulator, but I am working on it.
    • I also own some non-ROM-capable Casio keyboards: M-10, MT-68, MT-70.
      • The MT-70 is an interesting precursor, reading songs in from Barcode sheets
  • Track 2 of the PT-80, PT-50, and likely others in the same hardware family, cannot play certain instruments. Piano, Flute, and Clarinet work fine–but when the code calls for one of the other instruments, you will just get Piano, Flute, or Clarinet instead. Celesta sort of works but sounds a lot more muted than usual (almost like Flute but with a decay–honestly, I think it sounds a little better…). Track 1 works fine. See Instrument Change Command section for details.
  • Note 61, high C, has issues with all three of the octave-higher instruments (Flute, Violin, Celesta). These instruments choke a bit when it’s in Track 1 (they come in late, then drop a little off the front of the next note to make up for it). Their sound is also a little off, but aside from those issues, they will play. When used in Track 2, however, Flute actually plays the next note down along with a buzzy harmonic, which sounds awful. Violin and Celesta probably would too, but the other bug makes it impossible to use them on Track 2.
  • Proof the Phrase Counter counts even when there’s no song: strip 9000 from the start of a song. Play a rhythm in manual play mode, then stop it, keep the beat in your head, start the hacked song: the rhythm will pick up at wherever the PC is at.
  • Theorized could set phrase counter to arbitrary value. Tried 9048, 90FF. Didn’t work.
  • Tried using 8000 instead of 8080. It works, but probably creates problems.
  • Tried putting a count-in mid-song, with and without first cancelling rhythm. Didn’t work.
  • Can’t really know whether 80xx rewinds the Phrase Counter 4 beats or just sets to beat 5.
    • Interesting, though, that 80xx does not need 3-tick lead in order to play the first beat…
  • You can reset the Phrase Counter during a count-in, thus allowing for arbitrary count-ins
    • Tested on PT-80
    • Can be confusing: after the taps complete, rhythm picks up from whatever beat PC is on
    • Tested with 1000 9000 8080 C026 1030 9000 102D 5000 F120
      • Rock beat picks up after 4 taps, on beat 3 (consistent with waiting 2 beats after reset)
    • Tested with 1000 9000 8080 C026 1030 9000 10BD 5000 F120
      • Rock beat picks up on beat 1 after 6 taps. Auto-converts on beat 5, and auto-resets the PC
      • If it never makes it to beat 5 (keep resetting the PC), you can take as many taps as you like.
  • Tracks 1 & 2 first note must wait more than 3 ticks before play. Otherwise fails.
    • Best to just use count-in (unless Track R has no content)
  • Attempted zeroing out Footer and ROMs play fine. (Tested PT-50, PT-80)
  • Attempted zeroing out EoD+3 and ROMs play fine. (Tested PT-50, PT-80)
  • Attempted zeroing out End of Song and ROMs play fine. (Tested PT-50, PT-80)
  • Re-using tracks (eg, two songs use same Track R) works on some keyboards, but fails on others
    • Failed on PT-50, MT-800, and likely fails on all RAM-readers
  • Verified tempo changes work fine
  • Minimum requirements for empty tracks: (gleaned from RO-556 and RO-270)
    • Without the 10xx these tracks are zero length, useful only to create hidden tracks
      • Tr 1 100000 0E0000 1E4400 2E0100 600000 608000 (10xxxx) F00000
      • Tr 2 100000 600000 608000 (10xxxx) F00000
      • Tr R 1000 9000 C026 5080 (10xx 20xx) C0A6 F000
  • Theorized 100000 kills previous sounds, but NO
    • Attempted in code by playing short Celesta note followed by 100000
    • Attempted Celesta in manual play, then start song with 100000 stripped out–still kills
    • 10xxxx in general also does not kill a note
    • Tested at end of a song: 607000… 511800 60F000 100000 F00000, Celesta note still rings
    • Unable to determine what 100000 does (nor it’s Track R counterpart, 1000)
    • Unable to determine what mechanism kills notes when songs start
  • Proof of 10xxxx and 2000xx behavior is in RO-270 at $0D04: 441828 200002 102004 = 1 + 23 + 44
    • Celesta in Track 1 lays out while Trumpet in Track 2 takes lead

Keyboard-Specific Notes

  • Older keyboards with analog tempo controls (MT-800, others) won’t respond to tempo changes
  • Most keyboards read directly from ROM, but some of the older ones copy to RAM first
    • PT-50, MT-800, likely MT-85/86. I tend to call these “RAM Readers” in this document.
    • These keyboards have limits to size of song they can play (likely due to limited RAM)
    • Smaller song index (less songs) does not appear to free up more RAM for songs
      • In play-all, looking for read of index after song and not seeing would prove index is in RAM


  • The PT-50 cannot play C6–not only does it lack the key, but ROM code won’t play it, either
  • PT-50 is fussy about missing track closer. Not generally a problem, but others tolerate it
    • If seeing an error on PT-50 while making your own ROMs, this may be why
  • PT-50 count-in does not rewind the PC. If it matters to you, reset PC after count-in.
  • PT-50 behaves as it does in manual play, filling to the end of the bar regardless of your code
    • It also resets the Phrase Counter at the end of a bar of fill
  • The PT-50 has a song size limit of 2014 Bytes (likely due to amount of RAM available)


  • MT-800 apparently always applies the Chorus effect to Track 2
  • MT-800 has a song size limit of 1934 Bytes (likely due to amount of RAM available)
  • MT-800 has a lot of hiss, right speaker additional scratchy. Probably poor shielding
  • MT-800 has a bug where the first play of any song sets the lower keys to “fingered chord mode.”
    • Replaying a song (without re-loading from ROM) puts them in normal mode.

Appendix B: Remaining Questions

  • Footer. Unknown function, same in all files, never accessed (at least not by PT-80)
    • Zeroing it out makes no difference. Perhaps an abandoned bootstrap code, or just a signature?
  • 4th pointer in Track Index. Changing doesn’t break anything. End of song, or start of next?
    • Suspect it’s end of song so RAM-readers know how much to load
    • Time to load does not change when artificially lengthened.
    • Zeroing it out doesn’t seem to bother keyboards I tested on.
  • End of Data + 3. Unknown function, changing doesn’t break anything
  • 0Exxxx meta data block. Unknown function. Checked over dozen ROMs, never seen it used
    • Possibly was to be song title but took too much space with only 6k available
  • What does 50xxxx command do? Always directly follows 60xxxx. Remove doesn’t break.
    • Present in RO-551, RO-201. Not used at all in RO-270. Only in earlier ROMs?
    • Only shows as 5000xx / 5001xx, canceled with 5080xx / 5081xx (only in Tracks 1 and 2)
      • Canceled/abandoned feature?
      • Sustain/vibrato/chorus/8va/volume? –NO to all (tested on PT-50, MT-800)
  • Why must rhythms be disabled before switching to a new one?
    • Probably related, why is original rhythm enabled and immediately disabled for “Tango ending”?
    • Disabled rhythm likely configures the phrase counter for 6 vs. 8 beats
  • Why must instruments be disabled before switching to a new one?
    • Possible planned option to create new instruments by layering them onto each other?
  • Does sample-based hardware have Track 2 bug? (if ever solved, fix Inst Change table caption)
  • Are 100000 and 1000 at top of tracks necessary? deleting doesn’t break anything
    • Are 100000 and 1000 special forms of 10xxxx and 10xx?
    • Are they related to 10xx, a form of “continue doing what you’re doing”?
  • Similarly, 1000 inside Fills doesn’t seem to do anything–but removing breaks other songs!
    • Fills don’t quite come out right.
    • May yet be related to top-of-track 1000/100000
  • Is there a way to play non-sustained Piano, as heard in the PT-80 start-up jingle?
    • 6080xx buggy/slow; 50xxxx, 62xxxx, 10xxxx, nn00xx (zero-duration note) don’t work
    • Related to question of what kills sounds of current instruments when you start a song
  • User changing rhythm during ROM playback causes fills to be for user rhythm despite 60xx code

3 Replies to “Casio ROM Pack Analysis”

  1. You plan to dump the RO-267 ROM (which is unreleased as a ROM pack) from the EP-20 & record that on the PT-50 ? I’ll be interested to hear it play!

  2. I don’t plan to dump a whole lot. I’ve dumped a couple dozen or so, but I realized that’s a rabbit hole I can’t really afford to go down any further. I hope others will eventually dump theirs. But if someone were to dump it, I’d certainly be willing to play it on my PT-50.

  3. Really interesting and thorough analysis. Thank you. I must confess to not understanding all of it but I can confirm that the MT-85/86 and the CT810 are definitely RAM readers as they behave the same as the PT-50. You can control the speed of the chorus on the CT810 too.

    It all allows for some interesting possibilities for Rom pack composing.

Leave a Reply

Your email address will not be published. Required fields are marked *