1
0
Fork 0
mirror of https://github.com/zeldaret/oot.git synced 2024-11-29 03:34:07 +00:00
oot/include/audio/aseq.h

2347 lines
54 KiB
GAS
Raw Normal View History

/**
* @file aseq.h
* Zelda64 Music Macro Language (MML) Assembler Definitions
*
* This file implements the assembler for Zelda64 Music Macro Language. MML is an interpreted language that combines
* general MIDI with arithmetic and control flow instructions.
*
* There are multiple distinct sections:
* - .sequence: The top level of the program, this is unique and executes sequentially.
* - .channel: Spawned by the top-level sequence, there can be up to 16 channels active at any time that execute in
* parallel. Channels map directly to MIDI channels, control changes made in channels affect the notes
* in the layers spawned by the channel.
* - .layer: Spawned by channels, up to 4 layers per channel. These execute in parallel. The purpose of layers is
* to allow overlapping notes.
* - .table: Contains dyntable labels.
* - .array: Contains array-like data that can be read using sequence IO instructions.
* - .filter: Contains filter structures.
* - .envelope: Contains envelope scripts.
* - .buffer: Contains arbitrary data.
*
* Execution begins in the sequence section at position 0 of the sequence with no channels or layers initialized.
*
* Execution flows until it blocks on certain commands that induce delays for a fixed number of ticks (it is to be
* understood that ticks in this context refers to seqTicks in code), such as the delay instructions or note
* instructions. If this happens in a channel or layer, the other channels/layers will continue execution until they
* hit their own delays.
*
* Sequences can self-modify. The ldseq, stseq and related instructions can perform loads and stores to any location
* in a sequence, including the executable sections. This can be used to make up for the lack of registers and
* arithmetic instructions by replacing immediate values in the instructions themselves.
*
* The maximum call depth is 4. Call depth applies to both loops and subroutines. Loops are implemented like
* subroutines in that starting a loop pushes a return address onto the call stack and reaching the end of the loop
* decrements the loop counter. If the loop has iterations left, it jumps to the return address. When a loop completes
* all the iterations, or when the break instruction is executed, the return address is popped from the call stack and
* no jump occurs.
*
* In the instruction descriptions, we use a number of conventions for referring to various internal state:
* - PC : The location of the current instruction, within the respective sequence/channel/layer
* - SEQ : The sequence data, viewed as an array of s8/u8
* - TR : Temporary s8 register
* - TP : Temporary u16 register
* - CIO[15..0][7..0] : Channel IO
* - SIO[7..0] : Sequence IO
* - DYNTBL : Current dyntable, DYNTBL16 and DYNTBL8 are different views into the same memory
* - DYNTBL16 : Current dyntable, viewed as an array of s16/u16
* - DYNTBL8 : Current dyntable, viewed as an array of s8/u8
* - CALLDEPTH : The current subroutine nesting level
* - SHORTVELTBL : Current short notes velocity table
* - SHORTGATETBL : Current short notes gate time table
*/
#ifndef ASEQ_H
#define ASEQ_H
#include "versions.h"
/**
* IO Ports
*/
#define IO_PORT_0 0
#define IO_PORT_1 1
#define IO_PORT_2 2
#define IO_PORT_3 3
#define IO_PORT_4 4
#define IO_PORT_5 5
#define IO_PORT_6 6
#define IO_PORT_7 7
/**
* Parameter Constants
*/
#define TRUE 1
#define FALSE 0
#define PITCH_A0 0
#define PITCH_BF0 1
#define PITCH_B0 2
#define PITCH_C1 3
#define PITCH_DF1 4
#define PITCH_D1 5
#define PITCH_EF1 6
#define PITCH_E1 7
#define PITCH_F1 8
#define PITCH_GF1 9
#define PITCH_G1 10
#define PITCH_AF1 11
#define PITCH_A1 12
#define PITCH_BF1 13
#define PITCH_B1 14
#define PITCH_C2 15
#define PITCH_DF2 16
#define PITCH_D2 17
#define PITCH_EF2 18
#define PITCH_E2 19
#define PITCH_F2 20
#define PITCH_GF2 21
#define PITCH_G2 22
#define PITCH_AF2 23
#define PITCH_A2 24
#define PITCH_BF2 25
#define PITCH_B2 26
#define PITCH_C3 27
#define PITCH_DF3 28
#define PITCH_D3 29
#define PITCH_EF3 30
#define PITCH_E3 31
#define PITCH_F3 32
#define PITCH_GF3 33
#define PITCH_G3 34
#define PITCH_AF3 35
#define PITCH_A3 36
#define PITCH_BF3 37
#define PITCH_B3 38
#define PITCH_C4 39
#define PITCH_DF4 40
#define PITCH_D4 41
#define PITCH_EF4 42
#define PITCH_E4 43
#define PITCH_F4 44
#define PITCH_GF4 45
#define PITCH_G4 46
#define PITCH_AF4 47
#define PITCH_A4 48
#define PITCH_BF4 49
#define PITCH_B4 50
#define PITCH_C5 51
#define PITCH_DF5 52
#define PITCH_D5 53
#define PITCH_EF5 54
#define PITCH_E5 55
#define PITCH_F5 56
#define PITCH_GF5 57
#define PITCH_G5 58
#define PITCH_AF5 59
#define PITCH_A5 60
#define PITCH_BF5 61
#define PITCH_B5 62
#define PITCH_C6 63
#define PITCH_DF6 64
#define PITCH_D6 65
#define PITCH_EF6 66
#define PITCH_E6 67
#define PITCH_F6 68
#define PITCH_GF6 69
#define PITCH_G6 70
#define PITCH_AF6 71
#define PITCH_A6 72
#define PITCH_BF6 73
#define PITCH_B6 74
#define PITCH_C7 75
#define PITCH_DF7 76
#define PITCH_D7 77
#define PITCH_EF7 78
#define PITCH_E7 79
#define PITCH_F7 80
#define PITCH_GF7 81
#define PITCH_G7 82
#define PITCH_AF7 83
#define PITCH_A7 84
#define PITCH_BF7 85
#define PITCH_B7 86
#define PITCH_C8 87
#define PITCH_DF8 88
#define PITCH_D8 89
#define PITCH_EF8 90
#define PITCH_E8 91
#define PITCH_F8 92
#define PITCH_GF8 93
#define PITCH_G8 94
#define PITCH_AF8 95
#define PITCH_A8 96
#define PITCH_BF8 97
#define PITCH_B8 98
#define PITCH_C9 99
#define PITCH_DF9 100
#define PITCH_D9 101
#define PITCH_EF9 102
#define PITCH_E9 103
#define PITCH_F9 104
#define PITCH_GF9 105
#define PITCH_G9 106
#define PITCH_AF9 107
#define PITCH_A9 108
#define PITCH_BF9 109
#define PITCH_B9 110
#define PITCH_C10 111
#define PITCH_DF10 112
#define PITCH_D10 113
#define PITCH_EF10 114
#define PITCH_E10 115
#define PITCH_F10 116
#define PITCH_BFNEG1 117
#define PITCH_BNEG1 118
#define PITCH_C0 119
#define PITCH_DF0 120
#define PITCH_D0 121
#define PITCH_EF0 122
#define PITCH_E0 123
#define PITCH_F0 124
#define PITCH_GF0 125
#define PITCH_G0 126
#define PITCH_AF0 127
// Hardcoded Instruments
#define FONTANY_INSTR_SFX 126
#define FONTANY_INSTR_DRUM 127
// Instruments implemented in gWaveSamples
#define FONTANY_INSTR_SAWTOOTH 128
#define FONTANY_INSTR_TRIANGLE 129
#define FONTANY_INSTR_SINE 130
#define FONTANY_INSTR_SQUARE 131
#define FONTANY_INSTR_NOISE 132
#define FONTANY_INSTR_BELL 133
#define FONTANY_INSTR_8PULSE 134
#define FONTANY_INSTR_4PULSE 135
#define FONTANY_INSTR_ASM_NOISE 136
#ifdef _LANGUAGE_ASEQ
/**
* MML Version
*/
#ifndef MML_VERSION
#error "MML version not defined, define MML_VERSION in the cpp invocation"
#endif
#define MML_VERSION_OOT 0
#define MML_VERSION_MM 1
/**
* IDENT
*/
.ident "Zelda64 Audio Sequence compiled with GNU AS + aseq.h"
#if (MML_VERSION == MML_VERSION_OOT)
.ident "MML VERSION OOT"
#else
.ident "MML VERSION MM"
#endif
/**
* Sequence IDs
*/
// Some sequence instructions such as runseq would like sequence names for certain arguments.
// This facilitates making the sequence enum names available in sequence assembly files.
.set __SEQ_ID_CTR, 0
#define DEFINE_SEQUENCE(name, seqId, storageMedium, cachePolicy, seqFlags) \
.internal seqId; \
.set seqId, __SEQ_ID_CTR; \
.set __SEQ_ID_CTR, __SEQ_ID_CTR + 1
#define DEFINE_SEQUENCE_PTR(seqIdReal, seqId, storageMediumReal, cachePolicyReal, seqFlags) \
.internal seqId; \
.set seqId, __SEQ_ID_CTR; \
.set __SEQ_ID_CTR, __SEQ_ID_CTR + 1
#include "tables/sequence_table.h"
#undef DEFINE_SEQUENCE
#undef DEFINE_SEQUENCE_PTR
// Mark the counter as internal for the symbol table
.internal __SEQ_ID_CTR
/**
* STSEQ offsets
*/
#define STSEQ_HERE (. + 1)
// stores 1 byte (stseq)
#define STSEQ_LOOP_COUNT 1
#define STSEQ_VOL 1
#define STSEQ_PAN 1
#define STSEQ_REVERB 1
#define STSEQ_UNK_A4 1
#define STSEQ_FILTER_IDX 1
#define STSEQ_COMBFILTER_ARG1_HI 2
#define STSEQ_NOTEDV_OPCODE_PITCH 0
#define STSEQ_NOTEDV_DELAY 1
#define STSEQ_NOTEDV_DELAY_HI 1
#define STSEQ_NOTEDV_DELAY_LO 2
#define STSEQ_NOTEDV_VELOCITY 3
#define STSEQ_NOTEDV_VELOCITY_2 2
#define STSEQ_LDI_IMM 1
#define STSEQ_TRANSPOSITION 1
#define STSEQ_PORTAMENTO_MODE 1
#define STSEQ_PORTAMENTO_PITCH 2
#define STSEQ_PORTAMENTO_TIME 3
#define STSEQ_NOTEPAN 1
#define STSEQ_LDELAY 1
#define STSEQ_STSEQ_IMM 1
#define STSEQ_RAND 1
#define STSEQ_SURROUNDEFFECT 1
#define STSEQ_GENERAL_OPCODE 0
#define STSEQ_STEREO 1
#define STSEQ_BENDFINE 1
#define STSEQ_NOTEDVG_OPCODE_PITCH 0
#define STSEQ_NOTEDVG_DELAY_HI 1
#define STSEQ_NOTEDVG_DELAY_LO 2
#define STSEQ_NOTEDVG_VELOCITY_LONGDELAY 3
#define STSEQ_NOTEDVG_DELAY_SHORT 1
#define STSEQ_NOTEDVG_VELOCITY_SHORTDELAY 2
#define STSEQ_GAIN 1
#define STSEQ_INSTR 1
#define STSEQ_VIBDEPTH 1
#define STSEQ_SUB_IMM 1
#define STSEQ_INITCHAN_HI 1
#define STSEQ_INITCHAN_LO 2
#define STSEQ_VOLEXP 1
#define STSEQ_BEND 1
#define STSEQ_LDSEQ_SEQ_ID 1
// stores 2 bytes (stptrtoseq)
#define STSEQ_PTR_DYNTBL 1
#define STSEQ_PTR_LDLAYER 1
#define STSEQ_PTR_LDSEQ 1
#define STSEQ_PTR_STSEQ 2
// stores 2 bytes (so should be used with stptrtoseq)
#define STSEQ_ENVELOPE_POINT(n) (2 * (n))
/**
* Sequence Sections
*/
.internal ASEQ_MODE_NONE; .set ASEQ_MODE_NONE, 0
.internal ASEQ_MODE_SEQUENCE; .set ASEQ_MODE_SEQUENCE, 1
.internal ASEQ_MODE_CHANNEL; .set ASEQ_MODE_CHANNEL, 2
.internal ASEQ_MODE_LAYER; .set ASEQ_MODE_LAYER, 3
.internal ASEQ_MODE_TABLE; .set ASEQ_MODE_TABLE, 4
.internal ASEQ_MODE_ARRAY; .set ASEQ_MODE_ARRAY, 5
.internal ASEQ_MODE_FILTER; .set ASEQ_MODE_FILTER, 6
.internal ASEQ_MODE_ENVELOPE; .set ASEQ_MODE_ENVELOPE, 7
.internal ASEQ_MODE_BUFFER; .set ASEQ_MODE_BUFFER, 8
/* Macros that change structure based on current section */
.macro _RESET_SECTION
.purgem ldseq
.macro ldseq
.error "Invalid section for `ldseq`"
.endm
.purgem filter
.macro filter
.error "Invalid section for `filter`"
.endm
.purgem env
.macro env
.error "Invalid section for `env`"
.endm
.endm
/* Dummy macro definitions for above so purgem doesn't error on first run */
.macro ldseq
.endm
.macro filter
.endm
.macro env
.endm
/* Instantiate above macros */
_RESET_SECTION
/**
* Begin a sequence code section.
*/
.macro .sequence name
_RESET_SECTION
/* `ldseq` changes structure based on current section. */
.purgem ldseq
.macro ldseq ioPortNum, seqId, label
_wr_cmd_id ldseq, 0xB0,,,,,,,, \ioPortNum, 4
_wr_u8 \seqId
_wr_lbl \label
.endm
.set ASEQ_MODE, ASEQ_MODE_SEQUENCE
\name:
.endm
/**
* Begin a channel code section.
*/
.macro .channel name
_RESET_SECTION
/* `ldseq` changes structure based on current section. */
.purgem ldseq
.macro ldseq label
_wr_cmd_id ldseq, ,0xCB,,,,,,, 0, 0
_wr_lbl \label
.endm
/* `filter` changes structure based on current section. */
.purgem filter
.macro filter lowpassCutoff, highpassCutoff
_check_arg_bitwidth_u \lowpassCutoff, 4
_check_arg_bitwidth_u \highpassCutoff, 4
_wr_cmd_id filter, ,0xB3,,,,,,, 0, 0
_wr_u8 (\lowpassCutoff << 4) | (\highpassCutoff)
.endm
/* `env` changes structure based on current section. */
.purgem env
.macro env label
_wr_cmd_id env, ,0xDA,,,,,,, 0, 0
_wr_lbl \label
.endm
.set ASEQ_MODE, ASEQ_MODE_CHANNEL
\name:
.endm
/**
* Begin a layer code section.
*/
.macro .layer name
_RESET_SECTION
/* `env` changes structure based on current section. */
.purgem env
.macro env label, arg
_wr_cmd_id env, ,,0xCB,,,,,, 0, 0
_wr_lbl \label
_wr_u8 \arg
.endm
.set ASEQ_MODE, ASEQ_MODE_LAYER
\name:
.endm
/**
* Begin a table section.
*/
.macro .table name
.balign 2
.table_unaligned \name
.endm
/**
* Begin a table section at an unaligned offset.
*/
.macro .table_unaligned name
_RESET_SECTION
.set ASEQ_MODE, ASEQ_MODE_TABLE
\name:
.endm
/**
* Begin an array section.
*/
.macro .array name
_RESET_SECTION
.set ASEQ_MODE, ASEQ_MODE_ARRAY
\name:
.endm
/**
* Begin a filter section.
*/
.macro .filter name
_RESET_SECTION
/* `filter` changes structure based on current section. */
.purgem filter
.macro filter a0, a1, a2, a3, a4, a5, a6, a7
_wr_s16 \a0
_wr_s16 \a1
_wr_s16 \a2
_wr_s16 \a3
_wr_s16 \a4
_wr_s16 \a5
_wr_s16 \a6
_wr_s16 \a7
.endm
.set ASEQ_MODE, ASEQ_MODE_FILTER
.balign 16
\name:
.endm
/**
* Begin an envelope section.
*/
.macro .envelope name
_RESET_SECTION
.set ASEQ_MODE, ASEQ_MODE_ENVELOPE
.balign 2
\name:
.endm
/**
* Begin a buffer section.
*/
.macro .buffer name
_RESET_SECTION
.set ASEQ_MODE, ASEQ_MODE_BUFFER
.balign 16
\name:
.endm
.macro _section_invalid macro_name
.if ASEQ_MODE == ASEQ_MODE_SEQUENCE
.error "Invalid section for `\macro_name`: Sequence"
.elseif ASEQ_MODE == ASEQ_MODE_CHANNEL
.error "Invalid section for `\macro_name`: Channel"
.elseif ASEQ_MODE == ASEQ_MODE_LAYER
.error "Invalid section for `\macro_name`: Layer"
.elseif ASEQ_MODE == ASEQ_MODE_TABLE
.error "Invalid section for `\macro_name`: Table"
.elseif ASEQ_MODE == ASEQ_MODE_ARRAY
.error "Invalid section for `\macro_name`: Array"
.elseif ASEQ_MODE == ASEQ_MODE_FILTER
.error "Invalid section for `\macro_name`: Filter"
.elseif ASEQ_MODE == ASEQ_MODE_ENVELOPE
.error "Invalid section for `\macro_name`: Envelope"
.elseif ASEQ_MODE == ASEQ_MODE_BUFFER
.error "Invalid section for `\macro_name`: Buffer"
.else
.error "Invalid section"
.endif
.endm
/**
* Sequence Start/End
*/
.macro .startseq name
/* Begin a sequence. */
/* Write the sequence name into a special .name section */
.pushsection .name, "", @note
.asciz "\name"
.balign 4
.popsection
/* Reset section and write start symbol. */
.section .data, "wa", @progbits
.set ASEQ_MODE, ASEQ_MODE_NONE
.balign 16
.global \name\()_Start
\name\()_Start:
_seq_start:
.endm
.macro .endseq name
/* End a sequence. Align to 16 bytes, write end and size symbols. */
.balign 16
_seq_end:
.global \name\()_End
\name\()_End:
.global \name\()_Size
.set \name\()_Size, _seq_end - _seq_start
/* Mark ASEQ_MODE as an internal symbol at the very end since it is redefined many times */
.internal ASEQ_MODE
.endm
/**
* Parameters
*/
.macro _check_arg_bitwidth_u value, num
/* Checks if the unsigned integer `value` fits within `num` bits. */
.if (\value & ((1 << \num) - 1)) != \value
.error "value \value out of range for unsigned \num\()-bit argument"
.endif
.endm
.macro _check_arg_bitwidth_s value, num
/* Checks if the signed integer `value` fits within `num` bits. */
.if (\value & ((1 << (\num - 1)) - 1)) - (\value & (1 << (\num - 1))) != \value
.error "value \value out of range for signed \num\()-bit argument"
.endif
.endm
/* Long encoded numbers a la MIDI */
.macro _var_long x
_check_arg_bitwidth_u \x, 15
.byte (0x80 | (\x & 0x7f00) >> 8), (\x & 0xff)
.endm
/* Potentially long encoded numbers, but may not be. */
.macro _var x
.if (\x >= 0x80)
_var_long \x
.else
_check_arg_bitwidth_u \x, 8
.byte \x
.endif
.endm
.macro _wr8 value
/* Write 8 bits */
.byte (\value) & 0xFF
.endm
.macro _wr16 value
/* Write 16 bits (big-endian) */
.byte (\value) >> 8, (\value) & 0xFF
.endm
.macro _wr_s8 value
/* Ensure the provided arg value fits in 8 bits (signed) */
_check_arg_bitwidth_s \value, 8
_wr8 \value
.endm
.macro _wr_u8 value
/* Ensure the provided arg value fits in 8 bits (unsigned) */
_check_arg_bitwidth_u \value, 8
_wr8 \value
.endm
.macro _wr_s16 value
/* Ensure the provided arg value fits in 16 bits (signed) */
_check_arg_bitwidth_s \value, 16
_wr16 \value
.endm
.macro _wr_u16 value
/* Ensure the provided arg value fits in 16 bits (unsigned) */
_check_arg_bitwidth_u \value, 16
_wr16 \value
.endm
.macro _wr_lbl value
/* Subtract sequence start label to get a numeric offset that can be written at assembling time */
_wr16 (\value - _seq_start)
.endm
.macro _wr_16_rel value
/* Turn label into relative offset.
* Can't check encoding, complains about non-constant value.. */
_wr16 (\value - $reladdr\@)
$reladdr\@:
.endm
.macro _wr_8_rel value
/* Turn label into relative offset.
* Can't check encoding, complains about non-constant value.. */
_wr8 (\value - $reladdr\@)
$reladdr\@:
.endm
.macro _wr_cmd_id cmd_name, seq_id=-1, chan_id=-1, layer_id=-1, tbl_id=-1, arr_id=-1, filt_id=-1, env_id=-1, buf_id=-1, lo_bits, n_lo_bits
/* Helper for writing command ids. Will write the command id appropriate for the current section, optionally
* packing in low-order bits if a command uses them for an argument. */
_check_arg_bitwidth_u \lo_bits, \n_lo_bits
.if ASEQ_MODE == ASEQ_MODE_SEQUENCE && \seq_id != -1
_check_arg_bitwidth_u \seq_id, 8
.byte (\seq_id & 0xFF) | (\lo_bits)
.elseif ASEQ_MODE == ASEQ_MODE_CHANNEL && \chan_id != -1
_check_arg_bitwidth_u \chan_id, 8
.byte (\chan_id & 0xFF) | (\lo_bits)
.elseif ASEQ_MODE == ASEQ_MODE_LAYER && \layer_id != -1
_check_arg_bitwidth_u \layer_id, 8
.byte (\layer_id & 0xFF) | (\lo_bits)
.elseif ASEQ_MODE == ASEQ_MODE_TABLE && \tbl_id != -1
_check_arg_bitwidth_u \tbl_id, 8
.byte (\tbl_id & 0xFF) | (\lo_bits)
.elseif ASEQ_MODE == ASEQ_MODE_ARRAY && \arr_id != -1
_check_arg_bitwidth_u \arr_id, 8
.byte (\arr_id & 0xFF) | (\lo_bits)
.elseif ASEQ_MODE == ASEQ_MODE_FILTER && \filt_id != -1
_check_arg_bitwidth_u \filt_id, 8
.byte (\filt_id & 0xFF) | (\lo_bits)
.elseif ASEQ_MODE == ASEQ_MODE_ENVELOPE && \env_id != -1
_check_arg_bitwidth_u \env_id, 8
.byte (\env_id & 0xFF) | (\lo_bits)
.elseif ASEQ_MODE == ASEQ_MODE_BUFFER && \buf_id != -1
_check_arg_bitwidth_u \buf_id, 8
.byte (\buf_id & 0xFF) | (\lo_bits)
.else
_section_invalid \cmd_name
.endif
.endm
.macro .ptr value
_wr_lbl \value
.endm
.macro .ptr_raw value
_wr16 \value
.endm
/**
* Instructions
*/
/**
* end
*
* Marks the end of an executable section.
*
* If CALLDEPTH is not 0 (that is, execution is currently in a subroutine)
* the current subroutine is exited and execution continues at the saved
* return address. If CALLDEPTH is 0, the current layer/channel/sequence is
* closed. If the sequence is closed, all execution ends. If a channel is
* closed, so are its layers.
*/
.macro end
_wr_cmd_id end, 0xFF,0xFF,0xFF,,,,,, 0, 0
.endm
/**
* delay1
*
* Delays for one tick.
*/
.macro delay1
_wr_cmd_id delay1, 0xFE,0xFE,,,,,,, 0, 0
.endm
/**
* delay <delay:var>
*
* Delays for `delay` ticks.
*/
.macro delay delay
_wr_cmd_id delay, 0xFD,0xFD,,,,,,, 0, 0
_var \delay
.endm
/**
* call <label:lbl>
*
* Unconditionally enters a subroutine at `label`. Execution will resume following this instruction once the
* subroutine encounters an `end` instruction.
*/
.macro call label
_wr_cmd_id call, 0xFC,0xFC,0xFC,,,,,, 0, 0
_wr_lbl \label
.endm
/**
* jump <label:lbl>
*
* Branches to `label` unconditionally.
*/
.macro jump label
_wr_cmd_id jump, 0xFB,0xFB,0xFB,,,,,, 0, 0
_wr_lbl \label
.endm
/**
* beqz <label:lbl>
*
* Branches to `label` if TR == 0.
*/
.macro beqz label
_wr_cmd_id beqz, 0xFA,0xFA,0xFA,,,,,, 0, 0
_wr_lbl \label
.endm
/**
* bltz <label:lbl>
*
* Branches to `label` if TR < 0.
*/
.macro bltz label
_wr_cmd_id beqz, 0xF9,0xF9,0xF9,,,,,, 0, 0
_wr_lbl \label
.endm
/**
* loop <num:u8>
*
* Begin a loop for `num` iterations, pushing the address of the next
* instruction to the call stack.
*
* Maximum loop nesting level is 4 - CALLDEPTH, after this the call stack
* becomes full.
*/
.macro loop num
_wr_cmd_id loop, 0xF8,0xF8,0xF8,,,,,, 0, 0
_wr_u8 \num
.endm
/**
* loopend
*
* Indicates the end of a loop body. Whenever the instruction is executed
* the loop counter decrements and either branches back to the loop start
* if there are remaining iterations or exits the loop if all iterations
* have been completed. When the loop is to be exited, the top of the call
* stack is popped.
*/
.macro loopend
_wr_cmd_id loopend, 0xF7,0xF7,0xF7,,,,,, 0, 0
.endm
/**
* break
*
* Immediately ends the current loop, popping it from the call stack, but
* does not branch to the end of the loop.
*
* Programming Note: Do not allow execution to later flow into loopend, as
* the call stack would be popped twice.
*/
.macro break
_wr_cmd_id break, 0xF6,0xF6,0xF6,,,,,, 0, 0
.endm
/**
* bgez <label:lbl>
*
* Branches to `label` if TR >= 0.
*/
.macro bgez label
_wr_cmd_id bgez, 0xF5,0xF5,0xF5,,,,,, 0, 0
_wr_lbl \label
.endm
/**
* rjump <label:lbl>
*
* Branches to `label` unconditionally.
* `label` is a relative offset rather than an absolute offset, making it appropriate
* for use in position-independent code.
* Note that the range is reduced compared to absolute jumps, only labels within a
* signed 8-bit (+/-128) range are reachable.
*/
.macro rjump label
_wr_cmd_id rjump, 0xF4,0xF4,0xF4,,,,,, 0, 0
_wr_8_rel \label
.endm
/**
* rbeqz <label:lbl>
*
* Branches to `label` if TR == 0.
* `label` is a relative offset rather than an absolute offset, making it appropriate
* for use in position-independent code.
* Note that the range is reduced compared to absolute branches, only labels within a
* signed 8-bit (+/-128) range are reachable.
*/
.macro rbeqz label
_wr_cmd_id rbeqz, 0xF3,0xF3,0xF3,,,,,, 0, 0
_wr_8_rel \label
.endm
/**
* rbltz <label:lbl>
*
* Branches to `label` if TR < 0.
* `label` is a relative offset rather than an absolute offset, making it appropriate
* for use in position-independent code.
* Note that the range is reduced compared to absolute branches, only labels within a
* signed 8-bit (+/-128) range are reachable.
*/
.macro rbltz label
_wr_cmd_id rbltz, 0xF2,0xF2,0xF2,,,,,, 0, 0
_wr_8_rel \label
.endm
/**
* allocnotelist <num:u8>
*
* Clears the channel note pool and reallocates it with space for `num` notes.
*/
.macro allocnotelist num
_wr_cmd_id allocnotelist, 0xF1,0xF1,,,,,,, 0, 0
_wr_u8 \num
.endm
/**
* freenotelist
*
* Clears the channel note pool.
*/
.macro freenotelist
_wr_cmd_id freenotelist, 0xF0,0xF0,,,,,,, 0, 0
.endm
/**
* unk_EF
*
* Has no function.
*/
.macro unk_EF arg1, arg2
_wr_cmd_id unk_EF, 0xEF,,,,,,,, 0, 0
_wr_s16 \arg1
_w_u8 \arg2
.endm
/**
* bendfine <amt:s8>
*
* Fine-tunes the pitch bend amount for the channel or layer.
*/
.macro bendfine amt
_wr_cmd_id bendfine, ,0xEE,0xCE,,,,,, 0, 0
_wr_s8 \amt
.endm
/**
* gain <value:u8>
*
* Sets the channel gain (multiplicative volume scale factor) to the provided qu4.4 fixed-point value.
*/
.macro gain value
_wr_cmd_id gain, ,0xED,,,,,,, 0, 0
_wr_u8 \value
.endm
/**
* vibreset
*
* Resets channel vibrato, filter, gain, sustain, etc. state.
*/
.macro vibreset
_wr_cmd_id vibreset, ,0xEC,,,,,,, 0, 0
.endm
/**
* fontinstr <fontId:u8> <instId:u8>
*
* Updates the soundfont and instrument for the channel simultaneously.
*/
.macro fontinstr fontId, instId
_wr_cmd_id fontinstr, ,0xEB,,,,,,, 0, 0
_wr_u8 \fontId
_wr_u8 \instId
.endm
/**
* notepri <priority1:u4> <priority2:u4>
*
* Set note priorities.
*/
.macro notepri priority1, priority2
_check_arg_bitwidth_u \priority1, 4
_check_arg_bitwidth_u \priority2, 4
_wr_cmd_id notepri, ,0xE9,,,,,,, 0, 0
_wr_u8 (\priority1 << 4) | \priority2
.endm
/**
* params <muteBhv:u8> <noteAllocPolicy:u8> <channelPriority:u8> <transposition:u8>
* <pan:u8> <panWeight:u8> <reverb:u8> <reverbIndex:u8>
*
* Sets various channel parameters.
*/
.macro params muteBhv, noteAllocPolicy, channelPriority, transposition, pan, panWeight, reverb, reverbIndex
_wr_cmd_id params, ,0xE8,,,,,,, 0, 0
_wr_u8 \muteBhv
_wr_u8 \noteAllocPolicy
_wr_u8 \channelPriority
_wr_u8 \transposition
_wr_u8 \pan
_wr_u8 \panWeight
_wr_u8 \reverb
_wr_u8 \reverbIndex
.endm
/**
* ldparams <label:lbl>
*
* Sets various channel parameters by loading them from `label`. The data
* is ordered in the same way as the arguments in `params`.
*/
.macro ldparams label
_wr_cmd_id ldparams, ,0xE7,,,,,,, 0, 0
_wr_lbl \label
.endm
/**
* samplebook <value:u8>
*
* Sets the sample book mode.
*/
.macro samplebook value
_wr_cmd_id samplebook, ,0xE6,,,,,,, 0, 0
_wr_u8 \value
.endm
/**
* reverbidx <arg:u8>
*
* Sets the channel reverb.
*/
.macro reverbidx arg
_wr_cmd_id reverbidx, ,0xE5,,,,,,, 0, 0
_wr_u8 \arg
.endm
/**
* reverbidx <arg:u8>
*
* Sets the channel vibrato delay.
*/
.macro vibdelay arg
_wr_cmd_id vibdelay, ,0xE3,,,,,,, 0, 0
_wr_u8 \arg
.endm
/**
* vibdepthgrad <extentStart:u8> <extentTarget:u8> <extentChangeDelay:u8>
*
* Sets the vibrato extent.
*/
.macro vibdepthgrad arg0, arg1, arg2
_wr_cmd_id vibdepthgrad, ,0xE2,,,,,,, 0, 0
_wr_u8 \arg0
_wr_u8 \arg1
_wr_u8 \arg2
.endm
/**
* vibfreqgrad <rateStart:u8> <rateTarget:u8> <rateChangeDelay:u8>
*
* Sets the vibrato rate.
*/
.macro vibfreqgrad arg0, arg1, arg2
_wr_cmd_id vibfreqgrad, ,0xE1,,,,,,, 0, 0
_wr_u8 \arg0
_wr_u8 \arg1
_wr_u8 \arg2
.endm
/**
* volexp <amt:u8>
*
* Changes the expression amount for the channel.
*/
.macro volexp amt
_wr_cmd_id volexp, ,0xE0,,,,,,, 0, 0
_wr_u8 \amt
.endm
/**
* transpose <semitones:s8>
*
* Sets the transposition amount. Transposition shifts all note pitches by the
* provided number of semitones.
*/
.macro transpose semitones
_wr_cmd_id transpose, 0xDF,0xDB,0xC2,,,,,, 0, 0
_wr_s8 \semitones
.endm
/**
* rtranspose <semitones:s8>
*
* Adjusts the transposition amount. This is only available at the top sequence level.
*/
.macro rtranspose semitones
_wr_cmd_id rtranspose, 0xDE,,,,,,,, 0, 0
_wr_s8 \semitones
.endm
/**
* freqscale <arg:s16>
*
* Sets the freqScale for the current channel.
*/
.macro freqscale arg
_wr_cmd_id freqscale, ,0xDE,,,,,,, 0, 0
_wr_s16 \arg
.endm
/**
* tempo <bpm:u8>
*
* Changes the tempo of the sequence.
*/
.macro tempo bpm
_wr_cmd_id tempo, 0xDD,,,,,,,, 0, 0
_wr_u8 \bpm
.endm
/**
* tempochg <arg:s8>
*
* Sets the tempoChange for the sequence.
*/
.macro tempochg arg
_wr_cmd_id tempochg, 0xDC,,,,,,,, 0, 0
_wr_s8 \arg
.endm
/**
* pan <amt:u8>
*
* Changes the pan amount for a channel.
*/
.macro pan pan
/* pan can only take values in 0..127 */
_check_arg_bitwidth_u \pan, 7
_wr_cmd_id pan, ,0xDD,,,,,,, 0, 0
_wr_u8 \pan
.endm
/**
* panweight <weight:u8>
*
* Controls how much the final pan value is influenced by the channel pan and layer pan.
* The layer pan is set by the drum instrument pan.
* A value of 0 ignores channel pan, while a value of 127 ignores layer pan.
*
* finalPan = (weight * channelPan + (128 - weight) * layerPan) / 128
*/
.macro panweight weight
/* weight can only take values in 0..127 */
_check_arg_bitwidth_u \weight, 7
_wr_cmd_id panweight, ,0xDC,,,,,,, 0, 0
_wr_u8 \weight
.endm
/**
* vol <amt:u8>
*
* Sets the volume amount for this sequence or channel.
*/
.macro vol amt
_wr_cmd_id vol, 0xDB,0xDF,,,,,,, 0, 0
_wr_u8 \amt
.endm
/**
* volmode <mode:u8> <fadeTimer:s16>
*
* TODO DESCRIPTION
*/
.macro volmode mode, fadeTimer
_wr_cmd_id volmode, 0xDA,,,,,,,, 0, 0
_wr_u8 \mode
_wr_u16 \fadeTimer
.endm
/**
* volscale <arg:u8>
*
* Sets the fadeVolumeScale for the sequence.
*/
.macro volscale arg
_wr_cmd_id volscale, 0xD9,,,,,,,, 0, 0
_wr_u8 \arg
.endm
/**
* releaserate <release:u8>
*
* Sets the envelope release rate for this channel or layer.
*/
.macro releaserate release
_wr_cmd_id releaserate, ,0xD9,0xCF,,,,,, 0, 0
_wr_u8 \release
.endm
/**
* vibdepth <arg:u8>
*
* Sets the vibrato depth for the channel.
*/
.macro vibdepth arg
_wr_cmd_id vibdepth, ,0xD8,,,,,,, 0, 0
_wr_u8 \arg
.endm
/**
* vibfreq <arg:u8>
*
* Sets the vibrato rate for the channel.
*/
.macro vibfreq arg
_wr_cmd_id vibfreq, ,0xD7,,,,,,, 0, 0
_wr_u8 \arg
.endm
/**
* initchan <bitmask:u16>
*
* Initializes the channels marked in the provided bitmask.
*
* e.g. initchan 0b1 initializes channel 0.
* initchan 0b101 initializes channels 0 and 2.
*/
.macro initchan bitmask
_wr_cmd_id initchan, 0xD7,,,,,,,, 0, 0
_wr_u16 \bitmask
.endm
/**
* freechan <bitmask:u16>
*
* Frees the channels marked in the provided bitmask.
*/
.macro freechan bitmask
_wr_cmd_id freechan, 0xD6,,,,,,,, 0, 0
_wr_u16 \bitmask
.endm
/**
* mutescale <arg:s8>
*
* Sets the muteVolumeScale for the sequence.
*/
.macro mutescale arg
_wr_cmd_id mutescale, 0xD5,,,,,,,, 0, 0
_wr_s8 \arg
.endm
/**
* mute
*
* Mutes the sequence player.
*/
.macro mute
_wr_cmd_id mute, 0xD4,,,,,,,, 0, 0
.endm
/**
* reverb <amt:u8>
*
* Sets the reverb amount for this channel.
*/
.macro reverb amt
_wr_cmd_id reverb, ,0xD4,,,,,,, 0, 0
_wr_u8 \amt
.endm
/**
* mutebhv <flags:u8>
*
* Sets mute behavior for this sequence or channel.
*/
.macro mutebhv flags
_wr_cmd_id mutebhv, 0xD3,0xCA,,,,,,, 0, 0
_wr_u8 \flags
.endm
/**
* bend <amt:s8>
*
* Sets the pitch bend amount for this channel.
*/
.macro bend amt
_wr_cmd_id bend, ,0xD3,,,,,,, 0, 0
_wr_s8 \amt
.endm
/**
* ldshortvelarr <label:lbl>
*
* Sets the location of SHORTVELTBL.
*/
.macro ldshortvelarr label
_wr_cmd_id ldshortvelarr, 0xD2,,,,,,,, 0, 0
_wr_lbl \label
.endm
/**
* sustain <value:u8>
*
* Sets the adsr sustain value for this channel.
*/
.macro sustain value
_wr_cmd_id sustain, ,0xD2,,,,,,, 0, 0
_wr_u8 \value
.endm
/**
* ldshortgatearr <label:lbl>
*
* Sets the location of SHORTGATETBL.
*/
.macro ldshortgatearr label
_wr_cmd_id ldshortgatearr, 0xD1,,,,,,,, 0, 0
_wr_lbl \label
.endm
/**
* notealloc <arg:u8>
*
* Sets the noteAllocPolicy for either the sequence or the current channel.
*/
.macro notealloc arg
_wr_cmd_id notealloc, 0xD0,0xD1,,,,,,, 0, 0
_wr_u8 \arg
.endm
/**
* effects <headset:bool> <type:b2> <strongR:b1> <strongL:b1> <strongRvrbR:b1> <strongRvrbL:b1>
*
* Sets stereo effects.
*/
.macro effects headset, type, strongR, strongL, strongRvrbR, strongRvrbL
_check_arg_bitwidth_u \headset, 1
_check_arg_bitwidth_u \type, 2
_check_arg_bitwidth_u \strongR, 1
_check_arg_bitwidth_u \strongL, 1
_check_arg_bitwidth_u \strongRvrbR, 1
_check_arg_bitwidth_u \strongRvrbL, 1
_wr_cmd_id effects, ,0xD0,,,,,,, 0, 0
_wr_u8 (\headset << 7) | (\type << 4) | (\strongR << 3) | (\strongL << 2) | (\strongRvrbR << 1) | (\strongRvrbL << 0)
.endm
/**
* stptrtoseq <label:lbl>
*
* Stores TP -> label
*/
.macro stptrtoseq label
_wr_cmd_id stptrtoseq, ,0xCF,,,,,,, 0, 0
_wr_lbl \label
.endm
/**
* ldptr <label:lbl>
*
* Loads label -> TP
*/
.macro ldptr label
_wr_cmd_id ldptr, ,0xCE,,,,,,, 0, 0
_wr_lbl \label
.endm
/**
* ldptr <imm:u16>
*
* Loads imm -> TP
*/
.macro ldptri imm
_wr_cmd_id ldptr, ,0xCE,,,,,,, 0, 0
_wr_u16 \imm
.endm
/**
* rand <max:u8>
*
* Stores a random number in the range [0, max) into TR. If max is 0 the range is [0, 255]
*/
.macro rand max
_wr_cmd_id rand, 0xCE,0xB8,,,,,,, 0, 0
_wr_u8 \max
.endm
/**
* [sequence] dyncall <table:lbl>
*
* Jumps to table[TR], treating table as a u16 array, pushing the next PC to the call stack
*
* [channel] dyncall
*
* Jumps to DYNTBL16[TR], pushing the next PC to the call stack
*/
.macro dyncall table=-1
.if \table == -1
_wr_cmd_id dyncall, ,0xE4,,,,,,, 0, 0
.else
_wr_cmd_id dyncall, 0xCD,,,,,,,, 0, 0
_wr_lbl \table
.endif
.endm
/**
* ldi <imm:u8>
*
* Loads the immediate value `imm` into TR.
*/
.macro ldi imm
_wr_cmd_id ldi, 0xCC,0xCC,,,,,,, 0, 0
_wr_u8 \imm
.endm
/**
* and <imm:u8>
*
* Computes TR = TR & imm
*/
.macro and imm
_wr_cmd_id and, 0xC9,0xC9,,,,,,, 0, 0
_wr_u8 \imm
.endm
/**
* sub <imm:u8>
*
* Computes TR = TR - imm
*/
.macro sub imm
_wr_cmd_id sub, 0xC8,0xC8,,,,,,, 0, 0
_wr_u8 \imm
.endm
/**
* stseq <imm:u8> <label:lbl>
*
* Stores the u8 value `TR + imm` to the location specified by `label`.
*/
.macro stseq imm, label
_wr_cmd_id stseq, 0xC7,0xC7,,,,,,, 0, 0
_wr_u8 \imm
_wr_lbl \label
.endm
/**
* stop
*
* Immediately stops the sequence or channel.
*/
.macro stop
_wr_cmd_id stop, 0xC6,0xEA,,,,,,, 0, 0
.endm
/**
* font <fontId:u8>
*
* Set the current soundfont for this channel to `fontId`.
*/
.macro font fontId
_wr_cmd_id font, ,0xC6,,,,,,, 0, 0
_wr_u8 \fontId
.endm
/**
* scriptctr <arg:u16>
*
* Sets scriptCounter to arg.
*
* scriptCounter usually increments every time the sequence is updated but is otherwise
* never used, so changing it with this instruction has no useful effects.
*/
.macro scriptctr arg
_wr_cmd_id scriptctr, 0xC5,,,,,,,, 0, 0
_wr_u16 \arg
.endm
/**
* dyntbllookup
*
* Loads DYNTBL16[TR] -> DYNTBL
* unless TR is -1, in which case nothing happens.
*/
.macro dyntbllookup
_wr_cmd_id dyntbllookup, ,0xC5,,,,,,, 0, 0
.endm
/**
* runseq <seqPlayer:u8> <seqId:u8>
*
* Plays the sequence seqId on seqPlayer.
*/
.macro runseq seqPlayer, seqId
_wr_cmd_id runseq, 0xC4,,,,,,,, 0, 0
_wr_u8 \seqPlayer
_wr_u8 \seqId
.endm
#if (MML_VERSION == MML_VERSION_MM)
/**
* mutechan <arg0:s16>
*
* TODO DESCRIPTION
*/
.macro mutechan arg0
_wr_cmd_id mutechan, 0xC3,,,,,,,, 0, 0
_wr_s16 \arg0
.endm
#endif
/**
* noshort
*
* Disable short notes encoding.
*/
.macro noshort
_wr_cmd_id noshort, ,0xC4,,,,,,, 0, 0
.endm
/**
* short
*
* Enable short notes encoding.
*/
.macro short
_wr_cmd_id short, ,0xC3,,,,,,, 0, 0
.endm
/**
* dyntbl <label:lbl>
*
* Loads label -> DYNTBL
*/
.macro dyntbl label
_wr_cmd_id dyntbl, ,0xC2,,,,,,, 0, 0
_wr_lbl \label
.endm
/**
* instr <instNum:u8>
*
* Set instrument `instNum` from the current soundfont as the active instrument for this channel or layer.
*/
.macro instr instNum
_wr_cmd_id instr, ,0xC1,0xC6,,,,,, 0, 0
_wr_u8 \instNum
.endm
#if (MML_VERSION == MML_VERSION_MM)
/**
* unk_BE <arg0:u8>
*
* TODO DESCRIPTION
*/
.macro unk_BE arg0
_wr_cmd_id unk_BE, ,0xBE,,,,,,, 0, 0
_wr_u8 \arg0
.endm
#endif
/**
* randptr <range:u16> <offset:u16>
*
* Assigns a random number sampled from [offset,offset+range) -> TP
*
* If range is 0, it is treated as 65536.
*/
.macro randptr range, offset
#if (MML_VERSION == MML_VERSION_OOT)
_wr_cmd_id randptr, ,0xBD,,,,,,, 0, 0
#else
_wr_cmd_id randptr, ,0xA8,,,,,,, 0, 0
#endif
_wr_u16 \range
_wr_u16 \offset
.endm
#if (MML_VERSION == MML_VERSION_MM)
/**
* samplestart <arg0:u8>
*
* TODO DESCRIPTION
*/
.macro samplestart arg
_wr_cmd_id samplestart, ,0xBD,,,,,,, 0, 0
_wr_u8 \arg
.endm
/**
* unk_A7 <arg:u8>
*
* TODO DESCRIPTION
*/
.macro unk_A7 arg
_wr_cmd_id unk_A7, ,0xA7,,,,,,, 0, 0
_wr_u8 \arg
.endm
/**
* unk_A6 <arg0:u8> <arg1:s16>
*
* TODO DESCRIPTION
*/
.macro unk_A6 arg0, arg1
_wr_cmd_id unk_A6, ,0xA6,,,,,,, 0, 0
_wr_u8 \arg0
_wr_s16 \arg1
.endm
/**
* unk_A5
*
* TODO DESCRIPTION
*/
.macro unk_A5
_wr_cmd_id unk_A5, ,0xA5,,,,,,, 0, 0
.endm
/**
* unk_A4 <arg:u8>
*
* TODO DESCRIPTION
*/
.macro unk_A4 arg
_wr_cmd_id unk_A4, ,0xA4,,,,,,, 0, 0
_wr_u8 \arg
.endm
/**
* unk_A3
*
* TODO DESCRIPTION
*/
.macro unk_A3
_wr_cmd_id unk_A3, ,0xA3,,,,,,, 0, 0
.endm
/**
* unk_A2 <arg:s16>
*
* TODO DESCRIPTION
*/
.macro unk_A2 arg
_wr_cmd_id unk_A2, ,0xA2,,,,,,, 0, 0
_wr_s16 \arg
.endm
/**
* unk_A1
*
* TODO DESCRIPTION
*/
.macro unk_A1
_wr_cmd_id unk_A1, ,0xA1,,,,,,, 0, 0
.endm
/**
* unk_A0 <arg:s16>
*
* TODO DESCRIPTION
*/
.macro unk_A0 arg
_wr_cmd_id unk_A0, ,0xA0,,,,,,, 0, 0
_wr_s16 \arg
.endm
#endif
/**
* ptradd <value:lbl>
*
* Computes TP += value
*/
.macro ptradd value
_wr_cmd_id ptradd, ,0xBC,,,,,,, 0, 0
_wr_lbl \value
.endm
/**
* ptraddi <value:u16>
*
* Like ptradd but for immediates instead of labels
*
* Computes TP += value
*/
.macro ptraddi value
_wr_cmd_id ptradd, ,0xBC,,,,,,, 0, 0
_wr_u16 \value
.endm
/**
* combfilter <arg0:u8> <arg1:u16>
*
* Enable comb filter.
* TODO args? arg0=16,arg1=val<<8 maps well to midi chorus
*/
.macro combfilter arg0, arg1
_wr_cmd_id combfilter, ,0xBB,,,,,,, 0, 0
_wr_u8 \arg0
_wr_u16 \arg1
.endm
/**
* randgate <range:u8>
*
* Sets the range of random note gateTime fluctuations.
*
* NOTE: This feature is bugged. If this is non-zero it will actually use the range set by randvel.
*/
.macro randgate range
_wr_cmd_id randgate, ,0xBA,,,,,,, 0, 0
_wr_u8 \range
.endm
/**
* randvel <range:u8>
*
* Sets the range for random note velocity fluctuations.
*/
.macro randvel range
_wr_cmd_id randvel, ,0xB9,,,,,,, 0, 0
_wr_u8 \range
.endm
/**
* rand <max:u16>
*
* Stores a random number in the range [0, max) into TP. If max is 0 the range is [0, 65535]
*/
.macro randtoptr max
_wr_cmd_id randtoptr, ,0xB7,,,,,,, 0, 0
_wr_u16 \max
.endm
/**
* dyntblv
*
* Loads DYNTBL8[TR] -> TR
*/
.macro dyntblv
_wr_cmd_id dyntblv, ,0xB6,,,,,,, 0, 0
.endm
/**
* dyntbltoptr
*
* Loads DYNTBL16[TR] -> TP
*/
.macro dyntbltoptr
_wr_cmd_id dyntbltoptr, ,0xB5,,,,,,, 0, 0
.endm
/**
* ptrtodyntbl
*
* Transfers TP -> DYNTBL
*/
.macro ptrtodyntbl
_wr_cmd_id ptrtodyntbl, ,0xB4,,,,,,, 0, 0
.endm
/**
* ldseqtoptr <label:lbl>
*
* Loads SEQ[label + TR * 2] -> TP
*
* Note that TR acts as an index into an array of u16 starting at label.
*/
.macro ldseqtoptr label
_wr_cmd_id ldseqtoptr, ,0xB2,,,,,,, 0, 0
_wr_lbl \label
.endm
/**
* freefilter
*
* Invalidates the current active filter buffer.
*/
.macro freefilter
_wr_cmd_id freefilter, ,0xB1,,,,,,, 0, 0
.endm
/**
* ldfilter <filter:lbl>
*
* Sets the active filter buffer to the location specified by `filter`.
*/
.macro ldfilter filter
_wr_cmd_id ldfilter, ,0xB0,,,,,,, 0, 0
_wr_lbl \filter
.endm
/**
* cdelay <delay:b4>
*
* Short delay encoding for channel sections.
* Delays by `delay` ticks.
*/
.macro cdelay delay
_wr_cmd_id cdelay, ,0x00,,,,,,, \delay, 4
.endm
/**
* ldsample <portNum:b3>
*
* Triggers an async load of a sample belonging to either:
* - The Instrument ID in TR, if type is LDSAMPLE_INST.
* - The Sound Effect ID in TP, if type is LDSAMPLE_SFX.
*
* Load status is made available in CIO[CUR_CHANNEL][portNum].
*/
.macro ldsample type, portNum
.if \type == LDSAMPLE_INST
_wr_cmd_id ldsample, ,0x10,,,,,,, \portNum, 3
.elif \type == LDSAMPLE_SFX
_wr_cmd_id ldsample, ,0x18,,,,,,, \portNum, 3
.else
.error "ldsample: invalid type"
.endif
.endm
#define LDSAMPLE_INST 0
#define LDSAMPLE_SFX 1
/**
* stcio <channelNum:b4> <portNum:u8>
*
* Stores the contents of TR into CIO[channelNum][portNum]
*/
.macro stcio channelNum, portNum
_wr_cmd_id stcio, ,0x30,,,,,,, \channelNum, 4
_wr_u8 \portNum
.endm
/**
* ldcio <channelNum:b4> <portNum:u8>
*
* Loads the contents of CIO[channelNum][portNum] into TR.
*/
.macro ldcio channelNum, portNum
_wr_cmd_id ldcio, ,0x40,,,,,,, \channelNum, 4
_wr_u8 \portNum
.endm
/**
* rldlayer <layerNum:b3> <label:rel_lbl>
*
* Opens the note layer at `label` for index `layerNum`.
* `label` is a relative offset rather than an absolute offset, making it appropriate
* for use in position-independent code.
*/
.macro rldlayer layerNum, label
_wr_cmd_id rldlayer, ,0x78,,,,,,, \layerNum, 3
_wr_16_rel \label
.endm
/**
* testlayer <layerNum:b3>
*
* Tests if the layer specified by `layerNum` is finished.
* Stores result to TR:
* - 1 if layer is finished.
* - 0 if layer is not finished.
* - -1 if layer does not exist.
*/
.macro testlayer layerNum
_wr_cmd_id testlayer, ,0x80,,,,,,, \layerNum, 3
.endm
/**
* ldlayer <layerNum:b3> <label:lbl>
*
* Opens the note layer at `label` for index `layerNum`.
*/
.macro ldlayer layerNum, label
_wr_cmd_id ldlayer, ,0x88,,,,,,, \layerNum, 3
_wr_lbl \label
.endm
/**
* dellayer <layerNum:b3>
*
* Deletes the layer specified by index `layerNum`.
*/
.macro dellayer arg
_wr_cmd_id dellayer, ,0x90,,,,,,, \arg, 3
.endm
/**
* dynldlayer <arg:b3>
*
* Allocates a new layer starting at the pointer read from DYNTBL16[TR]
*/
.macro dynldlayer arg
_wr_cmd_id dynldlayer, ,0x98,,,,,,, \arg, 3
.endm
/**
* testchan <channelNum:b4>
*
* Tests if the channel specified by index `channelNum` is enabled.
* Stores result to TR:
* - 0 if enabled
* - 1 if disabled
*/
.macro testchan channelNum
_wr_cmd_id testchan, 0x00,,,,,,,, \channelNum, 4
.endm
/**
* stopchan <channelNum:b4>
*
* Disables the channel specified by channel `channelNum`.
*/
.macro stopchan channelNum
.if ASEQ_MODE == ASEQ_MODE_SEQUENCE
_wr_cmd_id stopchan, 0x40,,,,,,,, \channelNum, 4
.else
_wr_cmd_id stopchan, ,0xCD,,,,,,, 0, 0
_wr_u8 \channelNum
.endif
.endm
/**
* subio <portNum:b4>
*
* In sequence section:
* Computes TR = TR - SIO[portNum]
*
* In channel section:
* Computes TR = TR - CIO[CUR_CHANNEL][portNum]
*/
.macro subio portNum
_wr_cmd_id subio, 0x50,0x50,,,,,,, \portNum, 4
.endm
/**
* ldres <portNum:b4> <resType:u8> <resId:u8>
*
* Load Resource.
* resType (sync with SampleBankTableType)
* - 0 = Sequence
* - 1 = Font
* - 2 = Sample
*
* resId is either a sequence id, soundfont id or samplebank id.
*
* Load status is made available in SIO[portNum].
*/
.macro ldres portNum, resType, resId
_wr_cmd_id ldres, 0x60,,,,,,,, \portNum, 4
_wr_u8 \resType
_wr_u8 \resId
.endm
/**
* Sequence Section: stio <portNum:b4>
* Channel Section: stio <portNum:b3>
*
* Moves the contents of TR to either SIO[portNum] or CIO[CUR_CHANNEL][portNum]
* depending on current section.
*/
.macro stio portNum
.if ASEQ_MODE == ASEQ_MODE_CHANNEL
_wr_cmd_id stio, ,0x70,,,,,,, \portNum, 3
.else
_wr_cmd_id stio, 0x70,,,,,,,, \portNum, 4
.endif
.endm
/**
* ldio <portNum:b4>
*
* Moves the contents of either SIO[portNum] or CIO[CUR_CHANNEL][portNum] to TR
* depending on current section.
*/
.macro ldio portNum
_wr_cmd_id ldio, 0x80,0x60,,,,,,, \portNum, 4
.endm
/**
* ldchan <channelNum:b4> <label:lbl>
*
* Opens the sequence channel for index `channelNum` with data beginning at `label`.
*/
.macro ldchan channelNum, label
_wr_cmd_id ldchan, 0x90,0x20,,,,,,, \channelNum, 4
_wr_lbl \label
.endm
/**
* rldchan <channelNum:b4> <label:rel_lbl>
*
* Opens the sequence channel for index `channelNum` with data beginning at `label`.
* `label` is a relative offset rather than an absolute offset, making it appropriate
* for use in position-independent code.
*/
.macro rldchan channelNum, label
_wr_cmd_id rldchan, 0xA0,,,,,,,, \channelNum, 4
_wr_16_rel \label
.endm
/**
* ldelay <delay:var>
*
* Delay for `delay` ticks.
*/
.macro ldelay delay
_wr_cmd_id ldelay, ,,0xC0,,,,,, 0, 0
_var \delay
.endm
/**
* Workaround for bugged delays using a larger encoding than is needed.
* Acts like ldelay but forces a long var encoding even if the arg can fit
* in a smaller encoding.
* Should never be used when not required for matching purposes.
*/
.macro lldelay delay
_wr_cmd_id lldelay, ,0xFD,0xC0,,,,,, 0, 0
_var_long \delay
.endm
/**
* shortvel <velocity:u8>
*
* Set velocity used by short notes.
*/
.macro shortvel velocity
_wr_cmd_id shortvel, ,,0xC1,,,,,, 0, 0
_wr_u8 \velocity
.endm
/**
* shortdelay <delay:var>
*
* Set delay used by short notes.
*/
.macro shortdelay delay
_wr_cmd_id shortdelay, ,,0xC3,,,,,, 0, 0
_var \delay
.endm
/**
* legato
*
* Enables legato on the current layer.
*/
.macro legato
_wr_cmd_id legato, ,,0xC4,,,,,, 0, 0
.endm
/**
* nolegato
*
* Disables legato on the current layer.
*/
.macro nolegato
_wr_cmd_id nolegato, ,,0xC5,,,,,, 0, 0
.endm
/**
* portamento <mode> <target:u8> <time:u8/var>
*
* The time argument is either a var or a u8 depending on mode
*/
.macro portamento mode, target, time
_wr_cmd_id portamento, ,,0xC7,,,,,, 0, 0
_wr_u8 \mode
_wr_u8 \target
.if (\mode & 0x80) != 0
_wr_u8 \time
.else
_var \time
.endif
.endm
/**
* noportamento
*
* Disables portamento on the current layer.
*/
.macro noportamento
_wr_cmd_id noportamento, ,,0xC8,,,,,, 0, 0
.endm
/**
* shortgate <gateTime:u8>
*
* Sets gate time for short notes.
*/
.macro shortgate gateTime
_wr_cmd_id shortgate, ,,0xC9,,,,,, 0, 0
_wr_u8 \gateTime
.endm
/**
* notepan <pan:u8>
*
* Sets the pan for this layer. A value of 64 is center.
*/
.macro notepan pan
/* pan can only take values in 0..127 */
_check_arg_bitwidth_u \pan, 7
_wr_cmd_id notepan, ,,0xCA,,,,,, 0, 0
_wr_u8 \pan
.endm
/**
* nodrumpan
*
* Instructs the layer to ignore the pan value in drum instruments and only
* use pan set in the layer.
*/
.macro nodrumpan
_wr_cmd_id nodrumpan, ,,0xCC,,,,,, 0, 0
.endm
/**
* stereo <type:b2> <strongR:b1> <strongL:b1> <strongRvrbR:b1> <strongRvrbL:b1>
*
* TODO DESCRIPTION
*/
#define STEREO_OPCODE 0xCD
.macro stereo type, strongR, strongL, strongRvrbR, strongRvrbL
_check_arg_bitwidth_u \type, 2
_check_arg_bitwidth_u \strongR, 1
_check_arg_bitwidth_u \strongL, 1
_check_arg_bitwidth_u \strongRvrbR, 1
_check_arg_bitwidth_u \strongRvrbL, 1
_wr_cmd_id stereo, ,,STEREO_OPCODE,,,,,, 0, 0
_wr_u8 (\type << 4) | (\strongR << 3) | (\strongL << 2) | (\strongRvrbR << 1) | (\strongRvrbL << 0)
.endm
/**
* ldshortvel <velocity:b4>
*
* Sets the velocity used in short notes by reading from SHORTVELTBL[velocity]
*/
.macro ldshortvel velocity
_wr_cmd_id ldshortvel, ,,0xD0,,,,,, \velocity, 4
.endm
/**
* ldshortgate <gateTime:b4>
*
* Sets the gate time used in short notes by reading from SHORTGATETBL[gateTime]
*/
.macro ldshortgate gateTime
_wr_cmd_id ldshortgate, ,,0xE0,,,,,, \gateTime, 4
.endm
#if (MML_VERSION == MML_VERSION_MM)
/**
* unk_F0 <arg:s16>
*
* TODO DESCRIPTION
*/
.macro unk_F0 arg
_wr_cmd_id unk_F0, ,,0xF0,,,,,, 0, 0
_wr_s16 \arg
.endm
/**
* surroundeffect <arg:u8>
*
* TODO DESCRIPTION
*/
#define SURROUNDEFFECT_OPCODE 0xF1
.macro surroundeffect arg
_wr_cmd_id surroundeffect, ,,SURROUNDEFFECT_OPCODE,,,,,, 0, 0
_wr_u8 \arg
.endm
#endif
/**
* notedvg <pitch:b6> <delay:var> <velocity:u8> <gateTime:u8>
*
* Plays a note with the current instrument settings for a specified length of time.
*
* pitch : The note to play. Since pitch is only 6 bits it can only hold values 0..63, for access to more values
* use `transpose` first to shift the base pitch.
* delay : The time to play the note for, plus the delay before executing the next instruction.
* velocity : The relative volume.
* gateTime : The proportion of the delay for which the sound does not play.
*
* This instruction must only be used when long notes are enabled with the noshort instruction.
*/
#define NOTEDVG_OPCODE 0x00
.macro notedvg pitch, delay, velocity, gateTime
_wr_cmd_id notedvg, ,,0x00,,,,,, \pitch, 6
_var \delay
_wr_u8 \velocity
_wr_u8 \gateTime
.endm
/**
* notedv <pitch:b6> <delay:var> <velocity:u8>
*
* Like notedvg, but gateTime is fixed to 0 so there is no delay between when the sound stops and the next instruction.
*
* This instruction must only be used when long notes are enabled with the noshort instruction.
*/
#define NOTEDV_OPCODE 0x40
.macro notedv pitch, delay, velocity
_wr_cmd_id notedv, ,,NOTEDV_OPCODE,,,,,, \pitch, 6
_var \delay
_wr_u8 \velocity
.endm
/* Workaround for bugs in vanilla sequences, force long encoding for delay. This should not typically be used. */
.macro noteldv pitch, delay, velocity
_wr_cmd_id noteldv, ,,0x40,,,,,, \pitch, 6
_var_long \delay
_wr_u8 \velocity
.endm
/**
* notevg <pitch:b6> <velocity:u8> <gateTime:u8>
*
* Like notedvg, but delay is assumed to be the same as the delay for a previous note instruction.
*
* This instruction must only be used when long notes are enabled with the noshort instruction.
*/
.macro notevg pitch, velocity, gateTime
_wr_cmd_id notevg, ,,0x80,,,,,, \pitch, 6
_wr_u8 \velocity
_wr_u8 \gateTime
.endm
/**
* shortdvg <pitch:b6> <delay:var>
*
* Like notedvg, but encodes shorter by requiring that the velocity and gateTime be specified in advance using the
* shortvel and shortgate instructions.
*
* This instruction must only be used when short notes are enabled with the short instruction.
*/
.macro shortdvg pitch, delay
_wr_cmd_id shortdvg, ,,0x00,,,,,, \pitch, 6
_var \delay
.endm
/**
* shortdv <pitch:b6>
*
* Like shortdvg, but gateTime is fixed to 0 so there is no delay between when the sound stops and the next
* instruction. Uses the delay set by shortdelay, and the velocity set by shortvel.
*
* This instruction must only be used when short notes are enabled with the short instruction.
*/
.macro shortdv pitch
_wr_cmd_id shortdv, ,,0x40,,,,,, \pitch, 6
.endm
/**
* shortvg <pitch:b6>
*
* Like shortdvg, but delay is assumed to be the same as the delay for a previous note instruction. Uses the velocity
* set by shortvel.
*
* This instruction must only be used when short notes are enabled with the short instruction.
*/
.macro shortvg pitch
_wr_cmd_id shortvg, ,,0x80,,,,,, \pitch, 6
.endm
/**
* entry <label:lbl>
*
* For dyntable entries.
*/
.macro entry label
_wr_lbl \label
.endm
/**
* point <delay:s16> <arg:s16>
*
* Envelope point
*/
.macro point delay, arg
.if ASEQ_MODE != ASEQ_MODE_ENVELOPE
_section_invalid point
.endif
_wr_s16 \delay
_wr_s16 \arg
.endm
/**
* disable
*
* Envelope disable
*/
.macro disable
.if ASEQ_MODE != ASEQ_MODE_ENVELOPE
_section_invalid disable
.endif
_wr_u16 0
_wr_s16 0
.endm
/**
* hang
*
* Envelope hang
*/
.macro hang
.if ASEQ_MODE != ASEQ_MODE_ENVELOPE
_section_invalid hang
.endif
_wr_u16 0xFFFF // -1
_wr_s16 0
.endm
/**
* goto <index:s16>
*
* Envelope goto
*/
.macro goto index
.if ASEQ_MODE != ASEQ_MODE_ENVELOPE
_section_invalid goto
.endif
_wr_u16 0xFFFE // -2
_wr_s16 \index
.endm
/**
* restart
*
* Envelope restart
*/
.macro restart
.if ASEQ_MODE != ASEQ_MODE_ENVELOPE
_section_invalid restart
.endif
_wr_u16 0xFFFD // -3
_wr_s16 0
.endm
#endif /* _LANGUAGE_ASEQ */
#endif /* ASEQ_H */