/**
 *  @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

/**
 *  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



/**
 *  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


/**
 *  Command Opcode IDs
 */

// control flow commands
#define ASEQ_OP_CONTROL_FLOW_FIRST 0xF2
#define ASEQ_OP_RBLTZ   0xF2
#define ASEQ_OP_RBEQZ   0xF3
#define ASEQ_OP_RJUMP   0xF4
#define ASEQ_OP_BGEZ    0xF5
#define ASEQ_OP_BREAK   0xF6
#define ASEQ_OP_LOOPEND 0xF7
#define ASEQ_OP_LOOP    0xF8
#define ASEQ_OP_BLTZ    0xF9
#define ASEQ_OP_BEQZ    0xFA
#define ASEQ_OP_JUMP    0xFB
#define ASEQ_OP_CALL    0xFC
#define ASEQ_OP_DELAY   0xFD
#define ASEQ_OP_DELAY1  0xFE
#define ASEQ_OP_END     0xFF

// sequence commands
#define ASEQ_OP_SEQ_TESTCHAN        0x00 // low nibble used as argument
#define ASEQ_OP_SEQ_STOPCHAN        0x40 // low nibble used as argument
#define ASEQ_OP_SEQ_SUBIO           0x50 // low nibble used as argument
#define ASEQ_OP_SEQ_LDRES           0x60 // low nibble used as argument
#define ASEQ_OP_SEQ_STIO            0x70 // low nibble used as argument
#define ASEQ_OP_SEQ_LDIO            0x80 // low nibble used as argument
#define ASEQ_OP_SEQ_LDCHAN          0x90 // low nibble used as argument
#define ASEQ_OP_SEQ_RLDCHAN         0xA0 // low nibble used as argument
#define ASEQ_OP_SEQ_LDSEQ           0xB0 // low nibble used as argument
#if (MML_VERSION == MML_VERSION_MM)
#define ASEQ_OP_SEQ_C2              0xC2
#define ASEQ_OP_SEQ_C3              0xC3
#endif
#define ASEQ_OP_SEQ_RUNSEQ          0xC4
#define ASEQ_OP_SEQ_SCRIPTCTR       0xC5
#define ASEQ_OP_SEQ_STOP            0xC6
#define ASEQ_OP_SEQ_STSEQ           0xC7
#define ASEQ_OP_SEQ_SUB             0xC8
#define ASEQ_OP_SEQ_AND             0xC9
#define ASEQ_OP_SEQ_LDI             0xCC
#define ASEQ_OP_SEQ_DYNCALL         0xCD
#define ASEQ_OP_SEQ_RAND            0xCE
#define ASEQ_OP_SEQ_NOTEALLOC       0xD0
#define ASEQ_OP_SEQ_LDSHORTGATEARR  0xD1
#define ASEQ_OP_SEQ_LDSHORTVELARR   0xD2
#define ASEQ_OP_SEQ_MUTEBHV         0xD3
#define ASEQ_OP_SEQ_MUTE            0xD4
#define ASEQ_OP_SEQ_MUTESCALE       0xD5
#define ASEQ_OP_SEQ_FREECHAN        0xD6
#define ASEQ_OP_SEQ_INITCHAN        0xD7
#define ASEQ_OP_SEQ_VOLSCALE        0xD9
#define ASEQ_OP_SEQ_VOLMODE         0xDA
#define ASEQ_OP_SEQ_VOL             0xDB
#define ASEQ_OP_SEQ_TEMPOCHG        0xDC
#define ASEQ_OP_SEQ_TEMPO           0xDD
#define ASEQ_OP_SEQ_RTRANSPOSE      0xDE
#define ASEQ_OP_SEQ_TRANSPOSE       0xDF
#define ASEQ_OP_SEQ_EF              0xEF
#define ASEQ_OP_SEQ_FREENOTELIST    0xF0
#define ASEQ_OP_SEQ_ALLOCNOTELIST   0xF1

