|
|
- include "hardware.inc"
- include "huge.inc"
-
- add_a_to_r16: MACRO
- add \2
- ld \2, a
- adc \1
- sub \2
- ld \1, a
- ENDM
-
- add_a_to_hl: MACRO
- add_a_to_r16 h, l
- ENDM
-
- add_a_to_de: MACRO
- add_a_to_r16 d, e
- ENDM
-
- ret_dont_call_playnote: MACRO
- pop hl
- pop af
- and a ; Clear carry to avoid calling `play_chX_note`
- push af
- jp hl
- ENDM
-
- add_a_ind_ret_hl: MACRO
- ld hl, \1
- add [hl]
- inc hl
- ld h, [hl]
- ld l, a
- adc h
- sub l
- ld h, a
- ENDM
-
- load_hl_ind: MACRO
- ld hl, \1
- ld a, [hl+]
- ld h, [hl]
- ld l, a
- ENDM
-
- load_de_ind: MACRO
- ld a, [\1]
- ld e, a
- ld a, [\1+1]
- ld d, a
- ENDM
-
- retMute: MACRO
- bit \1, a
- ret nz
- ENDM
-
- checkMute: MACRO
- ld a, [mute_channels]
- bit \1, a
- jr nz, \2
- ENDM
-
- ;; Maximum pattern length
- PATTERN_LENGTH EQU 64
- ;; Amount to be shifted in order to skip a channel.
- CHANNEL_SIZE_EXPONENT EQU 3
-
- SECTION "Playback variables", WRAM0
- ;; Active song descriptor
- order_cnt: db
- _start_song_descriptor_pointers:
- ;; Pointers to the song's current four orders (one per channel)
- order1: dw
- order2: dw
- order3: dw
- order4: dw
-
- ;; Pointers to the instrument tables
- duty_instruments: dw
- wave_instruments: dw
- noise_instruments: dw
-
- ;; Misc. pointers
- routines: dw
- waves: dw
- _end_song_descriptor_pointers:
-
- ;; Pointers to the current patterns (sort of a cache)
- pattern1: dw
- pattern2: dw
- pattern3: dw
- pattern4: dw
-
- ;; How long a row lasts in ticks (1 = one row per call to `hUGE_dosound`, etc. 0 translates to 256)
- ticks_per_row: db
-
- _hUGE_current_wave::
- hUGE_current_wave::
- ;; ID of the wave currently loaded into wave RAM
- current_wave: db
- hUGE_NO_WAVE equ 100
- EXPORT hUGE_NO_WAVE
-
- ;; Everything between this and `end_zero` is zero-initialized by `hUGE_init`
- start_zero:
-
- mute_channels: db
- current_order: db
- next_order: db
- row_break: db
-
- temp_note_value: dw
- row: db
- tick: db
- counter: db
-
- channels:
- ;;;;;;;;;;;
- ;;Channel 1
- ;;;;;;;;;;;
- channel1:
- channel_period1: dw
- toneporta_target1: dw
- channel_note1: db
- vibrato_tremolo_phase1: db
- envelope1: db
- highmask1: db
-
- ;;;;;;;;;;;
- ;;Channel 2
- ;;;;;;;;;;;
- channel2:
- channel_period2: dw
- toneporta_target2: dw
- channel_note2: db
- vibrato_tremolo_phase2: db
- envelope2: db
- highmask2: db
-
- ;;;;;;;;;;;
- ;;Channel 3
- ;;;;;;;;;;;
- channel3:
- channel_period3: dw
- toneporta_target3: dw
- channel_note3: db
- vibrato_tremolo_phase3: db
- envelope3: db
- highmask3: db
-
- ;;;;;;;;;;;
- ;;Channel 4
- ;;;;;;;;;;;
- channel4:
- channel_period4: dw
- toneporta_target4: dw
- channel_note4: db
- vibrato_tremolo_phase4: db
- envelope4: db
- highmask4: db
-
- end_zero:
-
- SECTION "Sound Driver", ROM0
-
- ;;; Sets up hUGEDriver to play a song.
- ;;; !!! BE SURE THAT `hUGE_dosound` WILL NOT BE CALLED WHILE THIS RUNS !!!
- ;;; Param: HL = Pointer to the "song descriptor" you wish to load (typically exported by hUGETracker).
- ;;; Destroys: AF C DE HL
- _hUGE_init::
- hUGE_init::
- ld a, [hl+] ; tempo
- ld [ticks_per_row], a
-
- ld a, [hl+]
- ld e, a
- ld a, [hl+]
- ld d, a
- ld a, [de]
- ld [order_cnt], a
-
- ld c, _end_song_descriptor_pointers - (_start_song_descriptor_pointers)
- ld de, order1
-
- .copy_song_descriptor_loop:
- ld a, [hl+]
- ld [de], a
- inc de
- dec c
- jr nz, .copy_song_descriptor_loop
-
- IF !DEF(PREVIEW_MODE)
- ;; Zero some ram
- ld c, end_zero - start_zero
- ld hl, start_zero
- xor a
- .fill_loop:
- ld [hl+], a
- dec c
- jr nz, .fill_loop
- ENDC
-
- ;; These two are zero-initialized by the loop above, so these two writes must come after
- ld a, %11110000
- ld [envelope1], a
- ld [envelope2], a
-
- ;; Force loading the next wave
- ld a, hUGE_NO_WAVE
- ld [current_wave], a
-
- ;; Preview mode needs to load the order ID from memory
- IF !DEF(PREVIEW_MODE)
- ld c, 0
- ELSE
- ld a, [current_order]
- ld c, a
- ENDC
- ;; fallthrough (load the pattern pointers)
-
- ;;; Sets all 4 pattern pointers from a certain index in the respective 4 orders.
- ;;; Param: C = The index (in increments of 2)
- ;;; Destroy: AF DE HL
- load_patterns:
- IF DEF(PREVIEW_MODE)
- db $fc ; signal order update to tracker
- ENDC
-
- ld hl, order1
- ld de, pattern1
- call .load_pattern
-
- ld hl, order2
- call .load_pattern
-
- ld hl, order3
- call .load_pattern
-
- ld hl, order4
- ;; fallthrough
-
- .load_pattern:
- ld a, [hl+]
- add c
- ld h, [hl]
- ld l, a
- adc h
- sub l
- ld h, a
-
- ld a, [hl+]
- ld [de], a
- inc de
- ld a, [hl]
- ld [de], a
- inc de
- ret
-
-
- ;;; Sets a channel's muting status.
- ;;; Muted channels are left entirely alone by the driver, so that you can repurpose them,
- ;;; for example for sound effects, CH3 sample playback, etc.
- ;;; If muting the channel, the note being played will be cut.
- ;;; Param: B = Which channel to enable; 0 for CH1, 1 for CH2, etc.
- ;;; Param: C = 0 to unmute the channel, 1 to mute it
- ;;; Destroy: A C E HL
- hUGE_mute_channel::
- ld e, $fe
- ld a, b
- or a
- jr z, .enable_cut
- .enable_loop:
- sla c
- rlc e
- dec a
- jr nz, .enable_loop
- .enable_cut:
- ld a, [mute_channels]
- and e
- or c
- ld [mute_channels], a
- and c
- jp nz, note_cut
- ret
-
-
- ;;; Reads a pattern's current row.
- ;;; Param: BC = Pointer to the pattern
- ;;; Param: [row] = Index of the current ro<
- ;;; Return: A = Note ID
- ;;; Return: B = Instrument (upper nibble) & effect code (lower nibble)
- ;;; Return: C = Effect parameter
- ;;; Destroy: HL
- get_current_row:
- ld a, [row]
- ld h, a
- ;; Multiply by 3 for the note value
- add h
- add h
-
- ld h, 0
- ld l, a
- add hl, bc ; HL now points at the 3rd byte of the note
- ld a, [hl+]
- ld b, [hl]
- inc hl
- ld c, [hl]
- ret
-
- ;;; Gets the "period" of a pattern's current note.
- ;;; Param: HL = Pointer to the pattern pointer
- ;;; Param: [row] = Index of the current row
- ;;; Param: DE = Location to write the note's index to, if applicable
- ;;; Return: HL = Note's period
- ;;; Return: CF = Set if and only if a "valid" note (i.e. not a "rest")
- ;;; Return: [DE] = Note's ID, not updated if a "rest"
- ;;; Return: B = Instrument (upper nibble) & effect code (lower nibble)
- ;;; Return: C = Effect parameter
- ;;; Destroy: AF
- get_current_note:
- ld a, [hl+]
- ld c, a
- ld b, [hl]
-
- call get_current_row
- ld hl, 0
-
- ;; If the note we found is greater than LAST_NOTE, then it's not a valid note
- ;; and nothing needs to be updated.
- cp LAST_NOTE
- ret nc
-
- ;; Store the loaded note value in channel_noteX
- ld [de], a
-
- ;;; Gets a note's "period", i.e. what should be written to NRx3 and NRx4.
- ;;; Param: A = Note ID
- ;;; Return: HL = Note's period
- ;;; Return: CF = 1
- ;;; Destroy: AF
- get_note_period:
- add a ;; double it to get index into hi/lo table
- add LOW(note_table)
- ld l, a
- adc HIGH(note_table)
- sub l
- ld h, a
- ld a, [hl+]
- ld h, [hl]
- ld l, a
-
- scf
- ret
-
- ;;; Gets a note's "polynomial counter", i.e. what should be written to NR44.
- ;;; Param: A = Note ID
- ;;; Return: A = Note's poly
- ;;; Destroy: F HL
- get_note_poly:
- ;; Invert the order of the numbers
- add 192 ; (255 - 63)
- cpl
-
- ;; Thanks to RichardULZ for this formula
- ;; https://docs.google.com/spreadsheets/d/1O9OTAHgLk1SUt972w88uVHp44w7HKEbS/edit#gid=75028951
- ; if A > 7 then begin
- ; B := (A-4) div 4;
- ; C := (A mod 4)+4;
- ; A := (C or (B shl 4))
- ; end;
-
- ; if A < 7 then return
- cp 7
- ret c
-
- ld h, a
-
- ; B := (A-4) div 4;
- sub 4
- srl a
- srl a
- ld l, a
-
- ; C := (A mod 4)+4;
- ld a, h
- and 3 ; mod 4
- add 4
-
- ; A := (C or (B shl 4))
- swap l
- or l
- ret
-
-
- ;;; Computes the pointer to a member of a channel.
- ;;; Param: B = Which channel (0 = CH1, 1 = CH2, etc.)
- ;;; Param: D = Offset within the channel struct
- ;;; Return: HL = Pointer to the channel's member
- ;;; Destroy: AF
- ptr_to_channel_member:
- ld a, b
- REPT CHANNEL_SIZE_EXPONENT
- add a
- ENDR
- add d
- ld hl, channels
- add LOW(channels)
- ld l, a
- adc HIGH(channels)
- sub l
- ld h, a
- ret
-
-
- ;;; Updates a channel's frequency, and possibly restarts it.
- ;;; Note that CH4 is *never* restarted by this!
- ;;; Param: B = Which channel to update (0 = CH1, 1 = CH2, etc.)
- ;;; Param: (ignored for CH4) A = ORed to the value written to NRx4
- ;;; Param: (for CH4) E = Note ID
- ;;; Param: (otherwise) DE = Note period
- ;;; Destroy: AF B
- ;;; Destroy: (for CH4) HL
- update_channel_freq:
- ld c, a
- ld a, [mute_channels]
- dec b
- jr z, .update_channel2
- dec b
- jr z, .update_channel3
- dec b
- jr z, .update_channel4
-
- .update_channel1:
- retMute 0
-
- ld a, e
- ldh [rAUD1LOW], a
- ld a, d
- or c
- ldh [rAUD1HIGH], a
- ret
-
- .update_channel2:
- retMute 1
-
- ld a, e
- ldh [rAUD2LOW], a
- ld a, d
- or c
- ldh [rAUD2HIGH], a
- ret
-
- .update_channel3:
- retMute 2
-
- ld a, e
- ldh [rAUD3LOW], a
- ld a, d
- or c
- ldh [rAUD3HIGH], a
- ret
-
- .update_channel4:
- retMute 3
-
- ld a, e
- call get_note_poly
- ldh [rAUD4POLY], a
- xor a
- ldh [rAUD4GO], a
- ret
-
-
- play_note_routines:
- jr play_ch1_note
- jr play_ch2_note
- jr play_ch3_note
- jr play_ch4_note
-
- play_ch1_note:
- ld a, [mute_channels]
- retMute 0
-
- ;; Play a note on channel 1 (square wave)
- ld a, [temp_note_value]
- ld [channel_period1], a
- ldh [rAUD1LOW], a
-
- ld a, [temp_note_value+1]
- ld [channel_period1+1], a
-
- ;; Get the highmask and apply it.
- ld hl, highmask1
- or [hl]
- ldh [rAUD1HIGH], a
-
- ret
-
- play_ch2_note:
- ld a, [mute_channels]
- retMute 1
-
- ;; Play a note on channel 2 (square wave)
- ld a, [temp_note_value]
- ld [channel_period2], a
- ldh [rAUD2LOW], a
-
- ld a, [temp_note_value+1]
- ld [channel_period2+1], a
-
- ;; Get the highmask and apply it.
- ld hl, highmask2
- or [hl]
- ldh [rAUD2HIGH], a
-
- ret
-
- play_ch3_note:
- ld a, [mute_channels]
- retMute 2
-
- ;; Triggering CH3 while it's reading a byte corrupts wave RAM.
- ;; To avoid this, we kill the wave channel (0 → NR30), then re-enable it.
- ;; This way, CH3 will be paused when we trigger it by writing to NR34.
- ;; TODO: what if `highmask3` bit 7 is not set, though?
- xor a
- ldh [rAUD3ENA], a
- cpl
- ldh [rAUD3ENA], a
-
- ;; Play a note on channel 3 (waveform)
- ld a, [temp_note_value]
- ld [channel_period3], a
- ldh [rAUD3LOW], a
-
- ld a, [temp_note_value+1]
- ld [channel_period3+1], a
-
- ;; Get the highmask and apply it.
- ld hl, highmask3
- or [hl]
- ldh [rAUD3HIGH], a
-
- ret
-
- play_ch4_note:
- ld a, [mute_channels]
- retMute 3
-
- ;; Play a "note" on channel 4 (noise)
- ld a, [temp_note_value]
- ld [channel_period4+1], a
- ldh [rAUD4POLY], a
-
- ;; Get the highmask and apply it.
- ld a, [highmask4]
- ldh [rAUD4GO], a
-
- ret
-
-
- ;;; Performs an effect on a given channel.
- ;;; Param: E = Channel ID (0 = CH1, 1 = CH2, etc.)
- ;;; Param: B = Effect type (upper 4 bits ignored)
- ;;; Param: C = Effect parameters (depend on FX type)
- ;;; Destroy: AF BC DE HL
- do_effect:
- ;; Strip the instrument bits off leaving only effect code
- ld a, b
- and %00001111
- ;; Multiply by 2 to get offset into table
- add a
-
- add LOW(.jump)
- ld l, a
- adc HIGH(.jump)
- sub l
- ld h, a
-
- ld a, [hl+]
- ld h, [hl]
- ld l, a
-
- ld b, e
- ld a, [tick]
- or a ; We can return right off the bat if it's tick zero
- jp hl
-
- .jump:
- ;; Jump table for effect
- dw fx_arpeggio ;0xy
- dw fx_porta_up ;1xy
- dw fx_porta_down ;2xy
- dw fx_toneporta ;3xy
- dw fx_vibrato ;4xy
- dw fx_set_master_volume ;5xy ; global
- dw fx_call_routine ;6xy
- dw fx_note_delay ;7xy
- dw fx_set_pan ;8xy ; global
- dw fx_set_duty ;9xy
- dw fx_vol_slide ;Axy
- dw fx_pos_jump ;Bxy ; global
- dw fx_set_volume ;Cxy
- dw fx_pattern_break ;Dxy ; global
- dw fx_note_cut ;Exy
- dw fx_set_speed ;Fxy ; global
-
-
- ;;; Processes (global) effect 5, "set master volume".
- ;;; Param: C = Value to write to NR50
- ;;; Param: ZF = Set if and only if on tick 0
- ;;; Destroy: A
- fx_set_master_volume:
- ret nz
-
- ld a, c
- ldh [rAUDVOL], a
- ret
-
-
- ;;; Processes effect 6, "call routine".
- ;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.)
- ;;; Param: C = Routine ID
- ;;; Param: A = Current tick
- ;;; Param: ZF = Set if and only if on tick 0
- ;;; Destroy: Anything the routine does
- fx_call_routine:
- sla c
- ld a, [routines]
- add c
- ld l, a
- ld a, [routines+1]
- adc 0
- ld h, a
-
- ld a, [hl+]
- ld h, [hl]
- ld l, a
-
- ld a, [tick]
- or a ; set zero flag if tick 0 for compatibility
- IF DEF(GBDK) ; Pass the tick counter as a SDCC call parameter
- push af
- inc sp
- push bc
- call .call_hl
- add sp, 3
- ret
-
- .call_hl:
- ENDC
- jp hl
-
-
- ;;; Processes (global) effect 8, "set pan".
- ;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.)
- ;;; Param: C = Value to write to NR51
- ;;; Param: ZF = Set if and only if on tick 0
- ;;; Destroy: A
- fx_set_pan:
- ret nz
-
- ;; Pretty simple. The editor can create the correct value here without a bunch
- ;; of bit shifting manually.
- ld a, c
- ldh [rAUDTERM], a
- ret
-
-
- ;;; Processes effect 9, "set duty cycle".
- ;;; Param: B = Current channel ID (0 = CH1, anything else = CH2)
- ;;; Param: C = Value to write to NRx1
- ;;; Param: ZF = Set if and only if on tick 0
- ;;; Destroy: AF
- fx_set_duty:
- ret nz
-
- ;; $900 = 12.5%
- ;; $940 = 25%
- ;; $980 = 50%
- ;; $9C0 = 75%
-
- ld a, b
- or a
- ld a, [mute_channels]
- jr z, .chan1
- .chan2:
- retMute 1
- ld a, c
- ldh [rAUD2LEN], a
- ret
- .chan1:
- retMute 0
- ld a, c
- ldh [rAUD1LEN], a
- ret
-
-
- ;;; Processes effect A, "volume slide".
- ;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.)
- ;;; Param: C = FX param; either nibble should be 0, otherwise weird (unspecified) behavior may arise
- ;;; Param: ZF = Set if and only if on tick 0
- ;;; Destroy: AF C DE HL
- fx_vol_slide:
- ret nz
-
- ;; This is really more of a "retrigger note with lower volume" effect and thus
- ;; isn't really that useful. Instrument envelopes should be used instead.
- ;; Might replace this effect with something different if a new effect is
- ;; ever needed.
-
- ;; check channel mute
-
- ;; 0 → $01, 1 → $02, 2 → $04, 3 → $05
- ;; Overall, these two instructions add 1 to the number.
- ;; However, the first instruction will generate a carry for inputs of $02 and $03;
- ;; the `adc` will pick the carry up, and "separate" 0 / 1 from 2 / 3 by an extra 1.
- ;; Luckily, this yields correct results for 0 ($01), 1 ($02), and 2 ($03 + 1 = $04).
- ;; We'll see about fixing 3 afterwards.
- add -2
- adc 3
- ;; After being shifted left, the inputs are $02, $04, $08 and $0A; all are valid BCD,
- ;; except for $0A. Since we just performed `add a`, DAA will correct the latter to $10.
- ;; (This should be correctly emulated everywhere, since the inputs are identical to
- ;; "regular" BCD.)
- ;; When shifting the results back, we'll thus get $01, $02, $04 and $08!
- add a
- daa
- rra
- ld d, a
- ld a, [mute_channels]
- and d
- ret nz
-
- ;; setup the up and down params
- ld a, c
- and %00001111
- ld d, a
-
- ld a, c
- and %11110000
- ld e, a
- swap e
-
- ; There are 5 bytes between each envelope register
- ld a, b
- add a
- add a
- add b
- add LOW(rAUD1ENV)
- ld c, a
-
- ldh a, [c]
- and %11110000
- swap a
- sub d
- jr nc, .cont1
- xor a
- .cont1:
- add e
- cp $10
- jr c, .cont2
- ld a, $F
- .cont2:
- swap a
- ldh [c], a
-
- ; Go to rAUDxGO, which is 2 bytes after
- inc c
- inc c
- ldh a, [c]
- or %10000000
- ldh [c], a
-
- jr play_note
-
-
- ;;; Processes effect 7, "note delay".
- ;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.)
- ;;; Param: C = Amount of ticks by which to delay the note
- ;;; Caveats: 0 never plays the note, and a delay longer than a row's duration skips the note entirely
- ;;; Param: ZF = Set if and only if on tick 0
- ;;; Destroy: AF D HL
- fx_note_delay:
- jr nz, .play_note
-
- ;; Just store the note into the channel period, and don't play a note.
- ld d, 0
- call ptr_to_channel_member
-
- ld a, [temp_note_value]
- ld [hl+], a
- ld a, [temp_note_value+1]
- ld [hl], a
-
- ;; Don't call _playnote. This is done by grabbing the return
- ;; address and manually skipping the next call instruction.
- ret_dont_call_playnote
-
- .play_note:
- cp c
- ret nz ; wait until the correct tick to play the note
-
- ;; fallthrough
-
-
- ;;; Plays a channel's current note.
- ;;; Param: B = Which channel (0 = CH1, 1 = CH2, etc.)
- ;;; Destroy: AF D HL
- play_note:
- ld d, 0
- call ptr_to_channel_member
-
- ;; TODO: Change this to accept HL instead?
- ld a, [hl+]
- ld [temp_note_value], a
- ld a, [hl]
- ld [temp_note_value+1], a
-
- ld a, b
- add a
- add LOW(play_note_routines)
- ld l, a
- adc HIGH(play_note_routines)
- sub l
- ld h, a
- jp hl
-
-
- ;;; Processes (global) effect F, "set speed".
- ;;; Param: C = New amount of ticks per row
- ;;; Param: ZF = Set if and only if on tick 0
- ;;; Destroy: A
- fx_set_speed:
- ret nz
-
- ld a, c
- ld [ticks_per_row], a
- ret
-
-
- hUGE_set_position::
- ;;; Processes (global) effect B, "position jump".
- ;;; Param: C = ID of the order to jump to
- ;;; Destroy: A
- fx_pos_jump:
- ld a, 1
- ld [row_break], a
- ld a, c
- ld [next_order], a
- ret
-
-
- ;;; Processes (global) effect D, "pattern break".
- ;;; Param: C = ID of the next order's row to start on
- ;;; Destroy: A
- fx_pattern_break:
- ld a, c
- ld [row_break], a
- ret
-
-
- ;;; Processes effect E, "note cut".
- ;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.)
- ;;; Param: C = Tick to cut the note on (TODO: what does cutting on tick 0 do?)
- ;;; Param: A = Current tick
- ;;; Destroy: A
- fx_note_cut:
- cp c
- ret nz
-
- ;; check channel mute
-
- ;; 0 → $01, 1 → $02, 2 → $04, 3 → $05
- ;; Overall, these two instructions add 1 to the number.
- ;; However, the first instruction will generate a carry for inputs of $02 and $03;
- ;; the `adc` will pick the carry up, and "separate" 0 / 1 from 2 / 3 by an extra 1.
- ;; Luckily, this yields correct results for 0 ($01), 1 ($02), and 2 ($03 + 1 = $04).
- ;; We'll see about fixing 3 afterwards.
- add -2
- adc 3
- ;; After being shifted left, the inputs are $02, $04, $08 and $0A; all are valid BCD,
- ;; except for $0A. Since we just performed `add a`, DAA will correct the latter to $10.
- ;; (This should be correctly emulated everywhere, since the inputs are identical to
- ;; "regular" BCD.)
- ;; When shifting the results back, we'll thus get $01, $02, $04 and $08!
- add a
- daa
- rra
- ld d, a
- ld a, [mute_channels]
- and d
- ret nz
-
- ;; fallthrough
-
-
- ;;; Cuts note on a channel.
- ;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.)
- ;;; Destroy: AF HL
- note_cut:
- ld a, b
- add a
- add a
- add b ; multiply by 5
- add LOW(rAUD1ENV)
- ld l, a
- ld h, HIGH(rAUD1ENV)
- xor a
- ld [hl+], a
- ld a, b
- cp 2
- ret z ; return early if CH3-- no need to retrigger note
-
- ;; Retrigger note
- inc l ; Not `inc hl` because H stays constant (= $FF)
- ld [hl], $FF
- ret
-
-
- ;;; Processes effect C, "set volume".
- ;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.)
- ;;; Param: C = Volume to set the channel to
- ;;; Param: ZF = Set if and only if on tick 0
- ;;; Destroy: AF BC
- fx_set_volume:
- ret nz ; Return if we're not on tick zero.
-
- swap c
- ld a, [mute_channels]
- dec b
- jr z, .set_chn_2_vol
- dec b
- jr z, .set_chn_3_vol
- dec b
- jr z, .set_chn_4_vol
-
- .set_chn_1_vol:
- retMute 0
-
- ldh a, [rAUD1ENV]
- and %00001111
- or c
- ldh [rAUD1ENV], a
- ret
-
- .set_chn_2_vol:
- retMute 1
-
- ldh a, [rAUD2ENV]
- and %00001111
- or c
- ldh [rAUD2ENV], a
- ret
-
- .set_chn_3_vol:
- retMute 2
-
- ;; "Quantize" the more finely grained volume control down to one of 4 values.
- ld a, c
- cp 10 << 4
- jr nc, .one
- cp 5 << 4
- jr nc, .two
- or a
- jr z, .done ; Zero maps to zero
- .three:
- ld a, %01100000
- jr .done
- .two:
- ld a, %01000000
- jr .done
- .one:
- ld a, %00100000
- .done:
- ldh [rAUD3LEVEL], a
- ret
-
- .set_chn_4_vol:
- retMute 3
-
- ld a, c
- ldh [rAUD4ENV], a
- ret
-
-
- ;;; Processes effect 4, "vibrato".
- ;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.)
- ;;; Param: C = FX param
- ;;; Param: ZF = Set if and only if on tick 0
- ;;; Destroy: AF B DE HL
- fx_vibrato:
- ret z
-
- ;; Extremely poor man's vibrato.
- ;; Speed values:
- ;; (0x0 = 1.0)
- ;; (0x1 = 0.5)
- ;; (0x3 = 0.25)
- ;; (0x7 = 0.125)
- ;; (0xf = 0.0625)
- ld d, 4
- call ptr_to_channel_member
-
- ld a, c
- and %11110000
- swap a
- ld e, a
-
- ld a, [counter]
- and e
- ld a, [hl]
- jr z, .go_up
- .restore:
- call get_note_period
- jr .finish_vibrato
- .go_up:
- call get_note_period
- ld a, c
- and %00001111
- add_a_to_hl
- .finish_vibrato:
- ld d, h
- ld e, l
- xor a
- jp update_channel_freq
-
-
- ;;; Processes effect 8, "arpeggio".
- ;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.)
- ;;; Param: C = Offsets in semitones (each nibble)
- ;;; Param: ZF = Set if and only if on tick 0
- ;;; Destroy: AF B DE HL
- fx_arpeggio:
- ret z
-
- ld d, 4
- call ptr_to_channel_member
- ld d, [hl]
-
- ld a, [tick]
- dec a
-
- ;; TODO: A crappy modulo, because it's not a multiple of four :(
-
- jr .test_greater_than_two
- .greater_than_two:
- sub 3
- .test_greater_than_two:
- cp 3
- jr nc, .greater_than_two
-
- ;; Multiply by 2 to get offset into table
- add a
-
- add LOW(.arp_options)
- ld l, a
- adc HIGH(.arp_options)
- sub l
- ld h, a
- jp hl
-
- .arp_options:
- jr .set_arp1
- jr .set_arp2
- ;; No `jr .reset_arp`
-
- .reset_arp:
- ld a, d
- jr .finish_skip_add
-
- .set_arp2:
- ld a, c
- swap a
- db $FE ; cp <imm8> gobbles next byte
-
- .set_arp1:
- ld a, c
- .finish_arp:
- and %00001111
- add d
- .finish_skip_add:
- call get_note_period
- ld d, h
- ld e, l
- xor a
- jp update_channel_freq
-
-
- ;;; Processes effect 1, "portamento up".
- ;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.)
- ;;; Param: C = How many units to slide the pitch by per tick
- ;;; Param: ZF = Set if and only if on tick 0
- ;;; Destroy: A B DE HL
- fx_porta_up:
- ret z
-
- ld d, 0
- call ptr_to_channel_member
-
- ;; Add C to 16-bit value at HL
- ld a, [hl+]
- add c
- ld e, a
- adc [hl]
- sub e
-
- ;; Write back
- .finish:
- ld d, a ; Store A for call to `update_channel_freq`
- ld [hl-], a
- ld [hl], e
-
- xor a
- jp update_channel_freq
-
-
- ;;; Processes (global) effect 2, "portamento down".
- ;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.)
- ;;; Param: C = How many units to slide the pitch down by per tick
- ;;; Param: ZF = Set if and only if on tick 0
- ;;; Destroy: A B DE HL
- fx_porta_down:
- ret z
-
- ld d, 0
- call ptr_to_channel_member
-
- ;; Subtract C from 16-bit value at [HL]
- ld a, [hl+]
- sub c
- ld e, a
- sbc a
- add [hl]
-
- ;; Write back
- jr fx_porta_up.finish
-
-
- ;;; Processes effect 2, "tone portamento".
- ;;; Param: B = Current channel ID (0 = CH1, 1 = CH2, etc.)
- ;;; Param: C = Target note
- ;;; Param: ZF = Set if and only if on tick 0
- ;;; Destroy: A B DE HL
- fx_toneporta:
- jr nz, .do_toneporta
-
- ;; We're on tick zero, so just move the temp note value into the toneporta target.
- ld d, 2
- call ptr_to_channel_member
-
- ;; If the note is nonexistent, then just return
- ld a, [temp_note_value]
- or a
- jr z, .return_skip
-
- ld [hl+], a
- ld a, [temp_note_value+1]
- ld [hl], a
-
- ;; Don't call _playnote. This is done by grabbing the return
- ;; address and manually skipping the next call instruction.
- .return_skip:
- ret_dont_call_playnote
-
- .do_toneporta:
- ld d, 0
- call ptr_to_channel_member
- push hl
-
- ;; Read current period
- ld a, [hl+]
- ld e, a
- ld a, [hl+]
- ld d, a
-
- ;; Read target period
- ld a, [hl+]
- ld h, [hl]
- ld l, a
-
- ;; Do we need to porta up, or down? Compute (current - target) and check carry to know
- sub e
- ld a, h
- sbc d
- jr c, .porta_down ; Current period (DE) is higher than target one (HL), so down we go!
-
- ;; Add offset to current freq
- ld a, e
- add c
- ld e, a
- adc d
- sub e
- ld d, a
- ;; We don't need to worry about overflow given the relatively low values we work with
-
- ld c, 0 ; The overshoot comparison should yield no carry, like the above one
- jr .check_overshoot
-
- .porta_down:
- ;; Subtract offset from current freq
- ld a, e
- sub c
- ld e, a
- sbc a
- add d
- ld d, a
- jr c, .overshot ; There will be no underflows under my watch!
-
- ld c, $FF ; The overshoot comparison should yield carry, like the above one
-
- .check_overshoot:
- ld a, l
- sub e
- ld a, h
- sbc d
- rra ; Shift carry into bit 7
- xor c ; XOR it with provided value
- rla ; Shift maybe-toggled carry back
- jr nc, .no_overshoot
- .overshot:
- ;; Override computed new period with target
- ld d, h
- ld e, l
- .no_overshoot:
-
- pop hl
- ld a, e
- ld [hl+], a
- ld [hl], d
-
- ;; Do not retrigger channel
- ld a, 6
- add_a_to_hl
- ld a, [hl]
- res 7, [hl]
- ;; B must be preserved for this
- jp update_channel_freq
-
-
- ;; TODO: Find some way to de-duplicate this code!
- ;;; Computes the pointer to a CH4 instrument.
- ;;; Param: B = The instrument's ID
- ;;; Param: DE = Instrument pointer table
- ;;; Return: DE = Pointer to the instrument
- ;;; Return: ZF = Set if and only if there was no instrument (ID == 0)
- ;;; Destroy: AF
- setup_instrument_pointer_ch4:
- ;; Call with:
- ;; Instrument/High nibble of effect in B
- ;; Stores whether the instrument was real in the Z flag
- ;; Stores the instrument pointer in DE
- ld a, b
- and %11110000
- swap a
- ret z ; If there's no instrument, then return early.
-
- dec a ; Instrument 0 is "no instrument"
- add a
- jr setup_instrument_pointer.finish
-
- ;;; Computes the pointer to an instrument.
- ;;; Param: B = The instrument's ID
- ;;; Param: DE = Instrument pointer table
- ;;; Return: DE = Pointer to the instrument
- ;;; Return: ZF = Set if and only if there was no instrument (ID == 0)
- ;;; Destroy: AF
- setup_instrument_pointer:
- ;; Call with:
- ;; Instrument/High nibble of effect in B
- ;; Stores whether the instrument was real in the Z flag
- ;; Stores the instrument pointer in DE
- ld a, b
- and %11110000
- swap a
- ret z ; If there's no instrument, then return early.
-
- dec a ; Instrument 0 is "no instrument"
- .finish:
- ;; Shift left twice to multiply by 4
- add a
- add a
-
- add_a_to_de
-
- rla ; reset the Z flag
- ret
-
-
- _hUGE_dosound_banked::
- _hUGE_dosound::
- ;;; Ticks the sound engine once.
- ;;; Destroy: AF BC DE HL
- hUGE_dosound::
- ld a, [tick]
- or a
- jp nz, process_effects
-
- ;; Note playback
- ld hl, pattern1
- ld de, channel_note1
- call get_current_note
- push af
- jr nc, .do_setvol1
-
- load_de_ind duty_instruments
- call setup_instrument_pointer
- ld a, [highmask1]
- res 7, a ; Turn off the "initial" flag
- jr z, .write_mask1
-
- checkMute 0, .do_setvol1
-
- ld a, [de]
- inc de
- ldh [rAUD1SWEEP], a
- ld a, [de]
- inc de
- ldh [rAUD1LEN], a
- ld a, [de]
- ldh [rAUD1ENV], a
- inc de
- ld a, [de]
-
- .write_mask1:
- ld [highmask1], a
-
- .do_setvol1:
- ld a, l
- ld [temp_note_value], a
- ld a, h
- ld [temp_note_value+1], a
-
- ld e, 0
- call do_effect
-
- pop af
- call c, play_ch1_note
-
- process_ch2:
- ;; Note playback
- ld hl, pattern2
- ld de, channel_note2
- call get_current_note
- push af
- jr nc, .do_setvol2
-
- load_de_ind duty_instruments
- call setup_instrument_pointer
- ld a, [highmask2]
- res 7, a ; Turn off the "initial" flag
- jr z, .write_mask2
-
- checkMute 1, .do_setvol2
-
- inc de
- ld a, [de]
- inc de
- ldh [rAUD2LEN], a
- ld a, [de]
- ldh [rAUD2ENV], a
- inc de
- ld a, [de]
-
- .write_mask2:
- ld [highmask2], a
-
- .do_setvol2:
- ld a, l
- ld [temp_note_value], a
- ld a, h
- ld [temp_note_value+1], a
-
- ld e, 1
- call do_effect
-
- pop af
- call c, play_ch2_note
-
- process_ch3:
- ld hl, pattern3
- ld de, channel_note3
- call get_current_note
-
- ld a, l
- ld [temp_note_value], a
- ld a, h
- ld [temp_note_value+1], a
-
- push af
-
- jr nc, .do_setvol3
-
- load_de_ind wave_instruments
- call setup_instrument_pointer
- ld a, [highmask3]
- res 7, a ; Turn off the "initial" flag
- jr z, .write_mask3
-
- checkMute 2, .do_setvol3
-
- ld a, [de]
- inc de
- ldh [rAUD3LEN], a
- ld a, [de]
- inc de
- ldh [rAUD3LEVEL], a
- ld a, [de]
- inc de
-
- ;; Check to see if we need to copy a wave and then do so
- ld hl, current_wave
- cp [hl]
- jr z, .no_wave_copy
- ld [hl], a
- swap a
- add_a_ind_ret_hl waves
-
- xor a
- ldh [rAUD3ENA], a
-
- FOR OFS, 16
- ld a, [hl+]
- ldh [_AUD3WAVERAM + OFS], a
- ENDR
-
- ld a, %10000000
- ldh [rAUD3ENA], a
-
- .no_wave_copy:
- ld a, [de]
-
- .write_mask3:
- ld [highmask3], a
-
- .do_setvol3:
- ld e, 2
- call do_effect
-
- pop af
- call c, play_ch3_note
-
- process_ch4:
- ld hl, pattern4
- ld a, [hl+]
- ld c, a
- ld b, [hl]
- call get_current_row
- ld [channel_note4], a
- cp LAST_NOTE
- push af
- jr nc, .do_setvol4
-
- call get_note_poly
- ld [temp_note_value], a
-
- ld de, 0
- call setup_instrument_pointer
-
- ld a, [highmask4]
- res 7, a ; Turn off the "initial" flag
- jr z, .write_mask4
-
- checkMute 3, .do_setvol4
-
- load_hl_ind noise_instruments
- sla e
- add hl, de
-
- ld a, [hl+]
- ldh [rAUD4ENV], a
-
- ld a, [hl]
- and %00111111
- ldh [rAUD4LEN], a
-
- ld a, [temp_note_value]
- ld d, a
- ld a, [hl]
- and %10000000
- swap a
- or d
- ld [temp_note_value], a
-
- ld a, [hl]
- and %01000000
- or %10000000
- .write_mask4:
- ld [highmask4], a
-
- .do_setvol4:
- ld e, 3
- call do_effect
-
- pop af
- call c, play_ch4_note
-
- ;; finally just update the tick/order/row values
- jp process_tick
-
- process_effects:
- ;; Only do effects if not on tick zero
- checkMute 0, .after_effect1
-
- ld hl, pattern1
- ld a, [hl+]
- ld c, a
- ld b, [hl]
- call get_current_row
-
- ld a, c
- or a
- jr z, .after_effect1
-
- ld e, 0
- call do_effect ; make sure we never return with ret_dont_call_playnote macro
-
- .after_effect1:
- checkMute 1, .after_effect2
-
- ld hl, pattern2
- ld a, [hl+]
- ld c, a
- ld b, [hl]
- call get_current_row
-
- ld a, c
- or a
- jr z, .after_effect2
-
- ld e, 1
- call do_effect ; make sure we never return with ret_dont_call_playnote macro
-
- .after_effect2:
- checkMute 2, .after_effect3
-
- ld hl, pattern3
- ld a, [hl+]
- ld c, a
- ld b, [hl]
- call get_current_row
-
- ld a, c
- or a
- jr z, .after_effect3
-
- ld e, 2
- call do_effect ; make sure we never return with ret_dont_call_playnote macro
-
- .after_effect3:
- checkMute 3, .after_effect4
-
- ld hl, pattern4
- ld a, [hl+]
- ld c, a
- ld b, [hl]
- call get_current_row
- cp LAST_NOTE
- jr nc, .done_macro
- ld h, a
-
- load_de_ind noise_instruments
- call setup_instrument_pointer_ch4
- jr z, .done_macro ; No instrument, thus no macro
-
- ld a, [tick]
- cp 7
- jr nc, .done_macro
-
- inc de
-
- ld l, a
- ld a, h
- ld h, 0
- add hl, de
- add [hl]
- call get_note_poly
- ld l, a
- ld a, [de]
- ld e, a
- and %10000000
- swap a
- or l
- ldh [rAUD4POLY], a
-
- ld a, e
- and %01000000
- ldh [rAUD4GO], a
-
- .done_macro:
- ld a, c
- or a
- jr z, .after_effect4
-
- ld e, 3
- call do_effect ; make sure we never return with ret_dont_call_playnote macro
-
- .after_effect4:
-
- process_tick:
- ld hl, counter
- inc [hl]
-
- ld a, [ticks_per_row]
- ld b, a
-
- ld hl, tick
- ld a, [hl]
- inc a
-
- cp b
- jr z, .newrow
-
- ld [hl], a
- ret
-
- .newrow:
- ;; Reset tick to 0
- ld [hl], 0
-
- ;; Check if we need to perform a row break or pattern break
- ld a, [row_break]
- or a
- jr z, .no_break
-
- ;; These are offset by one so we can check to see if they've
- ;; been modified
- dec a
- ld b, a
-
- ld hl, row_break
- xor a
- ld [hl-], a
- or [hl] ; a = [next_order], zf = ([next_order] == 0)
- ld [hl], 0
-
- jr z, .neworder
-
- dec a
- add a ; multiply order by 2 (they are words)
-
- jr .update_current_order
-
- .no_break:
- ;; Increment row.
- ld a, [row]
- inc a
- ld b, a
- cp PATTERN_LENGTH
- jr nz, .noreset
-
- ld b, 0
- .neworder:
- ;; Increment order and change loaded patterns
- ld a, [order_cnt]
- ld c, a
- ld a, [current_order]
- add 2
- cp c
- jr nz, .update_current_order
- xor a
- .update_current_order:
- ;; Call with:
- ;; A: The order to load
- ;; B: The row for the order to start on
- ld [current_order], a
- ld c, a
- call load_patterns
-
- .noreset:
- ld a, b
- ld [row], a
-
- IF DEF(PREVIEW_MODE)
- db $fd ; signal row update to tracker
- ENDC
- ret
-
- note_table:
- include "huge_note_table.inc"
-
-
- IF DEF(GBDK)
-
- SECTION "hUGEDriver GBDK wrappers", ROM0
-
- _hUGE_init_banked::
- ld hl, sp+2+4
- jr continue_init
- _hUGE_init::
- ld hl, sp+2
- continue_init:
- push bc
- ld a, [hl+]
- ld h, [hl]
- ld l, a
- call hUGE_init
- pop bc
- ret
-
- _hUGE_mute_channel_banked::
- ld hl, sp+3+4
- jr continue_mute
- _hUGE_mute_channel::
- ld hl, sp+3
- continue_mute:
- push bc
- ld a, [hl-]
- and 1
- ld c, a
- ld b, [hl]
- call hUGE_mute_channel
- pop bc
- ret
-
- _hUGE_set_position_banked::
- ld hl, sp+2+4
- jr continue_set_position
- _hUGE_set_position::
- ld hl, sp+2
- continue_set_position:
- push bc
- ld c, [hl]
- call hUGE_set_position
- pop bc
- ret
-
- ENDC
|