mirror of
https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13.git
synced 2025-12-11 18:33:36 +00:00
ports my baystruments updates from tg (#13219)
* subsystem * fix * e * update * fix
This commit is contained in:
@@ -4,16 +4,26 @@ PROCESSING_SUBSYSTEM_DEF(instruments)
|
|||||||
init_order = INIT_ORDER_INSTRUMENTS
|
init_order = INIT_ORDER_INSTRUMENTS
|
||||||
flags = SS_KEEP_TIMING
|
flags = SS_KEEP_TIMING
|
||||||
priority = FIRE_PRIORITY_INSTRUMENTS
|
priority = FIRE_PRIORITY_INSTRUMENTS
|
||||||
var/static/list/datum/instrument/instrument_data = list() //id = datum
|
/// List of all instrument data, associative id = datum
|
||||||
|
var/static/list/datum/instrument/instrument_data = list()
|
||||||
|
/// List of all song datums.
|
||||||
var/static/list/datum/song/songs = list()
|
var/static/list/datum/song/songs = list()
|
||||||
|
/// Max lines in songs
|
||||||
var/static/musician_maxlines = 600
|
var/static/musician_maxlines = 600
|
||||||
|
/// Max characters per line in songs
|
||||||
var/static/musician_maxlinechars = 300
|
var/static/musician_maxlinechars = 300
|
||||||
|
/// Deciseconds between hearchecks. Too high and instruments seem to lag when people are moving around in terms of who can hear it. Too low and the server lags from this.
|
||||||
var/static/musician_hearcheck_mindelay = 5
|
var/static/musician_hearcheck_mindelay = 5
|
||||||
|
/// Maximum instrument channels total instruments are allowed to use. This is so you don't have instruments deadlocking all sound channels.
|
||||||
var/static/max_instrument_channels = MAX_INSTRUMENT_CHANNELS
|
var/static/max_instrument_channels = MAX_INSTRUMENT_CHANNELS
|
||||||
|
/// Current number of channels allocated for instruments
|
||||||
var/static/current_instrument_channels = 0
|
var/static/current_instrument_channels = 0
|
||||||
|
/// Single cached list for synthesizer instrument ids, so you don't have to have a new list with every synthesizer.
|
||||||
|
var/static/list/synthesizer_instrument_ids
|
||||||
|
|
||||||
/datum/controller/subsystem/processing/instruments/Initialize()
|
/datum/controller/subsystem/processing/instruments/Initialize()
|
||||||
initialize_instrument_data()
|
initialize_instrument_data()
|
||||||
|
synthesizer_instrument_ids = get_allowed_instrument_ids()
|
||||||
return ..()
|
return ..()
|
||||||
|
|
||||||
/datum/controller/subsystem/processing/instruments/proc/on_song_new(datum/song/S)
|
/datum/controller/subsystem/processing/instruments/proc/on_song_new(datum/song/S)
|
||||||
@@ -29,7 +39,10 @@ PROCESSING_SUBSYSTEM_DEF(instruments)
|
|||||||
continue
|
continue
|
||||||
I = new path
|
I = new path
|
||||||
I.Initialize()
|
I.Initialize()
|
||||||
instrument_data[I.id || "[I.type]"] = I
|
if(!I.id)
|
||||||
|
qdel(I)
|
||||||
|
continue
|
||||||
|
instrument_data[I.id] = I
|
||||||
CHECK_TICK
|
CHECK_TICK
|
||||||
|
|
||||||
/datum/controller/subsystem/processing/instruments/proc/get_instrument(id_or_path)
|
/datum/controller/subsystem/processing/instruments/proc/get_instrument(id_or_path)
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
#define MUSIC_MAXLINES 1000
|
#define MUSIC_MAXLINES 1000
|
||||||
#define MUSIC_MAXLINECHARS 300
|
#define MUSIC_MAXLINECHARS 300
|
||||||
|
|
||||||
|
/**
|
||||||
|
* # Song datum
|
||||||
|
*
|
||||||
|
* These are the actual backend behind instruments.
|
||||||
|
* They attach to an atom and provide the editor + playback functionality.
|
||||||
|
*/
|
||||||
/datum/song
|
/datum/song
|
||||||
/// Name of the song
|
/// Name of the song
|
||||||
var/name = "Untitled"
|
var/name = "Untitled"
|
||||||
@@ -15,6 +21,9 @@
|
|||||||
/// delay between notes in deciseconds
|
/// delay between notes in deciseconds
|
||||||
var/tempo = 5
|
var/tempo = 5
|
||||||
|
|
||||||
|
/// How far we can be heard
|
||||||
|
var/instrument_range = 15
|
||||||
|
|
||||||
/// Are we currently playing?
|
/// Are we currently playing?
|
||||||
var/playing = FALSE
|
var/playing = FALSE
|
||||||
|
|
||||||
@@ -53,17 +62,24 @@
|
|||||||
|
|
||||||
/////////////////// Playing variables ////////////////
|
/////////////////// Playing variables ////////////////
|
||||||
/**
|
/**
|
||||||
* Only used in synthesized playback - The chords we compiled. Non assoc list of lists:
|
* Build by compile_chords()
|
||||||
* list(list(key1, key2, key3..., tempo_divisor), list(key1, key2..., tempo_divisor), ...)
|
* Must be rebuilt on instrument switch.
|
||||||
* tempo_divisor always exists
|
|
||||||
* if key1 (and so if there's no keys) doesn't exist it's a rest
|
|
||||||
* Compilation happens when we start playing and is cleared after we finish playing.
|
* Compilation happens when we start playing and is cleared after we finish playing.
|
||||||
|
* Format: list of chord lists, with chordlists having (key1, key2, key3, tempodiv)
|
||||||
*/
|
*/
|
||||||
var/list/compiled_chords
|
var/list/compiled_chords
|
||||||
|
/// Current section of a long chord we're on, so we don't need to make a billion chords, one for every unit ticklag.
|
||||||
|
var/elapsed_delay
|
||||||
|
/// Amount of delay to wait before playing the next chord
|
||||||
|
var/delay_by
|
||||||
|
/// Current chord we're on.
|
||||||
|
var/current_chord
|
||||||
/// Channel as text = current volume percentage but it's 0 to 100 instead of 0 to 1.
|
/// Channel as text = current volume percentage but it's 0 to 100 instead of 0 to 1.
|
||||||
var/list/channels_playing = list()
|
var/list/channels_playing = list()
|
||||||
/// List of channels that aren't being used, as text. This is to prevent unnecessary freeing and reallocations from SSsounds/SSinstruments.
|
/// List of channels that aren't being used, as text. This is to prevent unnecessary freeing and reallocations from SSsounds/SSinstruments.
|
||||||
var/list/channels_idle = list()
|
var/list/channels_idle = list()
|
||||||
|
/// Person playing us
|
||||||
|
var/mob/user_playing
|
||||||
//////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////
|
||||||
|
|
||||||
/// Last world.time we checked for who can hear us
|
/// Last world.time we checked for who can hear us
|
||||||
@@ -72,8 +88,6 @@
|
|||||||
var/list/hearing_mobs
|
var/list/hearing_mobs
|
||||||
/// If this is enabled, some things won't be strictly cleared when they usually are (liked compiled_chords on play stop)
|
/// If this is enabled, some things won't be strictly cleared when they usually are (liked compiled_chords on play stop)
|
||||||
var/debug_mode = FALSE
|
var/debug_mode = FALSE
|
||||||
/// Last time we processed decay
|
|
||||||
var/last_process_decay
|
|
||||||
/// Max sound channels to occupy
|
/// Max sound channels to occupy
|
||||||
var/max_sound_channels = CHANNELS_PER_INSTRUMENT
|
var/max_sound_channels = CHANNELS_PER_INSTRUMENT
|
||||||
/// Current channels, so we can save a length() call.
|
/// Current channels, so we can save a length() call.
|
||||||
@@ -113,7 +127,7 @@
|
|||||||
var/cached_exponential_dropoff = 1.045
|
var/cached_exponential_dropoff = 1.045
|
||||||
/////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
/datum/song/New(atom/parent, list/instrument_ids)
|
/datum/song/New(atom/parent, list/instrument_ids, new_range)
|
||||||
SSinstruments.on_song_new(src)
|
SSinstruments.on_song_new(src)
|
||||||
lines = list()
|
lines = list()
|
||||||
tempo = sanitize_tempo(tempo)
|
tempo = sanitize_tempo(tempo)
|
||||||
@@ -125,6 +139,8 @@
|
|||||||
hearing_mobs = list()
|
hearing_mobs = list()
|
||||||
volume = clamp(volume, min_volume, max_volume)
|
volume = clamp(volume, min_volume, max_volume)
|
||||||
update_sustain()
|
update_sustain()
|
||||||
|
if(new_range)
|
||||||
|
instrument_range = new_range
|
||||||
|
|
||||||
/datum/song/Destroy()
|
/datum/song/Destroy()
|
||||||
stop_playing()
|
stop_playing()
|
||||||
@@ -135,12 +151,15 @@
|
|||||||
parent = null
|
parent = null
|
||||||
return ..()
|
return ..()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks and stores which mobs can hear us. Terminates sounds for mobs that leave our range.
|
||||||
|
*/
|
||||||
/datum/song/proc/do_hearcheck()
|
/datum/song/proc/do_hearcheck()
|
||||||
last_hearcheck = world.time
|
last_hearcheck = world.time
|
||||||
var/list/old = hearing_mobs.Copy()
|
var/list/old = hearing_mobs.Copy()
|
||||||
hearing_mobs.len = 0
|
hearing_mobs.len = 0
|
||||||
var/turf/source = get_turf(parent)
|
var/turf/source = get_turf(parent)
|
||||||
for(var/mob/M in get_hearers_in_view(15, source))
|
for(var/mob/M in get_hearers_in_view(instrument_range, source))
|
||||||
if(!(M?.client?.prefs?.toggles & SOUND_INSTRUMENTS))
|
if(!(M?.client?.prefs?.toggles & SOUND_INSTRUMENTS))
|
||||||
continue
|
continue
|
||||||
hearing_mobs[M] = get_dist(M, source)
|
hearing_mobs[M] = get_dist(M, source)
|
||||||
@@ -148,10 +167,15 @@
|
|||||||
for(var/i in exited)
|
for(var/i in exited)
|
||||||
terminate_sound_mob(i)
|
terminate_sound_mob(i)
|
||||||
|
|
||||||
/// I can either be a datum, id, or path (if the instrument has no id).
|
/**
|
||||||
|
* Sets our instrument, caching anything necessary for faster accessing. Accepts an ID, typepath, or instantiated instrument datum.
|
||||||
|
*/
|
||||||
/datum/song/proc/set_instrument(datum/instrument/I)
|
/datum/song/proc/set_instrument(datum/instrument/I)
|
||||||
|
terminate_all_sounds()
|
||||||
|
var/old_legacy
|
||||||
if(using_instrument)
|
if(using_instrument)
|
||||||
using_instrument.songs_using -= src
|
using_instrument.songs_using -= src
|
||||||
|
old_legacy = (using_instrument.instrument_flags & INSTRUMENT_LEGACY)
|
||||||
using_instrument = null
|
using_instrument = null
|
||||||
cached_samples = null
|
cached_samples = null
|
||||||
cached_legacy_ext = null
|
cached_legacy_ext = null
|
||||||
@@ -162,7 +186,7 @@
|
|||||||
if(istype(I))
|
if(istype(I))
|
||||||
using_instrument = I
|
using_instrument = I
|
||||||
I.songs_using += src
|
I.songs_using += src
|
||||||
var/instrument_legacy = CHECK_BITFIELD(I.instrument_flags, INSTRUMENT_LEGACY)
|
var/instrument_legacy = (I.instrument_flags & INSTRUMENT_LEGACY)
|
||||||
if(instrument_legacy)
|
if(instrument_legacy)
|
||||||
cached_legacy_ext = I.legacy_instrument_ext
|
cached_legacy_ext = I.legacy_instrument_ext
|
||||||
cached_legacy_dir = I.legacy_instrument_path
|
cached_legacy_dir = I.legacy_instrument_path
|
||||||
@@ -170,23 +194,37 @@
|
|||||||
else
|
else
|
||||||
cached_samples = I.samples
|
cached_samples = I.samples
|
||||||
legacy = FALSE
|
legacy = FALSE
|
||||||
|
if(isnull(old_legacy) || (old_legacy != instrument_legacy))
|
||||||
|
if(playing)
|
||||||
|
compile_chords()
|
||||||
|
|
||||||
/// THIS IS A BLOCKING CALL.
|
/**
|
||||||
|
* Attempts to start playing our song.
|
||||||
|
*/
|
||||||
/datum/song/proc/start_playing(mob/user)
|
/datum/song/proc/start_playing(mob/user)
|
||||||
if(playing)
|
if(playing)
|
||||||
return
|
return
|
||||||
if(!using_instrument?.ready())
|
if(!using_instrument?.ready())
|
||||||
to_chat(user, "<span class='warning'>An error has occured with [src]. Please reset the instrument.</span>")
|
to_chat(user, "<span class='warning'>An error has occured with [src]. Please reset the instrument.</span>")
|
||||||
return
|
return
|
||||||
|
compile_chords()
|
||||||
|
if(!length(compiled_chords))
|
||||||
|
to_chat(user, "<span class='warning'>Song is empty.</span>")
|
||||||
|
return
|
||||||
playing = TRUE
|
playing = TRUE
|
||||||
updateDialog()
|
updateDialog(user_playing)
|
||||||
//we can not afford to runtime, since we are going to be doing sound channel reservations and if we runtime it means we have a channel allocation leak.
|
//we can not afford to runtime, since we are going to be doing sound channel reservations and if we runtime it means we have a channel allocation leak.
|
||||||
//wrap the rest of the stuff to ensure stop_playing() is called.
|
//wrap the rest of the stuff to ensure stop_playing() is called.
|
||||||
last_process_decay = world.time
|
do_hearcheck()
|
||||||
|
elapsed_delay = 0
|
||||||
|
delay_by = 0
|
||||||
|
current_chord = 1
|
||||||
|
user_playing = user
|
||||||
START_PROCESSING(SSinstruments, src)
|
START_PROCESSING(SSinstruments, src)
|
||||||
. = do_play_lines(user)
|
|
||||||
stop_playing()
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops playing, terminating all sounds if in synthesized mode. Clears hearing_mobs.
|
||||||
|
*/
|
||||||
/datum/song/proc/stop_playing()
|
/datum/song/proc/stop_playing()
|
||||||
if(!playing)
|
if(!playing)
|
||||||
return
|
return
|
||||||
@@ -196,42 +234,93 @@
|
|||||||
STOP_PROCESSING(SSinstruments, src)
|
STOP_PROCESSING(SSinstruments, src)
|
||||||
terminate_all_sounds(TRUE)
|
terminate_all_sounds(TRUE)
|
||||||
hearing_mobs.len = 0
|
hearing_mobs.len = 0
|
||||||
updateDialog()
|
user_playing = null
|
||||||
|
|
||||||
/// THIS IS A BLOCKING CALL.
|
/**
|
||||||
/datum/song/proc/do_play_lines(user)
|
* Processes our song.
|
||||||
if(!playing)
|
*/
|
||||||
|
/datum/song/proc/process_song(wait)
|
||||||
|
if(!length(compiled_chords) || should_stop_playing(user_playing))
|
||||||
|
stop_playing()
|
||||||
return
|
return
|
||||||
do_hearcheck()
|
var/list/chord = compiled_chords[current_chord]
|
||||||
if(legacy)
|
if(++elapsed_delay >= delay_by)
|
||||||
do_play_lines_legacy(user)
|
play_chord(chord)
|
||||||
else
|
elapsed_delay = 0
|
||||||
do_play_lines_synthesized(user)
|
delay_by = tempodiv_to_delay(chord[length(chord)])
|
||||||
|
current_chord++
|
||||||
|
if(current_chord > length(compiled_chords))
|
||||||
|
if(repeat)
|
||||||
|
repeat--
|
||||||
|
current_chord = 1
|
||||||
|
return
|
||||||
|
else
|
||||||
|
stop_playing()
|
||||||
|
return
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a tempodiv to ticks to elapse before playing the next chord, taking into account our tempo.
|
||||||
|
*/
|
||||||
|
/datum/song/proc/tempodiv_to_delay(tempodiv)
|
||||||
|
if(!tempodiv)
|
||||||
|
tempodiv = 1 // no division by 0. some song converters tend to use 0 for when it wants to have no div, for whatever reason.
|
||||||
|
return max(1, round((tempo/tempodiv) / world.tick_lag, 1))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compiles chords.
|
||||||
|
*/
|
||||||
|
/datum/song/proc/compile_chords()
|
||||||
|
legacy? compile_legacy() : compile_synthesized()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays a chord.
|
||||||
|
*/
|
||||||
|
/datum/song/proc/play_chord(list/chord)
|
||||||
|
// last value is timing information
|
||||||
|
for(var/i in 1 to (length(chord) - 1))
|
||||||
|
legacy? playkey_legacy(chord[i][1], chord[i][2], chord[i][3], user_playing) : playkey_synth(chord[i], user_playing)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if we should halt playback.
|
||||||
|
*/
|
||||||
/datum/song/proc/should_stop_playing(mob/user)
|
/datum/song/proc/should_stop_playing(mob/user)
|
||||||
return QDELETED(parent) || !using_instrument || !playing
|
return QDELETED(parent) || !using_instrument || !playing
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitizes tempo to a value that makes sense and fits the current world.tick_lag.
|
||||||
|
*/
|
||||||
/datum/song/proc/sanitize_tempo(new_tempo)
|
/datum/song/proc/sanitize_tempo(new_tempo)
|
||||||
new_tempo = abs(new_tempo)
|
new_tempo = abs(new_tempo)
|
||||||
return clamp(round(new_tempo, world.tick_lag), world.tick_lag, 5 SECONDS)
|
return clamp(round(new_tempo, world.tick_lag), world.tick_lag, 5 SECONDS)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets our beats per minute based on our tempo.
|
||||||
|
*/
|
||||||
/datum/song/proc/get_bpm()
|
/datum/song/proc/get_bpm()
|
||||||
return 600 / tempo
|
return 600 / tempo
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets our tempo from a beats-per-minute, sanitizing it to a valid number first.
|
||||||
|
*/
|
||||||
/datum/song/proc/set_bpm(bpm)
|
/datum/song/proc/set_bpm(bpm)
|
||||||
tempo = sanitize_tempo(600 / bpm)
|
tempo = sanitize_tempo(600 / bpm)
|
||||||
|
|
||||||
/// Updates the window for our user. Override in subtypes.
|
/**
|
||||||
/datum/song/proc/updateDialog(mob/user = usr)
|
* Updates the window for our users. Override down the line.
|
||||||
|
*/
|
||||||
|
/datum/song/proc/updateDialog(mob/user)
|
||||||
ui_interact(user)
|
ui_interact(user)
|
||||||
|
|
||||||
/datum/song/process(wait)
|
/datum/song/process(wait)
|
||||||
if(!playing)
|
if(!playing)
|
||||||
return PROCESS_KILL
|
return PROCESS_KILL
|
||||||
var/delay = world.time - last_process_decay
|
// it's expected this ticks at every world.tick_lag. if it lags, do not attempt to catch up.
|
||||||
process_decay(delay)
|
process_song(world.tick_lag)
|
||||||
last_process_decay = world.time
|
process_decay(world.tick_lag)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates our cached linear/exponential falloff stuff, saving calculations down the line.
|
||||||
|
*/
|
||||||
/datum/song/proc/update_sustain()
|
/datum/song/proc/update_sustain()
|
||||||
// Exponential is easy
|
// Exponential is easy
|
||||||
cached_exponential_dropoff = sustain_exponential_dropoff
|
cached_exponential_dropoff = sustain_exponential_dropoff
|
||||||
@@ -241,21 +330,33 @@
|
|||||||
var/volume_decrease_per_decisecond = volume_diff / target_duration
|
var/volume_decrease_per_decisecond = volume_diff / target_duration
|
||||||
cached_linear_dropoff = volume_decrease_per_decisecond
|
cached_linear_dropoff = volume_decrease_per_decisecond
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for setting output volume.
|
||||||
|
*/
|
||||||
/datum/song/proc/set_volume(volume)
|
/datum/song/proc/set_volume(volume)
|
||||||
src.volume = clamp(volume, max(0, min_volume), min(100, max_volume))
|
src.volume = clamp(volume, max(0, min_volume), min(100, max_volume))
|
||||||
update_sustain()
|
update_sustain()
|
||||||
updateDialog()
|
updateDialog()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for setting how low the volume has to get before a note is considered "dead" and dropped
|
||||||
|
*/
|
||||||
/datum/song/proc/set_dropoff_volume(volume)
|
/datum/song/proc/set_dropoff_volume(volume)
|
||||||
sustain_dropoff_volume = clamp(volume, INSTRUMENT_MIN_SUSTAIN_DROPOFF, 100)
|
sustain_dropoff_volume = clamp(volume, INSTRUMENT_MIN_SUSTAIN_DROPOFF, 100)
|
||||||
update_sustain()
|
update_sustain()
|
||||||
updateDialog()
|
updateDialog()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for setting exponential falloff factor.
|
||||||
|
*/
|
||||||
/datum/song/proc/set_exponential_drop_rate(drop)
|
/datum/song/proc/set_exponential_drop_rate(drop)
|
||||||
sustain_exponential_dropoff = clamp(drop, INSTRUMENT_EXP_FALLOFF_MIN, INSTRUMENT_EXP_FALLOFF_MAX)
|
sustain_exponential_dropoff = clamp(drop, INSTRUMENT_EXP_FALLOFF_MIN, INSTRUMENT_EXP_FALLOFF_MAX)
|
||||||
update_sustain()
|
update_sustain()
|
||||||
updateDialog()
|
updateDialog()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for setting linear falloff duration.
|
||||||
|
*/
|
||||||
/datum/song/proc/set_linear_falloff_duration(duration)
|
/datum/song/proc/set_linear_falloff_duration(duration)
|
||||||
sustain_linear_duration = clamp(duration, 0.1, INSTRUMENT_MAX_TOTAL_SUSTAIN)
|
sustain_linear_duration = clamp(duration, 0.1, INSTRUMENT_MAX_TOTAL_SUSTAIN)
|
||||||
update_sustain()
|
update_sustain()
|
||||||
@@ -277,10 +378,8 @@
|
|||||||
// subtype for handheld instruments, like violin
|
// subtype for handheld instruments, like violin
|
||||||
/datum/song/handheld
|
/datum/song/handheld
|
||||||
|
|
||||||
/datum/song/handheld/updateDialog(mob/user = usr)
|
/datum/song/handheld/updateDialog(mob/user)
|
||||||
if(user.machine != src)
|
parent.ui_interact(user || usr)
|
||||||
return
|
|
||||||
parent.ui_interact(user)
|
|
||||||
|
|
||||||
/datum/song/handheld/should_stop_playing(mob/user)
|
/datum/song/handheld/should_stop_playing(mob/user)
|
||||||
. = ..()
|
. = ..()
|
||||||
@@ -292,10 +391,8 @@
|
|||||||
// subtype for stationary structures, like pianos
|
// subtype for stationary structures, like pianos
|
||||||
/datum/song/stationary
|
/datum/song/stationary
|
||||||
|
|
||||||
/datum/song/stationary/updateDialog(mob/user = usr)
|
/datum/song/stationary/updateDialog(mob/user)
|
||||||
if(user.machine != src)
|
parent.ui_interact(user || usr)
|
||||||
return
|
|
||||||
parent.ui_interact(user)
|
|
||||||
|
|
||||||
/datum/song/stationary/should_stop_playing(mob/user)
|
/datum/song/stationary/should_stop_playing(mob/user)
|
||||||
. = ..()
|
. = ..()
|
||||||
|
|||||||
@@ -1,48 +1,52 @@
|
|||||||
/// Playing legacy instruments - None of the "advanced" like sound reservations and decay are invoked.
|
/**
|
||||||
/datum/song/proc/do_play_lines_legacy(mob/user)
|
* Compiles our lines into "chords" with filenames for legacy playback. 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.
|
||||||
while(repeat >= 0)
|
*/
|
||||||
var/cur_oct[7]
|
/datum/song/proc/compile_legacy()
|
||||||
var/cur_acc[7]
|
if(!length(src.lines))
|
||||||
for(var/i = 1 to 7)
|
return
|
||||||
cur_oct[i] = 3
|
var/list/lines = src.lines //cache for hyepr speed!
|
||||||
cur_acc[i] = "n"
|
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[++compiled_chord.len] = list(key, accents[key], octaves[key])
|
||||||
|
compiled_chord += tempodiv //this goes last
|
||||||
|
if(length(compiled_chord))
|
||||||
|
compiled_chords[++compiled_chords.len] = compiled_chord
|
||||||
|
|
||||||
for(var/line in lines)
|
/**
|
||||||
for(var/beat in splittext(lowertext(line), ","))
|
* Proc to play a legacy note. Just plays the sound to hearing mobs (and does hearcheck if necessary), no fancy channel/sustain/management.
|
||||||
if(should_stop_playing(user))
|
*
|
||||||
return
|
* Arguments:
|
||||||
var/list/notes = splittext(beat, "/")
|
* * note is a number from 1-7 for A-G
|
||||||
if(length(notes)) //because some jack-butts are going to do ,,,, to symbolize 3 rests instead of something reasonable like ,/1.
|
* * acc is either "b", "n", or "#"
|
||||||
for(var/note in splittext(notes[1], "-"))
|
* * oct is 1-8 (or 9 for C)
|
||||||
if(length(note) == 0)
|
*/
|
||||||
continue
|
/datum/song/proc/playkey_legacy(note, acc as text, oct, mob/user)
|
||||||
var/cur_note = text2ascii(note) - 96
|
|
||||||
if(cur_note < 1 || cur_note > 7)
|
|
||||||
continue
|
|
||||||
for(var/i=2 to length(note))
|
|
||||||
var/ni = copytext(note,i,i+1)
|
|
||||||
if(!text2num(ni))
|
|
||||||
if(ni == "#" || ni == "b" || ni == "n")
|
|
||||||
cur_acc[cur_note] = ni
|
|
||||||
else if(ni == "s")
|
|
||||||
cur_acc[cur_note] = "#" // so shift is never required
|
|
||||||
else
|
|
||||||
cur_oct[cur_note] = text2num(ni)
|
|
||||||
playnote_legacy(cur_note, cur_acc[cur_note], cur_oct[cur_note])
|
|
||||||
if(notes.len >= 2 && text2num(notes[2]))
|
|
||||||
sleep(sanitize_tempo(tempo / text2num(notes[2])))
|
|
||||||
else
|
|
||||||
sleep(tempo)
|
|
||||||
if(should_stop_playing(user))
|
|
||||||
return
|
|
||||||
repeat--
|
|
||||||
updateDialog()
|
|
||||||
repeat = 0
|
|
||||||
|
|
||||||
// note is a number from 1-7 for A-G
|
|
||||||
// acc is either "b", "n", or "#"
|
|
||||||
// oct is 1-8 (or 9 for C)
|
|
||||||
/datum/song/proc/playnote_legacy(note, acc as text, oct)
|
|
||||||
// handle accidental -> B<>C of E<>F
|
// handle accidental -> B<>C of E<>F
|
||||||
if(acc == "b" && (note == 3 || note == 6)) // C or F
|
if(acc == "b" && (note == 3 || note == 6)) // C or F
|
||||||
if(note == 3)
|
if(note == 3)
|
||||||
|
|||||||
@@ -1,27 +1,7 @@
|
|||||||
/datum/song/proc/do_play_lines_synthesized(mob/user)
|
/**
|
||||||
compile_lines()
|
* 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.
|
||||||
while(repeat >= 0)
|
*/
|
||||||
if(should_stop_playing(user))
|
/datum/song/proc/compile_synthesized()
|
||||||
return
|
|
||||||
var/warned = FALSE
|
|
||||||
for(var/_chord in compiled_chords)
|
|
||||||
if(should_stop_playing(user))
|
|
||||||
return
|
|
||||||
var/list/chord = _chord
|
|
||||||
var/tempodiv = chord[chord.len]
|
|
||||||
for(var/i in 1 to chord.len - 1)
|
|
||||||
var/key = chord[i]
|
|
||||||
if(!playkey_synth(key))
|
|
||||||
if(!warned)
|
|
||||||
warned = TRUE
|
|
||||||
to_chat(user, "<span class='boldwarning'>Your instrument has ran out of channels. You might be playing your song too fast or be setting sustain to too high of a value. This warning will be suppressed for the rest of this cycle.</span>")
|
|
||||||
sleep(sanitize_tempo(tempo / (tempodiv || 1)))
|
|
||||||
repeat--
|
|
||||||
updateDialog()
|
|
||||||
repeat = 0
|
|
||||||
|
|
||||||
/// C-Db2-A-A4/2,A-B#4-C/3,/4,A,A-B-C as an example
|
|
||||||
/datum/song/proc/compile_lines()
|
|
||||||
if(!length(src.lines))
|
if(!length(src.lines))
|
||||||
return
|
return
|
||||||
var/list/lines = src.lines //cache for hyepr speed!
|
var/list/lines = src.lines //cache for hyepr speed!
|
||||||
@@ -57,10 +37,12 @@
|
|||||||
compiled_chord += tempodiv //this goes last
|
compiled_chord += tempodiv //this goes last
|
||||||
if(length(compiled_chord))
|
if(length(compiled_chord))
|
||||||
compiled_chords[++compiled_chords.len] = compiled_chord
|
compiled_chords[++compiled_chords.len] = compiled_chord
|
||||||
CHECK_TICK
|
|
||||||
return compiled_chords
|
|
||||||
|
|
||||||
/datum/song/proc/playkey_synth(key)
|
/**
|
||||||
|
* 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)
|
if(can_noteshift)
|
||||||
key = clamp(key + note_shift, key_min, key_max)
|
key = clamp(key + note_shift, key_min, key_max)
|
||||||
if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
|
if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
|
||||||
@@ -83,6 +65,9 @@
|
|||||||
M.playsound_local(get_turf(parent), null, volume, FALSE, K.frequency, INSTRUMENT_DISTANCE_NO_FALLOFF, channel, null, copy, distance_multiplier = INSTRUMENT_DISTANCE_FALLOFF_BUFF)
|
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
|
// 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)
|
/datum/song/proc/terminate_all_sounds(clear_channels = TRUE)
|
||||||
for(var/i in hearing_mobs)
|
for(var/i in hearing_mobs)
|
||||||
terminate_sound_mob(i)
|
terminate_sound_mob(i)
|
||||||
@@ -93,10 +78,16 @@
|
|||||||
using_sound_channels = 0
|
using_sound_channels = 0
|
||||||
SSsounds.free_datum_channels(src)
|
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)
|
/datum/song/proc/terminate_sound_mob(mob/M)
|
||||||
for(var/channel in channels_playing)
|
for(var/channel in channels_playing)
|
||||||
M.stop_sound_channel(text2num(channel))
|
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()
|
/datum/song/proc/pop_channel()
|
||||||
if(length(channels_idle)) //just pop one off of here if we have one available
|
if(length(channels_idle)) //just pop one off of here if we have one available
|
||||||
. = text2num(channels_idle[1])
|
. = text2num(channels_idle[1])
|
||||||
@@ -108,6 +99,12 @@
|
|||||||
if(!isnull(.))
|
if(!isnull(.))
|
||||||
using_sound_channels++
|
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)
|
/datum/song/proc/process_decay(wait_ds)
|
||||||
var/linear_dropoff = cached_linear_dropoff * wait_ds
|
var/linear_dropoff = cached_linear_dropoff * wait_ds
|
||||||
var/exponential_dropoff = cached_exponential_dropoff ** wait_ds
|
var/exponential_dropoff = cached_exponential_dropoff ** wait_ds
|
||||||
|
|||||||
Reference in New Issue
Block a user