// channel commands
#define ASEQ_OP_CHAN_CDELAY         0x00 // low nibble used as argument
#define ASEQ_OP_CHAN_LDSAMPLE       0x10 // low nibble used as argument
#define ASEQ_OP_CHAN_LDCHAN         0x20 // low nibble used as argument
#define ASEQ_OP_CHAN_STCIO          0x30 // low nibble used as argument
#define ASEQ_OP_CHAN_LDCIO          0x40 // low nibble used as argument
#define ASEQ_OP_CHAN_SUBIO          0x50 // low nibble used as argument
#define ASEQ_OP_CHAN_LDIO           0x60 // low nibble used as argument
#define ASEQ_OP_CHAN_STIO           0x70 // lower 3 bits used as argument
#define ASEQ_OP_CHAN_RLDLAYER       0x78 // lower 3 bits used as argument
#define ASEQ_OP_CHAN_TESTLAYER      0x80 // lower 3 bits used as argument
#define ASEQ_OP_CHAN_LDLAYER        0x88 // lower 3 bits used as argument
#define ASEQ_OP_CHAN_DELLAYER       0x90 // lower 3 bits used as argument
#define ASEQ_OP_CHAN_DYNLDLAYER     0x98 // lower 3 bits used as argument
#if (MML_VERSION == MML_VERSION_MM)
#define ASEQ_OP_CHAN_A0             0xA0
#define ASEQ_OP_CHAN_A1             0xA1
#define ASEQ_OP_CHAN_A2             0xA2
#define ASEQ_OP_CHAN_A3             0xA3
#define ASEQ_OP_CHAN_A4             0xA4
#define ASEQ_OP_CHAN_A5             0xA5
#define ASEQ_OP_CHAN_A6             0xA6
#define ASEQ_OP_CHAN_A7             0xA7
#define ASEQ_OP_CHAN_RANDPTR        0xA8
#endif
#define ASEQ_OP_CHAN_LDFILTER       0xB0
#define ASEQ_OP_CHAN_FREEFILTER     0xB1
#define ASEQ_OP_CHAN_LDSEQTOPTR     0xB2
#define ASEQ_OP_CHAN_FILTER         0xB3
#define ASEQ_OP_CHAN_PTRTODYNTBL    0xB4
#define ASEQ_OP_CHAN_DYNTBLTOPTR    0xB5
#define ASEQ_OP_CHAN_DYNTBLV        0xB6
#define ASEQ_OP_CHAN_RANDTOPTR      0xB7
#define ASEQ_OP_CHAN_RAND           0xB8
#define ASEQ_OP_CHAN_RANDVEL        0xB9
#define ASEQ_OP_CHAN_RANDGATE       0xBA
#define ASEQ_OP_CHAN_COMBFILTER     0xBB
#define ASEQ_OP_CHAN_PTRADD         0xBC
#if (MML_VERSION == MML_VERSION_OOT)
#define ASEQ_OP_CHAN_RANDPTR        0xBD
#endif
#if (MML_VERSION == MML_VERSION_MM)
#define ASEQ_OP_CHAN_SAMPLESTART    0xBD
#define ASEQ_OP_CHAN_UNK_BE         0xBE
#endif
#define ASEQ_OP_CHAN_INSTR          0xC1
#define ASEQ_OP_CHAN_DYNTBL         0xC2
#define ASEQ_OP_CHAN_SHORT          0xC3
#define ASEQ_OP_CHAN_NOSHORT        0xC4
#define ASEQ_OP_CHAN_DYNTBLLOOKUP   0xC5
#define ASEQ_OP_CHAN_FONT           0xC6
#define ASEQ_OP_CHAN_STSEQ          0xC7
#define ASEQ_OP_CHAN_SUB            0xC8
#define ASEQ_OP_CHAN_AND            0xC9
#define ASEQ_OP_CHAN_MUTEBHV        0xCA
#define ASEQ_OP_CHAN_LDSEQ          0xCB
#define ASEQ_OP_CHAN_LDI            0xCC
#define ASEQ_OP_CHAN_STOPCHAN       0xCD
#define ASEQ_OP_CHAN_LDPTR          0xCE
#define ASEQ_OP_CHAN_STPTRTOSEQ     0xCF
#define ASEQ_OP_CHAN_EFFECTS        0xD0
#define ASEQ_OP_CHAN_NOTEALLOC      0xD1
#define ASEQ_OP_CHAN_SUSTAIN        0xD2
#define ASEQ_OP_CHAN_BEND           0xD3
#define ASEQ_OP_CHAN_REVERB         0xD4
#define ASEQ_OP_CHAN_VIBFREQ        0xD7
#define ASEQ_OP_CHAN_VIBDEPTH       0xD8
#define ASEQ_OP_CHAN_RELEASERATE    0xD9
#define ASEQ_OP_CHAN_ENV            0xDA
#define ASEQ_OP_CHAN_TRANSPOSE      0xDB
#define ASEQ_OP_CHAN_PANWEIGHT      0xDC
#define ASEQ_OP_CHAN_PAN            0xDD
#define ASEQ_OP_CHAN_FREQSCALE      0xDE
#define ASEQ_OP_CHAN_VOL            0xDF
#define ASEQ_OP_CHAN_VOLEXP         0xE0
#define ASEQ_OP_CHAN_VIBFREQGRAD    0xE1
#define ASEQ_OP_CHAN_VIBDEPTHGRAD   0xE2
#define ASEQ_OP_CHAN_VIBDELAY       0xE3
#define ASEQ_OP_CHAN_DYNCALL        0xE4
#define ASEQ_OP_CHAN_REVERBIDX      0xE5
#define ASEQ_OP_CHAN_SAMPLEBOOK     0xE6
#define ASEQ_OP_CHAN_LDPARAMS       0xE7
#define ASEQ_OP_CHAN_PARAMS         0xE8
#define ASEQ_OP_CHAN_NOTEPRI        0xE9
#define ASEQ_OP_CHAN_STOP           0xEA
#define ASEQ_OP_CHAN_FONTINSTR      0xEB
#define ASEQ_OP_CHAN_VIBRESET       0xEC
#define ASEQ_OP_CHAN_GAIN           0xED
#define ASEQ_OP_CHAN_BENDFINE       0xEE
#define ASEQ_OP_CHAN_FREENOTELIST   0xF0
#define ASEQ_OP_CHAN_ALLOCNOTELIST  0xF1

