400 lines
15 KiB
GDScript
400 lines
15 KiB
GDScript
#warning-ignore-all:shadowed_variable
|
|
extends Node
|
|
const music := preload('res://scripts/loaders/snes/music_ff5.gd')
|
|
const EventType := music.EventType
|
|
var MUSIC := music.new()
|
|
|
|
var inst_map: Dictionary # int keys, AudioStreamSample values
|
|
var tracks: Array
|
|
var num_tracks: int
|
|
var players: Array
|
|
var tempo: float # Store as BPM
|
|
var seconds_per_pulse: float
|
|
var master_volume := 255
|
|
var channel_pointer := PoolIntArray()
|
|
var channel_instrument_idx := PoolByteArray()
|
|
var channel_next_pulse := PoolIntArray()
|
|
var channel_current_note := PoolByteArray()
|
|
var channel_velocity := PoolByteArray()
|
|
var channel_pan := PoolByteArray() # Reversed from MIDI
|
|
var channel_octave := PoolByteArray()
|
|
var channel_transpose := PoolByteArray()
|
|
var channel_fine_tuning := PoolRealArray()
|
|
var channel_adsr_attack := PoolByteArray()
|
|
var channel_adsr_decay := PoolByteArray()
|
|
var channel_adsr_sustain := PoolByteArray()
|
|
var channel_adsr_release := PoolByteArray()
|
|
var channel_noise_freq := PoolByteArray()
|
|
var channel_noise_on := PoolByteArray()
|
|
var channel_pan_lfo_rate := PoolByteArray()
|
|
var channel_pan_lfo_depth := PoolByteArray()
|
|
var channel_pan_lfo_on := PoolByteArray()
|
|
var channel_tremolo_delay := PoolByteArray()
|
|
var channel_tremolo_rate := PoolByteArray()
|
|
var channel_tremolo_depth := PoolByteArray()
|
|
var channel_tremolo_on := PoolByteArray()
|
|
var channel_vibrato_delay := PoolByteArray()
|
|
var channel_vibrato_rate := PoolByteArray()
|
|
var channel_vibrato_depth := PoolByteArray()
|
|
var channel_vibrato_on := PoolByteArray()
|
|
var channel_pitchmod_on := PoolByteArray()
|
|
var channel_echo_on := PoolByteArray()
|
|
var channel_echo_volume := PoolByteArray()
|
|
|
|
func set_tempo(tempo: float):
|
|
self.tempo = tempo
|
|
self.seconds_per_pulse = 60.0 / (tempo * music.PPQN)
|
|
|
|
func _init(tracks: Array, instrument_map: Dictionary):
|
|
self.tracks = tracks
|
|
self.num_tracks = len(self.tracks)
|
|
self.inst_map = instrument_map
|
|
self.players = []
|
|
self.set_tempo(120.0)
|
|
for i in num_tracks:
|
|
self.players.append(AudioStreamPlayer.new())
|
|
add_child(self.players[-1])
|
|
self.players[-1].bus = 'BGM%d' % i
|
|
self.channel_pointer.append(0)
|
|
self.channel_instrument_idx.append(0)
|
|
self.channel_next_pulse.append(0)
|
|
self.channel_current_note.append(0)
|
|
self.channel_velocity.append(100)
|
|
self.channel_pan.append(0)
|
|
self.channel_octave.append(5)
|
|
self.channel_transpose.append(0)
|
|
self.channel_fine_tuning.append(1.0)
|
|
self.channel_adsr_attack.append(0)
|
|
self.channel_adsr_decay.append(0)
|
|
self.channel_adsr_sustain.append(0)
|
|
self.channel_adsr_release.append(0)
|
|
self.channel_noise_freq.append(0)
|
|
self.channel_noise_on.append(0)
|
|
self.channel_pan_lfo_rate.append(0)
|
|
self.channel_pan_lfo_depth.append(0)
|
|
self.channel_pan_lfo_on.append(0)
|
|
self.channel_tremolo_delay.append(0)
|
|
self.channel_tremolo_rate.append(0)
|
|
self.channel_tremolo_depth.append(0)
|
|
self.channel_tremolo_on.append(0)
|
|
self.channel_vibrato_delay.append(0)
|
|
self.channel_vibrato_rate.append(0)
|
|
self.channel_vibrato_depth.append(0)
|
|
self.channel_vibrato_on.append(0)
|
|
self.channel_pitchmod_on.append(0)
|
|
self.channel_echo_on.append(0)
|
|
self.channel_echo_volume.append(0)
|
|
var panner: AudioEffectPanner = AudioServer.get_bus_effect(i+2, 0)
|
|
panner.set_pan(0.0)
|
|
var delay: AudioEffectDelay = AudioServer.get_bus_effect(i+2, 1)
|
|
delay.set_dry(1.0)
|
|
delay.set_tap1_active(false)
|
|
delay.set_tap2_active(false)
|
|
delay.set_feedback_active(false)
|
|
|
|
func play_channel(channel: int, time_offset: float = 0.0) -> int:
|
|
# Executes the track events until it hits a note/rest, in which case it returns the pulse count to the next action, or the end of the events, in which case it returns -1
|
|
self.players[channel].stop()
|
|
var track: Array = self.tracks[channel]
|
|
var l := len(track)
|
|
var player: AudioStreamPlayer = self.players[channel]
|
|
while true:
|
|
var ptr: int = self.channel_pointer[channel]
|
|
if ptr >= l:
|
|
break
|
|
var event = track[ptr]
|
|
self.channel_pointer[channel] += 1
|
|
match event[0]: # Control codes
|
|
EventType.NOTE:
|
|
var note = event[1]
|
|
var duration = event[2]
|
|
if note >= 0: # Don't shift or play rests
|
|
note += (12 * self.channel_octave[channel]) + self.channel_transpose[channel]
|
|
player.pitch_scale = pow(2.0, (note - MUSIC.REFERENCE_NOTE)/12.0) #* self.channel_fine_tuning[channel]
|
|
player.volume_db = linear2db((self.channel_velocity[channel]/255.0) * (self.master_volume/255.0))
|
|
player.play(max((SoundLoader.PLAY_START - time_offset)/player.pitch_scale, 0))
|
|
self.channel_current_note[channel] = note
|
|
else:
|
|
self.channel_current_note[channel] = -1
|
|
# TODO: Confirm tempo scaling
|
|
return duration # Pulses to next instruction
|
|
EventType.VOLUME:
|
|
self.channel_velocity[channel] = event[1]
|
|
EventType.VOLUME_SLIDE: # TODO: implement slides
|
|
var slide_duration: int = event[1]
|
|
self.channel_velocity[channel] = event[2]
|
|
EventType.PAN: # TODO: implement slides
|
|
self.channel_pan[channel] = event[1]
|
|
AudioServer.get_bus_effect(channel+2, 0).set_pan(1.0 - event[1]/127.0)
|
|
EventType.PAN_SLIDE: # TODO: implement slides
|
|
var slide_duration: int = event[1]
|
|
self.channel_pan[channel] = event[2]
|
|
AudioServer.get_bus_effect(channel+2, 0).set_pan(1.0 - event[2]/127.0)
|
|
EventType.PITCH_SLIDE: # TODO: implement slides
|
|
var slide_duration: int = event[1]
|
|
var target_pitch: int = event[2] # Signed
|
|
EventType.VIBRATO_ON:
|
|
self.channel_vibrato_delay[channel] = event[1]
|
|
self.channel_vibrato_rate[channel] = event[2]
|
|
self.channel_vibrato_depth[channel] = event[3]
|
|
self.channel_vibrato_on[channel] = 1
|
|
EventType.VIBRATO_OFF:
|
|
self.channel_vibrato_on[channel] = 0
|
|
EventType.TREMOLO_ON:
|
|
self.channel_tremolo_delay[channel] = event[1]
|
|
self.channel_tremolo_rate[channel] = event[2]
|
|
self.channel_tremolo_depth[channel] = event[3]
|
|
self.channel_tremolo_on[channel] = 1
|
|
EventType.TREMOLO_OFF:
|
|
self.channel_tremolo_on[channel] = 0
|
|
EventType.PAN_LFO_ON:
|
|
self.channel_pan_lfo_depth[channel] = event[1]
|
|
self.channel_pan_lfo_rate[channel] = event[2]
|
|
self.channel_pan_lfo_on[channel] = 1
|
|
EventType.PAN_LFO_OFF:
|
|
self.channel_pan_lfo_on[channel] = 0
|
|
EventType.NOISE_FREQ:
|
|
self.channel_noise_freq[channel] = event[1]
|
|
EventType.NOISE_ON:
|
|
self.channel_noise_on[channel] = 1
|
|
EventType.NOISE_OFF:
|
|
self.channel_noise_on[channel] = 0
|
|
EventType.PITCHMOD_ON:
|
|
self.channel_pitchmod_on[channel] = 1
|
|
EventType.PITCHMOD_OFF:
|
|
self.channel_pitchmod_on[channel] = 0
|
|
EventType.ECHO_ON:
|
|
self.channel_echo_on[channel] = 1
|
|
EventType.ECHO_OFF:
|
|
self.channel_echo_on[channel] = 0
|
|
EventType.OCTAVE:
|
|
self.channel_octave[channel] = event[1]
|
|
EventType.OCTAVE_UP:
|
|
self.channel_octave[channel] += 1
|
|
EventType.OCTAVE_DOWN:
|
|
self.channel_octave[channel] -= 1
|
|
EventType.TRANSPOSE_ABS:
|
|
self.channel_transpose[channel] = event[1]
|
|
EventType.TRANSPOSE_REL:
|
|
self.channel_transpose[channel] += event[1]
|
|
EventType.TUNING:
|
|
var fine_tune: int = event[1]
|
|
var scale: float
|
|
if fine_tune < 0x80:
|
|
scale = 1.0 + fine_tune/255.0
|
|
else:
|
|
scale = fine_tune/255.0
|
|
self.channel_fine_tuning[channel] = scale
|
|
EventType.PROGCHANGE:
|
|
self.channel_instrument_idx[channel] = event[1]
|
|
player.stream = self.inst_map[self.channel_instrument_idx[channel]]
|
|
# TODO - grab instrument envelope
|
|
EventType.ADSR_ATTACK:
|
|
self.channel_adsr_attack[channel] = event[1]
|
|
EventType.ADSR_DECAY:
|
|
self.channel_adsr_decay[channel] = event[1]
|
|
EventType.ADSR_SUSTAIN:
|
|
self.channel_adsr_sustain[channel] = event[1]
|
|
EventType.ADSR_RELEASE:
|
|
self.channel_adsr_release[channel] = event[1]
|
|
EventType.ADSR_DEFAULT: # TODO - grab instrument envelope
|
|
pass
|
|
EventType.TEMPO:
|
|
self.set_tempo(music.tempo_to_bpm(event[1]))
|
|
EventType.TEMPO_SLIDE:
|
|
self.set_tempo(music.tempo_to_bpm(event[2]))
|
|
var slide_duration: int = event[1]
|
|
EventType.ECHO_VOLUME:
|
|
self.channel_echo_volume[channel] = event[1]
|
|
EventType.ECHO_VOLUME_SLIDE: # TODO: implement slides
|
|
self.channel_echo_volume[channel] = event[2]
|
|
var slide_duration: int = event[1]
|
|
EventType.ECHO_FEEDBACK_FIR: # TODO
|
|
var feedback: int = event[1]
|
|
var filterIndex: int = event[2]
|
|
EventType.MASTER_VOLUME:
|
|
self.master_volume = event[1]
|
|
EventType.GOTO:
|
|
self.channel_pointer[channel] = event[1]
|
|
EventType.END:
|
|
break
|
|
_:
|
|
break
|
|
return -1 # End of track
|
|
|
|
func play_pulse(time_offset := 0.0) -> bool: # Return true if any channel played
|
|
var active_channels := 0
|
|
for channel in self.num_tracks:
|
|
if self.channel_next_pulse[channel] < 0:
|
|
continue # Channel not playing
|
|
active_channels += 1
|
|
if self.channel_next_pulse[channel] == 0:
|
|
self.channel_next_pulse[channel] = self.play_channel(channel, time_offset)
|
|
self.channel_next_pulse[channel] -= 1
|
|
return active_channels > 0
|
|
|
|
var is_playing := false
|
|
var bgm_timestamp := 0.0 # Note this will be behind by the maximum delay
|
|
func _process(delta: float) -> void:
|
|
if self.is_playing:
|
|
bgm_timestamp += delta
|
|
while bgm_timestamp > seconds_per_pulse:
|
|
bgm_timestamp -= seconds_per_pulse
|
|
self.is_playing = play_pulse(bgm_timestamp)
|
|
if not self.is_playing:
|
|
print('BGM finished playing')
|
|
|
|
|
|
func render_channel(channel: int, t_start: float, t_end: float, inst_map: Array) -> PoolByteArray:
|
|
var midi_events_bytes_t_start := StreamPeerBuffer.new()
|
|
var midi_events_bytes_t_end := StreamPeerBuffer.new()
|
|
var midi_events_bytes3 := StreamPeerBuffer.new()
|
|
var midi_events_bytes_adsr := StreamPeerBuffer.new()
|
|
|
|
var track: Array = self.tracks[channel]
|
|
var l := len(track)
|
|
var t := t_start
|
|
var num_notes: int = 0
|
|
var current_instrument := 0
|
|
while (t < t_end) and (num_notes < 2048):
|
|
var ptr: int = self.channel_pointer[channel]
|
|
if ptr >= l:
|
|
break
|
|
var event = track[ptr]
|
|
self.channel_pointer[channel] += 1
|
|
match event[0]: # Control codes
|
|
EventType.NOTE:
|
|
var note = event[1]
|
|
var duration = event[2]
|
|
if note >= 0: # Don't shift or play rests
|
|
note += (12 * self.channel_octave[channel]) + self.channel_transpose[channel]
|
|
midi_events_bytes_t_start.put_32(int(t * 32000)) # t_start
|
|
midi_events_bytes_t_end.put_32(int((t + duration * self.seconds_per_pulse) * 32000)) # t_end
|
|
midi_events_bytes3.put_u8(current_instrument) # instrument
|
|
midi_events_bytes3.put_u8(note) # pitch_idx #* self.channel_fine_tuning[channel]
|
|
midi_events_bytes3.put_u8((self.channel_velocity[channel]*self.master_volume)/255) # velocity
|
|
midi_events_bytes3.put_u8(self.channel_pan[channel]*255/127) # pan
|
|
midi_events_bytes_adsr.put_32(0) # ADSR
|
|
num_notes += 1
|
|
t += duration * self.seconds_per_pulse
|
|
# TODO: Confirm tempo scaling
|
|
# return duration # Pulses to next instruction
|
|
EventType.VOLUME:
|
|
self.channel_velocity[channel] = event[1]
|
|
EventType.VOLUME_SLIDE: # TODO: implement slides
|
|
var slide_duration: int = event[1]
|
|
self.channel_velocity[channel] = event[2]
|
|
EventType.PAN: # TODO: implement slides
|
|
self.channel_pan[channel] = event[1]
|
|
# AudioServer.get_bus_effect(channel+2, 0).set_pan(1.0 - event[1]/127.0)
|
|
EventType.PAN_SLIDE: # TODO: implement slides
|
|
var slide_duration: int = event[1]
|
|
self.channel_pan[channel] = event[2]
|
|
# AudioServer.get_bus_effect(channel+2, 0).set_pan(1.0 - event[2]/127.0)
|
|
EventType.PITCH_SLIDE: # TODO: implement slides
|
|
var slide_duration: int = event[1]
|
|
var target_pitch: int = event[2] # Signed
|
|
EventType.VIBRATO_ON:
|
|
self.channel_vibrato_delay[channel] = event[1]
|
|
self.channel_vibrato_rate[channel] = event[2]
|
|
self.channel_vibrato_depth[channel] = event[3]
|
|
self.channel_vibrato_on[channel] = 1
|
|
EventType.VIBRATO_OFF:
|
|
self.channel_vibrato_on[channel] = 0
|
|
EventType.TREMOLO_ON:
|
|
self.channel_tremolo_delay[channel] = event[1]
|
|
self.channel_tremolo_rate[channel] = event[2]
|
|
self.channel_tremolo_depth[channel] = event[3]
|
|
self.channel_tremolo_on[channel] = 1
|
|
EventType.TREMOLO_OFF:
|
|
self.channel_tremolo_on[channel] = 0
|
|
EventType.PAN_LFO_ON:
|
|
self.channel_pan_lfo_depth[channel] = event[1]
|
|
self.channel_pan_lfo_rate[channel] = event[2]
|
|
self.channel_pan_lfo_on[channel] = 1
|
|
EventType.PAN_LFO_OFF:
|
|
self.channel_pan_lfo_on[channel] = 0
|
|
EventType.NOISE_FREQ:
|
|
self.channel_noise_freq[channel] = event[1]
|
|
EventType.NOISE_ON:
|
|
self.channel_noise_on[channel] = 1
|
|
EventType.NOISE_OFF:
|
|
self.channel_noise_on[channel] = 0
|
|
EventType.PITCHMOD_ON:
|
|
self.channel_pitchmod_on[channel] = 1
|
|
EventType.PITCHMOD_OFF:
|
|
self.channel_pitchmod_on[channel] = 0
|
|
EventType.ECHO_ON:
|
|
self.channel_echo_on[channel] = 1
|
|
EventType.ECHO_OFF:
|
|
self.channel_echo_on[channel] = 0
|
|
EventType.OCTAVE:
|
|
self.channel_octave[channel] = event[1]
|
|
EventType.OCTAVE_UP:
|
|
self.channel_octave[channel] += 1
|
|
EventType.OCTAVE_DOWN:
|
|
self.channel_octave[channel] -= 1
|
|
EventType.TRANSPOSE_ABS:
|
|
self.channel_transpose[channel] = event[1]
|
|
EventType.TRANSPOSE_REL:
|
|
self.channel_transpose[channel] += event[1]
|
|
EventType.TUNING:
|
|
var fine_tune: int = event[1]
|
|
var scale: float
|
|
if fine_tune < 0x80:
|
|
scale = 1.0 + fine_tune/255.0
|
|
else:
|
|
scale = fine_tune/255.0
|
|
self.channel_fine_tuning[channel] = scale
|
|
EventType.PROGCHANGE:
|
|
current_instrument = inst_map[event[1]-0x20] - 1
|
|
EventType.ADSR_ATTACK:
|
|
self.channel_adsr_attack[channel] = event[1]
|
|
EventType.ADSR_DECAY:
|
|
self.channel_adsr_decay[channel] = event[1]
|
|
EventType.ADSR_SUSTAIN:
|
|
self.channel_adsr_sustain[channel] = event[1]
|
|
EventType.ADSR_RELEASE:
|
|
self.channel_adsr_release[channel] = event[1]
|
|
EventType.ADSR_DEFAULT: # TODO - grab instrument envelope
|
|
pass
|
|
EventType.TEMPO:
|
|
self.set_tempo(music.tempo_to_bpm(event[1]))
|
|
EventType.TEMPO_SLIDE:
|
|
self.set_tempo(music.tempo_to_bpm(event[2]))
|
|
var slide_duration: int = event[1]
|
|
EventType.ECHO_VOLUME:
|
|
self.channel_echo_volume[channel] = event[1]
|
|
EventType.ECHO_VOLUME_SLIDE: # TODO: implement slides
|
|
self.channel_echo_volume[channel] = event[2]
|
|
var slide_duration: int = event[1]
|
|
EventType.ECHO_FEEDBACK_FIR: # TODO
|
|
var feedback: int = event[1]
|
|
var filterIndex: int = event[2]
|
|
EventType.MASTER_VOLUME:
|
|
self.master_volume = event[1]
|
|
EventType.GOTO:
|
|
self.channel_pointer[channel] = event[1]
|
|
EventType.END:
|
|
break
|
|
_:
|
|
break
|
|
# End of track
|
|
# Fill up end of notes array with dummies
|
|
for i in range(num_notes, 2048):
|
|
midi_events_bytes_t_start.put_32(int(t_end*2*32000))
|
|
midi_events_bytes_t_end.put_32(int(t_end*2*32000))
|
|
midi_events_bytes3.put_u8(0) # instrument
|
|
midi_events_bytes3.put_u8(0) # pitch_idx
|
|
midi_events_bytes3.put_u8(0) # velocity
|
|
midi_events_bytes3.put_u8(0) # pan
|
|
midi_events_bytes_adsr.put_32(0) # ADSR
|
|
return midi_events_bytes_t_start.data_array + midi_events_bytes_t_end.data_array + midi_events_bytes3.data_array + midi_events_bytes_adsr.data_array
|
|
# audio_renderer.push_bytes(channel_data)
|
|
|
|
func render_channels(t_start: float, t_end: float, inst_map: Array) -> PoolByteArray:
|
|
var data := PoolByteArray()
|
|
for channel in self.num_tracks:
|
|
data += self.render_channel(channel, t_start, t_end, inst_map)
|
|
return data
|