BMS (File Format)

From Luma's Workshop
Revision as of 16:37, 27 May 2025 by SY24 (talk | contribs) (Documented B8 through BB)
Jump to navigation Jump to search

File Format

BMS files are Binary Music Sequences that store commands for the audio engine to play notes, similar to the MIDI file format.

Data

BMS files contain raw instructions for the audio engine. There are no headers or filesize checks. Each instruction (OpCode) stands for its own function in the engine. The following table lists all JAudio2 OpCodes that have functions in SMG2:

OpCode Function (SMG/SMG2) Usage Example Description
00
01
02
...
7D
7E
7F
{OpCode[1]} {Voice[1]} {Velocity[1]}
OpCode: one byte with range 0x00 through 0x7F. 0x00 = C-1, 0x3C = C4 (middle C), 0x7F = G9.
Voice: one byte with range 0x01 through 0x07 (needs testing/verification). Each Voice can only hold one Note.
Velocity: one byte with range 0x00 through 0x7F. 0x00 = 0, 0x7F = 127.

If the Voice is not 1 - 7, the game will treat this note as a Midi Gate note, and will expect a 4th value that is one or more bytes with range 0x00 through 0xFF. If the leading byte is 0x80 (bit 7 = 1) or greater, another following byte will be taken into account. It is unknown what effects this has on the output.
64 02 68

64: Key E4
02: Voice 2
68: Velocity 104
Starts playing a Note with the previously defined Instrument (see E1, E2 and E3).
80 Unknown. Seems to do nothing based on the code.
81
...
87
{OpCode[1]}
OpCode: one byte with range 0x81 through 0x87. 0x81 = Close Voice 1, 0x87 = Close Voice 7.
Stops playing a Note.
88
...
8F
Unknown
90
...
9F
Unknown
A0 NULL
A1 NULL
A2 NULL
A3 NULL
A4 NULL
A5 NULL
A6 NULL
A7 NULL
A8 NULL
A9 NULL
AA NULL
AB NULL
AC NULL
AD NULL
AE NULL
AF NULL
B0 Extended Opcode Indicator See B000 or B001.
B1 JASSeqParser::cmdNoteOn
B2 JASSeqParser::cmdNoteOff
B3 JASSeqParser::cmdNote
B4 JASSeqParser::cmdSetLastNote
B5 NULL
B6 NULL
B7 NULL
B8 JASSeqParser::cmdParamE {OpCode[1]} {Target[1]} {Parameter[1]}
OpCode: one byte, B8.
Target: one byte with range 0x00 through 0x03.
Parameter: one byte with range 0x00 through 0xFF (Int8), whichs purpose depends on the target:
  • Target 0: Volume. 0x7F = Full volume (100%), 0x00 = Silent (0%). 0x80 through 0xFF are treated the same as silence (0%).
  • Target 1: Pitch. Adding one (0x01) or subtracting one (0xFF) increases/decreases pitch by one key, like from D#4 to E4.
  • Target 2: Reverb. 0x7F = Strong reverb, 0x00 = no reverb. 0x80 through 0xFF are treated the same as no reverb.
  • Target 3: Panning. 0x7F = right channel, 0x00 = center, 0x80 = left channel.
B8 00 64

B8: OpCode
00: Target (Volume)
64: ~78% volume
Allows different operations onto the music track
B9 JASSeqParser::cmdParamI {OpCode[1]} {Target[1]} {Parameter[2]}
OpCode: one byte, B9.
Target: one byte with range 0x00 through 0x03.
Parameter: two bytes with range 0x0000 through 0xFFFF (Int16), whichs purpose depends on the target:
  • Target 0: Volume. 0x7FFF = Full volume (100%), 0x0000 = Silent (0%). 0x8000 through 0xFFFF are treated the same as silence (0%).
  • Target 1: Pitch. Adding one (0x0100) or subtracting one (0xFF00) increases/decreases pitch by one key, like from D#4 to E4. Values inbetween can be used for fine-tuning.
  • Target 2: Reverb. 0x7FFF = Strong reverb, 0x0000 = no reverb. 0x8000 through 0xFFFF are treated the same as no reverb.
  • Target 3: Panning. 0x7FFF = right channel, 0x0000 = center, 0x8000 = left channel.