// layer commands
#define ASEQ_OP_LAYER_NOTEDVG       0x00
#define ASEQ_OP_LAYER_NOTEDV        0x40
#define ASEQ_OP_LAYER_NOTEVG        0x80
#define ASEQ_OP_LAYER_LDELAY        0xC0
#define ASEQ_OP_LAYER_SHORTVEL      0xC1
#define ASEQ_OP_LAYER_TRANSPOSE     0xC2
#define ASEQ_OP_LAYER_SHORTDELAY    0xC3
#define ASEQ_OP_LAYER_LEGATO        0xC4
#define ASEQ_OP_LAYER_NOLEGATO      0xC5
#define ASEQ_OP_LAYER_INSTR         0xC6
#define ASEQ_OP_LAYER_PORTAMENTO    0xC7
#define ASEQ_OP_LAYER_NOPORTAMENTO  0xC8
#define ASEQ_OP_LAYER_SHORTGATE     0xC9
#define ASEQ_OP_LAYER_NOTEPAN       0xCA
#define ASEQ_OP_LAYER_ENV           0xCB
#define ASEQ_OP_LAYER_NODRUMPAN     0xCC
#define ASEQ_OP_LAYER_STEREO        0xCD
#define ASEQ_OP_LAYER_BENDFINE      0xCE
#define ASEQ_OP_LAYER_RELEASERATE   0xCF
#define ASEQ_OP_LAYER_LDSHORTVEL    0xD0 // low nibble used as an argument
#define ASEQ_OP_LAYER_LDSHORTGATE   0xE0 // low nibble used as an argument
#if (MML_VERSION == MML_VERSION_MM)
#define ASEQ_OP_LAYER_F0            0xF0
#define ASEQ_OP_LAYER_F1            0xF1
#endif


#ifdef _LANGUAGE_ASEQ

/**
 *  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, ASEQ_OP_SEQ_LDSEQ,,,,,,,, \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, ,ASEQ_OP_CHAN_LDSEQ,,,,,,, 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, ,ASEQ_OP_CHAN_FILTER,,,,,,, 0, 0
        _wr_u8 (\lowpassCutoff << 4) | (\highpassCutoff)
    .endm

    /* `env` changes structure based on current section. */
    .purgem env
    .macro env label
        _wr_cmd_id env, ,ASEQ_OP_CHAN_ENV,,,,,,, 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, ,,ASEQ_OP_LAYER_ENV,,,,,, 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 .note.name section */
    .pushsection .note.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 & 0xFFFF
.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, ASEQ_OP_END,ASEQ_OP_END,ASEQ_OP_END,,,,,, 0, 0
.endm

/**
 * delay1
 *
 *  Delays for one tick.
 */
.macro delay1
    _wr_cmd_id  delay1, ASEQ_OP_DELAY1,ASEQ_OP_DELAY1,,,,,,, 0, 0
.endm

/**
 * delay <delay:var>
 *
 *  Delays for `delay` ticks.
 */
.macro delay delay
    _wr_cmd_id  delay, ASEQ_OP_DELAY,ASEQ_OP_DELAY,,,,,,, 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, ASEQ_OP_CALL,ASEQ_OP_CALL,ASEQ_OP_CALL,,,,,, 0, 0
    _wr_lbl     \label
.endm

/**
 * jump <label:lbl>
 *
 *  Branches to `label` unconditionally.
 */
.macro jump label
    _wr_cmd_id  jump, ASEQ_OP_JUMP,ASEQ_OP_JUMP,ASEQ_OP_JUMP,,,,,, 0, 0
    _wr_lbl     \label
.endm

/**
 * beqz <label:lbl>
 *
 *  Branches to `label` if TR == 0.
 */
.macro beqz label
    _wr_cmd_id  beqz, ASEQ_OP_BEQZ,ASEQ_OP_BEQZ,ASEQ_OP_BEQZ,,,,,, 0, 0
    _wr_lbl     \label
.endm

/**
 * bltz <label:lbl>
 *
 *  Branches to `label` if TR < 0.
 */
.macro bltz label
    _wr_cmd_id  beqz, ASEQ_OP_BLTZ,ASEQ_OP_BLTZ,ASEQ_OP_BLTZ,,,,,, 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, ASEQ_OP_LOOP,ASEQ_OP_LOOP,ASEQ_OP_LOOP,,,,,, 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, ASEQ_OP_LOOPEND,ASEQ_OP_LOOPEND,ASEQ_OP_LOOPEND,,,,,, 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, ASEQ_OP_BREAK,ASEQ_OP_BREAK,ASEQ_OP_BREAK,,,,,, 0, 0
