Skip to content

Latest commit

 

History

History
659 lines (491 loc) · 23.3 KB

cotton.md

File metadata and controls

659 lines (491 loc) · 23.3 KB

cotton

The cotton errand assembles a text-based assembler language into a BMS file. The format is inspired by traditional languages, such as x86 and PPC. No assumptions are made about what the user wants in the sequence: the language corresponds 1:1 with the binary format. The arguments are as follows:

Parameter Description
-input <file> Specifies the path and filename to the input assembly text file.
-output <file> Specifies the path and filename to the output BMS file.

Grammar

The assembler language is line based. Files are interpreted one line at a time, with each line performing one action. All necessary arguments for a command must be on the same line as the command itself.

Comments

Comments allow you to separate and describe areas of your file. They begin with a pound sign # and end at the newline. The rest of the line following the pound sign is ignored and the assembler advances to the next line.

# this is a comment line

.define VARIABLE 0 # descriptive text here
# load r3, 0 # this command is interpreted as comment text

Immediates

Constant numerical values passed directly to directives and commands are known as immediates. There are various types of immediates, each specified with a suffix letter following the integer. If no identifier is specified, then the immediate shall take the form of the smallest fitting type. The types affect how the value is compressed and written into the sequence binary. When the value is too large to fit an argument to a command or directive, the unused most-significant bits are simply truncated.

Type Identifier Size Description
Int8 b 1b Simple, 8-bit integer. May be 0‑255 or -128‑127.
Half16 s 1b An 8-bit signed integer scaled up to 16 bits. Value may be -128‑127.
Int16 h 2b Simple, 16-bit integer. May be 0‑65,535 or -32,768‑32,767.
Int24 q 3b Simple, 24-bit integer. May be 0‑16,777,215 or -8,388,608‑8,388,607.
Int32 w 4b Simple, 32-bit integer. May be 0‑4,294,967,295 or -2,147,483,648‑2,147,483,647.

Numbers may also be specified in hexadecimal, rather than decimal. Preceeding the first digit (but after the negative sign) with a dollar sign $ signifies this.

.int16 0 # the smallest fitting type is used, in this case int8
.int8 16s # fits in 8 bits but scaled to 16 bits, in this case 4128h
.int8 $140 # 320 in decimal, truncated to 64 to fit in 8 bits
.int24 -$10 # hexadecimal immediates may be negative as well (does not perform bitwise-NOT)

You may also specify a MIDI note name (C-0 through G-10) instead of the 8-bit values 0‑127. The # and b characters are used for sharps and flat accidentals, respectively.

# the following commands are equivalent:
noteon C-5, 127, 1
noteon 60, 127, 1

Registers

There are 43 register parameters available on every track, each accessed by name. The indices range from 0‑13, 32‑35, 40‑48, and 64‑79. Indices outside these ranges will result in undefined behavior. Each register's name is a lowercase r, followed by the register number in decimal.

Registers may either be referenced or dereferenced:

  • Referencing a register is as simple as typing the register name. This assembles to an 8-bit immediate of the corresponding index. It is useful when an argument of a command represents a register index.
  • Dereferencing a register tells the sequence to load the value from the specified register. To dereference a register, enclose the register name in square brackets, similar to x86. How it is compiled into the sequence depends on the command and the argument.
load r0, C-5 # loads value 60 into r0
noteon [r0], 127, 1 # uses the value in r0 (60) as note number
add r0, [r0] # adds r0 with itself
noteon C-5, [r0], 1 # uses the value in r0 (120) as velocity

readport 8, r1 # r1 is the destination register
readport 8, [r1] # current value of r1 is the index of the destination register

Some registers have convenient aliases:

Alias Index Description
rcmp r3 Stores information from arithmetic operations and comparisons. Used by the jmp, call, and ret commands.
rx r4 Stores the high-order 16 bits of the result from the multiply command.
ry r5 Stores the low-order 16 bits of the result from the multiply command.
rpreset r6 Stores the bank and program number in the high and low 8 bits, respectively.
rpitch r7 Stores the range, in semitones, of the pitch timed parameter.
rbank r32 Virtual register which represents the top 8 bits of r6 (bank number). Storing values into this register keeps the program number intact.
rprogram r33 Virtual register which represents the bottom 8 bits of r6 (program number). Storing values into this register keeps the bank number intact.
rxy r35 Virtual register which is the bitwise-ORed 32-bit value of rx and ry.
rar0‑3 r40‑3 Four address registers 0‑3 storing raw addresses of other BMS sequences. Can be used by the jmp command.
rchild r44 Virtual register which contains a 32-bit bitmask of the child tracks on the track.
rchannel r45 Virtual register which contains a 32-bit bitmask of the channels on the track.
rloop r48 The current number of loops created by the last loops command. Decrements upon hitting a loope command until zero.

