0: VRC6 Pulse 1
2: VRC6 Sawtooth
3: BGM Square 1
4: BGM Square 2
5: SFX Square 1
6: SFX Square 2
7: Drumkit (defines DCM and Noise playback)
8: Noise
7-channel layout removes Drumkit and Noise.
5-channel layout also removes SFX channels.
When a new track is loaded via routine $E249, the track's header is loaded to initialize the required channels.
Track Initialization
00 ROM Bank
01 Extra Channels
Instrument Data
00 Instrument ID
01 First Note Address (low)
02 First Note Address (high)
Each step, the program checks if a channel has an audio track specified. If so, it checks if the pause jingle is playing. Otherwise it checks if music is paused (byte $06DC), in which case BGM tracks are ignored. If both those checks fail, then the ritenuto flag (byte $01BF) is checked for BGM tracks, in which case the current step would be ignored.
Once all those checks are out of the way, note_duration decreases. If it hits 0, the next sequence of note data is evaluated. Specific note data can take up any number of bytes. Each note starts with a Router, read as follows.
NNNN AAAA
Program behavior depends on N as follows.
0 to 11: Multiply the duration of the next note by A+1; if using an envelope, reset envelope position; multiply note duration by fade_settings & #0f, set fadeout_start to value saved in upper nybble (e.g. #1C->#01); clear alternate duty attribute unless one is specified; initialize the volume envelope if enabled; get the note-specific period from a LUT based on N, adjusted by key_change (Pulse channels range from #0D5C (C₁) up to #01C5 (B₃), whereas Sawtooth channel ranges from #0F44 (C₁) up to #0205 (B₂)); shift octave up by #05-octave_shift (LSR/ROR raises the octave); set the next sustained period; add pitchbend_value to the period; write period to register.
12: Multiply duration of next note by A+1; reset envelope position; initialize control register.
13: Set next note duration to A; set duty_cycle to upper nybble of next datapoint and next_volume to lower nybble; set volume envelope to next datapoint; set note_dampen to next_volume if positive, otherwise set it to #00; set fade_settings to lower nybble of next datapoint and fade_delay_next to upper nybble. Evaluate subsequent datapoint.
14: Set octave_shift to A if less than 6, else subtract 6 from A and goto corresponding subroutine.
1: Set volume envelope to next datapoint.
2: Set fade_settings and fade_delay_next to next datapoint.
3: If next datapoint is #00, set alternate duty to #00; else if it's a multiple of #10, set duty_cycle to datapoint; else set alternate duty to datapoint and set attribute.
4: Shift next datapoint left and set key_change to result; if carry bit set, set key_change to negative.
5: If next datapoint is #00, clear alternate duty attribute. Otherwise subtract #50 and set as sweep_envelope; set envelope attribute; expand next note duration by upper nybble of next datapoint; set sweep_rate to lower nybble.
6: If disabled attribute set and next datapoint is a factor of #10, or attribute is cleared and next datapoint is #00, disable sustain_state; otherwise set sustain_position to 0 and set sustain_state to datapoint value -3|#50.
7 or 9: Set pitchbend_value to next datapoint. (break)
8: Set ritenuto_cutoff to next datapoint.
15: Terminal points.
#FD: Volta bracket. If looping state set, goto coda_address. Otherwise set note address to next datapoint; add Y+1 to working address to get next ending address; set looping state. Evaluate subsequent datapoint.
#FE: Repeat bracket. If next datapoint is not #FF, increase loop count and compare; if equal, advance note address, otherwise jump to alsegno_address. Otherwise, set note address to next datapoint (da capo al segno). In all cases, evaluate subsequent datapoint.
#FF: End of track. Set track and channel volume to #00.
#F0 to #FC: Exept for #FB, sets note_dampen to lower nybble. Evaluate subsequent datapoint.
If not currently in "release" state, the pitchbend envelope is handled, assuming the pitchbend attribute is set. The modified period is then written to the registers.
If not currently in "release" state, the volume envelope is handled next. If note_duration is less than fadeout_start, the channel is set to the "release" state, then the duty cycle is fetched, coupled with the volume (after dampening), and written to the registers. Note that bits 2 and 3 of the alternate duty cycle are the volume, and bit 1 tells the channel to use the alternate duty cycle.
Now the current state is processed.
2: Sustain. The code checks if in the "release" state first, which is odd since it's never called elsewhere that I have seen. Then it exits the code if in the "release" state - that part actually makes sense in context. Anyway, this is just the volume envelope handling we already saw earlier. In other words, this state is just a waste of time.
D7 38 85 18 E2 C6
3. set volume_envelope to 85, set note_dampen to 0
4. set fade_settings to 8, set fade_delay_next to 1
Load next note data
5. redirect to routine 2, set pitch_sweep to 2
Load next note data
6. redirect to routine 0, set note_duration to #31 (7*(6+1))
2. same as (1), but set note to A#
3. same as (1)
4. same as (1), but set note to D
6. same as (2)
8. same as (1), but set note to A
Router (Nx) | Function | Argument (xA) | Datapoints (AB) |
---|---|---|---|
0x | Play C | Adjust note_duration | -- |
1x | Play C# | Adjust note_duration | -- |
2x | Play D | Adjust note_duration | -- |
3x | Play D# | Adjust note_duration | -- |
4x | Play E | Adjust note_duration | -- |
5x | Play F | Adjust note_duration | -- |
6x | Play F# | Adjust note_duration | -- |
7x | Play G | Adjust note_duration | -- |
8x | Play G# | Adjust note_duration | -- |
9x | Play A | Adjust note_duration | -- |
Ax | Play A# | Adjust note_duration | -- |
Bx | Play B | Adjust note_duration | -- |
Cx | Routine Reset envelopes |
Adjust note_duration | -- |
Dx | Routine | Set next_duration | 0: A=duty_cycle; B=note_dampen 1: A=fade_delay_next,B=fade_settings |
Ex | Routine | If <6 set octave_shift; else subroutine=A-6 |
Depends on if xA>6 (see next table) |
Fx | Routine | If <#b or =#c set note_dampen; #b: Al segno loop; #d: Volta bracket; #e: Repeat bracket; #f: End of track |
For #fe only: AB=repeat count; if AB=#ff, next address follows |
Subrouter | Datapoint 1 (AB) | Datapoint 2 (AB) |
---|---|---|
E6 | duration_next=AB | -- |
E7 | volume_envelope=AB | -- |
E8 | A=fade_delay_next,B=fade_settings | -- |
E9 | if B=0, duty_cycle=AB; else duty_cycle_alt=AB |
-- |
EA | key_change=AB<<1 td=""> 1> | -- |
EB | sweep_envelope=AB-#50 | next_duration=A, sweep_rate=B |
EC | sustain_state=AB-3|#50 | -- |
ED | pitchbend_value=AB | -- |
EE | ritenuto_cutoff=AB | -- |
No comments:
Post a Comment