.endm

/**
 * bgez <label:lbl>
 *
 *  Branches to `label` if TR >= 0.
 */
.macro bgez label
    _wr_cmd_id  bgez, ASEQ_OP_BGEZ,ASEQ_OP_BGEZ,ASEQ_OP_BGEZ,,,,,, 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, ASEQ_OP_RJUMP,ASEQ_OP_RJUMP,ASEQ_OP_RJUMP,,,,,, 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, ASEQ_OP_RBEQZ,ASEQ_OP_RBEQZ,ASEQ_OP_RBEQZ,,,,,, 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, ASEQ_OP_RBLTZ,ASEQ_OP_RBLTZ,ASEQ_OP_RBLTZ,,,,,, 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, ASEQ_OP_SEQ_ALLOCNOTELIST,ASEQ_OP_CHAN_ALLOCNOTELIST,,,,,,, 0, 0
    _wr_u8      \num
.endm

/**
 * freenotelist
 *
 *  Clears the channel note pool.
 */
.macro freenotelist
    _wr_cmd_id  freenotelist, ASEQ_OP_SEQ_FREENOTELIST,ASEQ_OP_CHAN_FREENOTELIST,,,,,,, 0, 0
.endm

/**
 * unk_EF
 *
 *  Has no function.
 */
.macro unk_EF arg1, arg2
    _wr_cmd_id  unk_EF, ASEQ_OP_SEQ_EF,,,,,,,, 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, ,ASEQ_OP_CHAN_BENDFINE,ASEQ_OP_LAYER_BENDFINE,,,,,, 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, ,ASEQ_OP_CHAN_GAIN,,,,,,, 0, 0
    _wr_u8      \value
.endm

/**
 * vibreset
 *
 *  Resets channel vibrato, filter, gain, sustain, etc. state.
 */
.macro vibreset
    _wr_cmd_id  vibreset, ,ASEQ_OP_CHAN_VIBRESET,,,,,,, 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, ,ASEQ_OP_CHAN_FONTINSTR,,,,,,, 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, ,ASEQ_OP_CHAN_NOTEPRI,,,,,,, 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, ,ASEQ_OP_CHAN_PARAMS,,,,,,, 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, ,ASEQ_OP_CHAN_LDPARAMS,,,,,,, 0, 0
    _wr_lbl     \label
.endm

/**
 * samplebook <value:u8>
 *
 *  Sets the sample book mode.
 */
.macro samplebook value
    _wr_cmd_id  samplebook, ,ASEQ_OP_CHAN_SAMPLEBOOK,,,,,,, 0, 0
    _wr_u8      \value
.endm

/**
 * reverbidx <arg:u8>
 *
 *  Sets the channel reverb.
 */
.macro reverbidx arg
    _wr_cmd_id  reverbidx, ,ASEQ_OP_CHAN_REVERBIDX,,,,,,, 0, 0
    _wr_u8      \arg
.endm

/**
 * reverbidx <arg:u8>
 *
 *  Sets the channel vibrato delay.
 */
.macro vibdelay arg
    _wr_cmd_id  vibdelay, ,ASEQ_OP_CHAN_VIBDELAY,,,,,,, 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, ,ASEQ_OP_CHAN_VIBDEPTHGRAD,,,,,,, 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, ,ASEQ_OP_CHAN_VIBFREQGRAD,,,,,,, 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, ,ASEQ_OP_CHAN_VOLEXP,,,,,,, 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, ASEQ_OP_SEQ_TRANSPOSE,ASEQ_OP_CHAN_TRANSPOSE,ASEQ_OP_LAYER_TRANSPOSE,,,,,, 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, ASEQ_OP_SEQ_RTRANSPOSE,,,,,,,, 0, 0
    _wr_s8      \semitones
.endm

/**
 * freqscale <arg:s16>
 *
 *  Sets the freqScale for the current channel.
 */
.macro freqscale arg
    _wr_cmd_id  freqscale, ,ASEQ_OP_CHAN_FREQSCALE,,,,,,, 0, 0
    _wr_s16     \arg
.endm

/**
 * tempo <bpm:u8>
 *
 *  Changes the tempo of the sequence.
 */
.macro tempo bpm
    _wr_cmd_id  tempo, ASEQ_OP_SEQ_TEMPO,,,,,,,, 0, 0
    _wr_u8      \bpm
.endm

/**
 * tempochg <arg:s8>
 *
 *  Sets the tempoChange for the sequence.
 */
.macro tempochg arg
    _wr_cmd_id  tempochg, ASEQ_OP_SEQ_TEMPOCHG,,,,,,,, 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, ,ASEQ_OP_CHAN_PAN,,,,,,, 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, ,ASEQ_OP_CHAN_PANWEIGHT,,,,,,, 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, ASEQ_OP_SEQ_VOL,ASEQ_OP_CHAN_VOL,,,,,,, 0, 0
    _wr_u8      \amt