Variables

You may assign convenient names to individual immediates using variables. Variables are declared using the .define directive and must be declared before the line they are first used. Variable names must begin with an uppercase letter and consist only of uppercase letters, digits, and underscores. Declaring a variable twice will simply reassign a new value; however, any previous use of the old value will not be changed.

# declaring these as variables makes it easier to refactor below
.define BANK 0
.define PROGRAM 20

load rbank, BANK
load rprogram, PROGRAM

.define PROGRAM 32

# the following command will use the new value
# the commands before will still use the old value
load rprogram, PROGRAM

Labels

You may give a name to the current point in the binary file (the "cursor"). This name may be referenced in supported arguments of directives and commands, even ones appearing before the label declaration itself.

Note: Internally, a label is always a 24-bit immediate whose value represents the cursor at the point the label was declared.

To declare a label, simply type the label's name followed by a colon. To reference a label, simply type an at symbol @, followed by the label's name. To undefine a label, use the .undefinelabel directive (see below).

BEGINLOOP: # label this position for later reference
noteon C-5, 127, 1
wait 24
noteoff 1
wait 24
jmp @BEGINLOOP # repeat this segment indefinitely

Label names must start with an uppercase letter and consist only of uppercase letters, digits, or underscores. You may have a label and variable of the same name. Multiple labels with the same name may not coexist and are not allowed.

Directives

An assembler directive begins with a full stop and a lowercase name. It may be preceeded by a number of arguments, separated by commas.

Directive Description
.include file Includes the file at file (relative to the current file), specified in quotes. If the file has already been included before, this directive will do nothing.
.define name value Creates a variable named name. value may be any immediate value, key number, or register reference.
.undefine name Undefines the variable named name. If none exists, the directive will do nothing.
.undefinelabel name Undefines the label named name. If none exists, the directive will do nothing.
.align multiple Writes padding zeroes until the binary file size is a multiple of multiple bytes. If the size is already of the specified multiple, nothing is written. multiple may be any immediate value or variable.

There are also POD directives, allowing you to embed unadulterated immediates into the binary file. Each of these directives may be followed by an immediate or a variable. The 24-bit directive also supports labels.

Directive Description
.int8 Writes the value given, truncated to 8 bits.
.int16 Writes the value given, truncated to 16 bits.
.int24 Writes the value given, truncated to 24 bits.
.int32 Writes the value given, truncated to 32 bits.

Commands

A command is specified by its lowercase name. One command passed to cotton will compile as one command in the BMS. The available commands are as follows:

Notes and gates

A track has eight channels. Each channel may be playing one note of any key and velocity. A note-on releases any previous note-on or gate-on still playing on the channel. A gate-on releases any previous note-on (but not gate-on) still playing on the channel. Gates may be used if you want to change the velocity or key number of a note overtime without releasing the note.


noteon kk, vv, cc
gateon kk, vv, cc
notesweep kk, vv, cc
gatesweep kk, vv, cc
Description
kk Key number of the note. May be either a register dereference or int8.
vv Velocity of the note (0‑127). May be either a register dereference or int8.
cc Index of channel on which to play the note. Must evaluate to a number between 1 and 7 (inclusive). May be either a register dereference or int8. If cc is a register dereference, the register index must still be only r1 through r7.

noteonz kk, vv, t1[, t2]
gateonz kk, vv, t1[, t2]
notesweepz kk, vv, t1[, t2]
gatesweepz kk, vv, t1[, t2]

The -z variations are used to play notes on channel zero. As there are no corresponding note offs for channel zero, the duration of the note is specified inline with the command. For note-ons, the track will automatically suspend for the note's duration, after which the note will be released automatically.

Description
kk Key number of the note. May be either a register dereference or int8.
vv Velocity of the note (0‑127). May be either a register dereference or int8.
t1 Time base for the notes duration, based on 100. May be either a register dereference or int8.
t2 The note's duration in ticks. The track will be delayed by this number of ticks. May be either a register dereference or int8.

The -on variations simply use the specified key number directly. Sweeps, on the other hand, "glide" (a la portamento) from the key number of the previous note to the current note over the course of the note's duration. If used on a non-zero channel, the note will simply use the previous note number directly and no portamento will occur.


Note-offs release a note on a channel, optionally overriding the oscillator's default release:

noteoff cc[, rr]
Description
cc Channel index. Must evaluate to a number between 1 and 7 (inclusive). May also be a register dereference; if so, the register index must be between r0 and r7 (inclusive).
rr Optional release value, specified as an int8. Values larger than 100 are passed through the formula (rr - 98) × 20. Units is in oscillator ticks.

