[MIRROR] Instrument Update (#11645)

Co-authored-by: Selis <12716288+ItsSelis@users.noreply.github.com>
Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
CHOMPStation2StaffMirrorBot
2025-09-14 13:46:26 -07:00
committed by GitHub
parent 19b8044f61
commit d8408a2c59
916 changed files with 1155 additions and 566 deletions

View File

@@ -2,5 +2,8 @@
// When the signal is called: (signal arguments)
// All signals send the source datum of the signal as the first argument
///when an atom starts playing a song datum (datum/song)
#define COMSIG_ATOM_STARTING_INSTRUMENT "atom_starting_instrument"
///When the transform or an atom is varedited through vv topic.
#define COMSIG_ATOM_VV_MODIFY_TRANSFORM "atom_vv_modify_transform"

View File

@@ -0,0 +1,12 @@
// /datum/song signals
///sent to the instrument when a song starts playing: (datum/starting_song, atom/player)
#define COMSIG_INSTRUMENT_START "instrument_start"
///sent to the instrument when a song stops playing
#define COMSIG_INSTRUMENT_END "instrument_end"
///sent to the instrument on /should_stop_playing(): (atom/player). Return values can be found in DEFINES/song.dm
#define COMSIG_INSTRUMENT_SHOULD_STOP_PLAYING "instrument_should_stop_playing"
///sent to the instrument (and player if available) when a song repeats (datum/song)
#define COMSIG_INSTRUMENT_REPEAT "instrument_repeat"
///sent to the instrument when tempo changes, skipped on new. (datum/song)
#define COMSIG_INSTRUMENT_TEMPO_CHANGE "instrument_tempo_change"

View File

@@ -6,6 +6,8 @@
/// Max number of playing notes per instrument.
#define CHANNELS_PER_INSTRUMENT 128
/// Minimum length a note should ever go for
#define INSTRUMENT_MIN_TOTAL_SUSTAIN 0.1
/// Maximum length a note should ever go for
#define INSTRUMENT_MAX_TOTAL_SUSTAIN (5 SECONDS)
@@ -16,8 +18,8 @@
/// Minimum volume for when the sound is considered dead.
#define INSTRUMENT_MIN_SUSTAIN_DROPOFF 0
#define SUSTAIN_LINEAR 1
#define SUSTAIN_EXPONENTIAL 2
#define SUSTAIN_LINEAR "Linear"
#define SUSTAIN_EXPONENTIAL "Exponential"
// /datum/instrument instrument_flags
#define INSTRUMENT_LEGACY (1<<0) //Legacy instrument. Implies INSTRUMENT_DO_NOT_AUTOSAMPLE

17
code/__defines/song.dm Normal file
View File

@@ -0,0 +1,17 @@
#define MUSICIAN_HEARCHECK_MINDELAY 4
#define MUSIC_MAXLINES 1000
#define MUSIC_MAXLINECHARS 300
#define BPM_TO_TEMPO_SETTING(value) (600 / round(value, 1))
//Return values of song/should_stop_playing()
///When the song should stop being played
#define STOP_PLAYING 1
///Will ignore the instrument checks and play the song anyway.
#define IGNORE_INSTRUMENT_CHECKS 2
///it's what monkeys play!
#define MONKEY_SONG "BPM: 200\nC4/0,14,C,A4-F2,F3,A3,F-F2,A-F,F4,G4,F,D4-Bb2-G2\nD3,G3,D-G2,G3-G2,D,D4-G3,D,B4-B2,G,B3,G-B2,B3-B2\nG4,A4,G,E4-C3,E3,G3,E-C,G-C,E,E4-G,E,C5-E-A3,C4\nA-E3,C,E4-C3,A4-C4,B4-A3-A2,C5-C4,D5-F-B3,D4,B-F3\nD,F4-D3,D4,F-B-B2,G4-D,A4-C-F3,F,C/2,B3/2,A3-C3/2\nB/2,C4,E-C3,F4,G-C,F-F3,F-C,C4/2,B/2,A-A2/2,G3/2\nF/I"
///song played by the mook bard
#define MOOK_SONG "BPM: 240\nA5,B5,C#6,D6,E6/0.17,A/0.5,A/0.25,A3/0.25\nA4/0.25,C#5/0.25,E5/0.25,A/0.25,C#/0.25,E/0.12\nC#6/0.25,C#/0.25,E6/0.25,A3/0.25,A4/0.25\nC#5/0.25,E5/0.25,A/0.25,C#/0.25,E/0.25,D/0.25\nG6/0.25,D/0.17,F6/0.17,C#6/0.5,E6/0.5,D4/0.25\nA/0.25,D5/0.25,F5/0.25,A/0.25,D/0.25,F/0.25\nD6/0.08,F6/0.08,D4/0.25,A/0.25,D5/0.25,F5/0.25\nCn4/0.2,B/0.17,D6/0.17,G5/0.5,G/0.25,B3/0.25\nD4/0.25,G4/0.25,B4/0.25,D/0.25,G/0.25,B/0.12\nB5/0.25,B/0.25,D6/0.25,G3/0.25,G4/0.25,B4/0.25\nF/0.25,G/0.25,B/0.25,F/0.25,D/0.25,F6/0.25\nC6/0.17,E/0.17,B5/0.5,D#/0.5,C4/0.25,G/0.25\nC5/0.25,E5/0.25,G/0.25,C/0.25,E/0.25,C6/0.08\nE6/0.08,C4/0.25,Dn4/0.25,E4/0.25,A5/0.17,B/0.5\nC6/0.25,F5/0.08,F4/0.08,C5/0.08,E5/0.12,G5/0.12\nC6/0.25,E6/0.25,E4/0.08,C5/0.08,B/0.17,F6/0.17\nE6/0.5,B/0.25,E4/0.08,G#4/0.08,C6/0.17,D6/0.5\nE6/0.25,A3/0.25,E4/0.25,C5/0.25,Gn3/0.25\nF5/0.12,A5/0.12,A6/0.25,F3/0.25,F4/0.12,A4/0.12\nC/0.12,F6/0.17,A6/0.17,G#6/0.5,A/0.25,F3/0.25\nF4/0.12,A4/0.12,D#5/0.12,B/0.17,G#/0.17,B6/0.5\nB5/0.25,G#/0.25,E3/0.25,E4/0.12,G#4/0.12\nDn/0.12,E6/0.08,E3/0.25,F#3/0.25,G#3/0.25\nE5/0.17,A5/0.17,E/0.5,E/0.25,A3/0.25,C#4/0.25\nE4/0.25,C#/0.25,E/0.12,A5/0.5,B/0.5,C#6/0.5\nD6/0.5,A3/0.25,C#4/0.25,E/0.25,C#/0.25,E/0.25\nE6/0.08,Gn/0.25,E4/0.25,A4/0.25,C#5/0.25,E/0.25\nA/0.25,C#/0.25,E6/0.17,E/0.5,Fn6/0.5,G6/0.5\nG3/0.25,E4/0.25,A/0.25,C#/0.25,E/0.25,A/0.25\nC#/0.25,F/0.08,A6/0.08,F3/0.25,F4/0.25,A4/0.25\nCn/0.25,F/0.25,A/0.25,C/0.25,G6/0.12,A6/0.12\nG A G F6 G3/0.25 D4/0.25 G4/0.25 B4/0.25 D/0.25\nG/0.25 B/0.25 E6/0.12 G6/0.12 F/0.71 G/0.71 F/0.71\nE3/0.25 E4/0.25 G4/0.25 B/0.25 E/0.25 G/0.25 B/0.25\nA5/0.08 E6/0.08 A3/0.25 E4/0.25 A4/0.25 C#/0.25 E/0.25 A/0.25 C#/0.25 D6/0.17 E6/0.5 F/0.25 B3/0.25 D4/0.12 F4/0.12 B4/0.12 F6/0.25 E/0.25 D6/0.25 G#3/0.25 E4/0.12 G#4/0.12 B/0.12 Cn6/0.12 D/0.25 A3/0.25 A4/0.25 C5/0.25 E5/0.25 G#3/0.25 Gn/0.25 C4/0.25 E4/0.25 A/"

View File

@@ -15,6 +15,10 @@
/// Removes everything enclose in < and > inclusive of the bracket, and limits the length of the message.
#define STRIP_HTML_FULL(text, limit) (GLOB.html_tags.Replace(copytext(text, 1, limit), ""))
/// BYOND's string procs don't support being used on datum references (as in it doesn't look for a name for stringification)
/// We just use this macro to ensure that we will only pass strings to this BYOND-level function without developers needing to really worry about it.
#define LOWER_TEXT(thing) lowertext(UNLINT("[thing]"))
/// Removes characters incompatible with file names.
#define SANITIZE_FILENAME(text) (GLOB.filename_forbidden_chars.Replace(text, ""))

View File

@@ -601,6 +601,113 @@ GLOBAL_LIST_EMPTY(text_tag_cache)
else
return trim(html_encode(user_input), max_length)
#define NO_CHARS_DETECTED 0
#define SPACES_DETECTED 1
#define SYMBOLS_DETECTED 2
#define NUMBERS_DETECTED 3
#define LETTERS_DETECTED 4
/**
* Filters out undesirable characters from names.
*
* * strict - return null immediately instead of filtering out
* * allow_numbers - allows numbers and common special characters - used for silicon/other weird things names
* * cap_after_symbols - words like Bob's will be capitalized to Bob'S by default. False is good for titles.
*/
/proc/reject_bad_name(t_in, allow_numbers = FALSE, max_length = MAX_NAME_LEN, ascii_only = TRUE, strict = FALSE, cap_after_symbols = TRUE)
if(!t_in)
return //Rejects the input if it is null
var/number_of_alphanumeric = 0
var/last_char_group = NO_CHARS_DETECTED
var/t_out = ""
var/t_len = length(t_in)
var/charcount = 0
var/char = ""
// This is a sanity short circuit, if the users name is three times the maximum allowable length of name
// We bail out on trying to process the name at all, as it could be a bug or malicious input and we don't
// Want to iterate all of it.
if(t_len > 3 * MAX_NAME_LEN)
return
for(var/i = 1, i <= t_len, i += length(char))
char = t_in[i]
switch(text2ascii(char))
// A .. Z
if(65 to 90) //Uppercase Letters
number_of_alphanumeric++
last_char_group = LETTERS_DETECTED
// a .. z
if(97 to 122) //Lowercase Letters
if(last_char_group == NO_CHARS_DETECTED || last_char_group == SPACES_DETECTED || cap_after_symbols && last_char_group == SYMBOLS_DETECTED) //start of a word
char = uppertext(char)
number_of_alphanumeric++
last_char_group = LETTERS_DETECTED
// 0 .. 9
if(48 to 57) //Numbers
if(!allow_numbers) //allow name to start with number if AI/Borg
if(strict)
return
continue
number_of_alphanumeric++
last_char_group = NUMBERS_DETECTED
// ' - .
if(39,45,46) //Common name punctuation
if(last_char_group == NO_CHARS_DETECTED)
if(strict)
return
continue
last_char_group = SYMBOLS_DETECTED
// ~ | @ : # $ % & * +
if(126,124,64,58,35,36,37,38,42,43) //Other symbols that we'll allow (mainly for AI)
if(last_char_group == NO_CHARS_DETECTED || !allow_numbers) //suppress at start of string
if(strict)
return
continue
last_char_group = SYMBOLS_DETECTED
//Space
if(32)
if(last_char_group == NO_CHARS_DETECTED || last_char_group == SPACES_DETECTED) //suppress double-spaces and spaces at start of string
if(strict)
return
continue
last_char_group = SPACES_DETECTED
if(127 to INFINITY)
if(ascii_only)
if(strict)
return
continue
last_char_group = SYMBOLS_DETECTED //for now, we'll treat all non-ascii characters like symbols even though most are letters
else
continue
t_out += char
charcount++
if(charcount >= max_length)
break
if(number_of_alphanumeric < 2)
return //protects against tiny names like "A" and also names like "' ' ' ' ' ' ' '"
if(last_char_group == SPACES_DETECTED)
t_out = copytext_char(t_out, 1, -1) //removes the last character (in this case a space)
//if(!filter_name_ic(t_out)) FIXME
// return
return t_out
#undef NO_CHARS_DETECTED
#undef SPACES_DETECTED
#undef NUMBERS_DETECTED
#undef LETTERS_DETECTED
//Adds 'char' ahead of 'text' until there are 'count' characters total
/proc/add_leading(text, count, char = " ")
text = "[text]"

View File

@@ -19,6 +19,10 @@ PROCESSING_SUBSYSTEM_DEF(instruments)
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
var/static/list/note_sustain_modes = list(
SUSTAIN_LINEAR,
SUSTAIN_EXPONENTIAL,
)
/datum/controller/subsystem/processing/instruments/Initialize()
initialize_instrument_data()
@@ -32,10 +36,11 @@ PROCESSING_SUBSYSTEM_DEF(instruments)
songs -= S
/datum/controller/subsystem/processing/instruments/proc/initialize_instrument_data()
for(var/datum/instrument/I as anything in subtypesof(/datum/instrument))
if(initial(I.instrument_type) == I)
for(var/path in subtypesof(/datum/instrument))
var/datum/instrument/I = path
if(initial(I.abstract_type) == path)
continue
I = new I
I = new path
I.Initialize()
if(!I.id)
qdel(I)

View File

@@ -119,7 +119,7 @@
if(hack_result && in_hack_mode)
to_chat(user, span_notice("Your hacking attempt was succesful!"))
user.playsound_local(get_turf(src), 'sound/instruments/piano/An6.ogg', 50)
user.playsound_local(get_turf(src), 'sound/runtime/instruments/piano/An6.ogg', 50)
else
to_chat(user, span_warning("Your hacking attempt failed!"))
return 0

View File

@@ -255,3 +255,17 @@
/datum/preference/numeric/living/jukebox_volume/apply_to_client_updated(client/client, value)
client?.media?.update_volume(value)
/datum/preference/numeric/volume
abstract_type = /datum/preference/numeric/volume
minimum = 0
maximum = 100
/datum/preference/numeric/volume/create_default_value()
return maximum
/// Controls hearing instruments
/datum/preference/numeric/volume/sound_instruments
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
savefile_key = "sound_instruments"
savefile_identifier = PREFERENCE_PLAYER

View File

@@ -17,14 +17,14 @@
* Since songs cache them while playing, there isn't realistic issues regarding performance from accessing.
*/
/datum/instrument
/// Used for categorization subtypes
abstract_type = /datum/instrument
/// Name of the instrument
var/name = "Generic instrument"
/// Uniquely identifies this instrument so runtime changes are possible as opposed to paths. If this is unset, things will use path instead.
var/id
/// Category
var/category = "Unsorted"
/// Used for categorization subtypes
var/instrument_type = /datum/instrument
/// Write here however many samples, follow this syntax: "%note num%"='%sample file%' eg. "27"='synthesizer/e2.ogg'. Key must never be lower than 0 and higher than 127
var/list/real_samples
/// assoc list key = /datum/instrument_key. do not fill this yourself!
@@ -70,7 +70,8 @@
/datum/instrument/Destroy()
SSinstruments.instrument_data -= id
for(var/datum/song/S as anything in songs_using)
for(var/i in songs_using)
var/datum/song/S = i
S.set_instrument(null)
real_samples = null
samples = null

View File

@@ -1,26 +1,26 @@
/datum/instrument/brass
name = "Generic brass instrument"
category = "Brass"
instrument_type = /datum/instrument/brass
abstract_type = /datum/instrument/brass
/datum/instrument/brass/crisis_section
name = "Crisis Brass Section"
id = "crbrass"
real_samples = list("36"='sound/instruments/synthesis_samples/brass/crisis_brass/c2.ogg',
"48"='sound/instruments/synthesis_samples/brass/crisis_brass/c3.ogg',
"60"='sound/instruments/synthesis_samples/brass/crisis_brass/c4.ogg',
"72"='sound/instruments/synthesis_samples/brass/crisis_brass/c5.ogg')
real_samples = list("36"='sound/runtime/instruments/synthesis_samples/brass/crisis_brass/c2.ogg',
"48"='sound/runtime/instruments/synthesis_samples/brass/crisis_brass/c3.ogg',
"60"='sound/runtime/instruments/synthesis_samples/brass/crisis_brass/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/brass/crisis_brass/c5.ogg')
/datum/instrument/brass/crisis_trombone
name = "Crisis Trombone"
id = "crtrombone"
real_samples = list("36"='sound/instruments/synthesis_samples/brass/crisis_trombone/c2.ogg',
"48"='sound/instruments/synthesis_samples/brass/crisis_trombone/c3.ogg',
"60"='sound/instruments/synthesis_samples/brass/crisis_trombone/c4.ogg',
"72"='sound/instruments/synthesis_samples/brass/crisis_trombone/c5.ogg')
real_samples = list("36"='sound/runtime/instruments/synthesis_samples/brass/crisis_trombone/c2.ogg',
"48"='sound/runtime/instruments/synthesis_samples/brass/crisis_trombone/c3.ogg',
"60"='sound/runtime/instruments/synthesis_samples/brass/crisis_trombone/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/brass/crisis_trombone/c5.ogg')
/datum/instrument/brass/crisis_trumpet
name = "Crisis Trumpet"
id = "crtrumpet"
real_samples = list("60"='sound/instruments/synthesis_samples/brass/crisis_trumpet/c4.ogg',
"72"='sound/instruments/synthesis_samples/brass/crisis_trumpet/c5.ogg')
real_samples = list("60"='sound/runtime/instruments/synthesis_samples/brass/crisis_trumpet/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/brass/crisis_trumpet/c5.ogg')

View File

@@ -1,31 +1,31 @@
/datum/instrument/chromatic
name = "Generic chromatic percussion instrument"
category = "Chromatic percussion"
instrument_type = /datum/instrument/chromatic
abstract_type = /datum/instrument/chromatic
/datum/instrument/chromatic/vibraphone1
name = "Crisis Vibraphone"
id = "crvibr"
real_samples = list("36"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c2.ogg',
"48"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c3.ogg',
"60"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c4.ogg',
"72"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c5.ogg')
real_samples = list("36"='sound/runtime/instruments/synthesis_samples/chromatic/vibraphone1/c2.ogg',
"48"='sound/runtime/instruments/synthesis_samples/chromatic/vibraphone1/c3.ogg',
"60"='sound/runtime/instruments/synthesis_samples/chromatic/vibraphone1/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/chromatic/vibraphone1/c5.ogg')
/datum/instrument/chromatic/musicbox1
name = "SGM Music Box"
id = "sgmmbox"
real_samples = list("36"='sound/instruments/synthesis_samples/chromatic/sgmbox/c2.ogg',
"48"='sound/instruments/synthesis_samples/chromatic/sgmbox/c3.ogg',
"60"='sound/instruments/synthesis_samples/chromatic/sgmbox/c4.ogg',
"72"='sound/instruments/synthesis_samples/chromatic/sgmbox/c5.ogg')
real_samples = list("36"='sound/runtime/instruments/synthesis_samples/chromatic/sgmbox/c2.ogg',
"48"='sound/runtime/instruments/synthesis_samples/chromatic/sgmbox/c3.ogg',
"60"='sound/runtime/instruments/synthesis_samples/chromatic/sgmbox/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/chromatic/sgmbox/c5.ogg')
/datum/instrument/chromatic/fluid_celeste
name = "FluidR3 Celeste"
id = "r3celeste"
real_samples = list("36"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c2.ogg',
"48"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c3.ogg',
"60"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c4.ogg',
"72"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c5.ogg',
"84"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c6.ogg',
"96"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c7.ogg',
"108"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c8.ogg')
real_samples = list("36"='sound/runtime/instruments/synthesis_samples/chromatic/fluid_celeste/c2.ogg',
"48"='sound/runtime/instruments/synthesis_samples/chromatic/fluid_celeste/c3.ogg',
"60"='sound/runtime/instruments/synthesis_samples/chromatic/fluid_celeste/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/chromatic/fluid_celeste/c5.ogg',
"84"='sound/runtime/instruments/synthesis_samples/chromatic/fluid_celeste/c6.ogg',
"96"='sound/runtime/instruments/synthesis_samples/chromatic/fluid_celeste/c7.ogg',
"108"='sound/runtime/instruments/synthesis_samples/chromatic/fluid_celeste/c8.ogg')

View File

@@ -1,7 +1,7 @@
/datum/instrument/fun
name = "Generic Fun Instrument"
category = "Fun"
instrument_type = /datum/instrument/fun
abstract_type = /datum/instrument/fun
/datum/instrument/fun/honk
name = "!!HONK!!"
@@ -18,6 +18,15 @@
id = "chime"
real_samples = list("79"='sound/machines/chime.ogg')
/datum/instrument/fun/meowsynth
name = "MeowSynth"
id = "meowsynth"
real_samples = list("36"='sound/runtime/instruments/synthesis_samples/meowsynth/c2.ogg',
"48"='sound/runtime/instruments/synthesis_samples/meowsynth/c3.ogg',
"60"='sound/runtime/instruments/synthesis_samples/meowsynth/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/meowsynth/c5.ogg',
"84"='sound/runtime/instruments/synthesis_samples/meowsynth/c6.ogg')
/datum/instrument/fun/mothscream
name = "Moth Scream"
id = "mothscream"

View File

@@ -1,36 +1,36 @@
/datum/instrument/guitar
name = "Generic guitar-like instrument"
category = "Guitar"
instrument_type = /datum/instrument/guitar
abstract_type = /datum/instrument/guitar
/datum/instrument/guitar/steel_crisis
name = "Crisis Steel String Guitar"
id = "csteelgt"
real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_steel/c2.ogg',
"48"='sound/instruments/synthesis_samples/guitar/crisis_steel/c3.ogg',
"60"='sound/instruments/synthesis_samples/guitar/crisis_steel/c4.ogg',
"72"='sound/instruments/synthesis_samples/guitar/crisis_steel/c5.ogg')
real_samples = list("36"='sound/runtime/instruments/synthesis_samples/guitar/crisis_steel/c2.ogg',
"48"='sound/runtime/instruments/synthesis_samples/guitar/crisis_steel/c3.ogg',
"60"='sound/runtime/instruments/synthesis_samples/guitar/crisis_steel/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/guitar/crisis_steel/c5.ogg')
/datum/instrument/guitar/nylon_crisis
name = "Crisis Nylon String Guitar"
id = "cnylongt"
real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c2.ogg',
"48"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c3.ogg',
"60"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c4.ogg',
"72"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c5.ogg')
real_samples = list("36"='sound/runtime/instruments/synthesis_samples/guitar/crisis_nylon/c2.ogg',
"48"='sound/runtime/instruments/synthesis_samples/guitar/crisis_nylon/c3.ogg',
"60"='sound/runtime/instruments/synthesis_samples/guitar/crisis_nylon/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/guitar/crisis_nylon/c5.ogg')
/datum/instrument/guitar/clean_crisis
name = "Crisis Clean Guitar"
id = "ccleangt"
real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_clean/c2.ogg',
"48"='sound/instruments/synthesis_samples/guitar/crisis_clean/c3.ogg',
"60"='sound/instruments/synthesis_samples/guitar/crisis_clean/c4.ogg',
"72"='sound/instruments/synthesis_samples/guitar/crisis_clean/c5.ogg')
real_samples = list("36"='sound/runtime/instruments/synthesis_samples/guitar/crisis_clean/c2.ogg',
"48"='sound/runtime/instruments/synthesis_samples/guitar/crisis_clean/c3.ogg',
"60"='sound/runtime/instruments/synthesis_samples/guitar/crisis_clean/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/guitar/crisis_clean/c5.ogg')
/datum/instrument/guitar/muted_crisis
name = "Crisis Muted Guitar"
id = "cmutedgt"
real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_muted/c2.ogg',
"48"='sound/instruments/synthesis_samples/guitar/crisis_muted/c3.ogg',
"60"='sound/instruments/synthesis_samples/guitar/crisis_muted/c4.ogg',
"72"='sound/instruments/synthesis_samples/guitar/crisis_muted/c5.ogg')
real_samples = list("36"='sound/runtime/instruments/synthesis_samples/guitar/crisis_muted/c2.ogg',
"48"='sound/runtime/instruments/synthesis_samples/guitar/crisis_muted/c3.ogg',
"60"='sound/runtime/instruments/synthesis_samples/guitar/crisis_muted/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/guitar/crisis_muted/c5.ogg')