.endm

/**
 * volmode <mode:u8> <fadeTimer:s16>
 *
 *  TODO DESCRIPTION
 */
.macro volmode mode, fadeTimer
    _wr_cmd_id  volmode, ASEQ_OP_SEQ_VOLMODE,,,,,,,, 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, ASEQ_OP_SEQ_VOLSCALE,,,,,,,, 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, ,ASEQ_OP_CHAN_RELEASERATE,ASEQ_OP_LAYER_RELEASERATE,,,,,, 0, 0
    _wr_u8      \release
.endm

/**
 * vibdepth <arg:u8>
 *
 *  Sets the vibrato depth for the channel.
 */
.macro vibdepth arg
    _wr_cmd_id  vibdepth, ,ASEQ_OP_CHAN_VIBDEPTH,,,,,,, 0, 0
    _wr_u8      \arg
.endm

/**
 * vibfreq <arg:u8>
 *
 *  Sets the vibrato rate for the channel.
 */
.macro vibfreq arg
    _wr_cmd_id  vibfreq, ,ASEQ_OP_CHAN_VIBFREQ,,,,,,, 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, ASEQ_OP_SEQ_INITCHAN,,,,,,,, 0, 0
    _wr_u16     \bitmask
.endm

/**
 * freechan <bitmask:u16>
 *
 *  Frees the channels marked in the provided bitmask.
 */
.macro freechan bitmask
    _wr_cmd_id  freechan, ASEQ_OP_SEQ_FREECHAN,,,,,,,, 0, 0
    _wr_u16     \bitmask
.endm

/**
 * mutescale <arg:s8>
 *
 *  Sets the muteVolumeScale for the sequence.
 */
.macro mutescale arg
    _wr_cmd_id  mutescale, ASEQ_OP_SEQ_MUTESCALE,,,,,,,, 0, 0
    _wr_s8      \arg
.endm

/**
 * mute
 *
 *  Mutes the sequence player.
 */
.macro mute
    _wr_cmd_id  mute, ASEQ_OP_SEQ_MUTE,,,,,,,, 0, 0
.endm

/**
 * reverb <amt:u8>
 *
 *  Sets the reverb amount for this channel.
 */
.macro reverb amt
    _wr_cmd_id  reverb, ,ASEQ_OP_CHAN_REVERB,,,,,,, 0, 0
    _wr_u8      \amt
.endm

/**
 * mutebhv <flags:u8>
 *
 *  Sets mute behavior for this sequence or channel.
 */
.macro mutebhv flags
    _wr_cmd_id  mutebhv, ASEQ_OP_SEQ_MUTEBHV,ASEQ_OP_CHAN_MUTEBHV,,,,,,, 0, 0
    _wr_u8      \flags
.endm

/**
 * bend <amt:s8>
 *
 *  Sets the pitch bend amount for this channel.
 */
.macro bend amt
    _wr_cmd_id  bend, ,ASEQ_OP_CHAN_BEND,,,,,,, 0, 0
    _wr_s8      \amt
.endm

/**
 * ldshortvelarr <label:lbl>
 *
 *  Sets the location of SHORTVELTBL.
 */
.macro ldshortvelarr label
    _wr_cmd_id  ldshortvelarr, ASEQ_OP_SEQ_LDSHORTVELARR,,,,,,,, 0, 0
    _wr_lbl     \label
.endm

/**
 * sustain <value:u8>
 *
 *  Sets the adsr sustain value for this channel.
 */
.macro sustain value
    _wr_cmd_id  sustain, ,ASEQ_OP_CHAN_SUSTAIN,,,,,,, 0, 0
    _wr_u8      \value
.endm

/**
 * ldshortgatearr <label:lbl>
 *
 *  Sets the location of SHORTGATETBL.
 */
.macro ldshortgatearr label
    _wr_cmd_id  ldshortgatearr, ASEQ_OP_SEQ_LDSHORTGATEARR,,,,,,,, 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, ASEQ_OP_SEQ_NOTEALLOC,ASEQ_OP_CHAN_NOTEALLOC,,,,,,, 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, ,ASEQ_OP_CHAN_EFFECTS,,,,,,, 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, ,ASEQ_OP_CHAN_STPTRTOSEQ,,,,,,, 0, 0
    _wr_lbl     \label
.endm

/**
 * ldptr <label:lbl>
 *
 *  Loads label -> TP
 */
.macro ldptr label
    _wr_cmd_id  ldptr, ,ASEQ_OP_CHAN_LDPTR,,,,,,, 0, 0
    _wr_lbl     \label
.endm

/**
 * ldptr <imm:u16>
 *
 *  Loads imm -> TP
 */
