#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.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) 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 elif note == music.NOTE_IS_TIE: pass else: self.players[channel].stop() 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] EventType.PAN_SLIDE: # TODO: implement slides var slide_duration: int = event[1] self.channel_pan[channel] = event[2] 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')