973839dee4
* subsystem * fix * e * update * fix
133 lines
5.2 KiB
Plaintext
133 lines
5.2 KiB
Plaintext
/**
|
|
* Compiles our lines into "chords" with numbers. This makes there have to be a bit of lag at the beginning of the song, but repeats will not have to parse it again, and overall playback won't be impacted by as much lag.
|
|
*/
|
|
/datum/song/proc/compile_synthesized()
|
|
if(!length(src.lines))
|
|
return
|
|
var/list/lines = src.lines //cache for hyepr speed!
|
|
compiled_chords = list()
|
|
var/list/octaves = list(3, 3, 3, 3, 3, 3, 3)
|
|
var/list/accents = list("n", "n", "n", "n", "n", "n", "n")
|
|
for(var/line in lines)
|
|
var/list/chords = splittext(lowertext(line), ",")
|
|
for(var/chord in chords)
|
|
var/list/compiled_chord = list()
|
|
var/tempodiv = 1
|
|
var/list/notes_tempodiv = splittext(chord, "/")
|
|
var/len = length(notes_tempodiv)
|
|
if(len >= 2)
|
|
tempodiv = text2num(notes_tempodiv[2])
|
|
if(len) //some dunkass is going to do ,,,, to make 3 rests instead of ,/1 because there's no standardization so let's be prepared for that.
|
|
var/list/notes = splittext(notes_tempodiv[1], "-")
|
|
for(var/note in notes)
|
|
if(length(note) == 0)
|
|
continue
|
|
// 1-7, A-G
|
|
var/key = text2ascii(note) - 96
|
|
if((key < 1) || (key > 7))
|
|
continue
|
|
for(var/i in 2 to length(note))
|
|
var/oct_acc = copytext(note, i, i + 1)
|
|
var/num = text2num(oct_acc)
|
|
if(!num) //it's an accidental
|
|
accents[key] = oct_acc //if they misspelled it/fucked up that's on them lmao, no safety checks.
|
|
else //octave
|
|
octaves[key] = clamp(num, octave_min, octave_max)
|
|
compiled_chord += clamp((note_offset_lookup[key] + octaves[key] * 12 + accent_lookup[accents[key]]), key_min, key_max)
|
|
compiled_chord += tempodiv //this goes last
|
|
if(length(compiled_chord))
|
|
compiled_chords[++compiled_chords.len] = compiled_chord
|
|
|
|
/**
|
|
* Plays a specific numerical key from our instrument to anyone who can hear us.
|
|
* Does a hearing check if enough time has passed.
|
|
*/
|
|
/datum/song/proc/playkey_synth(key, mob/user)
|
|
if(can_noteshift)
|
|
key = clamp(key + note_shift, key_min, key_max)
|
|
if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
|
|
do_hearcheck()
|
|
var/datum/instrument_key/K = using_instrument.samples[num2text(key)] //See how fucking easy it is to make a number text? You don't need a complicated 9 line proc!
|
|
//Should probably add channel limiters here at some point but I don't care right now.
|
|
var/channel = pop_channel()
|
|
if(isnull(channel))
|
|
return FALSE
|
|
. = TRUE
|
|
var/sound/copy = sound(K.sample)
|
|
var/volume = src.volume * using_instrument.volume_multiplier
|
|
copy.frequency = K.frequency
|
|
copy.volume = volume
|
|
var/channel_text = num2text(channel)
|
|
channels_playing[channel_text] = 100
|
|
last_channel_played = channel_text
|
|
for(var/i in hearing_mobs)
|
|
var/mob/M = i
|
|
M.playsound_local(get_turf(parent), null, volume, FALSE, K.frequency, INSTRUMENT_DISTANCE_NO_FALLOFF, channel, null, copy, distance_multiplier = INSTRUMENT_DISTANCE_FALLOFF_BUFF)
|
|
// Could do environment and echo later but not for now
|
|
|
|
/**
|
|
* Stops all sounds we are "responsible" for. Only works in synthesized mode.
|
|
*/
|
|
/datum/song/proc/terminate_all_sounds(clear_channels = TRUE)
|
|
for(var/i in hearing_mobs)
|
|
terminate_sound_mob(i)
|
|
if(clear_channels)
|
|
channels_playing.len = 0
|
|
channels_idle.len = 0
|
|
SSinstruments.current_instrument_channels -= using_sound_channels
|
|
using_sound_channels = 0
|
|
SSsounds.free_datum_channels(src)
|
|
|
|
/**
|
|
* Stops all sounds we are responsible for in a given person. Only works in synthesized mode.
|
|
*/
|
|
/datum/song/proc/terminate_sound_mob(mob/M)
|
|
for(var/channel in channels_playing)
|
|
M.stop_sound_channel(text2num(channel))
|
|
|
|
/**
|
|
* Pops a channel we have reserved so we don't have to release and re-request them from SSsounds every time we play a note. This is faster.
|
|
*/
|
|
/datum/song/proc/pop_channel()
|
|
if(length(channels_idle)) //just pop one off of here if we have one available
|
|
. = text2num(channels_idle[1])
|
|
channels_idle.Cut(1,2)
|
|
return
|
|
if(using_sound_channels >= max_sound_channels)
|
|
return
|
|
. = SSinstruments.reserve_instrument_channel(src)
|
|
if(!isnull(.))
|
|
using_sound_channels++
|
|
|
|
/**
|
|
* Decays our channels and updates their volumes to mobs who can hear us.
|
|
*
|
|
* Arguments:
|
|
* * wait_ds - the deciseconds we should decay by. This is to compensate for any lag, as otherwise songs would get pretty nasty during high time dilation.
|
|
*/
|
|
/datum/song/proc/process_decay(wait_ds)
|
|
var/linear_dropoff = cached_linear_dropoff * wait_ds
|
|
var/exponential_dropoff = cached_exponential_dropoff ** wait_ds
|
|
for(var/channel in channels_playing)
|
|
if(full_sustain_held_note && (channel == last_channel_played))
|
|
continue
|
|
var/current_volume = channels_playing[channel]
|
|
switch(sustain_mode)
|
|
if(SUSTAIN_LINEAR)
|
|
current_volume -= linear_dropoff
|
|
if(SUSTAIN_EXPONENTIAL)
|
|
current_volume /= exponential_dropoff
|
|
channels_playing[channel] = current_volume
|
|
var/dead = current_volume <= sustain_dropoff_volume
|
|
var/channelnumber = text2num(channel)
|
|
if(dead)
|
|
channels_playing -= channel
|
|
channels_idle += channel
|
|
for(var/i in hearing_mobs)
|
|
var/mob/M = i
|
|
M.stop_sound_channel(channelnumber)
|
|
else
|
|
for(var/i in hearing_mobs)
|
|
var/mob/M = i
|
|
M.set_sound_channel_volume(channelnumber, (current_volume * 0.01) * volume * using_instrument.volume_multiplier)
|