.macro ldptri imm
    _wr_cmd_id  ldptr, ,ASEQ_OP_CHAN_LDPTR,,,,,,, 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, ASEQ_OP_SEQ_RAND,ASEQ_OP_CHAN_RAND,,,,,,, 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, ,ASEQ_OP_CHAN_DYNCALL,,,,,,, 0, 0
    .else
        _wr_cmd_id  dyncall, ASEQ_OP_SEQ_DYNCALL,,,,,,,, 0, 0
        _wr_lbl     \table
    .endif
.endm

/**
 * ldi <imm:u8>
 *
 *  Loads the immediate value `imm` into TR.
 */
.macro ldi imm
    _wr_cmd_id  ldi, ASEQ_OP_SEQ_LDI,ASEQ_OP_CHAN_LDI,,,,,,, 0, 0
    _wr_u8      \imm
.endm

/**
 * and <imm:u8>
 *
 *  Computes TR = TR & imm
 */
.macro and imm
    _wr_cmd_id  and, ASEQ_OP_SEQ_AND,ASEQ_OP_CHAN_AND,,,,,,, 0, 0
    _wr_u8      \imm
.endm

/**
 * sub <imm:u8>
 *
 *  Computes TR = TR - imm
 */
.macro sub imm
    _wr_cmd_id  sub, ASEQ_OP_SEQ_SUB,ASEQ_OP_CHAN_SUB,,,,,,, 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, ASEQ_OP_SEQ_STSEQ,ASEQ_OP_CHAN_STSEQ,,,,,,, 0, 0
    _wr_u8      \imm
    _wr_lbl     \label
.endm

/**
 * stop
 *
 *  Immediately stops the sequence or channel.
 */
.macro stop
    _wr_cmd_id  stop, ASEQ_OP_SEQ_STOP,ASEQ_OP_CHAN_STOP,,,,,,, 0, 0
.endm

/**
 * font <fontId:u8>
 *
 *  Set the current soundfont for this channel to `fontId`.
 */
.macro font fontId
    _wr_cmd_id  font, ,ASEQ_OP_CHAN_FONT,,,,,,, 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, ASEQ_OP_SEQ_SCRIPTCTR,,,,,,,, 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, ,ASEQ_OP_CHAN_DYNTBLLOOKUP,,,,,,, 0, 0
.endm

/**
 * runseq <seqPlayer:u8> <seqId:u8>
 *
 *  Plays the sequence seqId on seqPlayer.
 */
.macro runseq seqPlayer, seqId
    _wr_cmd_id  runseq, ASEQ_OP_SEQ_RUNSEQ,,,,,,,, 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, ASEQ_OP_SEQ_C3,,,,,,,, 0, 0
        _wr_s16     \arg0
    .endm

#endif

/**
 * noshort
 *
 *  Disable short notes encoding.
 */
.macro noshort
    _wr_cmd_id  noshort, ,ASEQ_OP_CHAN_NOSHORT,,,,,,, 0, 0
.endm

/**
 * short
 *
 *  Enable short notes encoding.
 */
.macro short
    _wr_cmd_id  short, ,ASEQ_OP_CHAN_SHORT,,,,,,, 0, 0
.endm

/**
 * dyntbl <label:lbl>
 *
 *  Loads label -> DYNTBL
 */
.macro dyntbl label
    _wr_cmd_id  dyntbl, ,ASEQ_OP_CHAN_DYNTBL,,,,,,, 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, ,ASEQ_OP_CHAN_INSTR,ASEQ_OP_LAYER_INSTR,,,,,, 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, ,ASEQ_OP_CHAN_UNK_BE,,,,,,, 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
    _wr_cmd_id  randptr, ,ASEQ_OP_CHAN_RANDPTR,,,,,,, 0, 0
    _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, ,ASEQ_OP_CHAN_SAMPLESTART,,,,,,, 0, 0
        _wr_u8      \arg
    .endm

    /**
     * unk_A7 <arg:u8>
     *
     *  TODO DESCRIPTION
     */
    .macro unk_A7 arg
        _wr_cmd_id  unk_A7, ,ASEQ_OP_CHAN_A7,,,,,,, 0, 0
        _wr_u8      \arg
    .endm

    /**
     * unk_A6 <arg0:u8> <arg1:s16>
     *
     *  TODO DESCRIPTION
     */
    .macro unk_A6 arg0, arg1
        _wr_cmd_id  unk_A6, ,ASEQ_OP_CHAN_A6,,,,,,, 0, 0
        _wr_u8      \arg0
        _wr_s16     \arg1
    .endm

    /**
     * unk_A5
     *
     *  TODO DESCRIPTION
     */
    .macro unk_A5
        _wr_cmd_id  unk_A5, ,ASEQ_OP_CHAN_A5,,,,,,, 0, 0
    .endm

    /**
     * unk_A4 <arg:u8>
     *
     *  TODO DESCRIPTION
     */
    .macro unk_A4 arg
        _wr_cmd_id  unk_A4, ,ASEQ_OP_CHAN_A4,,,,,,, 0, 0
        _wr_u8      \arg
    .endm

    /**
     * unk_A3
     *
     *  TODO DESCRIPTION
     */
    .macro unk_A3
        _wr_cmd_id  unk_A3, ,ASEQ_OP_CHAN_A3,,,,,,, 0, 0
    .endm

    /**
     * unk_A2 <arg:s16>
     *
     *  TODO DESCRIPTION
     */
    .macro unk_A2 arg
        _wr_cmd_id  unk_A2, ,ASEQ_OP_CHAN_A2,,,,,,, 0, 0
        _wr_s16     \arg
    .endm

    /**
     * unk_A1
     *
     *  TODO DESCRIPTION
     */
    .macro unk_A1
        _wr_cmd_id  unk_A1, ,ASEQ_OP_CHAN_A1,,,,,,, 0, 0
    .endm

    /**
     * unk_A0 <arg:s16>
     *
     *  TODO DESCRIPTION
     */
    .macro unk_A0 arg
        _wr_cmd_id  unk_A0, ,ASEQ_OP_CHAN_A0,,,,,,, 0, 0
        _wr_s16     \arg
    .endm