B9 01 04 00

B9: OpCode
01: Target (Pitch)
04 00: Pitch of 4 keys
Allows different operations with higher precision onto the music track
BA JASSeqParser::cmdParamEI {OpCode[1]} {Target[1]} {Parameter[1]} {Fade time[2]}
OpCode: one byte, BA.
Target: one byte with range 0x00 through 0x03.
Parameter: one byte with range 0x00 through 0xFF (Int8), whichs purpose depends on the target:
  • Target 0: Volume. 0x7F = Full volume (100%), 0x00 = Silent (0%). 0x80 through 0xFF are treated the same as silence (0%).
  • Target 1: Pitch. Adding one (0x01) or subtracting one (0xFF) increases/decreases pitch by one key, like from D#4 to E4.
  • Target 2: Reverb. 0x7F = Strong reverb, 0x00 = no reverb. 0x80 through 0xFF are treated the same as no reverb.
  • Target 3: Panning. 0x7F = right channel, 0x00 = center, 0x80 = left channel.

Fade time: two bytes with range 0x0000 through 0xFFFF. The amount of time in ticks until the Parameter value is met.

BA 02 22 01 68

BA: OpCode
02: Target (Reverb)
22: Reverb of strength 34
01 68: 360 Ticks
Allows different operations with fade time onto the music track
BB JASSeqParser::cmdParamII {OpCode[1]} {Target[1]} {Parameter[2]} {Fade time[2]}
OpCode: one byte, BB.
Target: one byte with range 0x00 through 0x03.
Parameter: two bytes with range 0x0000 through 0xFFFF (Int16), whichs purpose depends on the target:
  • Target 0: Volume. 0x7FFF = Full volume (100%), 0x0000 = Silent (0%). 0x8000 through 0xFFFF are treated the same as silence (0%).
  • Target 1: Pitch. Adding one (0x0100) or subtracting one (0xFF00) increases/decreases pitch by one key, like from D#4 to E4. Values inbetween can be used for fine-tuning.
  • Target 2: Reverb. 0x7FFF = Strong reverb, 0x0000 = no reverb. 0x8000 through 0xFFFF are treated the same as no reverb.
  • Target 3: Panning. 0x7FFF = right channel, 0x0000 = center, 0x8000 = left channel.

Fade time: two bytes with range 0x0000 through 0xFFFF. The amount of time in ticks until the Parameter value is met.

BA 03 7F FF 02 00

BB: OpCode
03: Target (Panning)
7F FF: Panned to right channel
02 00: 512 Ticks
Allows different operations with higher precision and fade time onto the music track
BC NULL
BD NULL
BE NULL
BF NULL
C0 NULL
C1 JASSeqParser::cmdOpenTrack {OpCode[1]} {TrackNumber[1]} {Pointer[3]}
OpCode: one byte, C1.
TrackNumber: one byte with range 0x00 through 0x0F. 0x00 = Track 0, 0x0F = Track 15.
Pointer: three bytes with range 0x000000 through 0xFFFFFF. Defines an offset in the file.
C1 04 05 B4 73

C1: OpCode
04: Track 4
05 B4 73: Offset
Creates a Track which can play Notes. Track 15 is usually reserved for Yoshi Drums.
C2 JASSeqParser::cmdCloseTrack
C3 JASSeqParser::cmdCall {OpCode[1]} {Pointer[3]}
OpCode: one byte, C3.
Pointer: three bytes with range 0x000000 through 0xFFFFFF. Defines an offset in the file.
C3 00 00 40