The previous key number may be overridden using the setlastnote command:

setlastnote kk
Description
kk Key number to which to set the last note. May also be a register dereference.

Each track also stores a transpose value by which to offset all key numbers for notes and gates:

transpose vv
Description
vv The transpose of the track. This is a signed int8 measured in semitones. May also be a register dereference.

Timing

In BMS, commands are read one after another immediately without stopping. You may insert a wait command to suspend a track for a number of ticks. Note-ons using the zero channel may also suspend a track. Even while a track is suspended from waiting, any child tracks are still processed recursively.

wait tt
Description
tt The number of ticks to wait before processing commands on this track again. If zero, the track will not be suspended and will return to processing commands immediately. tt may be a register dereference, int8, int16, or int24.

Each track has its own tempo and time base. The time base controls how many sequence ticks occur per beat. The tempo controls how many beats occur per minute. The tempo and time base values are inherited by parent tracks by default until the child track changes either to a different value.

tempo tt
timebase bb
Description
tt The tempo, measured in beats per minute, to assign the track. May be a register dereference or int16.
bb The time base, measured in sequence ticks per beat, to assign the track. May be a register dereference or int16.

Timed parameters

A track has 18 timed parameters that can interpolate a given duration. Each is a normalized floating-point number ranging from ±1. These parameters are wired to track properties, such as volume, pitch, and pan.

Index Alias Description
0 tvolume Amplitude for the track. Zero is mute and one is full amplitude.
1 tpitch Fine-tuning of the track. Values greater than zero tune upwards in pitch, whereas less than zero tune downwards in pitch.
3 tpan Stereo placement of the track. Zero is left, half is center, one is right.

The timedparam command allows you to control these parameters on the current track:

timedparam pp, vv[, tt]
Description
pp Destination parameter index. See above for the known timed-parameter list.
vv The value to assign the parameter. vv may be a register dereference, int8, half16, or int16.
tt Optional time value, measured in sequence ticks, over which to interpolate from the parameter's current value to vv. tt may be a register dereference, int8, or int16.

Register parameters

There are several commands for manipulating register parameters on the current track, including arithmetic, comparison, and bitwise operations.


load rr, vv

Loads the value vv directly into register rr. The value of vv is also copied into rcmp.

Description
rr Index of the destination register.
vv Immediate value to load into the register. May be a register dereference, int8, half16, or int16.

add rr, vv

Adds the signed value vv to the register rr and stores the sum in register rr. The sum is also copied into rcmp.

Description
rr Index of register whose value to add.
vv Value to add to the register. May be a register dereference, int8, or int16. The addend is interpreted as signed.

Note: negative values effectively subtract from the register.


subtract rr, vv

Subtracts the value vv from the register rr and stores the difference in register rr. The difference is also copied into rcmp.

Description
rr Index of register whose value from which to subtract.
vv Value to subtract from the register. Must be an int8.

multiply rr, vv

Multiplies the value in register rr by value vv. The top 16 bits of the product is stored in rx. The bottom 16 bits of the product is stored in ry. The value in rcmp is not modified by this command.

Description
rr Index of the multiplicand register.
vv Multiplier value. May be a register dereference, int8, or int16.

bshift rr[, vv]
bshiftu rr[, vv]

Shifts the value in register rr by vv number of bits and stores the result in register rr. The result of the operation is also copied into rcmp. The value of vv is interpreted as signed; positive values shift leftward and negative values shift rightward. The bshift form performs a signed bitshift (i.e. shifting right copies the sign bit into the extra bits). The bshiftu form performs an unsigned bitshift (i.e. shifting right copies zeroes into the extra bits). If vv is omited, a value of -1 is used.

Description
rr Index of the register whose value to bitshift.
vv The number of bits to shift leftward. May be a register dereference, int8, or int16. Negative values shift rightward.

band rr[, vv]
bor rr[, vv]
bxor rr[, vv]

Performs a bitwise operation on the value of register rr and stores the result in register rr. The result of the operation is also copied into rcmp. The band form bitwise-ANDs the values together. The bor form bitwise-ORs the values together. The bxor form bitwise-XORs the values together. If vv is omited, a value of -1 is used.

Description
rr Index of the register whose value to perform the operation.
vv The operand of the bitwise operation. May be a register dereference, int8, or int16.

negate rr[, vv]

Negates the value in register rr and stores the result in register rr. The result of the operation is also copied into rcmp. This is not the same as the bitwise-NOT operation. If specified, vv is ignored.

Description
rr Index of the register whose value to negate.

random rr[, vv]