View File

@@ -2,7 +2,7 @@
//SONGS WILL BE AUTOMATICALLY SWITCHED TO LEGACY MODE IF THEY USE THIS KIND OF INSTRUMENT!
//I'd prefer these stayed. They sound different from the mechanical synthesis of synthed instruments, and I quite like them that way. It's not legacy, it's hardcoded, old style. - kevinz000
/datum/instrument/hardcoded
instrument_type = /datum/instrument/hardcoded
abstract_type = /datum/instrument/hardcoded
category = "Non-Synthesized"
instrument_flags = INSTRUMENT_LEGACY
volume_multiplier = 1 //not as loud as synth'd
@@ -23,7 +23,7 @@
name = "Electric Guitar"
id = "eguitar"
legacy_instrument_ext = "ogg"
legacy_instrument_path = "eguitar"
legacy_instrument_path = "electric_guitar"
/datum/instrument/hardcoded/glockenspiel
name = "Glockenspiel"

View File

@@ -1,43 +1,43 @@
/datum/instrument/organ
name = "Generic organ"
category = "Organ"
instrument_type = /datum/instrument/organ
abstract_type = /datum/instrument/organ
/datum/instrument/organ/crisis_church
name = "Crisis Church Organ"
id = "crichugan"
real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_church/c2.ogg',
"48"='sound/instruments/synthesis_samples/organ/crisis_church/c3.ogg',
"60"='sound/instruments/synthesis_samples/organ/crisis_church/c4.ogg',
"72"='sound/instruments/synthesis_samples/organ/crisis_church/c5.ogg')
real_samples = list("36"='sound/runtime/instruments/synthesis_samples/organ/crisis_church/c2.ogg',
"48"='sound/runtime/instruments/synthesis_samples/organ/crisis_church/c3.ogg',
"60"='sound/runtime/instruments/synthesis_samples/organ/crisis_church/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/organ/crisis_church/c5.ogg')
/datum/instrument/organ/crisis_hammond
name = "Crisis Hammond Organ"
id = "crihamgan"
real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_hammond/c2.ogg',
"48"='sound/instruments/synthesis_samples/organ/crisis_hammond/c3.ogg',
"60"='sound/instruments/synthesis_samples/organ/crisis_hammond/c4.ogg',
"72"='sound/instruments/synthesis_samples/organ/crisis_hammond/c5.ogg')
real_samples = list("36"='sound/runtime/instruments/synthesis_samples/organ/crisis_hammond/c2.ogg',
"48"='sound/runtime/instruments/synthesis_samples/organ/crisis_hammond/c3.ogg',
"60"='sound/runtime/instruments/synthesis_samples/organ/crisis_hammond/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/organ/crisis_hammond/c5.ogg')
/datum/instrument/organ/crisis_accordian
name = "Crisis Accordian"
id = "crack"
real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_accordian/c2.ogg',
"48"='sound/instruments/synthesis_samples/organ/crisis_accordian/c3.ogg',
"60"='sound/instruments/synthesis_samples/organ/crisis_accordian/c4.ogg',
"72"='sound/instruments/synthesis_samples/organ/crisis_accordian/c5.ogg')
real_samples = list("36"='sound/runtime/instruments/synthesis_samples/organ/crisis_accordian/c2.ogg',
"48"='sound/runtime/instruments/synthesis_samples/organ/crisis_accordian/c3.ogg',
"60"='sound/runtime/instruments/synthesis_samples/organ/crisis_accordian/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/organ/crisis_accordian/c5.ogg')
/datum/instrument/organ/crisis_harmonica
name = "Crisis Harmonica"
id = "crharmony"
real_samples = list("48"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c3.ogg',
"60"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c4.ogg',
"72"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c5.ogg')
real_samples = list("48"='sound/runtime/instruments/synthesis_samples/organ/crisis_harmonica/c3.ogg',
"60"='sound/runtime/instruments/synthesis_samples/organ/crisis_harmonica/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/organ/crisis_harmonica/c5.ogg')
/datum/instrument/organ/crisis_tango_accordian
name = "Crisis Tango Accordian"
id = "crtango"
real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c2.ogg',
"48"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c3.ogg',
"60"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c4.ogg',
"72"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c5.ogg')
real_samples = list("36"='sound/runtime/instruments/synthesis_samples/organ/crisis_tangaccordian/c2.ogg',
"48"='sound/runtime/instruments/synthesis_samples/organ/crisis_tangaccordian/c3.ogg',
"60"='sound/runtime/instruments/synthesis_samples/organ/crisis_tangaccordian/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/organ/crisis_tangaccordian/c5.ogg')

View File

@@ -1,56 +1,56 @@
/datum/instrument/piano
name = "Generic piano"
category = "Piano"
instrument_type = /datum/instrument/piano
abstract_type = /datum/instrument/piano
/datum/instrument/piano/fluid_piano
name = "FluidR3 Grand Piano"
id = "r3grand"
real_samples = list("36"='sound/instruments/synthesis_samples/piano/fluid_piano/c2.ogg',
"48"='sound/instruments/synthesis_samples/piano/fluid_piano/c3.ogg',
"60"='sound/instruments/synthesis_samples/piano/fluid_piano/c4.ogg',
"72"='sound/instruments/synthesis_samples/piano/fluid_piano/c5.ogg',
"84"='sound/instruments/synthesis_samples/piano/fluid_piano/c6.ogg',
"96"='sound/instruments/synthesis_samples/piano/fluid_piano/c7.ogg',
"108"='sound/instruments/synthesis_samples/piano/fluid_piano/c8.ogg')
real_samples = list("36"='sound/runtime/instruments/synthesis_samples/piano/fluid_piano/c2.ogg',
"48"='sound/runtime/instruments/synthesis_samples/piano/fluid_piano/c3.ogg',
"60"='sound/runtime/instruments/synthesis_samples/piano/fluid_piano/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/piano/fluid_piano/c5.ogg',
"84"='sound/runtime/instruments/synthesis_samples/piano/fluid_piano/c6.ogg',
"96"='sound/runtime/instruments/synthesis_samples/piano/fluid_piano/c7.ogg',
"108"='sound/runtime/instruments/synthesis_samples/piano/fluid_piano/c8.ogg')
/datum/instrument/piano/fluid_harpsichord
name = "FluidR3 Harpsichord"
id = "r3harpsi"
real_samples = list("36"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c2.ogg',
"48"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c3.ogg',
"60"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c4.ogg',
"72"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c5.ogg',
"84"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c6.ogg',
"96"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c7.ogg',
"108"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c8.ogg')
real_samples = list("36"='sound/runtime/instruments/synthesis_samples/piano/fluid_harpsi/c2.ogg',
"48"='sound/runtime/instruments/synthesis_samples/piano/fluid_harpsi/c3.ogg',
"60"='sound/runtime/instruments/synthesis_samples/piano/fluid_harpsi/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/piano/fluid_harpsi/c5.ogg',
"84"='sound/runtime/instruments/synthesis_samples/piano/fluid_harpsi/c6.ogg',
"96"='sound/runtime/instruments/synthesis_samples/piano/fluid_harpsi/c7.ogg',
"108"='sound/runtime/instruments/synthesis_samples/piano/fluid_harpsi/c8.ogg')
/datum/instrument/piano/crisis_harpsichord
name = "Crisis Harpsichord"
id = "crharpsi"
real_samples = list("36"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c2.ogg',
"48"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c3.ogg',
"60"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c4.ogg',
"72"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c5.ogg')
real_samples = list("36"='sound/runtime/instruments/synthesis_samples/piano/crisis_harpsichord/c2.ogg',
"48"='sound/runtime/instruments/synthesis_samples/piano/crisis_harpsichord/c3.ogg',
"60"='sound/runtime/instruments/synthesis_samples/piano/crisis_harpsichord/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/piano/crisis_harpsichord/c5.ogg')
/datum/instrument/piano/crisis_grandpiano_uni
name = "Crisis Grand Piano One"
id = "crgrand1"
real_samples = list("36"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c2.ogg',
"48"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c3.ogg',
"60"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c4.ogg',
"72"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c5.ogg',
"84"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c6.ogg',
"96"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c7.ogg',
"108"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c8.ogg')
real_samples = list("36"='sound/runtime/instruments/synthesis_samples/piano/crisis_grand_piano/c2.ogg',
"48"='sound/runtime/instruments/synthesis_samples/piano/crisis_grand_piano/c3.ogg',
"60"='sound/runtime/instruments/synthesis_samples/piano/crisis_grand_piano/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/piano/crisis_grand_piano/c5.ogg',
"84"='sound/runtime/instruments/synthesis_samples/piano/crisis_grand_piano/c6.ogg',
"96"='sound/runtime/instruments/synthesis_samples/piano/crisis_grand_piano/c7.ogg',
"108"='sound/runtime/instruments/synthesis_samples/piano/crisis_grand_piano/c8.ogg')
/datum/instrument/piano/crisis_brightpiano_uni
name = "Crisis Bright Piano One"
id = "crbright1"
real_samples = list("36"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c2.ogg',
"48"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c3.ogg',
"60"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c4.ogg',
"72"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c5.ogg',
"84"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c6.ogg',
"96"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c7.ogg',
"108"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c8.ogg')
real_samples = list("36"='sound/runtime/instruments/synthesis_samples/piano/crisis_bright_piano/c2.ogg',
"48"='sound/runtime/instruments/synthesis_samples/piano/crisis_bright_piano/c3.ogg',
"60"='sound/runtime/instruments/synthesis_samples/piano/crisis_bright_piano/c4.ogg',
"72"='sound/runtime/instruments/synthesis_samples/piano/crisis_bright_piano/c5.ogg',
"84"='sound/runtime/instruments/synthesis_samples/piano/crisis_bright_piano/c6.ogg',
"96"='sound/runtime/instruments/synthesis_samples/piano/crisis_bright_piano/c7.ogg',
"108"='sound/runtime/instruments/synthesis_samples/piano/crisis_bright_piano/c8.ogg')

View File

@@ -1,19 +1,19 @@
/datum/instrument/tones
name = "Ideal tone"
category = "Tones"
instrument_type = /datum/instrument/tones
abstract_type = /datum/instrument/tones
/datum/instrument/tones/square_wave
name = "Ideal square wave"
id = "square"
real_samples = list("81"='sound/instruments/synthesis_samples/tones/Square.ogg')
real_samples = list("81"='sound/runtime/instruments/synthesis_samples/tones/Square.ogg')
/datum/instrument/tones/sine_wave
name = "Ideal sine wave"
id = "sine"
real_samples = list("81"='sound/instruments/synthesis_samples/tones/Sine.ogg')
real_samples = list("81"='sound/runtime/instruments/synthesis_samples/tones/Sine.ogg')
/datum/instrument/tones/saw_wave
name = "Ideal sawtooth wave"
id = "saw"
real_samples = list("81"='sound/instruments/synthesis_samples/tones/Sawtooth.ogg')
real_samples = list("81"='sound/runtime/instruments/synthesis_samples/tones/Sawtooth.ogg')

View File

@@ -9,7 +9,7 @@
slot_l_hand_str = 'icons/mob/items/lefthand_instruments.dmi',
slot_r_hand_str = 'icons/mob/items/righthand_instruments.dmi',
)
abstract_type = /obj/item/instrument
/// Our song datum.
var/datum/song/handheld/song
/// Our allowed list of instrument ids. This is nulled on initialize.
@@ -20,27 +20,30 @@
/obj/item/instrument/Initialize(mapload)
. = ..()
song = new(src, allowed_instrument_ids, instrument_range)
allowed_instrument_ids = null //We don't need this clogging memory after it's used.
allowed_instrument_ids = null //We don't need this clogging memory after its used.
/obj/item/instrument/Destroy()
QDEL_NULL(song)
return ..()
/obj/item/instrument/proc/should_stop_playing(mob/user)
return user.incapacitated() || !((loc == user) || (isturf(loc) && Adjacent(user))) // sorry, no more TK playing.
/obj/item/instrument/proc/can_play(atom/music_player)
if(!ismob(music_player))
return FALSE
var/mob/user = music_player
if(user.incapacitated())
return FALSE
if(!Adjacent(user))
return FALSE
return TRUE
/obj/item/instrument/attack_self(mob/user)
if(!user.IsAdvancedToolUser())
to_chat(user, span_warning("You don't have the dexterity to do this!"))
return TRUE
interact(user)
/obj/item/instrument/interact(mob/living/user)
if(!isliving(user) || user.incapacitated())
/obj/item/instrument/attack_self(mob/M)
if(!M.IsAdvancedToolUser())
return
user.set_machine(src)
song.interact(user)
tgui_interact(M)
/obj/item/instrument/tgui_interact(mob/user, datum/tgui/ui)
return song.tgui_interact(user)
/obj/item/instrument/violin
name = "space violin"
@@ -60,67 +63,6 @@
icon_state = "xylophone"
allowed_instrument_ids = "xylophone"
/obj/item/instrument/piano_synth
name = "synthesizer"
desc = "An advanced electronic synthesizer that can be used as various instruments."
icon_state = "synth"
allowed_instrument_ids = "piano"
/obj/item/instrument/piano_synth/Initialize(mapload)
. = ..()
song.allowed_instrument_ids = SSinstruments.synthesizer_instrument_ids
/obj/item/instrument/piano_synth/headphones
name = "headphones"
desc = "Unce unce unce unce. Boop!"
icon_state = "headphones"
slot_flags = SLOT_EARS | SLOT_HEAD
force = 0
w_class = ITEMSIZE_SMALL
instrument_range = 1
/obj/item/instrument/piano_synth/headphones/Initialize(mapload)
. = ..()
RegisterSignal(src, COMSIG_SONG_START, PROC_REF(start_playing))
RegisterSignal(src, COMSIG_SONG_END, PROC_REF(stop_playing))
/**
* Called by a component signal when our song starts playing.
*/
/obj/item/instrument/piano_synth/headphones/proc/start_playing()
SIGNAL_HANDLER
icon_state = "[initial(icon_state)]_on"
if(ishuman(loc))
var/mob/living/carbon/human/H = loc
if(H.l_ear == src || H.r_ear == src)
H.update_inv_ears()
else if(H.head == src)
H.update_inv_head()
/**
* Called by a component signal when our song stops playing.
*/
/obj/item/instrument/piano_synth/headphones/proc/stop_playing()
SIGNAL_HANDLER
icon_state = "[initial(icon_state)]"
if(ishuman(loc))
var/mob/living/carbon/human/H = loc
if(H.l_ear == src || H.r_ear == src)
H.update_inv_ears()
else if(H.head == src)
H.update_inv_head()
/obj/item/instrument/piano_synth/headphones/spacepods
name = "\improper Nanotrasen space pods"
desc = "Flex your money, AND ignore what everyone else says, all at once!"
icon_state = "spacepods"
slot_flags = SLOT_EARS
//strip_delay = 100 //air pods don't fall out
instrument_range = 0 //you're paying for quality here
/obj/item/instrument/banjo
name = "banjo"
desc = "A 'Mura' brand banjo. It's pretty much just a drum with a neck and strings."
@@ -177,7 +119,7 @@
AddComponent(/datum/component/spooky)
*/
/obj/item/instrument/trumpet/spectral/attack(mob/living/carbon/C, mob/user)
playsound (src, 'sound/instruments/trombone/En4.mid', 100,1,-1)
playsound (src, 'sound/runtime/instruments/trombone/En4.mid', 100,1,-1)
..()
/obj/item/instrument/saxophone
@@ -200,7 +142,7 @@
*/
/obj/item/instrument/saxophone/spectral/attack(mob/living/carbon/C, mob/user)
playsound (src, 'sound/instruments/saxophone/En4.mid', 100,1,-1)
playsound (src, 'sound/runtime/instruments/saxophone/En4.mid', 100,1,-1)
..()
/obj/item/instrument/trombone
@@ -223,7 +165,7 @@
*/
/obj/item/instrument/trombone/spectral/attack(mob/living/carbon/C, mob/user)
playsound (src, 'sound/instruments/trombone/Cn4.mid', 100,1,-1)
playsound (src, 'sound/runtime/instruments/trombone/Cn4.mid', 100,1,-1)
..()
/obj/item/instrument/recorder

View File

@@ -0,0 +1,60 @@
/obj/item/instrument/piano_synth
name = "synthesizer"
desc = "An advanced electronic synthesizer that can be used as various instruments."
icon_state = "synth"
allowed_instrument_ids = "piano"
/obj/item/instrument/piano_synth/Initialize(mapload)
. = ..()
song.allowed_instrument_ids = SSinstruments.synthesizer_instrument_ids
/obj/item/instrument/piano_synth/headphones
name = "headphones"
desc = "Unce unce unce unce. Boop!"
icon_state = "headphones"
slot_flags = SLOT_EARS | SLOT_HEAD
force = 0
w_class = ITEMSIZE_SMALL
instrument_range = 1
/obj/item/instrument/piano_synth/headphones/Initialize(mapload)
. = ..()
RegisterSignal(src, COMSIG_SONG_START, PROC_REF(start_playing))
RegisterSignal(src, COMSIG_SONG_END, PROC_REF(stop_playing))
/**
* Called by a component signal when our song starts playing.
*/
/obj/item/instrument/piano_synth/headphones/proc/start_playing()
SIGNAL_HANDLER
icon_state = "[initial(icon_state)]_on"
if(ishuman(loc))
var/mob/living/carbon/human/H = loc
if(H.l_ear == src || H.r_ear == src)
H.update_inv_ears()
else if(H.head == src)
H.update_inv_head()
/**
* Called by a component signal when our song stops playing.
*/
/obj/item/instrument/piano_synth/headphones/proc/stop_playing()
SIGNAL_HANDLER
icon_state = "[initial(icon_state)]"
if(ishuman(loc))
var/mob/living/carbon/human/H = loc
if(H.l_ear == src || H.r_ear == src)
H.update_inv_ears()
else if(H.head == src)
H.update_inv_head()
/obj/item/instrument/piano_synth/headphones/spacepods
name = "\improper Nanotrasen space pods"
desc = "Flex your money, AND ignore what everyone else says, all at once!"
icon_state = "spacepods"
slot_flags = SLOT_EARS
//strip_delay = 100 //air pods don't fall out
instrument_range = 0 //you're paying for quality here

View File

@@ -1,7 +1,3 @@
#define MUSICIAN_HEARCHECK_MINDELAY 4
#define MUSIC_MAXLINES 1000
#define MUSIC_MAXLINECHARS 300
/**
* # Song datum
*
@@ -12,6 +8,9 @@
/// Name of the song
var/name = "Untitled"
/// ID for syncing songs together
var/id = ""
/// The atom we're attached to/playing from
var/atom/parent
@@ -27,11 +26,6 @@
/// Are we currently playing?
var/playing = FALSE
/// Are we currently editing?
var/editing = TRUE
/// Is the help screen open?
var/help = FALSE
/// Repeats left
var/repeat = 0
/// Maximum times we can repeat
@@ -78,8 +72,8 @@
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.
var/list/channels_idle = list()
/// Person playing us
var/mob/user_playing
/// Who or what's playing us
var/atom/music_player
//////////////////////////////////////////////////////
/// Last world.time we checked for who can hear us
@@ -111,7 +105,6 @@
var/note_shift = 0
var/note_shift_min = -100
var/note_shift_max = 100
var/can_noteshift = TRUE
/// The kind of sustain we're using
var/sustain_mode = SUSTAIN_LINEAR
/// When a note is considered dead if it is below this in volume
@@ -130,10 +123,10 @@
/datum/song/New(atom/parent, list/instrument_ids, new_range)
SSinstruments.on_song_new(src)
lines = list()
tempo = sanitize_tempo(tempo)
tempo = sanitize_tempo(tempo, TRUE)
src.parent = parent
if(instrument_ids)
allowed_instrument_ids = islist(instrument_ids)? instrument_ids : list(instrument_ids)
allowed_instrument_ids = islist(instrument_ids) ? instrument_ids : list(instrument_ids)
if(length(allowed_instrument_ids))
set_instrument(allowed_instrument_ids[1])
hearing_mobs = list()
@@ -161,6 +154,8 @@
var/list/old = hearing_mobs.Copy()
hearing_mobs.len = 0
var/turf/source = get_turf(parent)
// FIXME
// for(var/mob/M in get_hearers_in_view(instrument_range, source))
var/list/in_range = get_mobs_and_objs_in_view_fast(source, instrument_range, remote_ghosts = FALSE)
for(var/mob/M in in_range["mobs"])
hearing_mobs[M] = get_dist(M, source)
@@ -202,64 +197,98 @@
/**
* Attempts to start playing our song.
*/
/datum/song/proc/start_playing(mob/user)
/datum/song/proc/start_playing(atom/user)
if(playing)
return
if(!using_instrument?.ready())
to_chat(user, span_warning("An error has occurred with [src]. Please reset the instrument."))
to_chat(user, span_warning("An error has occured with [src]. Please reset the instrument."))
return
compile_chords()
if(!length(compiled_chords))
to_chat(user, span_warning("Song is empty."))
return
playing = TRUE
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.
//wrap the rest of the stuff to ensure stop_playing() is called.
do_hearcheck()
SEND_SIGNAL(parent, COMSIG_SONG_START)
SEND_SIGNAL(parent, COMSIG_INSTRUMENT_START, src, user)
SEND_SIGNAL(user, COMSIG_ATOM_STARTING_INSTRUMENT, src)
elapsed_delay = 0
delay_by = 0
current_chord = 1
user_playing = user
music_player = user
START_PROCESSING(SSinstruments, src)
if(id)
sync_play()
/**
* Attempts to find other instruments with the same ID and syncs them to our song.
*/
/datum/song/proc/sync_play()
for(var/datum/song/other_instrument as anything in SSinstruments.songs)
if(other_instrument == src || other_instrument.id != id)
continue
if(other_instrument.playing)
continue
var/atom/other_player = other_instrument.find_sync_player()
if(isnull(other_player) || !(other_player in view(get_turf(parent))))
continue
// copies the main song info to target songs
other_instrument.lines = lines.Copy()
other_instrument.max_repeats = max_repeats
other_instrument.tempo = tempo
other_instrument.start_playing(other_player)
/**
* Finds a player which would reasonably be able to play this song.
*/
/datum/song/proc/find_sync_player()
return null
/**
* Stops playing, terminating all sounds if in synthesized mode. Clears hearing_mobs.
*
* Arguments:
* * finished: boolean, whether the song ended via reaching the end.
*/
/datum/song/proc/stop_playing()
/datum/song/proc/stop_playing(finished = FALSE)
if(!playing)
return
playing = FALSE
if(!debug_mode)
compiled_chords = null
STOP_PROCESSING(SSinstruments, src)
SEND_SIGNAL(parent, COMSIG_SONG_END)
SEND_SIGNAL(parent, COMSIG_INSTRUMENT_END, finished)
terminate_all_sounds(TRUE)
hearing_mobs.len = 0
user_playing = null
music_player = null
/**
* Processes our song.
*/
/datum/song/proc/process_song(wait)
if(!length(compiled_chords) || should_stop_playing(user_playing))
stop_playing()
if(!length(compiled_chords))
stop_playing(TRUE)
return
if(should_stop_playing(music_player) == STOP_PLAYING)
stop_playing(FALSE)
return
var/list/chord = compiled_chords[current_chord]
if(++elapsed_delay >= delay_by)
play_chord(chord)
elapsed_delay = 0
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
elapsed_delay++
if(elapsed_delay < delay_by)
return
play_chord(chord)
elapsed_delay = 0
delay_by = tempodiv_to_delay(chord[length(chord)])
current_chord++
if(current_chord <= length(compiled_chords))
return
if(!repeat)
stop_playing(TRUE)
return
repeat--
current_chord = 1
SEND_SIGNAL(parent, COMSIG_INSTRUMENT_REPEAT, TRUE)
/**
* Converts a tempodiv to ticks to elapse before playing the next chord, taking into account our tempo.
@@ -281,19 +310,34 @@
/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)
legacy ? playkey_legacy(chord[i][1], chord[i][2], chord[i][3], music_player) : playkey_synth(chord[i], music_player)
/**
* Checks if we should halt playback.
*/
/datum/song/proc/should_stop_playing(mob/user)
return QDELETED(parent) || !using_instrument || !playing
/datum/song/proc/should_stop_playing(atom/player)
if(QDELETED(player) || !using_instrument || !playing)
return STOP_PLAYING
return SEND_SIGNAL(parent, COMSIG_INSTRUMENT_SHOULD_STOP_PLAYING, player)
/// Sets and sanitizes the repeats variable.
/datum/song/proc/set_repeats(new_repeats_value)
if(playing)
return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing.
repeat = round(new_repeats_value)
if(repeat < 0)
repeat = 0
if(repeat > max_repeats)
repeat = max_repeats
/**
* 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, initializing = FALSE)
new_tempo = abs(new_tempo)
if(!initializing) // not only is it not helpful while initializing but it will runtime really hard since nothing is set up
SEND_SIGNAL(parent, COMSIG_INSTRUMENT_TEMPO_CHANGE, src)
return clamp(round(new_tempo, world.tick_lag), world.tick_lag, 5 SECONDS)
/**
@@ -308,12 +352,6 @@
/datum/song/proc/set_bpm(bpm)
tempo = sanitize_tempo(600 / bpm)
/**
* Updates the window for our users. Override down the line.
*/
/datum/song/proc/updateDialog(mob/user)
interact(user)
/datum/song/process(wait)
if(!playing)
return PROCESS_KILL
@@ -337,33 +375,29 @@
* Setter for setting output volume.
*/
/datum/song/proc/set_volume(volume)
src.volume = clamp(volume, max(0, min_volume), min(100, max_volume))
src.volume = clamp(round(volume, 1), max(0, min_volume), min(100, max_volume))
update_sustain()
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)
sustain_dropoff_volume = clamp(volume, INSTRUMENT_MIN_SUSTAIN_DROPOFF, 100)
sustain_dropoff_volume = clamp(round(volume, 0.01), INSTRUMENT_MIN_SUSTAIN_DROPOFF, 100)
update_sustain()
updateDialog()
/**
* Setter for setting exponential falloff factor.
*/
/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(round(drop, 0.00001), INSTRUMENT_EXP_FALLOFF_MIN, INSTRUMENT_EXP_FALLOFF_MAX)
update_sustain()
updateDialog()
/**
* Setter for setting linear falloff duration.
*/
/datum/song/proc/set_linear_falloff_duration(duration)
sustain_linear_duration = clamp(duration, 0.1, INSTRUMENT_MAX_TOTAL_SUSTAIN)
sustain_linear_duration = clamp(round(duration * 10, world.tick_lag), world.tick_lag, INSTRUMENT_MAX_TOTAL_SUSTAIN)
update_sustain()
updateDialog()
/datum/song/vv_edit_var(var_name, var_value)
. = ..()
@@ -381,25 +415,34 @@
// subtype for handheld instruments, like violin
/datum/song/handheld
/datum/song/handheld/updateDialog(mob/user)
parent.interact(user || usr)
/datum/song/handheld/should_stop_playing(mob/user)
/datum/song/handheld/should_stop_playing(atom/player)
. = ..()
if(.)
return TRUE
if(. == STOP_PLAYING || . == IGNORE_INSTRUMENT_CHECKS)
return
var/obj/item/instrument/I = parent
return I.should_stop_playing(user)
return I.can_play(player) ? NONE : STOP_PLAYING
/datum/song/handheld/find_sync_player()
var/obj/item/instrument/instrument = parent
var/mob/living/player = get(parent, /mob/living)
if(instrument.can_play(player))
return player
return null
// subtype for stationary structures, like pianos
/datum/song/stationary
/datum/song/stationary/updateDialog(mob/user)
parent.interact(user || usr)
/datum/song/stationary/should_stop_playing(mob/user)
/datum/song/stationary/should_stop_playing(atom/player)
. = ..()
if(.)
if(. == STOP_PLAYING || . == IGNORE_INSTRUMENT_CHECKS)
return TRUE
var/obj/structure/musician/M = parent
return M.should_stop_playing(user)
return M.can_play(player) ? NONE : STOP_PLAYING
/datum/song/stationary/find_sync_player()
var/obj/structure/musician/piano = parent
for(var/mob/living/player in view(parent, 1))
if(piano.can_play(player))
return player
return null

View File

@@ -1,251 +1,216 @@
/**
* Returns the HTML for the status UI for this song datum.
*/
/datum/song/proc/instrument_status_ui()
. = list()
. += "<div class='statusDisplay'>"
. += span_bold("<a href='byond://?src=[REF(src)];switchinstrument=1'>Current instrument</a>:") + " "
if(!using_instrument)
. += span_danger("No instrument loaded!") + "<br>"
else
. += "[using_instrument.name]<br>"
. += "Playback Settings:<br>"
if(can_noteshift)
. += "<a href='byond://?src=[REF(src)];setnoteshift=1'>Note Shift/Note Transpose</a>: [note_shift] keys / [round(note_shift / 12, 0.01)] octaves<br>"
var/smt
var/modetext = ""
/datum/song/tgui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if (!ui)
ui = new(user, src, "InstrumentEditor", parent.name)
ui.open()
/datum/song/tgui_host(mob/user)
return parent
/datum/song/tgui_data(mob/user)
var/list/data = ..()
data["id"] = id
data["using_instrument"] = using_instrument?.name || "No instrument loaded!"
data["note_shift"] = note_shift
data["octaves"] = round(note_shift / 12, 0.01)
data["sustain_mode"] = sustain_mode
switch(sustain_mode)
if(SUSTAIN_LINEAR)
smt = "Linear"
modetext = "<a href='byond://?src=[REF(src)];setlinearfalloff=1'>Linear Sustain Duration</a>: [sustain_linear_duration / 10] seconds<br>"
data["sustain_mode_button"] = "Linear Sustain Duration (in seconds)"
data["sustain_mode_duration"] = sustain_linear_duration / 10
data["sustain_mode_min"] = INSTRUMENT_MIN_TOTAL_SUSTAIN
data["sustain_mode_max"] = INSTRUMENT_MAX_TOTAL_SUSTAIN
if(SUSTAIN_EXPONENTIAL)
smt = "Exponential"
modetext = "<a href='byond://?src=[REF(src)];setexpfalloff=1'>Exponential Falloff Factor</a>: [sustain_exponential_dropoff]% per decisecond<br>"
. += "<a href='byond://?src=[REF(src)];setsustainmode=1'>Sustain Mode</a>: [smt]<br>"
. += modetext
. += using_instrument?.ready()? ("Status: " + span_green("Ready") + "<br>") : ("Status: " + span_red("!Instrument Definition Error!") + "<br>")
. += "Instrument Type: [legacy? "Legacy" : "Synthesized"]<br>"
. += "<a href='byond://?src=[REF(src)];setvolume=1'>Volume</a>: [volume]<br>"
. += "<a href='byond://?src=[REF(src)];setdropoffvolume=1'>Volume Dropoff Threshold</a>: [sustain_dropoff_volume]<br>"
. += "<a href='byond://?src=[REF(src)];togglesustainhold=1'>Sustain indefinitely last held note</a>: [full_sustain_held_note? "Enabled" : "Disabled"].<br>"
. += "</div>"
data["sustain_mode_button"] = "Exponential Falloff Factor (% per decisecond)"
data["sustain_mode_duration"] = sustain_exponential_dropoff
data["sustain_mode_min"] = INSTRUMENT_EXP_FALLOFF_MIN
data["sustain_mode_max"] = INSTRUMENT_EXP_FALLOFF_MAX
data["instrument_ready"] = using_instrument?.ready()
data["volume"] = volume
data["volume_dropoff_threshold"] = sustain_dropoff_volume
data["sustain_indefinitely"] = full_sustain_held_note
data["playing"] = playing
data["repeat"] = repeat
data["bpm"] = round(60 SECONDS / tempo)
data["lines"] = list()
var/linecount
for(var/line in lines)
linecount++
data["lines"] += list(list(
"line_count" = linecount,
"line_text" = line,
))
return data
/datum/song/proc/interact(mob/user)
var/list/dat = list()
/datum/song/tgui_static_data(mob/user)
var/list/data = ..()
data["can_switch_instrument"] = (length(allowed_instrument_ids) > 1)
data["possible_instruments"] = list()
for(var/instrument in allowed_instrument_ids)
UNTYPED_LIST_ADD(data["possible_instruments"], list("name" = SSinstruments.instrument_data[instrument], "id" = instrument))
data["sustain_modes"] = SSinstruments.note_sustain_modes
data["max_repeats"] = max_repeats
data["min_volume"] = min_volume
data["max_volume"] = max_volume
data["note_shift_min"] = note_shift_min
data["note_shift_max"] = note_shift_max
data["max_line_chars"] = MUSIC_MAXLINECHARS
data["max_lines"] = MUSIC_MAXLINES
return data
dat += instrument_status_ui()
/datum/song/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
. = ..()
var/mob/user = ui.user
if(!istype(user))
return FALSE
if(lines.len > 0)
dat += "<H3>Playback</H3>"
if(!playing)
dat += "<A href='byond://?src=[REF(src)];play=1'>Play</A> " + span_linkOn("Stop") + "<BR><BR>"
dat += "Repeat Song: "
dat += repeat > 0 ? "<A href='byond://?src=[REF(src)];repeat=-10'>-</A><A href='byond://?src=[REF(src)];repeat=-1'>-</A>" : (span_linkOff("-") + span_linkOff("-"))
dat += " [repeat] times "
dat += repeat < max_repeats ? "<A href='byond://?src=[REF(src)];repeat=1'>+</A><A href='byond://?src=[REF(src)];repeat=10'>+</A>" : (span_linkOff("+") + span_linkOff("+"))
dat += "<BR>"
else
dat += span_linkOn("Play") + " <A href='byond://?src=[REF(src)];stop=1'>Stop</A><BR>"
dat += "Repeats left: " + span_bold("[repeat]") + "<BR>"
if(!editing)
dat += "<BR>" + span_bold("<A href='byond://?src=[REF(src)];edit=2'>Show Editor</A>") + "<BR>"
else
dat += "<H3>Editing</H3>"
dat += span_bold("<A href='byond://?src=[REF(src)];edit=1'>Hide Editor</A>")
dat += " <A href='byond://?src=[REF(src)];newsong=1'>Start a New Song</A>"
dat += " <A href='byond://?src=[REF(src)];import=1'>Import a Song</A><BR><BR>"
var/bpm = round(600 / tempo)
dat += "Tempo: <A href='byond://?src=[REF(src)];tempo=[world.tick_lag]'>-</A> [bpm] BPM <A href='byond://?src=[REF(src)];tempo=-[world.tick_lag]'>+</A><BR><BR>"
var/linecount = 0
for(var/line in lines)
linecount += 1
dat += "Line [linecount]: <A href='byond://?src=[REF(src)];modifyline=[linecount]'>Edit</A> <A href='byond://?src=[REF(src)];deleteline=[linecount]'>X</A> [line]<BR>"
dat += "<A href='byond://?src=[REF(src)];newline=1'>Add Line</A><BR><BR>"
if(help)
dat += span_bold("<A href='byond://?src=[REF(src)];help=1'>Hide Help</A>") + "<BR>"
dat += {"
Lines are a series of chords, separated by commas (,), each with notes separated by hyphens (-).<br>
Every note in a chord will play together, with chord timed by the tempo.<br>
<br>
Notes are played by the names of the note, and optionally, the accidental, and/or the octave number.<br>
By default, every note is natural and in octave 3. Defining otherwise is remembered for each note.<br>
Example: <i>C,D,E,F,G,A,B</i> will play a C major scale.<br>
After a note has an accidental placed, it will be remembered: <i>C,C4,C,C3</i> is <i>C3,C4,C4,C3</i><br>
Chords can be played simply by separating each note with a hyphen: <i>A-C#,Cn-E,E-G#,Gn-B</i><br>
A pause may be denoted by an empty chord: <i>C,E,,C,G</i><br>
To make a chord be a different time, end it with /x, where the chord length will be length<br>
defined by tempo / x: <i>C,G/2,E/4</i><br>
Combined, an example is: <i>E-E4/4,F#/2,G#/8,B/8,E3-E4/4</i>
<br>
Lines may be up to [MUSIC_MAXLINECHARS] characters.<br>
A song may only contain up to [MUSIC_MAXLINES] lines.<br>
"}
else
dat += span_bold("<A href='byond://?src=[REF(src)];help=2'>Show Help</A>") + "<BR>"
switch(action)
//SETTINGS
if("play_music")
if(!playing)
INVOKE_ASYNC(src, PROC_REF(start_playing), user)
else
stop_playing()
return TRUE
if("set_instrument_id")
var/new_id = reject_bad_name(LOWER_TEXT(params["id"]), max_length = 20, allow_numbers = TRUE, cap_after_symbols = FALSE)
if(new_id)
id = new_id
return TRUE
if("change_instrument")
var/new_instrument = params["new_instrument"]
//only one instrument, so no need to bother changing it.
if(!length(allowed_instrument_ids))
return FALSE
if(!(new_instrument in allowed_instrument_ids))
return FALSE
set_instrument(new_instrument)
return TRUE
if("tempo")
var/move_direction = params["tempo_change"]
var/tempo_diff
if(move_direction == "increase_speed")
tempo_diff = world.tick_lag
else
tempo_diff = -world.tick_lag
tempo = sanitize_tempo(tempo + tempo_diff)
return TRUE
var/datum/browser/popup = new(user, "instrument", parent?.name || "instrument", 700, 500)
popup.set_content(dat.Join(""))
popup.open()
//SONG MAKING
if("import_song")
var/song_text = ""
do
song_text = tgui_input_text(user, "Please paste the entire song, formatted:", name, max_length = (MUSIC_MAXLINES * MUSIC_MAXLINECHARS), multiline = TRUE)
if(!in_range(parent, user))
return
if(length_char(song_text) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
var/should_continue = tgui_alert(user, "Your message is too long! Would you like to continue editing it?", "Warning", list("Yes", "No"))
if(should_continue != "Yes")
break
while(length_char(song_text) > MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
ParseSong(user, song_text)
return TRUE
if("start_new_song")
name = ""
lines = new()
tempo = sanitize_tempo(5) // default 120 BPM
return TRUE
if("add_new_line")
var/newline = tgui_input_text(user, "Enter your line", parent.name, max_length = MUSIC_MAXLINECHARS)
if(!newline || !in_range(parent, user))
return
if(lines.len > MUSIC_MAXLINES)
return
if(length(newline) > MUSIC_MAXLINECHARS)
newline = copytext(newline, 1, MUSIC_MAXLINECHARS)
lines.Add(newline)
if("delete_line")
var/line_to_delete = params["line_deleted"]
if(line_to_delete > lines.len || line_to_delete < 1)
return FALSE
lines.Cut(line_to_delete, line_to_delete + 1)
return TRUE
if("modify_line")
var/line_to_edit = params["line_editing"]
if(line_to_edit > lines.len || line_to_edit < 1)
return FALSE
var/new_line_text = tgui_input_text(user, "Enter your line ", parent.name, lines[line_to_edit], max_length = MUSIC_MAXLINECHARS)
if(isnull(new_line_text) || !in_range(parent, user))
return FALSE
lines[line_to_edit] = new_line_text
return TRUE
//MODE STUFF
if("set_sustain_mode")
var/new_mode = params["new_mode"]
if(isnull(new_mode) || !(new_mode in SSinstruments.note_sustain_modes))
return FALSE
sustain_mode = new_mode
return TRUE
if("set_note_shift")
var/amount = params["amount"]
if(!isnum(amount))
return FALSE
note_shift = clamp(amount, note_shift_min, note_shift_max)
return TRUE
if("set_volume")
var/new_volume = params["amount"]
if(!isnum(new_volume))
return FALSE
set_volume(new_volume)
return TRUE
if("set_dropoff_volume")
var/dropoff_threshold = params["amount"]
if(!isnum(dropoff_threshold))
return FALSE
set_dropoff_volume(dropoff_threshold)
return TRUE
if("toggle_sustain_hold_indefinitely")
full_sustain_held_note = !full_sustain_held_note
return TRUE
if("set_repeat_amount")
if(playing)
return
var/repeat_amount = params["amount"]
if(!isnum(repeat_amount))
return FALSE
set_repeats(repeat_amount)
return TRUE
if("edit_sustain_mode")
var/sustain_amount = params["amount"]
if(isnull(sustain_amount) || !isnum(sustain_amount))
return
switch(sustain_mode)
if(SUSTAIN_LINEAR)
set_linear_falloff_duration(sustain_amount)
if(SUSTAIN_EXPONENTIAL)
set_exponential_drop_rate(sustain_amount)
/**
* Parses a song the user has input into lines and stores them.
*/
/datum/song/proc/ParseSong(text)
/datum/song/proc/ParseSong(mob/user, new_song)
set waitfor = FALSE
//split into lines
lines = splittext(text, "\n")
lines = islist(new_song) ? new_song : splittext(new_song, "\n")
if(lines.len)
var/bpm_string = "BPM: "
if(findtext(lines[1], bpm_string, 1, length(bpm_string) + 1))
var/divisor = text2num(copytext(lines[1], length(bpm_string) + 1)) || 120 // default
tempo = sanitize_tempo(600 / round(divisor, 1))
tempo = sanitize_tempo(BPM_TO_TEMPO_SETTING(divisor))
lines.Cut(1, 2)
else
tempo = sanitize_tempo(5) // default 120 BPM
if(lines.len > MUSIC_MAXLINES)
to_chat(usr, "Too many lines!")
if(user)
to_chat(user, "Too many lines!")
lines.Cut(MUSIC_MAXLINES + 1)
var/linenum = 1
for(var/l in lines)
if(length_char(l) > MUSIC_MAXLINECHARS)
to_chat(usr, "Line [linenum] too long!")
if(user)
to_chat(user, "Line [linenum] too long!")
lines.Remove(l)
else
linenum++
updateDialog(usr) // make sure updates when complete
/datum/song/Topic(href, href_list)
if(!parent.CanUseTopic(usr))
usr << browse(null, "window=instrument")
usr.unset_machine()
return
parent.add_fingerprint(usr)
if(href_list["newsong"])
lines = new()
tempo = sanitize_tempo(5) // default 120 BPM
name = ""
else if(href_list["import"])
var/t = ""
do
t = tgui_input_text(usr, "Please paste the entire song, formatted:", text("[]", name), t, multiline = TRUE, prevent_enter = TRUE)
if(!in_range(parent, usr))
return
if(length_char(t) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
var/cont = tgui_alert(usr, "Your message is too long! Would you like to continue editing it?", "Too long!", list("Yes", "No"))
if(cont != "Yes")
break
while(length_char(t) > MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
ParseSong(t)
else if(href_list["help"])
help = text2num(href_list["help"]) - 1
else if(href_list["edit"])
editing = text2num(href_list["edit"]) - 1
if(href_list["repeat"]) //Changing this from a toggle to a number of repeats to avoid infinite loops.
if(playing)
return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing.
repeat += round(text2num(href_list["repeat"]))
if(repeat < 0)
repeat = 0
if(repeat > max_repeats)
repeat = max_repeats
else if(href_list["tempo"])
tempo = sanitize_tempo(tempo + text2num(href_list["tempo"]))
else if(href_list["play"])
INVOKE_ASYNC(src, PROC_REF(start_playing), usr)
else if(href_list["newline"])
var/newline = tgui_input_text(usr, "Enter your line: ", parent.name)
if(!newline || !in_range(parent, usr))
return
if(lines.len > MUSIC_MAXLINES)
return
if(length(newline) > MUSIC_MAXLINECHARS)
newline = copytext(newline, 1, MUSIC_MAXLINECHARS)
lines.Add(newline)
else if(href_list["deleteline"])
var/num = round(text2num(href_list["deleteline"]))
if(num > lines.len || num < 1)
return
lines.Cut(num, num+1)
else if(href_list["modifyline"])
var/num = round(text2num(href_list["modifyline"]),1)
var/content = tgui_input_text(usr, "Enter your line: ", parent.name, lines[num], MUSIC_MAXLINECHARS, encode=TRUE)
if(!content || !in_range(parent, usr))
return
if(num > lines.len || num < 1)
return
lines[num] = content
else if(href_list["stop"])
stop_playing()
else if(href_list["setlinearfalloff"])
var/amount = tgui_input_number(usr, "Set linear sustain duration in seconds", "Linear Sustain Duration", round_value=FALSE)
if(!isnull(amount))
set_linear_falloff_duration(round(amount * 10, world.tick_lag))
else if(href_list["setexpfalloff"])
var/amount = tgui_input_number(usr, "Set exponential sustain factor", "Exponential sustain factor", round_value=FALSE)
if(!isnull(amount))
set_exponential_drop_rate(round(amount, 0.00001))
else if(href_list["setvolume"])
var/amount = tgui_input_number(usr, "Set volume", "Volume")
if(!isnull(amount))
set_volume(round(amount, 1))
else if(href_list["setdropoffvolume"])
var/amount = tgui_input_number(usr, "Set dropoff threshold", "Dropoff Threshold Volume", round_value=FALSE)
if(!isnull(amount))
set_dropoff_volume(round(amount, 0.01))
else if(href_list["switchinstrument"])
if(!length(allowed_instrument_ids))
return
else if(length(allowed_instrument_ids) == 1)
set_instrument(allowed_instrument_ids[1])
return
var/list/categories = list()
for(var/i in allowed_instrument_ids)
var/datum/instrument/I = SSinstruments.get_instrument(i)
if(I)
LAZYSET(categories[I.category || "ERROR CATEGORY"], I.name, I.id)
var/cat = tgui_input_list(usr, "Select Category", "Instrument Category", categories)
if(!cat)
return
var/list/instruments = categories[cat]
var/choice = tgui_input_list(usr, "Select Instrument", "Instrument Selection", instruments)
if(!choice)
return
choice = instruments[choice] //get id
if(choice)
set_instrument(choice)
else if(href_list["setnoteshift"])
var/amount = tgui_input_number(usr, "Set note shift", "Note Shift", null, note_shift_max, note_shift_min)
if(!isnull(amount))
note_shift = clamp(amount, note_shift_min, note_shift_max)
else if(href_list["setsustainmode"])
var/choice = tgui_input_list(usr, "Choose a sustain mode", "Sustain Mode", list("Linear", "Exponential"))
switch(choice)
if("Linear")
sustain_mode = SUSTAIN_LINEAR
if("Exponential")
sustain_mode = SUSTAIN_EXPONENTIAL
else if(href_list["togglesustainhold"])
full_sustain_held_note = !full_sustain_held_note
updateDialog()

View File

@@ -9,7 +9,7 @@
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), ",")
var/list/chords = splittext(LOWER_TEXT(line), ",")
for(var/chord in chords)
var/list/compiled_chord = list()
var/tempodiv = 1
@@ -46,7 +46,7 @@
* * acc is either "b", "n", or "#"
* * oct is 1-8 (or 9 for C)
*/
/datum/song/proc/playkey_legacy(note, acc as text, oct, mob/user)
/datum/song/proc/playkey_legacy(note, acc as text, oct, atom/player)
// handle accidental -> B<>C of E<>F
if(acc == "b" && (note == 3 || note == 6)) // C or F
if(note == 3)
@@ -70,7 +70,7 @@
return
// now generate name
var/soundfile = "sound/instruments/[cached_legacy_dir]/[ascii2text(note+64)][acc][oct].[cached_legacy_ext]"
var/soundfile = "sound/runtime/instruments/[cached_legacy_dir]/[ascii2text(note+64)][acc][oct].[cached_legacy_ext]"
soundfile = file(soundfile)
// make sure the note exists
if(!fexists(soundfile))
@@ -80,14 +80,13 @@
if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
do_hearcheck()
var/sound/music_played = sound(soundfile)
for(var/mob/M as anything in hearing_mobs)
/* Would be nice
if(user && HAS_TRAIT(user, TRAIT_MUSICIAN) && isliving(M))
var/mob/living/L = M
L.apply_status_effect(STATUS_EFFECT_GOOD_MUSIC)
*/
if(!M)
hearing_mobs -= M
for(var/i in hearing_mobs)
var/mob/M = i
//if(player && HAS_TRAIT(player, TRAIT_MUSICIAN) && isliving(M))
// var/mob/living/L = M
// L.apply_status_effect(/datum/status_effect/good_music)
var/pref_volume = M?.client?.prefs.read_preference(/datum/preference/numeric/volume/sound_instruments)
if(!pref_volume)
continue
M.playsound_local(source, null, volume * using_instrument.volume_multiplier, S = music_played, preference = /datum/preference/toggle/instrument_toggle, volume_channel = VOLUME_CHANNEL_INSTRUMENTS)
M.playsound_local(source, null, volume * using_instrument.volume_multiplier * (pref_volume/100), S = music_played)
// Could do environment and echo later but not for now

View File

@@ -9,7 +9,7 @@
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), ",")
var/list/chords = splittext(LOWER_TEXT(line), ",")
for(var/chord in chords)
var/list/compiled_chord = list()
var/tempodiv = 1
@@ -42,9 +42,8 @@
* 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)
/datum/song/proc/playkey_synth(key, atom/player)
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!
@@ -60,27 +59,15 @@
var/channel_text = num2text(channel)
channels_playing[channel_text] = 100
last_channel_played = channel_text
var/turf/source = get_turf(parent)
for(var/mob/M as anything in hearing_mobs)
/* Maybe someday
if(user && HAS_TRAIT(user, TRAIT_MUSICIAN) && isliving(M))
var/mob/living/L = M
L.apply_status_effect(STATUS_EFFECT_GOOD_MUSIC)
*/
// Jeez
M.playsound_local(
turf_source = source,
soundin = null,
vol = volume,
vary = FALSE,
frequency = K.frequency,
falloff = FALLOFF_SOUNDS, //CHOMPEdit
is_global = null,
channel = channel,
pressure_affected = null,
S = copy,
preference = /datum/preference/toggle/instrument_toggle,
volume_channel = VOLUME_CHANNEL_INSTRUMENTS)
for(var/i in hearing_mobs)
var/mob/M = i
//if(player && HAS_TRAIT(player, TRAIT_MUSICIAN) && isliving(M))
// var/mob/living/L = M
// L.apply_status_effect(/datum/status_effect/good_music)
var/pref_volume = M?.client?.prefs.read_preference(/datum/preference/numeric/volume/sound_instruments)
if(!pref_volume)
continue
M.playsound_local(get_turf(parent), null, volume * (pref_volume/100), FALSE, K.frequency, null, channel, null, copy)
// Could do environment and echo later but not for now
/**
@@ -141,8 +128,10 @@
if(dead)
channels_playing -= channel
channels_idle += channel
for(var/mob/M in hearing_mobs)
for(var/i in hearing_mobs)
var/mob/M = i
M.stop_sound_channel(channelnumber)
else
for(var/mob/M in hearing_mobs)
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)

View File

@@ -1,9 +1,12 @@
/obj/structure/musician
name = "Not A Piano"
desc = "Something broke, contact coderbus."
/// IF FALSE music stops when the piano is unanchored.
var/can_play_unanchored = FALSE
/// Our allowed list of instrument ids. This is nulled on initialize.
var/list/allowed_instrument_ids = list("r3grand","r3harpsi","crharpsi","crgrand1","crbright1", "crichugan", "crihamgan","piano")
var/datum/song/song
/// Our song datum.
var/datum/song/stationary/song
/obj/structure/musician/Initialize(mapload)
. = ..()
@@ -14,64 +17,62 @@
QDEL_NULL(song)
return ..()
/obj/structure/musician/proc/can_play(atom/music_player)
if(!anchored && !can_play_unanchored)
return FALSE
if(!ismob(music_player))
return FALSE
var/mob/user = music_player
if(!user.IsAdvancedToolUser())
return FALSE
if(user.incapacitated())
return FALSE
if(!Adjacent(user))
return FALSE
return TRUE
/obj/structure/musician/attack_hand(mob/M)
if(!M.IsAdvancedToolUser())
return
interact(M)
tgui_interact(M)
// CHOMPAdd - Grand piano moving
/obj/structure/musician/tgui_interact(mob/user)
return song.tgui_interact(user)
/obj/structure/musician/attackby(obj/item/W as obj, mob/user as mob)
if(W.has_tool_quality(TOOL_WRENCH))
playsound(src, W.usesound, 100, 1)
if(anchored)
user.visible_message(span_filter_notice("[user] begins unsecuring \the [src] from the floor."), span_filter_notice("You start unsecuring \the [src] from the floor."))
else
user.visible_message(span_filter_notice("[user] begins securing \the [src] to the floor."), span_filter_notice("You start securing \the [src] to the floor."))
if(do_after(user, 20 * W.toolspeed))
if(!src) return
to_chat(user, span_notice("You [anchored? "un" : ""]secured \the [src]!"))
anchored = !anchored
return
// CHOMPEnd
/obj/structure/musician/proc/should_stop_playing(mob/user)
if(!(anchored || can_play_unanchored))
return TRUE
if(!user)
return FALSE
return !CanUseTopic(user)
/obj/structure/musician/interact(mob/user)
/* FIXME
/obj/structure/musician/wrench_act(mob/living/user, obj/item/tool)
. = ..()
song.interact(user)
/*
/obj/structure/musician/wrench_act(mob/living/user, obj/item/I)
default_unfasten_wrench(user, I, 40)
return TRUE
default_unfasten_wrench(user, tool, time = 4 SECONDS)
return ITEM_INTERACT_SUCCESS
*/
/obj/structure/musician/piano
name = "space minimoog"
name = "space piano"
desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't."
icon = 'icons/obj/musician.dmi'
icon_state = "minimoog"
anchored = TRUE
density = TRUE
var/broken_icon_state = "pianobroken"
/obj/structure/musician/piano/Initialize(mapload)
. = ..()
AddElement(/datum/element/climbable)
/** FIXME: We do not have atom_break implemented yet
/obj/structure/musician/piano/atom_break(damage_flag)
. = ..()
if(!broken)
broken = TRUE
icon_state = broken_icon_state
*/
/obj/structure/musician/piano/unanchored
anchored = FALSE
/obj/structure/musician/piano/Initialize(mapload)
. = ..()
if(prob(50) && icon_state == initial(icon_state))
name = "space minimoog"
desc = "This is a minimoog, like a space piano, but more spacey!"
icon_state = "minimoog"
else
name = "space piano"
desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't."
icon_state = "piano"
/obj/structure/musician/piano/minimoog
name = "space minimoog"
desc = "This is a minimoog, like a space piano, but more spacey!"
icon_state = "minimoog"
broken_icon_state = "minimoogbroken"

View File

@@ -0,0 +1,13 @@
/obj/structure/musician/attackby(obj/item/W, mob/user)
if(W.has_tool_quality(TOOL_WRENCH))
playsound(src, W.usesound, 100, 1)
if(anchored)
user.visible_message(span_filter_notice("[user] begins unsecuring \the [src] from the floor."), span_filter_notice("You start unsecuring \the [src] from the floor."))
else
user.visible_message(span_filter_notice("[user] begins securing \the [src] to the floor."), span_filter_notice("You start securing \the [src] to the floor."))
if(do_after(user, 20 * W.toolspeed))
if(!src) return
to_chat(user, span_notice("You [anchored? "un" : ""]secured \the [src]!"))
anchored = !anchored
return

5
sound/runtime/README.md Normal file
View File

@@ -0,0 +1,5 @@
# Runtime Loaded Sounds
These sounds are not compiled into the .rsc or are otherwise loaded at runtime.
Please keep all (non-config) sounds that do this in this folder as it is needed by the [deploy.sh](../../tools/deploy.sh) script to minimize build output.

Some files were not shown because too many files have changed in this diff Show More