C3: OpCode
00 00 40: Offset
Redirects execution to the defined offset. Execution can return with C5.
C4 JASSeqParser::cmdCallF
C5 JASSeqParser::cmdRet {OpCode[1]}
OpCode: one byte, C5.
Returns redirected execution (like from C3) to the initial call.
C6 JASSeqParser::cmdRetF
C7 JASSeqParser::cmdJmp {OpCode[1]} {Pointer[3]}
OpCode: one byte, C7.
Pointer: three bytes with range 0x000000 through 0xFFFFFF. Defines an offset in the file.
C7 08 02 34

C7: OpCode
08 02 34: Offset
Redirects execution to the defined offset, used for Loops.
C8 JASSeqParser::cmdJmpF
C9 JASSeqParser::cmdJmpTable
CA JASSeqParser::cmdCallTable
CB JASSeqParser::cmdLoopS
CC JASSeqParser::cmdLoopE
CD NULL
CE NULL
CF NULL
D0 JASSeqParser::cmdReadPort
D1 JASSeqParser::cmdWritePort
D2 JASSeqParser::cmdCheckPortImport
D3 JASSeqParser::cmdCheckPortExport
D4 JASSeqParser::cmdParentWritePort
D5 JASSeqParser::cmdChildWritePort
D6 JASSeqParser::cmdParentReadPort
D7 JASSeqParser::cmdChildReadPort
D8 JASSeqParser::cmdRegLoad {OpCode[1]} {Setting[1]} {Unknown[1]} {Variable[1]}
OpCode: one byte, D8.
Setting: one byte with unknown range. Different bytes will trigger different effects:
- 0x62: set PPQN (seen in BMS)
- 0x6B: set PPQN (seen in SC)
Unknown: one byte with unknown range and purpose.
Variable: one byte with different range and purpose depending on the Setting byte.
D8 62 00 78

D8: OpCode
62: set PPQN
00: Unknown
78: PPQN 120
Different purposes.
D9 JASSeqParser::cmdReg
DA JASSeqParser::cmdReg
DB JASSeqParser::cmdRegUni
DC JASSeqParser::cmdRegTblLoad
DD NULL
DE NULL
DF NULL
E0 JASSeqParser::cmdTempo {OpCode[1]} {Unknown[1]} {BPM[1]}
OpCode: one byte, E0.
Unknown: one byte with unknown range and purpose.
BPM: one byte with range 0x00 through 0xFF. 0x78 = 120, F0 = 240.
E0 00 90

E0: OpCode
00: Unknown
90: BPM 144
Defines the tempo (playback speed) of the BMS file.
E1 JASSeqParser::cmdBankPrg {OpCode[1]} {Bank[1]} {Program[1]}
OpCode: one byte, E1.
Bank: one byte with range 0x00 through 0xFF. This byte represents a WSYS ID.
Program: one byte ranging from 0x00 through 0xFF. This byte represents a LIST entry in the IBNK belonging to the defined WSYS ID.
E1 01 0F

E1: OpCode
01: WSYS ID 1
0F: IBNK LIST entry 15
(01 0F is the percussion set for Yoshi Drums)
Defines the Instrument to be used in a Track.
E2 JASSeqParser::cmdBank {OpCode[1]} {Bank[1]}
OpCode: one byte, E2.
Bank: one byte with range 0x00 through 0xFF. This byte represents a WSYS ID.
E2 53

E2: OpCode
53: WSYS ID 83
(This bank holds sounds for Megahammer)
Defines the WSYS/IBNK whose sounds are to be used in a Track.
E3 JASSeqParser::cmdPrg {OpCode[1]} {Program[1]}
OpCode: one byte, E3.
Program: one byte with range 0x00 through 0xFF. This byte represents a LIST entry in the IBNK belonging to the defined WSYS ID.
E3 0B