Loads a random value modulo vv into register rr. The result of the operation is also copied into rcmp. If vv is omitted, a value of -1 is used.

Description
rr Index of the destination register.
vv Value by which to modulo the random value. The random value will be in the range of zero to vv ‑ 1 (inclusive). May be a register dereference, int8, or int16.

compare rr, vv

Compares the value of register vv to the value vv. The comparison information is stored in rcmp.

Description
rr Index of the register of which to compare the value.
vv Value by which to compare the register's value. May be a register dereference, int8, or int16.

Branching and conditions

You may branch unconditionally or conditionally to another part of the sequence. Branches can be performed either directly via an immediate offset or indirectly through a jump table.

call [cc,] pp
call [cc,] rr, tt
jmp [cc,] pp
jmp [cc,] rr, tt
ret [cc]
  • The call forms push to the call stack and branch. The depth of the call stack is eight.
  • The jmp forms simply branch with no effect on call stack.
  • The ret form pops the call stack and branches to the command directly after the corresponding call command.
Description
cc Optional condition identifier by which to branch. The result of the last register command will be used. If the comparison fails, no branch will be performed. If omitted, the branch will be unconditional.
pp Position to which to branch. May be a register dereference, int24, or label reference.
rr Index into the jump table at which to get the actual branch destination. Must be a register dereference.
tt Base position of the jump table. May be a register dereference or label reference.

The available condition identifiers are as follows:

Description
eq branch if equal to
ne branch if not equal to
one branch if one
le branch if less than or equal to
gt branch if greater than

A track may be told to play a segment a specified number of times before continuing. This is done using the loops and loope commands. All commands after the loops command and before its corresponding loope command will be repeated. These commands are analogous to the PowerPC instructions mtctr and bdnz.

loops vv
loope
Description
vv The total number of times to play the following commands. May be a register dereference or int16.

Track control

Any track may have up to sixteen children tracks. The hierarchy may go to any depth. Certain properties are inherited from the parent, such as tempo and time base.


opentrack ii, pp

Opens a child track on the current track at index ii. Any track previously open at the index will be closed beforehand. The opened track will begin at offset pp in the sequence.

Description
ii Index at which to open the track. Must be in the range of 0‑15. May be a register dereference or int8.
pp Offset at which the track will begin playing commands. May be a register dereference, int24, or label reference.

opentrackbros ii, pp

Opens a child track on the current track's parent (i.e. a sibling to the current track) at index ii. Any track previously open at that index will be closed beforehand. The opened track will begin at offset pp in the sequence. If the current track has no parent (i.e. the root track of a sequence), the command will do nothing.

Description
ii Index at which to open the track. Must be in the range of 0‑15. May be a register dereference or int8.
pp Offset at which the track will begin playing commands. May be a register dereference, int24, or label reference.

closetrack ii

Closes the child track at index ii. Any notes on the child track will be force-stopped. The child track's child tracks are also closed recursively. If there is no child track open at the index, the command will do nothing.

Description
ii Index of the track to close. Must be in the range of 0‑15. May be a register dereference or int8.

finish

Closes the track and any child track. All notes active on any of these tracks are force stopped. If the root track of a sequence is closed, the sequence ends.

Syncing

The audio system in JSYSTEM allows for multiple ways for the game and tracks to communicate. You may utilize a simple synchronous callback system or an asynchronous port system.


synccpu vv

Calls the registered track callback with the argument vv. The result of the callback is stored in rcmp.

Description
vv The argument to pass to the callback. May be a register dereference or an int16.

readport ii, rr
writeport ii, ww

Gets or sets the value of port ii on the current track.

Description
ii The port number to read or write. May be either a register dereference or int8.
rr The register index in which to store the read value. May be either an int8 or register dereference.
ww The value to write to the port. Must be a register dereference.

Debugging

There are several BMS commands used for debugging.


checkwave ww

Checks whether the specified wave is loaded and stores the result in rcmp.

Description
ww Wave id of which to check the load status. May be a register dereference or int16.

Note: In Super Mario Sunshine, this command is defunct and always returns false.


printf ss[, vv[, ...]]

Prints a formatted string to the debug console.

Description
ss C-style format string to print.
vv Zero-to-four immediates to use in the format string. Each will be truncated to a int8.

The percent character % is used to format a value inline into the final string. The character after the percent determines how to format the corresponding vv argument into the string:

Description
d Formats vv as a decimal.
x Formats vv in hexadecimal.
r Formats the value of the register vv in decimal.
R Formats the value of the register vv in hexadecimal.
t Formats the unique track identifier as hexadecimal.
% Escapes the format specifier and prints a percent character.

Note: in Super Mario Sunshine, this command is defunct and does not log the output to the console.