#endif

/**
 * ptradd <value:lbl>
 *
 *  Computes TP += value
 */
.macro ptradd value
    _wr_cmd_id  ptradd, ,ASEQ_OP_CHAN_PTRADD,,,,,,, 0, 0
    _wr_lbl     \value
.endm

/**
 * ptraddi <value:s16>
 *
 *  Like ptradd but for immediates instead of labels
 *
 *  Computes TP += value
 */
.macro ptraddi value
    _wr_cmd_id  ptradd, ,ASEQ_OP_CHAN_PTRADD,,,,,,, 0, 0
    _wr_s16     \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, ,ASEQ_OP_CHAN_COMBFILTER,,,,,,, 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, ,ASEQ_OP_CHAN_RANDGATE,,,,,,, 0, 0
    _wr_u8      \range
.endm

/**
 * randvel <range:u8>
 *
 *  Sets the range for random note velocity fluctuations.
 */
.macro randvel range
    _wr_cmd_id  randvel, ,ASEQ_OP_CHAN_RANDVEL,,,,,,, 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, ,ASEQ_OP_CHAN_RANDTOPTR,,,,,,, 0, 0
    _wr_u16     \max
.endm

/**
 * dyntblv
 *
 *  Loads DYNTBL8[TR] -> TR
 */
.macro dyntblv
    _wr_cmd_id  dyntblv, ,ASEQ_OP_CHAN_DYNTBLV,,,,,,, 0, 0
.endm

/**
 * dyntbltoptr
 *
 *  Loads DYNTBL16[TR] -> TP
 */
.macro dyntbltoptr
    _wr_cmd_id  dyntbltoptr, ,ASEQ_OP_CHAN_DYNTBLTOPTR,,,,,,, 0, 0
.endm

/**
 * ptrtodyntbl
 *
 *  Transfers TP -> DYNTBL
 */
.macro ptrtodyntbl
    _wr_cmd_id  ptrtodyntbl, ,ASEQ_OP_CHAN_PTRTODYNTBL,,,,,,, 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, ,ASEQ_OP_CHAN_LDSEQTOPTR,,,,,,, 0, 0
    _wr_lbl     \label
.endm

/**
 * freefilter
 *
 *  Invalidates the current active filter buffer.
 */
.macro freefilter
    _wr_cmd_id  freefilter, ,ASEQ_OP_CHAN_FREEFILTER,,,,,,, 0, 0
.endm

/**
 * ldfilter <filter:lbl>
 *
 *  Sets the active filter buffer to the location specified by `filter`.
 */
.macro ldfilter filter
    _wr_cmd_id  ldfilter, ,ASEQ_OP_CHAN_LDFILTER,,,,,,, 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, ,ASEQ_OP_CHAN_CDELAY,,,,,,, \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, ,ASEQ_OP_CHAN_LDSAMPLE,,,,,,, \portNum, 3
    .elif \type == LDSAMPLE_SFX
        _wr_cmd_id  ldsample, ,ASEQ_OP_CHAN_LDSAMPLE | 8,,,,,,, \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, ,ASEQ_OP_CHAN_STCIO,,,,,,, \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, ,ASEQ_OP_CHAN_LDCIO,,,,,,, \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, ,ASEQ_OP_CHAN_RLDLAYER,,,,,,, \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, ,ASEQ_OP_CHAN_TESTLAYER,,,,,,, \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, ,ASEQ_OP_CHAN_LDLAYER,,,,,,, \layerNum, 3
    _wr_lbl     \label
.endm

/**
 * dellayer <layerNum:b3>
 *
 *  Deletes the layer specified by index `layerNum`.
 */