E3: OpCode
0B: IBNK LIST entry 11
(In relation to Megahammer, this Program holds movement related sounds)
Defines the Keyboard which holds more specific sounds to an IBNK.
E4 NULL
E5 NULL
E6 NULL
E7 JASSeqParser::cmdEnvScaleSet
E8 JASSeqParser::cmdEnvSet
E9 JASSeqParser::cmdSimpleADSR {OpCode[1]} {Attack[2]} {Sustain[2]} {Decay[2]} {Amplitude[2]} {Release[2]}
OpCode: one byte, E9.
Attack: two bytes with range 0x0000 through 0x7FFF. Define the duration of fade-in in ticks.
Sustain: two bytes with range 0x0000 through 0x7FFF. Define the duration for how long the wave is kept at maximum amplitude in ticks.
Decay: two bytes with range 0x0000 through 0x7FFF. Define the duration of fade between maximum and controlled amplitude in ticks.
Amplitude: two bytes with range 0x0000 through 0x7FFF. Define the volume of the wave for the 2nd sustain duration.
Release: two bytes with range 0x0000 through 0x7FFF. Define the duration of fade-out in ticks.

If Amplitude is set to 0x7FFF, Sustain and Decay won't have an audible effect to the wave.

The Attack, Decay and Release operations affect the volume of the wave linearly.
E9 00 80 01 00 00 80 5F FF 00 3C

E9: OpCode
00 80: Attack (fade-in) for 128 ticks
01 00: Sustain for 256 ticks
00 80: Decay for 128 ticks
5F FF: Amplitude of 75% compared to maximum
00 3C: Release (fade-out) for 60 ticks
Perform ADSR operations on a key. Must be placed before the actual key presses. See this image for a visual representation of the ADSR operations.
EA JASSeqParser::cmdBusConnect {OpCode[1]} {Unknown[1]} {Unknown[1]} {Unknown[1]}
OpCode: one byte, EA.
Unknown: one byte with unknown range and purpose.
EA 00 FF FF

EA: OpCode
00: Unknown
FF: Unknown
FF: Unknown
Unknown purpose, only seen in SC.
EB JASSeqParser::cmdIIRCutOff
EC JASSeqParser::cmdIIRSet
ED JASSeqParser::cmdFIRSet
EE NULL
EF NULL
F0 JASSeqParser::cmdWait {OpCode[1]} {WaitTime[n]}
OpCode: one byte, 0xF0.
WaitTime: one or more bytes with range 0x00 through 0xFF. If the leading byte is 0x80 (bit 7 = 1) or greater, another following byte will be taken into account. Time is measured in ticks here.
F0 60 (96 Ticks)
F0 81 56 (214 Ticks)
F0 83 81 30 (49328 Ticks)
Creates a delay, used to define the length of a Note or gap between Notes.
F1 JASSeqParser::cmdWaitByte
F2 NULL
F3 JASSeqParser::cmdSetIntTable
F4 JASSeqParser::cmdSetInterrupt
F5 JASSeqParser::cmdDisInterrupt
F6 JASSeqParser::cmdRetI
F7 JASSeqParser::cmdClrI
F8 JASSeqParser::cmdIntTimer
F9 JASSeqParser::cmdSyncCPU
FA NULL
FB NULL
FC NULL
FD JASSeqParser::cmdPrintf {OpCode[1]} {Message[n]} {NullTerminator[1]}
OpCode: one byte, FD.
Message: ASCII string of undefined length, ending with \n.
NullTerminator: one byte, 00.
FD 48 65 6C 6C 6F 5C 6E 00

FD: OpCode
48 65 6C 6C 6F 5C 6E: "Hello\n"
00: Null-terminator
Prints a specified message to OSReport.
FE JASSeqParser::cmdNop
FF JASSeqParser::cmdFinish {OpCode[1]}
OpCode: one byte, 0xFF.
Stops execution of a Track.
B000 NULL
B001 JASSeqParser::cmdDump

Tools

BMS_DEC (converts BMS to MIDI)
JaiSeqX (playback for JAudio1 and JAudio2 BMS files)