650 lines
24 KiB
GDScript
650 lines
24 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.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')
|
|
|
|
|
|
# TODO: need to interleave channels for tempo and master volume!
|
|
const MAX_NOTE_EVENTS := 2048
|
|
class NoteEvent:
|
|
var p_start: int # In pulse space
|
|
var p_end: int
|
|
var instrument: int
|
|
var pitch: int
|
|
var velocity: float
|
|
var adsr_attack: int
|
|
var adsr_decay: int
|
|
var adsr_sustain: int
|
|
var adsr_release: int
|
|
|
|
|
|
class TrackCurve: # built-in Curve class is too restrictive for this
|
|
var default: float
|
|
var entries: PoolVector3Array
|
|
var baked_integrals: PoolRealArray
|
|
func _init(default: float = 0.0):
|
|
self.default = default
|
|
self.entries = PoolVector3Array()
|
|
self.baked_integrals = PoolRealArray()
|
|
|
|
func add_point(pulse: int, value: float, ramp_to_next: bool) -> void:
|
|
var l := len(self.entries)
|
|
var entry := Vector3(float(pulse), value, float(ramp_to_next))
|
|
if l == 0 or self.entries[-1].x < pulse:
|
|
self.entries.append(entry)
|
|
else: # Find the first entry bigger than pulse, and insert before
|
|
for i in l:
|
|
if self.entries[i].x > pulse:
|
|
self.entries.insert(i, entry)
|
|
break
|
|
|
|
var last_pulse_block_get: int = -1 # Cache previous position for sequential lookups
|
|
func get_pulse(pulse: float) -> float:
|
|
var l := len(self.entries)
|
|
if l == 0 or pulse < self.entries[-1].x:
|
|
return self.default
|
|
if pulse > self.entries[-1].x:
|
|
return self.entries[-1].y
|
|
for i in l-2:
|
|
# Find first entry beyond
|
|
if pulse < self.entries[i+1].x:
|
|
if self.entries[i].z > 0: # ramp_to_next
|
|
return range_lerp(pulse, self.entries[i].x, self.entries[i+1].x, self.entries[i].y, self.entries[i+1].y)
|
|
else:
|
|
return self.entries[i].y
|
|
return self.default # Should be unreachable
|
|
|
|
func bake_integrals():
|
|
# Store the starting integrated value (i.e. time for the tempo curve) of each pulse value
|
|
self.baked_integrals.clear()
|
|
var last_pulse := 0.0
|
|
var last_value := self.default
|
|
var last_integral := 0.0
|
|
var last_ramp := false
|
|
for entry in self.entries:
|
|
var step_pulse = entry.x - last_pulse
|
|
var integral := last_integral
|
|
if last_ramp:
|
|
# Treat it as a rectangle where the height is the average of the slanted top.
|
|
integral += step_pulse * (last_value + entry.y)/2.0
|
|
else:
|
|
integral += step_pulse * last_value
|
|
self.baked_integrals.append(integral)
|
|
last_pulse = entry.x
|
|
last_value = entry.y
|
|
last_integral = integral
|
|
last_ramp = entry.z > 0
|
|
|
|
var last_integral_block_get: int = -1 # Cache previous position for sequential lookups
|
|
func get_integral(pulse: float) -> float:
|
|
# This is for tempo -> time. Need to bake it to have any hope of efficiency.
|
|
if self.baked_integrals.empty():
|
|
self.bake_integrals()
|
|
# Find first entry earlier than the pulse
|
|
for i in range(len(self.entries)-1, -1, -1):
|
|
var entry = self.entries[i]
|
|
if pulse > entry.x:
|
|
var integral = self.baked_integrals[i]
|
|
var step_pulse = pulse - entry.x
|
|
if entry.z: # Ramp to next
|
|
# Treat it as a rectangle where the height is the average of the slanted top.
|
|
integral += step_pulse * (entry.y + entries[i+1].y)/2.0 # If last entry somehow has ramp-to-next (it shouldn't), this will out-of-range error
|
|
else:
|
|
integral += step_pulse * entry.y
|
|
return integral
|
|
return 0.0
|
|
|
|
|
|
func render_channels(_t_start: float, _t_end: float, inst_map: Array) -> Array: # [data: PoolByteArray, target_time_length: float in seconds]
|
|
# Since some channels contain global events (tempo and global volume for now),
|
|
# the strategy will be to preprocess each channel in a global-state-agnostic way,
|
|
# then once all the global tracks are known, as well as the longest unlooped length,
|
|
# do a second pass to generate the final events
|
|
# self.print_channel_events(inst_map)
|
|
var instrument_adsrs = RomLoader.snes_data.bgm_instrument_adsrs # TODO: UNHARDCODE THIS
|
|
var all_note_events = []
|
|
|
|
var curve_master_volume := TrackCurve.new(100.0/255.0) # [0.0, 1.0] for now
|
|
var curve_master_tempo := TrackCurve.new(120.0) # bpm is too big, need pulses per second
|
|
|
|
var curve_channel_pans := []
|
|
|
|
for channel in self.num_tracks:
|
|
var curve_velocity := TrackCurve.new(100.0/255.0) # [0.0, 1.0] for now
|
|
var curve_pan := TrackCurve.new() # [-1.0, 1.0] for now
|
|
var channel_note_events = []
|
|
var track: Array = self.tracks[channel]
|
|
var l := len(track)
|
|
var p := 0 # current pulse
|
|
|
|
if l == 0: # Empty channel, move on
|
|
all_note_events.append(channel_note_events)
|
|
curve_channel_pans.append(curve_pan)
|
|
continue
|
|
|
|
# var num_notes: int = 0
|
|
var current_instrument := 0
|
|
var current_octave := 5
|
|
var current_transpose := 0
|
|
# var current_velocity := 100
|
|
var current_adsr_attack := 0
|
|
var current_adsr_decay := 0
|
|
var current_adsr_sustain := 0
|
|
var current_adsr_release := 0
|
|
|
|
# First, check if it ends in a GOTO, then store the program counter of the destination
|
|
var infinite_loop_target_program_counter = -1
|
|
var infinite_loop_target_pulse = -1
|
|
if track[-1][0] == EventType.GOTO:
|
|
infinite_loop_target_program_counter = track[-1][1]
|
|
|
|
var program_counter := 0
|
|
while true: #num_notes < MAX_NOTE_EVENTS:
|
|
if program_counter >= l:
|
|
break
|
|
if program_counter == infinite_loop_target_program_counter:
|
|
infinite_loop_target_pulse = p
|
|
var event = track[program_counter]
|
|
program_counter += 1
|
|
match event[0]: # Control codes
|
|
EventType.GOTO: # This is a preprocessed event list, so GOTO is a final infinite loop marker
|
|
var note_event = NoteEvent.new()
|
|
note_event.p_start = p
|
|
note_event.p_end = infinite_loop_target_pulse # Fake final note event using p_start > p_end to encode the infinite jump back loop.
|
|
# Note that event[1] points to an Event, not a NoteEvent, not a Pulse, so we looked it up earlier
|
|
channel_note_events.append(note_event)
|
|
break
|
|
EventType.MASTER_VOLUME:
|
|
curve_master_volume.add_point(p, event[1]/255.0, false)
|
|
EventType.TEMPO:
|
|
var new_tempo = music.tempo_to_seconds_per_pulse(event[1])
|
|
curve_master_tempo.add_point(p, new_tempo, false)
|
|
EventType.TEMPO_SLIDE:
|
|
var old_tempo = curve_master_tempo.get_pulse(p)
|
|
var new_tempo = music.tempo_to_seconds_per_pulse(event[2])
|
|
var slide_duration: int = event[1] # TODO: work out how this is scaled
|
|
curve_master_tempo.add_point(p, old_tempo, true)
|
|
curve_master_tempo.add_point(p + slide_duration, new_tempo, false)
|
|
EventType.NOTE:
|
|
var note = event[1]
|
|
var duration = event[2]
|
|
if note >= 0: # Don't shift or play rests
|
|
note += (12 * current_octave) + current_transpose
|
|
var note_event = NoteEvent.new()
|
|
note_event.p_start = p
|
|
note_event.p_end = p + duration
|
|
note_event.instrument = current_instrument
|
|
note_event.pitch = note # pitch_idx #* self.channel_fine_tuning[channel]
|
|
note_event.velocity = curve_velocity.get_pulse(p) # current_velocity
|
|
note_event.adsr_attack = current_adsr_attack
|
|
note_event.adsr_decay = current_adsr_decay
|
|
note_event.adsr_sustain = current_adsr_sustain
|
|
note_event.adsr_release = current_adsr_release
|
|
channel_note_events.append(note_event)
|
|
# num_notes += 1
|
|
elif note == music.NOTE_IS_TIE:
|
|
channel_note_events[-1].p_end += duration
|
|
p += duration
|
|
EventType.VOLUME:
|
|
var new_velocity: float = event[1]/255.0
|
|
curve_velocity.add_point(p, new_velocity, false)
|
|
EventType.VOLUME_SLIDE: # TODO: implement slides
|
|
var old_velocity = curve_velocity.get_pulse(p)
|
|
var slide_duration: int = event[1]
|
|
var new_velocity: float = event[2]/255.0
|
|
curve_velocity.add_point(p, old_velocity, true)
|
|
curve_velocity.add_point(p + slide_duration, new_velocity, false)
|
|
EventType.PAN:
|
|
var new_pan = 1.0 - event[1]/127.5
|
|
curve_pan.add_point(p, new_pan, false)
|
|
EventType.PAN_SLIDE: # TODO: implement slides
|
|
var old_pan = curve_pan.get_pulse(p)
|
|
var new_pan = 1.0 - event[2]/127.5
|
|
var slide_duration: int = event[1] # TODO: work out how slides are scaled
|
|
curve_pan.add_point(p, old_pan, true)
|
|
curve_pan.add_point(p + slide_duration, new_pan, false)
|
|
EventType.PITCH_SLIDE: # TODO: implement slides
|
|
var slide_duration: int = event[1]
|
|
var target_pitch: int = event[2] # Signed
|
|
EventType.OCTAVE:
|
|
current_octave = event[1]
|
|
EventType.OCTAVE_UP:
|
|
current_octave += 1
|
|
EventType.OCTAVE_DOWN:
|
|
current_octave -= 1
|
|
EventType.TRANSPOSE_ABS:
|
|
current_transpose = event[1]
|
|
EventType.TRANSPOSE_REL:
|
|
current_transpose += 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:
|
|
var event_idx = event[1]-0x20
|
|
if event_idx >= 0:
|
|
current_instrument = inst_map[event_idx] - 1
|
|
if current_instrument < len(instrument_adsrs) and current_instrument > 0:
|
|
var adsr = instrument_adsrs[current_instrument]
|
|
current_adsr_attack = adsr[2]
|
|
current_adsr_decay = adsr[3]
|
|
current_adsr_sustain = adsr[0]
|
|
current_adsr_release = adsr[1]
|
|
EventType.ADSR_DEFAULT: # TODO - Investigate actual scaling and order
|
|
if current_instrument < len(instrument_adsrs) and current_instrument > 0:
|
|
var adsr = instrument_adsrs[current_instrument]
|
|
current_adsr_attack = adsr[2]
|
|
current_adsr_decay = adsr[3]
|
|
current_adsr_sustain = adsr[0]
|
|
current_adsr_release = adsr[1]
|
|
EventType.ADSR_ATTACK:
|
|
current_adsr_attack = event[1]
|
|
EventType.ADSR_DECAY:
|
|
current_adsr_decay = event[1]
|
|
EventType.ADSR_SUSTAIN:
|
|
current_adsr_sustain = event[1]
|
|
EventType.ADSR_RELEASE:
|
|
current_adsr_release = event[1]
|
|
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.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.END:
|
|
break
|
|
_:
|
|
break
|
|
# End of track
|
|
all_note_events.append(channel_note_events)
|
|
curve_channel_pans.append(curve_pan)
|
|
|
|
# Integrate tempo so we can get a pulse->time mapping
|
|
curve_master_tempo.bake_integrals()
|
|
# Find the longest channel
|
|
var channel_loop_p_returns = PoolIntArray()
|
|
var channel_loop_p_lengths = PoolIntArray()
|
|
var longest_channel_idx = 0
|
|
var longest_channel_p_end = 0
|
|
var highest_channel_p_return = -1
|
|
for channel in self.num_tracks:
|
|
if all_note_events[channel].empty():
|
|
channel_loop_p_returns.append(-1)
|
|
continue
|
|
var note_event: NoteEvent = all_note_events[channel][-1]
|
|
var p_end = note_event.p_end
|
|
if p_end < note_event.p_start:
|
|
# Ends on infinite loop
|
|
channel_loop_p_returns.append(p_end)
|
|
channel_loop_p_lengths.append(note_event.p_start - p_end)
|
|
if p_end > highest_channel_p_return:
|
|
highest_channel_p_return = p_end
|
|
p_end = note_event.p_start
|
|
else:
|
|
channel_loop_p_returns.append(-1)
|
|
channel_loop_p_lengths.append(0)
|
|
|
|
if p_end > longest_channel_p_end:
|
|
longest_channel_p_end = p_end
|
|
longest_channel_idx = channel
|
|
|
|
var target_pulse_length = longest_channel_p_end + 200
|
|
var target_time_length = curve_master_tempo.get_integral(target_pulse_length)
|
|
|
|
# Second pass - encode the notes with the now-known global tempo and volume curves
|
|
var data := PoolByteArray()
|
|
for channel in self.num_tracks:
|
|
var events = all_note_events[channel]
|
|
var loop_return_note_event_idx = -1
|
|
var loop_return_p = channel_loop_p_returns[channel]
|
|
var curve_pan: TrackCurve = curve_channel_pans[channel]
|
|
|
|
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 num_notes: int = 0
|
|
var event_ptr := 0
|
|
var l_events := len(events)
|
|
var loop_p_offset := 0
|
|
for i in MAX_NOTE_EVENTS:
|
|
if event_ptr >= l_events:
|
|
break
|
|
if (loop_return_p >= 0) and event_ptr == l_events-1:
|
|
event_ptr = loop_return_note_event_idx
|
|
loop_p_offset += channel_loop_p_lengths[channel]
|
|
var event: NoteEvent = events[event_ptr]
|
|
var p = event.p_start
|
|
if loop_return_note_event_idx < 0 and p >= loop_return_p:
|
|
loop_return_note_event_idx = event_ptr
|
|
midi_events_bytes_t_start.put_32(int(curve_master_tempo.get_integral(p + loop_p_offset) * 32000))
|
|
midi_events_bytes_t_end.put_32(int(curve_master_tempo.get_integral(event.p_end + loop_p_offset) * 32000)) # t_end
|
|
midi_events_bytes3.put_u8(event.instrument)
|
|
midi_events_bytes3.put_u8(event.pitch)
|
|
midi_events_bytes3.put_u8(int(event.velocity * curve_master_volume.get_pulse(p) * 255.0)) # velocity
|
|
midi_events_bytes3.put_u8(int((curve_pan.get_pulse(p)+1.0) * 127.5)) # pan
|
|
midi_events_bytes_adsr.put_u8(event.adsr_attack)
|
|
midi_events_bytes_adsr.put_u8(event.adsr_decay)
|
|
midi_events_bytes_adsr.put_u8(event.adsr_sustain)
|
|
midi_events_bytes_adsr.put_u8(event.adsr_release)
|
|
|
|
event_ptr += 1
|
|
num_notes += 1
|
|
# Fill up end of notes array with dummies
|
|
for i in range(num_notes, MAX_NOTE_EVENTS):
|
|
midi_events_bytes_t_start.put_32(0x0FFFFFFF)
|
|
midi_events_bytes_t_end.put_32(0x0FFFFFFF)
|
|
midi_events_bytes3.put_32(0)
|
|
midi_events_bytes_adsr.put_32(0)
|
|
data += 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
|
|
var smp_loop_start = -1
|
|
var smp_loop_end = -1
|
|
if highest_channel_p_return > 0:
|
|
smp_loop_start = curve_master_tempo.get_integral(highest_channel_p_return + 100) * 32000
|
|
smp_loop_end = curve_master_tempo.get_integral(longest_channel_p_end + 100) * 32000
|
|
return [data, target_time_length, [smp_loop_start, smp_loop_end]]
|
|
|
|
func print_channel_events(inst_map: Array) -> void:
|
|
for channel in self.num_tracks:
|
|
print('================Channel %d================'%channel)
|
|
var track: Array = self.tracks[channel]
|
|
var l := len(track)
|
|
var p := 0 # current pulse
|
|
for event in track: #num_notes < MAX_NOTE_EVENTS:
|
|
var print_str := 'p=%6d : %s '%[p, EventType.keys()[event[0]]]
|
|
match event[0]:
|
|
EventType.NOTE:
|
|
var note = event[1]
|
|
var duration = event[2]
|
|
match note:
|
|
music.NOTE_IS_REST:
|
|
print('p=%6d : NOTE_REST %d pulses'%[p, duration])
|
|
music.NOTE_IS_TIE:
|
|
print('p=%6d : NOTE_TIE %d pulses'%[p, duration])
|
|
_:
|
|
print(print_str, event.slice(1, -1))
|
|
p += duration
|
|
EventType.PROGCHANGE:
|
|
var event_idx = event[1]-0x20
|
|
if event_idx >= 0:
|
|
print(print_str, ' instrument %02d'%(inst_map[event_idx] - 1))
|
|
else:
|
|
print(print_str, event.slice(1, -1))
|
|
_:
|
|
print(print_str, event.slice(1, -1))
|