.macro dellayer arg
    _wr_cmd_id  dellayer, ,ASEQ_OP_CHAN_DELLAYER,,,,,,, \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, ,ASEQ_OP_CHAN_DYNLDLAYER,,,,,,, \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, ASEQ_OP_SEQ_TESTCHAN,,,,,,,, \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, ASEQ_OP_SEQ_STOPCHAN,,,,,,,, \channelNum, 4
    .else
        _wr_cmd_id  stopchan, ,ASEQ_OP_CHAN_STOPCHAN,,,,,,, 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, ASEQ_OP_SEQ_SUBIO,ASEQ_OP_CHAN_SUBIO,,,,,,, \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, ASEQ_OP_SEQ_LDRES,,,,,,,, \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, ,ASEQ_OP_CHAN_STIO,,,,,,, \portNum, 3
    .else
        _wr_cmd_id  stio, ASEQ_OP_SEQ_STIO,,,,,,,, \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, ASEQ_OP_SEQ_LDIO,ASEQ_OP_CHAN_LDIO,,,,,,, \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, ASEQ_OP_SEQ_LDCHAN,ASEQ_OP_CHAN_LDCHAN,,,,,,, \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, ASEQ_OP_SEQ_RLDCHAN,,,,,,,, \channelNum, 4
    _wr_16_rel  \label
.endm

/**
 * ldelay <delay:var>
 *
 *  Delay for `delay` ticks.
 */
.macro ldelay delay
    _wr_cmd_id  ldelay, ,,ASEQ_OP_LAYER_LDELAY,,,,,, 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, ,ASEQ_OP_DELAY,ASEQ_OP_LAYER_LDELAY,,,,,, 0, 0
    _var_long   \delay
.endm

/**
 * shortvel <velocity:u8>
 *
 * Set velocity used by short notes.
 */
.macro shortvel velocity
    _wr_cmd_id  shortvel, ,,ASEQ_OP_LAYER_SHORTVEL,,,,,, 0, 0
    _wr_u8      \velocity
.endm

/**
 * shortdelay <delay:var>
 *
 * Set delay used by short notes.
 */
.macro shortdelay delay
    _wr_cmd_id  shortdelay, ,,ASEQ_OP_LAYER_SHORTDELAY,,,,,, 0, 0
    _var        \delay
.endm

/**
 * legato
 *
 *  Enables legato on the current layer.
 */
.macro legato
    _wr_cmd_id  legato, ,,ASEQ_OP_LAYER_LEGATO,,,,,, 0, 0
.endm

/**
 * nolegato
 *
 *  Disables legato on the current layer.
 */
.macro nolegato
    _wr_cmd_id  nolegato, ,,ASEQ_OP_LAYER_NOLEGATO,,,,,, 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, ,,ASEQ_OP_LAYER_PORTAMENTO,,,,,, 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, ,,ASEQ_OP_LAYER_NOPORTAMENTO,,,,,, 0, 0
.endm

/**
 * shortgate <gateTime:u8>
 *
 *  Sets gate time for short notes.
 */
.macro shortgate gateTime
    _wr_cmd_id  shortgate, ,,ASEQ_OP_LAYER_SHORTGATE,,,,,, 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, ,,ASEQ_OP_LAYER_NOTEPAN,,,,,, 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, ,,ASEQ_OP_LAYER_NODRUMPAN,,,,,, 0, 0
.endm

/**
 * stereo <type:b2> <strongR:b1> <strongL:b1> <strongRvrbR:b1> <strongRvrbL:b1>
 *
 *  TODO DESCRIPTION
 */
.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, ,,ASEQ_OP_LAYER_STEREO,,,,,, 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, ,,ASEQ_OP_LAYER_LDSHORTVEL,,,,,, \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, ,,ASEQ_OP_LAYER_LDSHORTGATE,,,,,, \gateTime, 4
.endm

#if (MML_VERSION == MML_VERSION_MM)

    /**
     * unk_F0 <arg:s16>
     *
     *  TODO DESCRIPTION
     */
    .macro unk_F0 arg
        _wr_cmd_id  unk_F0, ,,ASEQ_OP_LAYER_F0,,,,,, 0, 0
        _wr_s16     \arg
    .endm

    /**
     * surroundeffect <arg:u8>
     *
     *  TODO DESCRIPTION
     */
    .macro surroundeffect arg
        _wr_cmd_id  surroundeffect, ,,ASEQ_OP_LAYER_F1,,,,,, 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.
 */
.macro notedvg pitch, delay, velocity, gateTime
    _wr_cmd_id  notedvg, ,,ASEQ_OP_LAYER_NOTEDVG,,,,,, \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.
 */
.macro notedv pitch, delay, velocity
    _wr_cmd_id  notedv, ,,ASEQ_OP_LAYER_NOTEDV,,,,,, \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, ,,ASEQ_OP_LAYER_NOTEDV,,,,,, \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, ,,ASEQ_OP_LAYER_NOTEVG,,,,,, \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, ,,ASEQ_OP_LAYER_NOTEDVG,,,,,, \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, ,,ASEQ_OP_LAYER_NOTEDV,,,,,, \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, ,,ASEQ_OP_LAYER_NOTEVG,,,,,, \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 */
