gdfxr/addons/gdfxr/SFXRGenerator.gd

275 lines
6.8 KiB
GDScript

# GDScript port of the original SFXR
# https://www.drpetter.se/project_sfxr.html
const SFXRConfig := preload("SFXRConfig.gd")
const master_vol := 0.05
enum WavBits {
WAV_BITS_8,
WAV_BITS_16,
}
enum WavFreq {
WAV_FREQ_44100,
WAV_FREQ_22050,
}
var _config: SFXRConfig
var rep_time: int
var rep_limit: int
var arp_time: int
var arp_limit: int
var arp_mod: float
var period: int
var fperiod: float
var fmaxperiod: float
var fslide: float
var fdslide: float
var vib_amp: float
var vib_phase: float
var vib_speed: float
var square_duty: float
var square_slide: float
var env_vol: float
var env_length := PoolIntArray([0, 0, 0])
var phase: int
var fphase: float
var fdphase: float
var iphase: int
var flthp: float
var flthp_d: float
var noise_buffer := PoolRealArray([])
var phaser_buffer := PoolRealArray([])
var ipp: int
var fltp: float
var fltdp: float
var fltw: float
var fltw_d: float
var fltdmp: float
var fltphp: float
func generate_audio_stream(
config: SFXRConfig,
wav_bits: int = WavBits.WAV_BITS_8,
wav_freq: int = WavFreq.WAV_FREQ_44100
) -> AudioStreamSample:
var stream := AudioStreamSample.new()
stream.format = AudioStreamSample.FORMAT_8_BITS if wav_bits == WavBits.WAV_BITS_8 else AudioStreamSample.FORMAT_16_BITS
stream.mix_rate = 44100 if wav_freq == WavFreq.WAV_FREQ_44100 else 22050
_config = config
stream.data = _generate_samples(wav_bits, wav_freq).data_array
_config = null
return stream
func generate_samples(
config: SFXRConfig,
wav_bits: int = WavBits.WAV_BITS_8,
wav_freq: int = WavFreq.WAV_FREQ_44100
) -> PoolByteArray:
_config = config
var data := _generate_samples(wav_bits, wav_freq).data_array
_config = null
return data
func _generate_samples(wav_bits: int, wav_freq: int) -> StreamPeerBuffer:
_reset_sample(true)
var playing_sample := true
var env_stage := 0
var env_time := 0
var filesample: float = 0
var fileacc := 0
var buffer := StreamPeerBuffer.new()
# SynthSample
while playing_sample:
rep_time += 1
if rep_limit != 0 and rep_time >= rep_limit:
rep_time = 0
_reset_sample(false)
# frequency envelopes/arpeggios
arp_time += 1
if arp_limit != 0 and arp_time >= arp_limit:
arp_limit = 0
fperiod *= arp_mod
fslide += fdslide
fperiod *= fslide
if fperiod > fmaxperiod:
fperiod = fmaxperiod
if _config.p_freq_limit > 0:
playing_sample = false
var rfperiod := fperiod
if vib_amp > 0.0:
vib_phase += vib_speed
rfperiod = fperiod * (1.0 + sin(vib_phase) * vib_amp)
period = int(max(8, rfperiod))
square_duty = clamp(square_duty + square_slide, 0.0, 0.5)
# volume envelope
env_time += 1 # Note: this skips 0 of env_stage 0. Seems problematic.
if env_time > env_length[env_stage]:
env_time = 0
while true:
env_stage += 1
if env_stage == 3:
playing_sample = false
break
if env_length[env_stage] != 0:
break
match env_stage:
0:
env_vol = float(env_time) / env_length[0]
1:
env_vol = 1.0 + pow(1.0 - float(env_time) / env_length[1], 1.0) * 2.0 * _config.p_env_punch
2:
env_vol = 1.0 - float(env_time) / env_length[2]
# phaser step
fphase += fdphase
iphase = int(min(abs(fphase), 1023))
if flthp_d != 0.0:
flthp = clamp(flthp * flthp_d, 0.00001, 0.1)
var ssample := 0.0
for si in 8: # 8x supersampling
var sample := 0.0
phase += 1
if phase >= period:
phase %= period
if _config.wave_type == SFXRConfig.WaveType.NOISE:
for j in 32:
noise_buffer[j] = rand_range(-1.0, +1.0)
# base waveform
var fp := float(phase) / period
match _config.wave_type:
SFXRConfig.WaveType.SQUARE_WAVE:
sample = 0.5 if fp < square_duty else -0.5
SFXRConfig.WaveType.SAWTOOTH:
sample = 1.0 - fp * 2
SFXRConfig.WaveType.SINE_WAVE:
sample = sin(fp * 2 * PI)
SFXRConfig.WaveType.NOISE:
sample = noise_buffer[phase * 32 / period]
# lp filter
var pp := fltp
fltw = clamp(fltw * fltw_d, 0.0, 0.1)
if _config.p_lpf_freq == 1.0:
fltp = sample
fltdp = 0.0
else:
fltdp += (sample - fltp) * fltw
fltdp -= fltdp * fltdmp
fltp += fltdp
# hp filter
fltphp += fltp - pp
fltphp -= fltphp * flthp
sample = fltphp
# phaser
phaser_buffer[ipp & 1023] = sample
sample += phaser_buffer[(ipp - iphase + 1024) & 1023]
ipp = (ipp + 1) & 1023
# final accumulation and envelope application
ssample += sample * env_vol
ssample = ssample / 8 * master_vol
ssample *= 2.0 * _config.sound_vol
ssample *= 4.0 # arbitrary gain to get reasonable output volume...
ssample = clamp(ssample, -1.0, +1.0)
filesample += ssample
fileacc += 1
if wav_freq == WavFreq.WAV_FREQ_44100 or fileacc == 2:
filesample /= fileacc
fileacc = 0
if wav_bits == WavBits.WAV_BITS_8:
buffer.put_8(filesample * 255)
else:
buffer.put_16(filesample * 32000)
filesample = 0
return buffer
func _reset_sample(restart: bool) -> void:
fperiod = 100.0 / (_config.p_base_freq * _config.p_base_freq + 0.001)
period = int(fperiod)
fmaxperiod = 100.0 / (_config.p_freq_limit * _config.p_freq_limit + 0.001)
fslide = 1.0 - pow(_config.p_freq_ramp, 3.0) * 0.01
fdslide = -pow(_config.p_freq_dramp, 3.0) * 0.000001
square_duty = 0.5 - _config.p_duty * 0.5
square_slide = -_config.p_duty_ramp * 0.00005
if _config.p_arp_mod >= 0.0:
arp_mod = 1.0 - pow(_config.p_arp_mod, 2.0) * 0.9
else:
arp_mod = 1.0 + pow(_config.p_arp_mod, 2.0) * 10.0
arp_time = 0
arp_limit = int(pow(1.0 - _config.p_arp_speed, 2.0) * 20000 + 32)
if _config.p_arp_speed == 1.0:
arp_limit = 0
if restart:
phase = 0
# Reset filter.
fltp = 0.0
fltdp = 0.0
fltw = pow(_config.p_lpf_freq, 3.0) * 0.1
fltw_d = 1.0 + _config.p_lpf_ramp * 0.0001
fltdmp = min(5.0 / (1.0 + pow(_config.p_lpf_resonance, 2.0) * 20.0) * (0.01 + fltw), 0.8)
fltphp = 0.0
flthp = pow(_config.p_hpf_freq, 2.0) * 0.1
flthp_d = 1.0 + _config.p_hpf_ramp * 0.0003
# Reset vibrato
vib_phase = 0.0
vib_speed = pow(_config.p_vib_speed, 2.0) * 0.01
vib_amp = _config.p_vib_strength * 0.5
# Reset envelope
env_vol = 0.0
env_length[0] = int(_config.p_env_attack * _config.p_env_attack * 100000.0)
env_length[1] = int(_config.p_env_sustain * _config.p_env_sustain * 100000.0)
env_length[2] = int(_config.p_env_decay * _config.p_env_decay * 100000.0)
fphase = pow(_config.p_pha_offset, 2.0) * 1020.0
if _config.p_pha_offset < 0.0:
fphase = -fphase
fdphase = pow(_config.p_pha_ramp, 2.0) * 1.0
if _config.p_pha_ramp < 0.0:
fdphase = -fdphase
iphase = int(abs(fphase))
ipp = 0
phaser_buffer.resize(1024)
for i in phaser_buffer.size():
phaser_buffer[i] = 0.0
noise_buffer.resize(32)
for i in noise_buffer.size():
noise_buffer[i] = rand_range(-1.0, +1.0)
rep_time = 0
rep_limit = int(pow(1.0 - _config.p_repeat_speed, 2.0) * 20000 + 32)
if _config.p_repeat_speed == 0.0:
rep_limit = 0