diff --git a/_maps/RandomRuins/SpaceRuins/spacehotel.dmm b/_maps/RandomRuins/SpaceRuins/spacehotel.dmm
index 69b56946f2..209feb82e3 100644
--- a/_maps/RandomRuins/SpaceRuins/spacehotel.dmm
+++ b/_maps/RandomRuins/SpaceRuins/spacehotel.dmm
@@ -2688,7 +2688,7 @@
/obj/structure/window/reinforced{
dir = 1
},
-/obj/structure/piano{
+/obj/structure/musician/piano{
icon_state = "piano"
},
/turf/open/floor/plasteel/grimy,
diff --git a/_maps/RandomRuins/SpaceRuins/vaporwave.dmm b/_maps/RandomRuins/SpaceRuins/vaporwave.dmm
index 75adc455b0..a1b2271d96 100644
--- a/_maps/RandomRuins/SpaceRuins/vaporwave.dmm
+++ b/_maps/RandomRuins/SpaceRuins/vaporwave.dmm
@@ -28,7 +28,7 @@
/turf/open/floor/holofloor/beach,
/area/ruin/space/has_grav/powered/aesthetic)
"i" = (
-/obj/structure/piano,
+/obj/structure/musician/piano,
/obj/structure/window{
dir = 8
},
diff --git a/_maps/RandomZLevels/Cabin.dmm b/_maps/RandomZLevels/Cabin.dmm
index 4d07c02565..a0862213a2 100644
--- a/_maps/RandomZLevels/Cabin.dmm
+++ b/_maps/RandomZLevels/Cabin.dmm
@@ -713,7 +713,7 @@
/turf/open/floor/wood,
/area/awaymission/cabin)
"cz" = (
-/obj/structure/piano,
+/obj/structure/musician/piano,
/turf/open/floor/wood,
/area/awaymission/cabin)
"cA" = (
diff --git a/_maps/map_files/BoxStation/BoxStation.dmm b/_maps/map_files/BoxStation/BoxStation.dmm
index 086067a6fb..c8cd2ab499 100644
--- a/_maps/map_files/BoxStation/BoxStation.dmm
+++ b/_maps/map_files/BoxStation/BoxStation.dmm
@@ -11677,7 +11677,7 @@
/turf/open/floor/plating,
/area/maintenance/starboard/fore)
"aAs" = (
-/obj/structure/piano{
+/obj/structure/musician/piano{
icon_state = "piano"
},
/turf/open/floor/plating,
@@ -55621,7 +55621,7 @@
},
/area/crew_quarters/theatre)
"iTU" = (
-/obj/structure/piano{
+/obj/structure/musician/piano{
icon_state = "piano"
},
/turf/open/floor/wood,
@@ -56804,7 +56804,7 @@
/turf/open/floor/plating,
/area/security/main)
"myh" = (
-/obj/structure/piano,
+/obj/structure/musician/piano,
/obj/structure/window/reinforced,
/obj/machinery/atmospherics/pipe/simple/supply/hidden,
/turf/open/floor/wood,
diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm
index 82ac8604ef..a4ba19dd57 100644
--- a/_maps/map_files/Deltastation/DeltaStation2.dmm
+++ b/_maps/map_files/Deltastation/DeltaStation2.dmm
@@ -26182,7 +26182,7 @@
/turf/open/floor/plasteel/dark,
/area/crew_quarters/bar/atrium)
"aWX" = (
-/obj/structure/piano{
+/obj/structure/musician/piano{
icon_state = "piano"
},
/obj/machinery/airalarm{
@@ -115313,7 +115313,7 @@
/obj/structure/window/reinforced{
dir = 8
},
-/obj/structure/piano,
+/obj/structure/musician/piano,
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/plating,
/area/crew_quarters/theatre/abandoned)
diff --git a/_maps/map_files/LambdaStation/lambda.dmm b/_maps/map_files/LambdaStation/lambda.dmm
index f761cee126..431f94bb32 100644
--- a/_maps/map_files/LambdaStation/lambda.dmm
+++ b/_maps/map_files/LambdaStation/lambda.dmm
@@ -30338,7 +30338,7 @@
/turf/open/floor/plasteel,
/area/crew_quarters/bar/atrium)
"bbF" = (
-/obj/structure/piano,
+/obj/structure/musician/piano,
/obj/structure/window/reinforced{
dir = 8
},
@@ -62828,7 +62828,7 @@
},
/area/maintenance/aft)
"ciL" = (
-/obj/structure/piano,
+/obj/structure/musician/piano,
/obj/effect/turf_decal/tile/neutral{
dir = 1
},
diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm
index a08aaf094f..0d8d956184 100644
--- a/_maps/map_files/MetaStation/MetaStation.dmm
+++ b/_maps/map_files/MetaStation/MetaStation.dmm
@@ -79574,7 +79574,7 @@
/turf/closed/wall,
/area/crew_quarters/bar)
"dhU" = (
-/obj/structure/piano,
+/obj/structure/musician/piano,
/obj/structure/window/reinforced{
dir = 8
},
diff --git a/_maps/map_files/OmegaStation/OmegaStation.dmm b/_maps/map_files/OmegaStation/OmegaStation.dmm
index 2ca3c3deee..8a951881f9 100644
--- a/_maps/map_files/OmegaStation/OmegaStation.dmm
+++ b/_maps/map_files/OmegaStation/OmegaStation.dmm
@@ -11768,7 +11768,7 @@
/turf/open/floor/plasteel/dark,
/area/crew_quarters/theatre)
"atY" = (
-/obj/structure/piano{
+/obj/structure/musician/piano{
icon_state = "piano"
},
/obj/machinery/light{
diff --git a/_maps/map_files/PubbyStation/PubbyStation.dmm b/_maps/map_files/PubbyStation/PubbyStation.dmm
index fc5f6e9778..465f03bb6e 100644
--- a/_maps/map_files/PubbyStation/PubbyStation.dmm
+++ b/_maps/map_files/PubbyStation/PubbyStation.dmm
@@ -43015,7 +43015,7 @@
/area/maintenance/department/engine)
"bUb" = (
/obj/structure/grille/broken,
-/obj/structure/piano,
+/obj/structure/musician/piano,
/turf/open/floor/plating,
/area/maintenance/department/engine)
"bUc" = (
@@ -58271,7 +58271,7 @@
/turf/open/floor/plating,
/area/chapel/office)
"plA" = (
-/obj/structure/piano,
+/obj/structure/musician/piano,
/turf/open/floor/plasteel/dark,
/area/maintenance/department/crew_quarters/dorms)
"pmB" = (
diff --git a/_maps/map_files/generic/CentCom.dmm b/_maps/map_files/generic/CentCom.dmm
index daff5e51ad..c9b81cefd9 100644
--- a/_maps/map_files/generic/CentCom.dmm
+++ b/_maps/map_files/generic/CentCom.dmm
@@ -14420,7 +14420,7 @@
/obj/machinery/light{
dir = 8
},
-/obj/structure/piano,
+/obj/structure/musician/piano,
/turf/open/indestructible/hotelwood,
/area/centcom/holding)
"Hk" = (
diff --git a/_maps/shuttles/emergency_bar.dmm b/_maps/shuttles/emergency_bar.dmm
index 7c14d9ebd0..7bc7de9bf1 100644
--- a/_maps/shuttles/emergency_bar.dmm
+++ b/_maps/shuttles/emergency_bar.dmm
@@ -197,7 +197,7 @@
/turf/open/floor/plasteel,
/area/shuttle/escape)
"aF" = (
-/obj/structure/piano{
+/obj/structure/musician/piano{
icon_state = "piano"
},
/turf/open/floor/plasteel/grimy,
diff --git a/code/__DEFINES/instruments.dm b/code/__DEFINES/instruments.dm
new file mode 100644
index 0000000000..a72976faf5
--- /dev/null
+++ b/code/__DEFINES/instruments.dm
@@ -0,0 +1,29 @@
+#define INSTRUMENT_MIN_OCTAVE 1
+#define INSTRUMENT_MAX_OCTAVE 9
+#define INSTRUMENT_MIN_KEY 0
+#define INSTRUMENT_MAX_KEY 127
+
+/// Max number of playing notes per instrument.
+#define CHANNELS_PER_INSTRUMENT 128
+
+/// Distance multiplier that makes us not be impacted by 3d sound as much. This is a multiplier so lower it is the closer we will pretend to be to people.
+#define INSTRUMENT_DISTANCE_FALLOFF_BUFF 0.2
+/// How many tiles instruments have no falloff for
+#define INSTRUMENT_DISTANCE_NO_FALLOFF 3
+
+/// Maximum length a note should ever go for
+#define INSTRUMENT_MAX_TOTAL_SUSTAIN (5 SECONDS)
+
+/// These are per decisecond.
+#define INSTRUMENT_EXP_FALLOFF_MIN 1.025 //100/(1.025^50) calculated for [INSTRUMENT_MIN_SUSTAIN_DROPOFF] to be 30.
+#define INSTRUMENT_EXP_FALLOFF_MAX 10
+
+/// Minimum volume for when the sound is considered dead.
+#define INSTRUMENT_MIN_SUSTAIN_DROPOFF 30
+
+#define SUSTAIN_LINEAR 1
+#define SUSTAIN_EXPONENTIAL 2
+
+// /datum/instrument instrument_flags
+#define INSTRUMENT_LEGACY (1<<0) //Legacy instrument. Implies INSTRUMENT_DO_NOT_AUTOSAMPLE
+#define INSTRUMENT_DO_NOT_AUTOSAMPLE (1<<1) //Do not automatically sample
diff --git a/code/__DEFINES/sound.dm b/code/__DEFINES/sound.dm
index 581bb2bc29..73781154c5 100644
--- a/code/__DEFINES/sound.dm
+++ b/code/__DEFINES/sound.dm
@@ -20,6 +20,7 @@
#define CHANNEL_HIGHEST_AVAILABLE 1008 //CIT CHANGE - COMPENSATES FOR VORESOUND CHANNELS
+#define MAX_INSTRUMENT_CHANNELS (128 * 6)
#define SOUND_MINIMUM_PRESSURE 10
#define FALLOFF_SOUNDS 1
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index a693b26b20..5aa0301bf6 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -50,17 +50,19 @@
#define INIT_ORDER_PROFILER 100
#define INIT_ORDER_FAIL2TOPIC 99
#define INIT_ORDER_TITLE 98
-#define INIT_ORDER_GARBAGE 97
-#define INIT_ORDER_DBCORE 95
-#define INIT_ORDER_BLACKBOX 94
-#define INIT_ORDER_SERVER_MAINT 93
-#define INIT_ORDER_INPUT 85
+#define INIT_ORDER_GARBAGE 95
+#define INIT_ORDER_DBCORE 93
+#define INIT_ORDER_BLACKBOX 92
+#define INIT_ORDER_SERVER_MAINT 91
+#define INIT_ORDER_INPUT 90
+#define INIT_ORDER_SOUNDS 85
#define INIT_ORDER_VIS 80
#define INIT_ORDER_RESEARCH 75
#define INIT_ORDER_EVENTS 70
#define INIT_ORDER_JOBS 65
#define INIT_ORDER_QUIRKS 60
#define INIT_ORDER_TICKER 55
+#define INIT_ORDER_INSTRUMENTS 53
#define INIT_ORDER_MAPPING 50
#define INIT_ORDER_NETWORKS 45
#define INIT_ORDER_ATOMS 30
@@ -100,6 +102,7 @@
#define FIRE_PRIORITY_PROCESS 25
#define FIRE_PRIORITY_THROWING 25
#define FIRE_PRIORITY_SPACEDRIFT 30
+#define FIRE_PRIORITY_INSTRUMENTS 30
#define FIRE_PRIORITY_FIELDS 30
#define FIRE_PRIOTITY_SMOOTHING 35
#define FIRE_PRIORITY_NETWORKS 40
diff --git a/code/controllers/subsystem/processing/chemistry.dm b/code/controllers/subsystem/processing/chemistry.dm
index da31d65fb3..64a780c1ad 100644
--- a/code/controllers/subsystem/processing/chemistry.dm
+++ b/code/controllers/subsystem/processing/chemistry.dm
@@ -1,5 +1,4 @@
PROCESSING_SUBSYSTEM_DEF(chemistry)
wait = 5
flags = SS_KEEP_TIMING
-
-
+ name = "Chemistry"
diff --git a/code/controllers/subsystem/processing/instruments.dm b/code/controllers/subsystem/processing/instruments.dm
new file mode 100644
index 0000000000..a4e0d7703f
--- /dev/null
+++ b/code/controllers/subsystem/processing/instruments.dm
@@ -0,0 +1,43 @@
+PROCESSING_SUBSYSTEM_DEF(instruments)
+ name = "Instruments"
+ wait = 0.5
+ init_order = INIT_ORDER_INSTRUMENTS
+ flags = SS_KEEP_TIMING
+ priority = FIRE_PRIORITY_INSTRUMENTS
+ var/static/list/datum/instrument/instrument_data = list() //id = datum
+ var/static/list/datum/song/songs = list()
+ var/static/musician_maxlines = 600
+ var/static/musician_maxlinechars = 300
+ var/static/musician_hearcheck_mindelay = 5
+ var/static/max_instrument_channels = MAX_INSTRUMENT_CHANNELS
+ var/static/current_instrument_channels = 0
+
+/datum/controller/subsystem/processing/instruments/Initialize()
+ initialize_instrument_data()
+ return ..()
+
+/datum/controller/subsystem/processing/instruments/proc/on_song_new(datum/song/S)
+ songs += S
+
+/datum/controller/subsystem/processing/instruments/proc/on_song_del(datum/song/S)
+ songs -= S
+
+/datum/controller/subsystem/processing/instruments/proc/initialize_instrument_data()
+ for(var/path in subtypesof(/datum/instrument))
+ var/datum/instrument/I = path
+ if(initial(I.abstract_type) == path)
+ continue
+ I = new path
+ I.Initialize()
+ instrument_data[I.id || "[I.type]"] = I
+ CHECK_TICK
+
+/datum/controller/subsystem/processing/instruments/proc/get_instrument(id_or_path)
+ return instrument_data["[id_or_path]"]
+
+/datum/controller/subsystem/processing/instruments/proc/reserve_instrument_channel(datum/instrument/I)
+ if(current_instrument_channels > max_instrument_channels)
+ return
+ . = SSsounds.reserve_sound_channel(I)
+ if(!isnull(.))
+ current_instrument_channels++
diff --git a/code/controllers/subsystem/processing/status_effects.dm b/code/controllers/subsystem/processing/status_effects.dm
index e8984a44cb..57b340b2ce 100644
--- a/code/controllers/subsystem/processing/status_effects.dm
+++ b/code/controllers/subsystem/processing/status_effects.dm
@@ -1,3 +1,4 @@
PROCESSING_SUBSYSTEM_DEF(status_effects)
wait = 1
flags = SS_TICKER
+ name = "Status Effects"
diff --git a/code/controllers/subsystem/sounds.dm b/code/controllers/subsystem/sounds.dm
new file mode 100644
index 0000000000..5e7c5e6545
--- /dev/null
+++ b/code/controllers/subsystem/sounds.dm
@@ -0,0 +1,91 @@
+#define DATUMLESS "NO_DATUM"
+
+SUBSYSTEM_DEF(sounds)
+ name = "Sounds"
+ flags = SS_NO_FIRE
+ init_order = INIT_ORDER_SOUNDS
+ var/static/using_channels_max = CHANNEL_HIGHEST_AVAILABLE //BYOND max channels
+
+ // Hey uh these two needs to be initialized fast because the whole "things get deleted before init" thing.
+ /// Assoc list, "[channel]" = either the datum using it or TRUE for an unsafe-reserved (datumless reservation) channel
+ var/list/using_channels = list()
+ /// Assoc list datum = list(channel1, channel2, ...) for what channels something reserved.
+ var/list/using_channels_by_datum = list()
+ /// List of all available channels with associations set to TRUE for fast lookups/allocation.
+ var/list/available_channels
+
+/datum/controller/subsystem/sounds/Initialize()
+ setup_available_channels()
+ return ..()
+
+/datum/controller/subsystem/sounds/proc/setup_available_channels()
+ available_channels = list()
+ for(var/i in 1 to using_channels_max)
+ available_channels[num2text(i)] = TRUE
+
+/// Removes a channel from using list.
+/datum/controller/subsystem/sounds/proc/free_sound_channel(channel)
+ channel = num2text(channel)
+ var/using = using_channels[channel]
+ using_channels -= channel
+ if(using)
+ using_channels_by_datum[using] -= channel
+ if(!length(using_channels_by_datum[using]))
+ using_channels_by_datum -= using
+ available_channels[channel] = TRUE
+
+/// Frees all the channels a datum is using.
+/datum/controller/subsystem/sounds/proc/free_datum_channels(datum/D)
+ var/list/L = using_channels_by_datum[D]
+ if(!L)
+ return
+ for(var/channel in L)
+ using_channels -= channel
+ available_channels[channel] = TRUE
+ using_channels_by_datum -= D
+
+/// Frees all datumless channels
+/datum/controller/subsystem/sounds/proc/free_datumless_channels()
+ free_datum_channels(DATUMLESS)
+
+/// NO AUTOMATIC CLEANUP - If you use this, you better manually free it later! Returns an integer for channel.
+/datum/controller/subsystem/sounds/proc/reserve_sound_channel_datumless()
+ var/channel = random_available_channel_text()
+ if(!channel) //oh no..
+ return FALSE
+ available_channels -= channel
+ using_channels[channel] = DATUMLESS
+ LAZYINITLIST(using_channels_by_datum[DATUMLESS])
+ using_channels_by_datum[DATUMLESS] += channel
+ return text2num(channel)
+
+/// Reserves a channel for a datum. Automatic cleanup only when the datum is deleted. Returns an integer for channel.
+/datum/controller/subsystem/sounds/proc/reserve_sound_channel(datum/D)
+ if(!D) //i don't like typechecks but someone will fuck it up
+ CRASH("Attempted to reserve sound channel without datum using the managed proc.")
+ var/channel = random_available_channel_text()
+ if(!channel)
+ return FALSE
+ available_channels -= channel
+ using_channels[channel] = D
+ LAZYINITLIST(using_channels_by_datum[D])
+ using_channels_by_datum[D] += channel
+ return text2num(channel)
+
+/// Random available channel, returns text.
+/datum/controller/subsystem/sounds/proc/random_available_channel_text()
+ return pick(available_channels)
+
+/// Random available channel, returns number
+/datum/controller/subsystem/sounds/proc/random_available_channel()
+ return text2num(pick(available_channels))
+
+/// If a channel is available
+/datum/controller/subsystem/sounds/proc/is_channel_available(channel)
+ return available_channels[num2text(channel)]
+
+/// How many channels we have left.
+/datum/controller/subsystem/sounds/proc/available_channels_left()
+ return length(available_channels)
+
+#undef DATUMLESS
diff --git a/code/datums/action.dm b/code/datums/action.dm
index 74df2bf9a1..6c08a33f33 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -302,16 +302,6 @@
name = "Toggle Friendly Fire \[ON\]"
..()
-/datum/action/item_action/synthswitch
- name = "Change Synthesizer Instrument"
- desc = "Change the type of instrument your synthesizer is playing as."
-
-/datum/action/item_action/synthswitch/Trigger()
- if(istype(target, /obj/item/instrument/piano_synth))
- var/obj/item/instrument/piano_synth/synth = target
- return synth.selectInstrument()
- return ..()
-
/datum/action/item_action/vortex_recall
name = "Vortex Recall"
desc = "Recall yourself, and anyone nearby, to an attuned hierophant beacon at any time.
If the beacon is still attached, will detach it."
diff --git a/code/datums/datum.dm b/code/datums/datum.dm
index a856f2392d..d7ff5db26a 100644
--- a/code/datums/datum.dm
+++ b/code/datums/datum.dm
@@ -109,6 +109,8 @@
UnregisterSignal(target, signal_procs[target])
//END: ECS SHIT
+ SSsounds.free_datum_channels(src)
+
return QDEL_HINT_QUEUE
#ifdef DATUMVAR_DEBUGGING_MODE
diff --git a/code/datums/looping_sounds/_looping_sound.dm b/code/datums/looping_sounds/_looping_sound.dm
index 49942976ce..bafb6fbf0e 100644
--- a/code/datums/looping_sounds/_looping_sound.dm
+++ b/code/datums/looping_sounds/_looping_sound.dm
@@ -73,7 +73,7 @@
var/list/atoms_cache = output_atoms
var/sound/S = sound(soundfile)
if(direct)
- S.channel = open_sound_channel()
+ S.channel = SSsounds.random_available_channel()
S.volume = volume
for(var/i in 1 to atoms_cache.len)
var/atom/thing = atoms_cache[i]
diff --git a/code/game/objects/structures/musician.dm b/code/game/objects/structures/musician.dm
deleted file mode 100644
index bba51035fe..0000000000
--- a/code/game/objects/structures/musician.dm
+++ /dev/null
@@ -1,383 +0,0 @@
-
-#define MUSICIAN_HEARCHECK_MINDELAY 4
-#define MUSIC_MAXLINES 600
-#define MUSIC_MAXLINECHARS 150
-
-/datum/song
- var/name = "Untitled"
- var/list/lines = new()
- var/tempo = 5 // delay between notes
-
- var/playing = 0 // if we're playing
- var/help = 0 // if help is open
- var/edit = 1 // if we're in editing mode
- var/repeat = 0 // number of times remaining to repeat
- var/max_repeats = 10 // maximum times we can repeat
-
- var/instrumentDir = "piano" // the folder with the sounds
- var/instrumentExt = "ogg" // the file extension
- var/obj/instrumentObj = null // the associated obj playing the sound
- var/last_hearcheck = 0
- var/list/hearing_mobs
-
-/datum/song/New(dir, obj, ext = "ogg")
- tempo = sanitize_tempo(tempo)
- instrumentDir = dir
- instrumentObj = obj
- instrumentExt = ext
-
-/datum/song/Destroy()
- instrumentObj = null
- return ..()
-
-// 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(note, acc as text, oct)
- // handle accidental -> B<>C of E<>F
- if(acc == "b" && (note == 3 || note == 6)) // C or F
- if(note == 3)
- oct--
- note--
- acc = "n"
- else if(acc == "#" && (note == 2 || note == 5)) // B or E
- if(note == 2)
- oct++
- note++
- acc = "n"
- else if(acc == "#" && (note == 7)) //G#
- note = 1
- acc = "b"
- else if(acc == "#") // mass convert all sharps to flats, octave jump already handled
- acc = "b"
- note++
-
- // check octave, C is allowed to go to 9
- if(oct < 1 || (note == 3 ? oct > 9 : oct > 8))
- return
-
- // now generate name
- var/soundfile = "sound/instruments/[instrumentDir]/[ascii2text(note+64)][acc][oct].[instrumentExt]"
- soundfile = file(soundfile)
- // make sure the note exists
- if(!fexists(soundfile))
- return
- // and play
- var/turf/source = get_turf(instrumentObj)
- if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
- LAZYCLEARLIST(hearing_mobs)
- for(var/mob/M in get_hearers_in_view(15, source))
- if(!M.client || !(M.client.prefs.toggles & SOUND_INSTRUMENTS))
- continue
- LAZYADD(hearing_mobs, M)
- last_hearcheck = world.time
-
- var/sound/music_played = sound(soundfile)
- for(var/i in hearing_mobs)
- var/mob/M = i
- M.playsound_local(source, null, 100, falloff = 5, S = music_played)
-
-/datum/song/proc/updateDialog(mob/user)
- instrumentObj.updateDialog() // assumes it's an object in world, override if otherwise
-
-/datum/song/proc/shouldStopPlaying(mob/user)
- if(instrumentObj)
- if(!user.canUseTopic(instrumentObj, TRUE, FALSE, FALSE, FALSE))
- return TRUE
- return !instrumentObj.anchored // add special cases to stop in subclasses
- else
- return TRUE
-
-/datum/song/proc/playsong(mob/user)
- while(repeat >= 0)
- var/cur_oct[7]
- var/cur_acc[7]
- for(var/i = 1 to 7)
- cur_oct[i] = 3
- cur_acc[i] = "n"
-
- for(var/line in lines)
- for(var/beat in splittext(lowertext(line), ","))
- var/list/notes = splittext(beat, "/")
- for(var/note in splittext(notes[1], "-"))
- if(!playing || shouldStopPlaying(user))//If the instrument is playing, or special case
- playing = FALSE
- hearing_mobs = null
- return
- if(!length(note))
- continue
- var/cur_note = text2ascii(note) - 96
- if(cur_note < 1 || cur_note > 7)
- continue
- var/notelen = length(note)
- var/ni = ""
- for(var/i = length(note[1]) + 1, i <= notelen, i += length(ni))
- ni = note[i]
- 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)
- if(user.dizziness > 0 && prob(user.dizziness / 2))
- cur_note = CLAMP(cur_note + rand(round(-user.dizziness / 10), round(user.dizziness / 10)), 1, 7)
- if(user.dizziness > 0 && prob(user.dizziness / 5))
- if(prob(30))
- cur_acc[cur_note] = "#"
- else if(prob(42))
- cur_acc[cur_note] = "b"
- else if(prob(75))
- cur_acc[cur_note] = "n"
- playnote(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)
- repeat--
- hearing_mobs = null
- playing = FALSE
- repeat = 0
- updateDialog(user)
-
-/datum/song/proc/interact(mob/user)
- var/dat = ""
-
- if(lines.len > 0)
- dat += "
Playback
"
- if(!playing)
- dat += "Play Stop
"
- dat += "Repeat Song: "
- dat += repeat > 0 ? "--" : "--"
- dat += " [repeat] times "
- dat += repeat < max_repeats ? "++" : "++"
- dat += "
"
- else
- dat += "Play Stop
"
- dat += "Repeats left: [repeat]
"
- if(!edit)
- dat += "
Show Editor
"
- else
- dat += "Editing
"
- dat += "Hide Editor"
- dat += " Start a New Song"
- dat += " Import a Song
"
- var/bpm = round(600 / tempo)
- dat += "Tempo: - [bpm] BPM +
"
- var/linecount = 0
- for(var/line in lines)
- linecount += 1
- dat += "Line [linecount]: Edit X [line]
"
- dat += "Add Line
"
- if(help)
- dat += "Hide Help
"
- dat += {"
- Lines are a series of chords, separated by commas (,), each with notes separated by hyphens (-).
- Every note in a chord will play together, with chord timed by the tempo.
-
- Notes are played by the names of the note, and optionally, the accidental, and/or the octave number.
- By default, every note is natural and in octave 3. Defining otherwise is remembered for each note.
- Example: C,D,E,F,G,A,B will play a C major scale.
- After a note has an accidental placed, it will be remembered: C,C4,C,C3 is C3,C4,C4,C3
- Chords can be played simply by seperating each note with a hyphon: A-C#,Cn-E,E-G#,Gn-B
- A pause may be denoted by an empty chord: C,E,,C,G
- To make a chord be a different time, end it with /x, where the chord length will be length
- defined by tempo / x: C,G/2,E/4
- Combined, an example is: E-E4/4,F#/2,G#/8,B/8,E3-E4/4
-
- Lines may be up to [MUSIC_MAXLINECHARS] characters.
- A song may only contain up to [MUSIC_MAXLINES] lines.
- "}
- else
- dat += "Show Help
"
-
- var/datum/browser/popup = new(user, "instrument", instrumentObj.name, 700, 500)
- popup.set_content(dat)
- popup.set_title_image(user.browse_rsc_icon(instrumentObj.icon, instrumentObj.icon_state))
- popup.open()
-
-/datum/song/proc/ParseSong(text)
- set waitfor = FALSE
- //split into lines
- lines = splittext(text, "\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))
- lines.Cut(1, 2)
- else
- tempo = sanitize_tempo(5) // default 120 BPM
- if(lines.len > MUSIC_MAXLINES)
- to_chat(usr, "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!")
- lines.Remove(l)
- else
- linenum++
- updateDialog(usr) // make sure updates when complete
-
-/datum/song/Topic(href, href_list)
- if(!usr.canUseTopic(instrumentObj, TRUE, FALSE, FALSE, FALSE))
- usr << browse(null, "window=instrument")
- usr.unset_machine()
- return
-
- instrumentObj.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 = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message)
- if(!in_range(instrumentObj, usr))
- return
-
- if(length_char(t) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
- var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no")
- if(cont == "no")
- 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"])
- edit = 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"])
- playing = TRUE
- spawn()
- playsong(usr)
-
- else if(href_list["newline"])
- var/newline = html_encode(input("Enter your line: ", instrumentObj.name) as text|null)
- if(!newline || !in_range(instrumentObj, 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 = stripped_input(usr, "Enter your line: ", instrumentObj.name, lines[num], MUSIC_MAXLINECHARS)
- if(!content || !in_range(instrumentObj, usr))
- return
- if(num > lines.len || num < 1)
- return
- lines[num] = content
-
- else if(href_list["stop"])
- playing = FALSE
- hearing_mobs = null
-
- updateDialog(usr)
- return
-
-/datum/song/proc/sanitize_tempo(new_tempo)
- new_tempo = abs(new_tempo)
- return max(round(new_tempo, world.tick_lag), world.tick_lag)
-
-// subclass for handheld instruments, like violin
-/datum/song/handheld
-
-/datum/song/handheld/updateDialog(mob/user)
- instrumentObj.interact(user)
-
-/datum/song/handheld/shouldStopPlaying()
- if(instrumentObj)
- return !isliving(instrumentObj.loc)
- else
- return TRUE
-
-
-//////////////////////////////////////////////////////////////////////////
-
-
-/obj/structure/piano
- name = "space minimoog"
- icon = 'icons/obj/musician.dmi'
- icon_state = "minimoog"
- anchored = TRUE
- density = TRUE
- var/datum/song/song
-
-/obj/structure/piano/unanchored
- anchored = FALSE
-
-/obj/structure/piano/New()
- ..()
- song = new("piano", src)
-
- 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/piano/Destroy()
- qdel(song)
- song = null
- return ..()
-
-/obj/structure/piano/Initialize(mapload)
- . = ..()
- if(mapload)
- song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded
-
-/obj/structure/piano/attack_hand(mob/user)
- . = ..()
- if(.)
- return
- interact(user)
-
-/obj/structure/piano/attack_paw(mob/user)
- return attack_hand(user)
-
-/obj/structure/piano/interact(mob/user)
- ui_interact(user)
-
-/obj/structure/piano/ui_interact(mob/user)
- if(!user || !anchored)
- return
-
- if(!user.IsAdvancedToolUser())
- to_chat(user, "You don't have the dexterity to do this!")
- return 1
- user.set_machine(src)
- song.interact(user)
-
-/obj/structure/piano/wrench_act(mob/living/user, obj/item/I)
- default_unfasten_wrench(user, I, 40)
- return TRUE
diff --git a/code/game/sound.dm b/code/game/sound.dm
index c285026d5b..577b058178 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -8,7 +8,7 @@
return
//allocate a channel if necessary now so its the same for everyone
- channel = channel || open_sound_channel()
+ channel = channel || SSsounds.random_available_channel()
// Looping through the player list has the added bonus of working for mobs inside containers
var/sound/S = sound(get_sfx(soundin))
@@ -26,10 +26,10 @@
if(get_dist(M, turf_source) <= maxdistance)
M.playsound_local(turf_source, soundin, vol, vary, frequency, falloff, channel, pressure_affected, S, soundenvwet, soundenvdry)
-/mob/proc/playsound_local(turf/turf_source, soundin, vol as num, vary, frequency, falloff, channel = 0, pressure_affected = TRUE, sound/S, envwet = -10000, envdry = 0, manual_x, manual_y)
+/mob/proc/playsound_local(turf/turf_source, soundin, vol as num, vary, frequency, falloff, channel = 0, pressure_affected = TRUE, sound/S, envwet = -10000, envdry = 0, manual_x, manual_y, distance_multiplier = 1)
if(audiovisual_redirect)
var/turf/T = get_turf(src)
- audiovisual_redirect.playsound_local(turf_source, soundin, vol, vary, frequency, falloff, channel, pressure_affected, S, 0, -1000, turf_source.x - T.x, turf_source.y - T.y)
+ audiovisual_redirect.playsound_local(turf_source, soundin, vol, vary, frequency, falloff, channel, pressure_affected, S, 0, -1000, turf_source.x - T.x, turf_source.y - T.y, distance_multiplier)
if(!client || !can_hear())
return
@@ -37,7 +37,7 @@
S = sound(get_sfx(soundin))
S.wait = 0 //No queue
- S.channel = channel || open_sound_channel()
+ S.channel = channel || SSsounds.random_available_channel()
S.volume = vol
S.environment = 7
@@ -55,6 +55,8 @@
if(!manual_x && !manual_y)
distance = get_dist(T, turf_source)
+ distance *= distance_multiplier
+
S.volume -= max(distance - world.view, 0) * 2 //multiplicative falloff to add on top of natural audio falloff.
if(pressure_affected)
@@ -86,13 +88,13 @@
dx = turf_source.x - T.x
else
dx = manual_x
- S.x = dx
+ S.x = dx * distance_multiplier
var/dz = 0 // Hearing from infront/behind
if(!manual_x)
dz = turf_source.y - T.y
else
dz = manual_y
- S.z = dz
+ S.z = dz * distance_multiplier
// The y value is for above your head, but there is no ceiling in 2d spessmens.
S.y = 1
S.falloff = (falloff ? falloff : FALLOFF_SOUNDS)
@@ -107,15 +109,14 @@
var/mob/M = m
M.playsound_local(M, null, volume, vary, frequency, falloff, channel, pressure_affected, S)
-/proc/open_sound_channel()
- var/static/next_channel = 1 //loop through the available 1024 - (the ones we reserve) channels and pray that its not still being used
- . = ++next_channel
- if(next_channel > CHANNEL_HIGHEST_AVAILABLE)
- next_channel = 1
-
/mob/proc/stop_sound_channel(chan)
SEND_SOUND(src, sound(null, repeat = 0, wait = 0, channel = chan))
+/mob/proc/set_sound_channel_volume(channel, volume)
+ var/sound/S = sound(null, FALSE, FALSE, channel, volume)
+ S.status = SOUND_UPDATE
+ SEND_SOUND(src, S)
+
/client/proc/playtitlemusic(vol = 85)
set waitfor = FALSE
UNTIL(SSticker.login_music) //wait for SSticker init to set the login music
diff --git a/code/modules/cargo/packs/misc.dm b/code/modules/cargo/packs/misc.dm
index 273acb10e2..14828729cd 100644
--- a/code/modules/cargo/packs/misc.dm
+++ b/code/modules/cargo/packs/misc.dm
@@ -152,7 +152,7 @@
/obj/item/instrument/trombone,
/obj/item/instrument/recorder,
/obj/item/instrument/harmonica,
- /obj/structure/piano/unanchored)
+ /obj/structure/musician/piano/unanchored)
crate_type = /obj/structure/closet/crate/wooden
/datum/supply_pack/misc/casinocrate
diff --git a/code/modules/instruments/instrument_data/_instrument_data.dm b/code/modules/instruments/instrument_data/_instrument_data.dm
new file mode 100644
index 0000000000..447bcdffb1
--- /dev/null
+++ b/code/modules/instruments/instrument_data/_instrument_data.dm
@@ -0,0 +1,107 @@
+/proc/path_to_instrument_ids(path)
+ if(!ispath(path))
+ path = text2path(path)
+ if(!ispath(path))
+ return
+ if(!ispath(path, /datum/instrument))
+ return
+ . = list()
+ for(var/i in typesof(path))
+ var/datum/instrument/I = i
+ var/init_id = initial(I.id)
+ if(init_id)
+ . |= init_id
+
+/// Get all non admin_only instruments.
+/proc/get_allowed_instrument_ids()
+ . = list()
+ for(var/id in SSinstruments.instrument_data)
+ var/datum/instrument/I = SSinstruments.instrument_data[id]
+ if(!I.admin_only)
+ . += I.id
+
+/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/abstract_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!
+ var/list/samples
+ /// See __DEFINES/flags/instruments.dm
+ var/instrument_flags = NONE
+ /// For legacy instruments, the path to our notes
+ var/legacy_instrument_path
+ /// For legacy instruments, our file extension
+ var/legacy_instrument_ext
+ /// What songs are using us
+ var/list/datum/song/songs_using = list()
+ /// Don't touch this
+ var/static/HIGHEST_KEY = 127
+ /// Don't touch this x2
+ var/static/LOWEST_KEY = 0
+ /// Oh no - For truly troll instruments.
+ var/admin_only = FALSE
+ /// Volume multiplier. Synthesized instruments are quite loud and I don't like to cut off potential detail via editing. (someone correct me if this isn't a thing)
+ var/volume_multiplier = 1/3
+
+/datum/instrument/New()
+ if(isnull(id))
+ id = "[type]"
+
+/datum/instrument/proc/Initialize()
+ if(CHECK_BITFIELD(instrument_flags, INSTRUMENT_LEGACY | INSTRUMENT_DO_NOT_AUTOSAMPLE))
+ return
+ calculate_samples()
+
+/datum/instrument/proc/ready()
+ if(CHECK_BITFIELD(instrument_flags, INSTRUMENT_LEGACY))
+ return legacy_instrument_path && legacy_instrument_ext
+ else if(CHECK_BITFIELD(instrument_flags, INSTRUMENT_DO_NOT_AUTOSAMPLE))
+ return length(samples)
+ return (length(samples) >= 128)
+
+/datum/instrument/Destroy()
+ SSinstruments.instrument_data -= id
+ for(var/i in songs_using)
+ var/datum/song/S = i
+ S.set_instrument(null)
+ real_samples = null
+ samples = null
+ songs_using = null
+ return ..()
+
+/datum/instrument/proc/calculate_samples()
+ if(!length(real_samples))
+ CRASH("No real samples defined for [id] [type] on calculate_samples() call.")
+ var/list/real_keys = list()
+ samples = list()
+ for(var/key in real_samples)
+ real_keys += text2num(key)
+ sortTim(real_keys, /proc/cmp_numeric_asc, associative = FALSE)
+
+ for(var/i in 1 to (length(real_keys) - 1))
+ var/from_key = real_keys[i]
+ var/to_key = real_keys[i+1]
+ var/sample1 = real_samples[num2text(from_key)]
+ var/sample2 = real_samples[num2text(to_key)]
+ var/pivot = FLOOR((from_key + to_key) / 2, 1) //original code was a round but I replaced it because that's effectively a floor, thanks Baystation! who knows what was intended.
+ for(var/key in from_key to pivot)
+ samples[num2text(key)] = new /datum/instrument_key(sample1, key, key - from_key)
+ for(var/key in (pivot + 1) to to_key)
+ samples[num2text(key)] = new /datum/instrument_key(sample2, key, key - to_key)
+
+ // Fill in 0 to first key and last key to 127
+ var/first_key = real_keys[1]
+ var/last_key = real_keys[length(real_keys)]
+ var/first_sample = real_samples[num2text(first_key)]
+ var/last_sample = real_samples[num2text(last_key)]
+ for(var/key in LOWEST_KEY to (first_key - 1))
+ samples[num2text(key)] = new /datum/instrument_key(first_sample, key, key - first_key)
+ for(var/key in last_key to HIGHEST_KEY)
+ samples[num2text(key)] = new /datum/instrument_key(last_sample, key, key - last_key)
diff --git a/code/modules/instruments/instrument_data/_instrument_key.dm b/code/modules/instruments/instrument_data/_instrument_key.dm
new file mode 100644
index 0000000000..012270ae11
--- /dev/null
+++ b/code/modules/instruments/instrument_data/_instrument_key.dm
@@ -0,0 +1,20 @@
+/datum/instrument_key
+ var/key //1 to 127
+ var/sample //file
+ var/frequency //frequency generated
+ var/deviation //deviation up/down towards pivot from sample (??)
+
+/datum/instrument_key/New(sample = src.sample, key = src.key, deviation = src.deviation, frequency = src.frequency)
+ src.sample = sample
+ src.key = key
+ src.deviation = deviation
+ src.frequency = frequency
+ if(!frequency && deviation)
+ calculate()
+
+/datum/instrument_key/proc/calculate()
+ if(!deviation)
+ CRASH("Invalid calculate call: No deviation or sample in instrument_key")
+ #define KEY_TWELTH (1/12)
+ frequency = 2 ** (KEY_TWELTH * deviation)
+ #undef KEY_TWELTH
diff --git a/code/modules/instruments/instrument_data/brass.dm b/code/modules/instruments/instrument_data/brass.dm
new file mode 100644
index 0000000000..7f8f103831
--- /dev/null
+++ b/code/modules/instruments/instrument_data/brass.dm
@@ -0,0 +1,26 @@
+/datum/instrument/brass
+ name = "Generic brass instrument"
+ category = "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')
+
+/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')
+
+/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')
diff --git a/code/modules/instruments/instrument_data/chromatic_percussion.dm b/code/modules/instruments/instrument_data/chromatic_percussion.dm
new file mode 100644
index 0000000000..cafa9e31ed
--- /dev/null
+++ b/code/modules/instruments/instrument_data/chromatic_percussion.dm
@@ -0,0 +1,31 @@
+/datum/instrument/chromatic
+ name = "Generic chromatic percussion instrument"
+ category = "Chromatic percussion"
+ 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')
+
+/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')
+
+/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')
diff --git a/code/modules/instruments/instrument_data/fun.dm b/code/modules/instruments/instrument_data/fun.dm
new file mode 100644
index 0000000000..44d78b8ccd
--- /dev/null
+++ b/code/modules/instruments/instrument_data/fun.dm
@@ -0,0 +1,25 @@
+/datum/instrument/fun
+ name = "Generic Fun Instrument"
+ category = "Fun"
+ abstract_type = /datum/instrument/fun
+
+/datum/instrument/fun/honk
+ name = "!!HONK!!"
+ id = "honk"
+ real_samples = list("74"='sound/items/bikehorn.ogg') // Cluwne Heaven
+
+/datum/instrument/fun/signal
+ name = "Ping"
+ id = "ping"
+ real_samples = list("79"='sound/machines/ping.ogg')
+
+/datum/instrument/fun/chime
+ name = "Chime"
+ id = "chime"
+ real_samples = list("79"='sound/machines/chime.ogg')
+
+/datum/instrument/fun/nya
+ name = "NYA NYA NYA"
+ id = "nyanyanya"
+ real_samples = list("79" = 'modular_citadel/sound/voice/nya.ogg')
+ admin_only = TRUE
diff --git a/code/modules/instruments/instrument_data/guitar.dm b/code/modules/instruments/instrument_data/guitar.dm
new file mode 100644
index 0000000000..be7cfbe467
--- /dev/null
+++ b/code/modules/instruments/instrument_data/guitar.dm
@@ -0,0 +1,36 @@
+/datum/instrument/guitar
+ name = "Generic guitar-like instrument"
+ category = "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')
+
+/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')
+
+/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')
+
+/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')
diff --git a/code/modules/instruments/instrument_data/hardcoded.dm b/code/modules/instruments/instrument_data/hardcoded.dm
new file mode 100644
index 0000000000..757fea08a8
--- /dev/null
+++ b/code/modules/instruments/instrument_data/hardcoded.dm
@@ -0,0 +1,86 @@
+//THESE ARE HARDCODED INSTRUMENT SAMPLES.
+//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
+ abstract_type = /datum/instrument/hardcoded
+ category = "Non-Synthesized"
+ instrument_flags = INSTRUMENT_LEGACY
+ volume_multiplier = 1 //not as loud as synth'd
+
+/datum/instrument/hardcoded/accordian
+ name = "Accordian"
+ id = "accordian"
+ legacy_instrument_ext = "mid"
+ legacy_instrument_path = "accordian"
+
+/datum/instrument/hardcoded/bikehorn
+ name = "Bike Horn"
+ id = "bikehorn"
+ legacy_instrument_ext = "ogg"
+ legacy_instrument_path = "bikehorn"
+
+/datum/instrument/hardcoded/eguitar
+ name = "Electric Guitar"
+ id = "eguitar"
+ legacy_instrument_ext = "ogg"
+ legacy_instrument_path = "eguitar"
+
+/datum/instrument/hardcoded/glockenspiel
+ name = "Glockenspiel"
+ id = "glockenspiel"
+ legacy_instrument_ext = "mid"
+ legacy_instrument_path = "glockenspiel"
+
+/datum/instrument/hardcoded/guitar
+ name = "Guitar"
+ id = "guitar"
+ legacy_instrument_ext = "ogg"
+ legacy_instrument_path = "guitar"
+
+/datum/instrument/hardcoded/harmonica
+ name = "Harmonica"
+ id = "harmonica"
+ legacy_instrument_ext = "mid"
+ legacy_instrument_path = "harmonica"
+
+/datum/instrument/hardcoded/piano
+ name = "Piano"
+ id = "piano"
+ legacy_instrument_ext = "ogg"
+ legacy_instrument_path = "piano"
+
+/datum/instrument/hardcoded/recorder
+ name = "Recorder"
+ id = "recorder"
+ legacy_instrument_ext = "mid"
+ legacy_instrument_path = "recorder"
+
+/datum/instrument/hardcoded/saxophone
+ name = "Saxophone"
+ id = "saxophone"
+ legacy_instrument_ext = "mid"
+ legacy_instrument_path = "saxophone"
+
+/datum/instrument/hardcoded/trombone
+ name = "Trombone"
+ id = "trombone"
+ legacy_instrument_ext = "mid"
+ legacy_instrument_path = "trombone"
+
+/datum/instrument/hardcoded/violin
+ name = "Violin"
+ id = "violin"
+ legacy_instrument_ext = "mid"
+ legacy_instrument_path = "violin"
+
+/datum/instrument/hardcoded/xylophone
+ name = "Xylophone"
+ id = "xylophone"
+ legacy_instrument_ext = "mid"
+ legacy_instrument_path = "xylophone"
+
+/datum/instrument/hardcoded/banjo
+ name = "Banjo"
+ id = "banjo"
+ legacy_instrument_ext = "ogg"
+ legacy_instrument_path = "banjo"
diff --git a/code/modules/instruments/instrument_data/organ.dm b/code/modules/instruments/instrument_data/organ.dm
new file mode 100644
index 0000000000..25da740998
--- /dev/null
+++ b/code/modules/instruments/instrument_data/organ.dm
@@ -0,0 +1,43 @@
+/datum/instrument/organ
+ name = "Generic organ"
+ category = "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')
+
+/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')
+
+/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')
+
+/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')
+
+/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')
diff --git a/code/modules/instruments/instrument_data/piano.dm b/code/modules/instruments/instrument_data/piano.dm
new file mode 100644
index 0000000000..fdd2f6e938
--- /dev/null
+++ b/code/modules/instruments/instrument_data/piano.dm
@@ -0,0 +1,56 @@
+/datum/instrument/piano
+ name = "Generic piano"
+ category = "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')
+
+/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')
+
+/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')
+
+/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')
+
+/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')
diff --git a/code/modules/instruments/instrument_data/synth_tones.dm b/code/modules/instruments/instrument_data/synth_tones.dm
new file mode 100644
index 0000000000..9ad9250f40
--- /dev/null
+++ b/code/modules/instruments/instrument_data/synth_tones.dm
@@ -0,0 +1,19 @@
+/datum/instrument/tones
+ name = "Ideal tone"
+ category = "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')
+
+/datum/instrument/tones/sine_wave
+ name = "Ideal sine wave"
+ id = "sine"
+ real_samples = list("81"='sound/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')
diff --git a/code/game/objects/items/devices/instruments.dm b/code/modules/instruments/instruments/item.dm
similarity index 78%
rename from code/game/objects/items/devices/instruments.dm
rename to code/modules/instruments/instruments/item.dm
index 8dedc2cb86..fa3e75e431 100644
--- a/code/game/objects/items/devices/instruments.dm
+++ b/code/modules/instruments/instruments/item.dm
@@ -1,83 +1,80 @@
//copy pasta of the space piano, don't hurt me -Pete
/obj/item/instrument
name = "generic instrument"
- resistance_flags = FLAMMABLE
force = 10
max_integrity = 100
+ resistance_flags = FLAMMABLE
icon = 'icons/obj/musician.dmi'
lefthand_file = 'icons/mob/inhands/equipment/instruments_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/instruments_righthand.dmi'
var/datum/song/handheld/song
- var/instrumentId = "generic"
- var/instrumentExt = "mid"
- var/tune_time = 0
-
-/obj/item/instrument/Initialize()
- . = ..()
- song = new(instrumentId, src, instrumentExt)
-
-/obj/item/instrument/Destroy()
- if (tune_time)
- STOP_PROCESSING(SSobj, src)
- qdel(song)
- song = null
- return ..()
-
-/obj/item/instrument/suicide_act(mob/user)
- user.visible_message("[user] begins to play 'Gloomy Sunday'! It looks like [user.p_theyre()] trying to commit suicide!")
- return (BRUTELOSS)
+ var/list/allowed_instrument_ids
+ var/tune_time_left = 0
/obj/item/instrument/Initialize(mapload)
. = ..()
- if(mapload)
- song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded
+ song = new(src, allowed_instrument_ids)
+ allowed_instrument_ids = null //We don't need this clogging memory after it's used.
-/obj/item/instrument/attack_self(mob/user)
- if(!user.IsAdvancedToolUser())
- to_chat(user, "You don't have the dexterity to do this!")
- return 1
- interact(user)
+/obj/item/instrument/Destroy()
+ QDEL_NULL(song)
+ if(tune_time_left)
+ STOP_PROCESSING(SSprocessing, src)
+ return ..()
-/obj/item/instrument/interact(mob/user)
- ui_interact(user)
+/obj/item/instrument/proc/should_stop_playing(mob/user)
+ return !user.CanReach(src) || !user.canUseTopic(src, FALSE, TRUE, FALSE, FALSE)
-/obj/item/instrument/ui_interact(mob/user)
- if(!user)
- return
-
- if(!isliving(user) || user.stat || user.restrained() || user.lying)
- return
-
- user.set_machine(src)
- song.interact(user)
-
-/obj/item/instrument/attackby(obj/item/W, mob/user, params)
- if(istype(W, /obj/item/musicaltuner))
- var/mob/living/carbon/human/H = user
- if (HAS_TRAIT(H, TRAIT_MUSICIAN))
- if (!tune_time)
- H.visible_message("[H] tunes the [src] to perfection!", "You tune the [src] to perfection!")
- tune_time = 300
- START_PROCESSING(SSobj, src)
- else
- to_chat(H, "[src] is already well tuned!")
- else
- to_chat(H, "You have no idea how to use this.")
-
-/obj/item/instrument/process()
- if (tune_time)
+/obj/item/instrument/process(wait)
+ if(is_tuned())
if (song.playing)
for (var/mob/living/M in song.hearing_mobs)
M.dizziness = max(0,M.dizziness-2)
M.jitteriness = max(0,M.jitteriness-2)
M.confused = max(M.confused-1)
SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "goodmusic", /datum/mood_event/goodmusic)
- tune_time--
+ tune_time_left -= wait
else
- if (!tune_time)
- if (song.playing)
- loc.visible_message("[src] starts sounding a little off...")
- STOP_PROCESSING(SSobj, src)
+ tune_time_left = 0
+ if (song.playing)
+ loc.visible_message("[src] starts sounding a little off...")
+ STOP_PROCESSING(SSprocessing, src)
+
+/obj/item/instrument/suicide_act(mob/user)
+ user.visible_message("[user] begins to play 'Gloomy Sunday'! It looks like [user.p_theyre()] trying to commit suicide!")
+ return (BRUTELOSS)
+
+/obj/item/instrument/attack_self(mob/user)
+ if(!user.IsAdvancedToolUser())
+ to_chat(user, "You don't have the dexterity to do this!")
+ return TRUE
+ interact(user)
+
+/obj/item/instrument/attackby(obj/item/W, mob/user, params)
+ if(istype(W, /obj/item/musicaltuner))
+ var/mob/living/carbon/human/H = user
+ if (HAS_TRAIT(H, TRAIT_MUSICIAN))
+ if (!is_tuned())
+ H.visible_message("[H] tunes the [src] to perfection!", "You tune the [src] to perfection!")
+ tune_time_left = 600 SECONDS
+ START_PROCESSING(SSprocessing, src)
+ else
+ to_chat(H, "[src] is already well tuned!")
+ else
+ to_chat(H, "You have no idea how to use this.")
+
+/obj/item/instrument/proc/is_tuned()
+ return tune_time_left > 0
+
+/obj/item/instrument/interact(mob/user)
+ ui_interact(user)
+
+/obj/item/instrument/ui_interact(mob/living/user)
+ if(!isliving(user) || user.stat || user.restrained())
+ return
+
+ user.set_machine(src)
+ song.ui_interact(user)
/obj/item/instrument/violin
name = "space violin"
@@ -85,7 +82,7 @@
icon_state = "violin"
item_state = "violin"
hitsound = "swing_hit"
- instrumentId = "violin"
+ allowed_instrument_ids = "violin"
/obj/item/instrument/violin/golden
name = "golden violin"
@@ -99,40 +96,20 @@
desc = "An advanced electronic synthesizer that can be used as various instruments."
icon_state = "synth"
item_state = "synth"
- instrumentId = "piano"
- instrumentExt = "ogg"
- var/static/list/insTypes = list("accordion" = "mid", "bikehorn" = "ogg", "glockenspiel" = "mid", "banjo" = "ogg", "guitar" = "ogg", "harmonica" = "mid", "piano" = "ogg", "recorder" = "mid", "saxophone" = "mid", "trombone" = "mid", "violin" = "mid", "xylophone" = "mid") //No eguitar you ear-rapey fuckers.
- actions_types = list(/datum/action/item_action/synthswitch)
+ allowed_instrument_ids = "piano"
-/obj/item/instrument/piano_synth/proc/changeInstrument(name = "piano")
- song.instrumentDir = name
- song.instrumentExt = insTypes[name]
-
-/obj/item/instrument/piano_synth/proc/selectInstrument() // Moved here so it can be used by the action and PAI software panel without copypasta
- var/chosen = input("Choose the type of instrument you want to use", "Instrument Selection", song.instrumentDir) as null|anything in insTypes
- if(!insTypes[chosen])
- return
- return changeInstrument(chosen)
-
-/obj/item/instrument/banjo
- name = "banjo"
- desc = "A 'Mura' brand banjo. It's pretty much just a drum with a neck and strings."
- icon_state = "banjo"
- item_state = "banjo"
- instrumentExt = "ogg"
- attack_verb = list("scruggs-styled", "hum-diggitied", "shin-digged", "clawhammered")
- hitsound = 'sound/weapons/banjoslap.ogg'
- instrumentId = "banjo"
+/obj/item/instrument/piano_synth/Initialize()
+ . = ..()
+ song.allowed_instrument_ids = get_allowed_instrument_ids()
/obj/item/instrument/guitar
name = "guitar"
desc = "It's made of wood and has bronze strings."
icon_state = "guitar"
item_state = "guitar"
- instrumentExt = "ogg"
attack_verb = list("played metal on", "serenaded", "crashed", "smashed")
hitsound = 'sound/weapons/stringsmash.ogg'
- instrumentId = "guitar"
+ allowed_instrument_ids = "guitar"
/obj/item/instrument/eguitar
name = "electric guitar"
@@ -142,29 +119,28 @@
force = 12
attack_verb = list("played metal on", "shredded", "crashed", "smashed")
hitsound = 'sound/weapons/stringsmash.ogg'
- instrumentId = "eguitar"
- instrumentExt = "ogg"
+ allowed_instrument_ids = "eguitar"
/obj/item/instrument/glockenspiel
name = "glockenspiel"
desc = "Smooth metal bars perfect for any marching band."
icon_state = "glockenspiel"
item_state = "glockenspiel"
- instrumentId = "glockenspiel"
+ allowed_instrument_ids = "glockenspiel"
/obj/item/instrument/accordion
name = "accordion"
desc = "Pun-Pun not included."
icon_state = "accordion"
item_state = "accordion"
- instrumentId = "accordion"
+ allowed_instrument_ids = "accordion"
/obj/item/instrument/trumpet
name = "trumpet"
desc = "To announce the arrival of the king!"
icon_state = "trumpet"
item_state = "trombone"
- instrumentId = "trombone"
+ allowed_instrument_ids = "trombone"
/obj/item/instrument/trumpet/spectral
name = "spectral trumpet"
@@ -172,7 +148,6 @@
icon_state = "trumpet"
item_state = "trombone"
force = 0
- instrumentId = "trombone"
attack_verb = list("played","jazzed","trumpeted","mourned","dooted","spooked")
/obj/item/instrument/trumpet/spectral/Initialize()
@@ -188,14 +163,13 @@
desc = "This soothing sound will be sure to leave your audience in tears."
icon_state = "saxophone"
item_state = "saxophone"
- instrumentId = "saxophone"
+ allowed_instrument_ids = "saxophone"
/obj/item/instrument/saxophone/spectral
name = "spectral saxophone"
desc = "This spooky sound will be sure to leave mortals in bones."
icon_state = "saxophone"
item_state = "saxophone"
- instrumentId = "saxophone"
force = 0
attack_verb = list("played","jazzed","saxxed","mourned","dooted","spooked")
@@ -204,7 +178,7 @@
AddComponent(/datum/component/spooky)
/obj/item/instrument/saxophone/spectral/attack(mob/living/carbon/C, mob/user)
- playsound (loc, 'sound/instruments/saxophone/En4.mid', 100,1,-1)
+ playsound(loc, 'sound/instruments/saxophone/En4.mid', 100,1,-1)
..()
/obj/item/instrument/trombone
@@ -212,12 +186,11 @@
desc = "How can any pool table ever hope to compete?"
icon_state = "trombone"
item_state = "trombone"
- instrumentId = "trombone"
+ allowed_instrument_ids = "trombone"
/obj/item/instrument/trombone/spectral
name = "spectral trombone"
desc = "A skeleton's favorite instrument. Apply directly on the mortals."
- instrumentId = "trombone"
icon_state = "trombone"
item_state = "trombone"
force = 0
@@ -237,14 +210,14 @@
force = 5
icon_state = "recorder"
item_state = "recorder"
- instrumentId = "recorder"
+ allowed_instrument_ids = "recorder"
/obj/item/instrument/harmonica
name = "harmonica"
desc = "For when you get a bad case of the space blues."
icon_state = "harmonica"
item_state = "harmonica"
- instrumentId = "harmonica"
+ allowed_instrument_ids = "harmonica"
slot_flags = ITEM_SLOT_MASK
force = 5
w_class = WEIGHT_CLASS_SMALL
@@ -271,14 +244,22 @@
lefthand_file = 'icons/mob/inhands/equipment/horns_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/horns_righthand.dmi'
attack_verb = list("beautifully honks")
- instrumentId = "bikehorn"
- instrumentExt = "ogg"
+ allowed_instrument_ids = "bikehorn"
w_class = WEIGHT_CLASS_TINY
force = 0
throw_speed = 3
throw_range = 15
hitsound = 'sound/items/bikehorn.ogg'
+/obj/item/instrument/banjo
+ name = "banjo"
+ desc = "A 'Mura' brand banjo. It's pretty much just a drum with a neck and strings."
+ icon_state = "banjo"
+ item_state = "banjo"
+ attack_verb = list("scruggs-styled", "hum-diggitied", "shin-digged", "clawhammered")
+ hitsound = 'sound/weapons/banjoslap.ogg'
+ allowed_instrument_ids = "banjo"
+
/obj/item/musicaltuner
name = "musical tuner"
desc = "A device for tuning musical instruments both manual and electronic alike."
diff --git a/code/modules/instruments/instruments/stationary.dm b/code/modules/instruments/instruments/stationary.dm
new file mode 100644
index 0000000000..f236927c38
--- /dev/null
+++ b/code/modules/instruments/instruments/stationary.dm
@@ -0,0 +1,52 @@
+/obj/structure/musician
+ name = "Not A Piano"
+ desc = "Something broke, contact coderbus."
+ interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT | INTERACT_ATOM_REQUIRES_DEXTERITY
+ var/can_play_unanchored = FALSE
+ var/list/allowed_instrument_ids
+ var/datum/song/song
+
+/obj/structure/musician/Initialize(mapload)
+ . = ..()
+ song = new(src, allowed_instrument_ids)
+ allowed_instrument_ids = null
+
+/obj/structure/musician/Destroy()
+ QDEL_NULL(song)
+ return ..()
+
+/obj/structure/musician/proc/should_stop_playing(mob/user)
+ if(!(anchored || can_play_unanchored))
+ return TRUE
+ if(!user)
+ return FALSE
+ return !user.canUseTopic(src, FALSE, TRUE, FALSE, FALSE) //can play with TK and while resting because fun.
+
+/obj/structure/musician/ui_interact(mob/user)
+ . = ..()
+ song.ui_interact(user)
+
+/obj/structure/musician/wrench_act(mob/living/user, obj/item/I)
+ default_unfasten_wrench(user, I, 40)
+ return TRUE
+
+/obj/structure/musician/piano
+ name = "space minimoog"
+ icon = 'icons/obj/musician.dmi'
+ icon_state = "minimoog"
+ anchored = TRUE
+ density = TRUE
+
+/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"
diff --git a/code/modules/instruments/songs/_song.dm b/code/modules/instruments/songs/_song.dm
new file mode 100644
index 0000000000..517ee5e49a
--- /dev/null
+++ b/code/modules/instruments/songs/_song.dm
@@ -0,0 +1,301 @@
+#define MUSICIAN_HEARCHECK_MINDELAY 4
+#define MUSIC_MAXLINES 1000
+#define MUSIC_MAXLINECHARS 300
+
+/datum/song
+ /// Name of the song
+ var/name = "Untitled"
+
+ /// The atom we're attached to/playing from
+ var/atom/parent
+
+ /// Our song lines
+ var/list/lines
+
+ /// delay between notes in deciseconds
+ var/tempo = 5
+
+ /// 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
+ var/max_repeats = 10
+
+ /// Our volume
+ var/volume = 75
+ /// Max volume
+ var/max_volume = 75
+ /// Min volume - This is so someone doesn't decide it's funny to set it to 0 and play invisible songs.
+ var/min_volume = 1
+
+ /// What instruments our built in picker can use. The picker won't show unless this is longer than one.
+ var/list/allowed_instrument_ids = list("r3grand")
+
+ //////////// Cached instrument variables /////////////
+ /// Instrument we are currently using
+ var/datum/instrument/using_instrument
+ /// Cached legacy ext for legacy instruments
+ var/cached_legacy_ext
+ /// Cached legacy dir for legacy instruments
+ var/cached_legacy_dir
+ /// Cached list of samples, referenced directly from the instrument for synthesized instruments
+ var/list/cached_samples
+ /// Are we operating in legacy mode (so if the instrument is a legacy instrument)
+ var/legacy = FALSE
+ //////////////////////////////////////////////////////
+
+ /////////////////// Playing variables ////////////////
+ /**
+ * Only used in synthesized playback - The chords we compiled. Non assoc list of lists:
+ * list(list(key1, key2, key3..., tempo_divisor), list(key1, key2..., tempo_divisor), ...)
+ * 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.
+ */
+ var/list/compiled_chords
+ /// Channel as text = current volume
+ 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()
+ //////////////////////////////////////////////////////
+
+ /// Last world.time we checked for who can hear us
+ var/last_hearcheck = 0
+ /// The list of mobs that can hear us
+ 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)
+ var/debug_mode = FALSE
+ /// Last time we processed decay
+ var/last_process_decay
+ /// Max sound channels to occupy
+ var/max_sound_channels = CHANNELS_PER_INSTRUMENT
+ /// Current channels, so we can save a length() call.
+ var/using_sound_channels = 0
+ /// Last channel to play. text.
+ var/last_channel_played
+ /// Should we not decay our last played note?
+ var/full_sustain_held_note = TRUE
+
+ /////////////////////// DO NOT TOUCH THESE ///////////////////
+ var/octave_min = INSTRUMENT_MIN_OCTAVE
+ var/octave_max = INSTRUMENT_MAX_OCTAVE
+ var/key_min = INSTRUMENT_MIN_KEY
+ var/key_max = INSTRUMENT_MAX_KEY
+ var/static/list/note_offset_lookup = list(9, 11, 0, 2, 4, 5, 7)
+ var/static/list/accent_lookup = list("b" = -1, "s" = 1, "#" = 1, "n" = 0)
+ //////////////////////////////////////////////////////////////
+
+ ///////////// !!FUN!! - Only works in synthesized mode! /////////////////
+ /// Note numbers to shift.
+ 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
+ var/sustain_dropoff_volume = 0
+ /// Total duration of linear sustain for 100 volume note to get to SUSTAIN_DROPOFF
+ var/sustain_linear_duration = 5
+ /// Exponential sustain dropoff rate per decisecond
+ var/sustain_exponential_dropoff = 1.4
+ ////////// DO NOT DIRECTLY SET THESE!
+ /// Do not directly set, use update_sustain()
+ var/cached_linear_dropoff = 10
+ /// Do not directly set, use update_sustain()
+ var/cached_exponential_dropoff = 1.045
+ /////////////////////////////////////////////////////////////////////////
+
+/datum/song/New(atom/parent, list/instrument_ids)
+ SSinstruments.on_song_new(src)
+ lines = list()
+ tempo = sanitize_tempo(tempo)
+ src.parent = parent
+ if(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()
+ volume = clamp(volume, min_volume, max_volume)
+ update_sustain()
+
+/datum/song/Destroy()
+ stop_playing()
+ SSinstruments.on_song_del(src)
+ lines = null
+ using_instrument = null
+ allowed_instrument_ids = null
+ parent = null
+ return ..()
+
+/datum/song/proc/do_hearcheck()
+ last_hearcheck = world.time
+ var/list/old = hearing_mobs.Copy()
+ hearing_mobs.len = 0
+ var/turf/source = get_turf(parent)
+ for(var/mob/M in get_hearers_in_view(15, source))
+ if(!(M?.client?.prefs?.toggles & SOUND_INSTRUMENTS))
+ continue
+ hearing_mobs[M] = get_dist(M, source)
+ var/list/exited = old - hearing_mobs
+ for(var/i in exited)
+ terminate_sound_mob(i)
+
+/// I can either be a datum, id, or path (if the instrument has no id).
+/datum/song/proc/set_instrument(datum/instrument/I)
+ if(using_instrument)
+ using_instrument.songs_using -= src
+ using_instrument = null
+ cached_samples = null
+ cached_legacy_ext = null
+ cached_legacy_dir = null
+ legacy = null
+ if(istext(I) || ispath(I))
+ I = SSinstruments.instrument_data[I]
+ if(istype(I))
+ using_instrument = I
+ I.songs_using += src
+ var/instrument_legacy = CHECK_BITFIELD(I.instrument_flags, INSTRUMENT_LEGACY)
+ if(instrument_legacy)
+ cached_legacy_ext = I.legacy_instrument_ext
+ cached_legacy_dir = I.legacy_instrument_path
+ legacy = TRUE
+ else
+ cached_samples = I.samples
+ legacy = FALSE
+
+/// THIS IS A BLOCKING CALL.
+/datum/song/proc/start_playing(mob/user)
+ if(playing)
+ return
+ if(!using_instrument?.ready())
+ to_chat(user, "An error has occured with [src]. Please reset the instrument.")
+ return
+ playing = TRUE
+ updateDialog()
+ //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.
+ last_process_decay = world.time
+ START_PROCESSING(SSinstruments, src)
+ . = do_play_lines(user)
+ stop_playing()
+
+/datum/song/proc/stop_playing()
+ if(!playing)
+ return
+ playing = FALSE
+ if(!debug_mode)
+ compiled_chords = null
+ STOP_PROCESSING(SSinstruments, src)
+ terminate_all_sounds(TRUE)
+ hearing_mobs.len = 0
+ updateDialog()
+
+/// THIS IS A BLOCKING CALL.
+/datum/song/proc/do_play_lines(user)
+ if(!playing)
+ return
+ do_hearcheck()
+ if(legacy)
+ do_play_lines_legacy(user)
+ else
+ do_play_lines_synthesized(user)
+
+/datum/song/proc/should_stop_playing(mob/user)
+ return QDELETED(parent) || !using_instrument || !playing
+
+/datum/song/proc/sanitize_tempo(new_tempo)
+ new_tempo = abs(new_tempo)
+ return CLAMP(round(new_tempo, world.tick_lag), world.tick_lag, 5 SECONDS)
+
+/datum/song/proc/get_bpm()
+ return 600 / tempo
+
+/datum/song/proc/set_bpm(bpm)
+ tempo = sanitize_tempo(600 / bpm)
+
+/// Updates the window for our user. Override in subtypes.
+/datum/song/proc/updateDialog(mob/user)
+ ui_interact(user)
+
+/datum/song/process(wait)
+ if(!playing)
+ return PROCESS_KILL
+ var/delay = world.time - last_process_decay
+ process_decay(delay)
+ last_process_decay = world.time
+
+/datum/song/proc/update_sustain()
+ // Exponential is easy
+ cached_exponential_dropoff = sustain_exponential_dropoff
+ // Linear, not so much, since it's a target duration from 100 volume rather than an exponential rate.
+ var/target_duration = sustain_linear_duration
+ var/volume_diff = max(0, volume - sustain_dropoff_volume)
+ var/volume_decrease_per_decisecond = volume_diff / target_duration
+ cached_linear_dropoff = volume_decrease_per_decisecond
+
+/datum/song/proc/set_volume(volume)
+ src.volume = CLAMP(volume, max(0, min_volume), min(100, max_volume))
+ update_sustain()
+ updateDialog()
+
+/datum/song/proc/set_dropoff_volume(volume)
+ sustain_dropoff_volume = CLAMP(volume, INSTRUMENT_MIN_SUSTAIN_DROPOFF, 100)
+ update_sustain()
+ updateDialog()
+
+/datum/song/proc/set_exponential_drop_rate(drop)
+ sustain_exponential_dropoff = CLAMP(drop, INSTRUMENT_EXP_FALLOFF_MIN, INSTRUMENT_EXP_FALLOFF_MAX)
+ update_sustain()
+ updateDialog()
+
+/datum/song/proc/set_linear_falloff_duration(duration)
+ sustain_linear_duration = CLAMP(duration, 0.1, INSTRUMENT_MAX_TOTAL_SUSTAIN)
+ update_sustain()
+ updateDialog()
+
+/datum/song/vv_edit_var(var_name, var_value)
+ . = ..()
+ if(.)
+ switch(var_name)
+ if(NAMEOF(src, volume))
+ set_volume(var_value)
+ if(NAMEOF(src, sustain_dropoff_volume))
+ set_dropoff_volume(var_value)
+ if(NAMEOF(src, sustain_exponential_dropoff))
+ set_exponential_drop_rate(var_value)
+ if(NAMEOF(src, sustain_linear_duration))
+ set_linear_falloff_duration(var_value)
+
+// subtype for handheld instruments, like violin
+/datum/song/handheld
+
+/datum/song/handheld/updateDialog(mob/user)
+ parent.ui_interact(user || usr)
+
+/datum/song/handheld/should_stop_playing(mob/user)
+ . = ..()
+ if(.)
+ return TRUE
+ var/obj/item/instrument/I = parent
+ return I.should_stop_playing(user)
+
+// subtype for stationary structures, like pianos
+/datum/song/stationary
+
+/datum/song/stationary/updateDialog(mob/user)
+ parent.ui_interact(user || usr)
+
+/datum/song/stationary/should_stop_playing(mob/user)
+ . = ..()
+ if(.)
+ return TRUE
+ var/obj/structure/musician/M = parent
+ return M.should_stop_playing(user)
diff --git a/code/modules/instruments/songs/editor.dm b/code/modules/instruments/songs/editor.dm
new file mode 100644
index 0000000000..873ff0e1a7
--- /dev/null
+++ b/code/modules/instruments/songs/editor.dm
@@ -0,0 +1,246 @@
+/datum/song/proc/instrument_status_ui()
+ . = list()
+ . += ""
+ . += "
Current instrument: "
+ if(!using_instrument)
+ . += "
No instrument loaded!"
+ else
+ . += "[using_instrument.name]
"
+ . += "Playback Settings:
"
+ if(can_noteshift)
+ . += "
Note Shift/Note Transpose: [note_shift] keys / [round(note_shift / 12, 0.01)] octaves
"
+ var/smt
+ var/modetext = ""
+ switch(sustain_mode)
+ if(SUSTAIN_LINEAR)
+ smt = "Linear"
+ modetext = "
Linear Sustain Duration: [sustain_linear_duration / 10] seconds
"
+ if(SUSTAIN_EXPONENTIAL)
+ smt = "Exponential"
+ modetext = "
Exponential Falloff Factor: [sustain_exponential_dropoff]% per decisecond
"
+ . += "
Sustain Mode: [smt]
"
+ . += modetext
+ . += using_instrument?.ready()? "Status:
Ready" : "Status:
!Instrument Definition Error!"
+ . += "Instrument Type: [legacy? "Legacy" : "Synthesized"]
"
+ . += "
Volume: [volume]
"
+ . += "
Volume Dropoff Threshold: [sustain_dropoff_volume]
"
+ . += "
Sustain indefinitely last held note: [full_sustain_held_note? "Enabled" : "Disabled"].
"
+ . += "
"
+
+/datum/song/ui_interact(mob/user)
+ var/list/dat = list()
+
+ dat += instrument_status_ui()
+
+ if(lines.len > 0)
+ dat += "Playback
"
+ if(!playing)
+ dat += "Play Stop
"
+ dat += "Repeat Song: "
+ dat += repeat > 0 ? "--" : "--"
+ dat += " [repeat] times "
+ dat += repeat < max_repeats ? "++" : "++"
+ dat += "
"
+ else
+ dat += "Play Stop
"
+ dat += "Repeats left: [repeat]
"
+ if(!editing)
+ dat += "
Show Editor
"
+ else
+ dat += "Editing
"
+ dat += "Hide Editor"
+ dat += " Start a New Song"
+ dat += " Import a Song
"
+ var/bpm = round(600 / tempo)
+ dat += "Tempo: - [bpm] BPM +
"
+ var/linecount = 0
+ for(var/line in lines)
+ linecount += 1
+ dat += "Line [linecount]: Edit X [line]
"
+ dat += "Add Line
"
+ if(help)
+ dat += "Hide Help
"
+ dat += {"
+ Lines are a series of chords, separated by commas (,), each with notes separated by hyphens (-).
+ Every note in a chord will play together, with chord timed by the tempo.
+
+ Notes are played by the names of the note, and optionally, the accidental, and/or the octave number.
+ By default, every note is natural and in octave 3. Defining otherwise is remembered for each note.
+ Example: C,D,E,F,G,A,B will play a C major scale.
+ After a note has an accidental placed, it will be remembered: C,C4,C,C3 is C3,C4,C4,C3
+ Chords can be played simply by seperating each note with a hyphon: A-C#,Cn-E,E-G#,Gn-B
+ A pause may be denoted by an empty chord: C,E,,C,G
+ To make a chord be a different time, end it with /x, where the chord length will be length
+ defined by tempo / x: C,G/2,E/4
+ Combined, an example is: E-E4/4,F#/2,G#/8,B/8,E3-E4/4
+
+ Lines may be up to [MUSIC_MAXLINECHARS] characters.
+ A song may only contain up to [MUSIC_MAXLINES] lines.
+ "}
+ else
+ dat += "Show Help
"
+
+ var/datum/browser/popup = new(user, "instrument", parent?.name || "instrument", 700, 500)
+ popup.set_content(dat.Join(""))
+ popup.set_title_image(user.browse_rsc_icon(parent.icon, parent.icon_state))
+ popup.open()
+
+/datum/song/proc/ParseSong(text)
+ set waitfor = FALSE
+ //split into lines
+ lines = splittext(text, "\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))
+ lines.Cut(1, 2)
+ else
+ tempo = sanitize_tempo(5) // default 120 BPM
+ if(lines.len > MUSIC_MAXLINES)
+ to_chat(usr, "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!")
+ lines.Remove(l)
+ else
+ linenum++
+ updateDialog(usr) // make sure updates when complete
+
+/datum/song/Topic(href, href_list)
+ if(!usr.canUseTopic(parent, TRUE, FALSE, FALSE, FALSE))
+ 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 = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message)
+ if(!in_range(parent, usr))
+ return
+
+ if(length_char(t) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
+ var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no")
+ if(cont == "no")
+ 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/start_playing, usr)
+
+ else if(href_list["newline"])
+ var/newline = html_encode(input("Enter your line: ", parent.name) as text|null)
+ 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 = stripped_input(usr, "Enter your line: ", parent.name, lines[num], MUSIC_MAXLINECHARS)
+ 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 = input(usr, "Set linear sustain duration in seconds", "Linear Sustain Duration") as null|num
+ if(!isnull(amount))
+ set_linear_falloff_duration(round(amount * 10, world.tick_lag))
+
+ else if(href_list["setexpfalloff"])
+ var/amount = input(usr, "Set exponential sustain factor", "Exponential sustain factor") as null|num
+ if(!isnull(amount))
+ set_exponential_drop_rate(round(amount, 0.00001))
+
+ else if(href_list["setvolume"])
+ var/amount = input(usr, "Set volume", "Volume") as null|num
+ if(!isnull(amount))
+ set_volume(round(amount, 1))
+
+ else if(href_list["setdropoffvolume"])
+ var/amount = input(usr, "Set dropoff threshold", "Dropoff Threshold Volume") as null|num
+ 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 = input(usr, "Select Category", "Instrument Category") as null|anything in categories
+ if(!cat)
+ return
+ var/list/instruments = categories[cat]
+ var/choice = input(usr, "Select Instrument", "Instrument Selection") as null|anything in instruments
+ if(!choice)
+ return
+ choice = instruments[choice] //get id
+ if(choice)
+ set_instrument(choice)
+
+ else if(href_list["setnoteshift"])
+ var/amount = input(usr, "Set note shift", "Note Shift") as null|num
+ if(!isnull(amount))
+ note_shift = CLAMP(amount, note_shift_min, note_shift_max)
+
+ else if(href_list["setsustainmode"])
+ var/choice = input(usr, "Choose a sustain mode", "Sustain Mode") as null|anything in 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()
diff --git a/code/modules/instruments/songs/play_legacy.dm b/code/modules/instruments/songs/play_legacy.dm
new file mode 100644
index 0000000000..fa64656ebc
--- /dev/null
+++ b/code/modules/instruments/songs/play_legacy.dm
@@ -0,0 +1,82 @@
+/// Playing legacy instruments - None of the "advanced" like sound reservations and decay are invoked.
+/datum/song/proc/do_play_lines_legacy(mob/user)
+ while(repeat >= 0)
+ var/cur_oct[7]
+ var/cur_acc[7]
+ for(var/i = 1 to 7)
+ cur_oct[i] = 3
+ cur_acc[i] = "n"
+
+ for(var/line in lines)
+ for(var/beat in splittext(lowertext(line), ","))
+ if(should_stop_playing(user))
+ return
+ var/list/notes = splittext(beat, "/")
+ if(length(notes)) //because some jack-butts are going to do ,,,, to symbolize 3 rests instead of something reasonable like ,/1.
+ for(var/note in splittext(notes[1], "-"))
+ if(length(note) == 0)
+ continue
+ 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
+ if(acc == "b" && (note == 3 || note == 6)) // C or F
+ if(note == 3)
+ oct--
+ note--
+ acc = "n"
+ else if(acc == "#" && (note == 2 || note == 5)) // B or E
+ if(note == 2)
+ oct++
+ note++
+ acc = "n"
+ else if(acc == "#" && (note == 7)) //G#
+ note = 1
+ acc = "b"
+ else if(acc == "#") // mass convert all sharps to flats, octave jump already handled
+ acc = "b"
+ note++
+
+ // check octave, C is allowed to go to 9
+ if(oct < 1 || (note == 3 ? oct > 9 : oct > 8))
+ return
+
+ // now generate name
+ var/soundfile = "sound/instruments/[cached_legacy_dir]/[ascii2text(note+64)][acc][oct].[cached_legacy_ext]"
+ soundfile = file(soundfile)
+ // make sure the note exists
+ if(!fexists(soundfile))
+ return
+ // and play
+ var/turf/source = get_turf(parent)
+ if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
+ do_hearcheck()
+ var/sound/music_played = sound(soundfile)
+ for(var/i in hearing_mobs)
+ var/mob/M = i
+ M.playsound_local(source, null, volume * using_instrument.volume_multiplier, falloff = 5, S = music_played)
+ // Could do environment and echo later but not for now
diff --git a/code/modules/instruments/songs/play_synthesized.dm b/code/modules/instruments/songs/play_synthesized.dm
new file mode 100644
index 0000000000..4da9c70e68
--- /dev/null
+++ b/code/modules/instruments/songs/play_synthesized.dm
@@ -0,0 +1,135 @@
+/datum/song/proc/do_play_lines_synthesized(mob/user)
+ compile_lines()
+ while(repeat >= 0)
+ if(should_stop_playing(user))
+ 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, "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.")
+ 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))
+ return
+ var/list/lines = src.lines //cache for hyepr speed!
+ compiled_chords = list()
+ var/list/octaves = list(3, 3, 3, 3, 3, 3, 3)
+ var/list/accents = list("n", "n", "n", "n", "n", "n", "n")
+ for(var/line in lines)
+ var/list/chords = splittext(lowertext(line), ",")
+ for(var/chord in chords)
+ var/list/compiled_chord = list()
+ var/tempodiv = 1
+ var/list/notes_tempodiv = splittext(chord, "/")
+ var/len = length(notes_tempodiv)
+ if(len >= 2)
+ tempodiv = text2num(notes_tempodiv[2])
+ if(len) //some dunkass is going to do ,,,, to make 3 rests instead of ,/1 because there's no standardization so let's be prepared for that.
+ var/list/notes = splittext(notes_tempodiv[1], "-")
+ for(var/note in notes)
+ if(length(note) == 0)
+ continue
+ // 1-7, A-G
+ var/key = text2ascii(note) - 96
+ if((key < 1) || (key > 7))
+ continue
+ for(var/i in 2 to length(note))
+ var/oct_acc = copytext(note, i, i + 1)
+ var/num = text2num(oct_acc)
+ if(!num) //it's an accidental
+ accents[key] = oct_acc //if they misspelled it/fucked up that's on them lmao, no safety checks.
+ else //octave
+ octaves[key] = CLAMP(num, octave_min, octave_max)
+ compiled_chord += CLAMP((note_offset_lookup[key] + octaves[key] * 12 + accent_lookup[accents[key]]), key_min, key_max)
+ compiled_chord += tempodiv //this goes last
+ if(length(compiled_chord))
+ compiled_chords[++compiled_chords.len] = compiled_chord
+ CHECK_TICK
+ return compiled_chords
+
+/datum/song/proc/playkey_synth(key)
+ if(can_noteshift)
+ key = clamp(key + note_shift, key_min, key_max)
+ if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
+ do_hearcheck()
+ var/datum/instrument_key/K = using_instrument.samples[num2text(key)] //See how fucking easy it is to make a number text? You don't need a complicated 9 line proc!
+ //Should probably add channel limiters here at some point but I don't care right now.
+ var/channel = pop_channel()
+ if(isnull(channel))
+ return FALSE
+ . = TRUE
+ var/sound/copy = sound(K.sample)
+ var/volume = src.volume * using_instrument.volume_multiplier
+ copy.frequency = K.frequency
+ copy.volume = volume
+ var/channel_text = num2text(channel)
+ channels_playing[channel_text] = volume
+ last_channel_played = channel_text
+ for(var/i in hearing_mobs)
+ var/mob/M = i
+ M.playsound_local(get_turf(parent), null, volume, FALSE, K.frequency, INSTRUMENT_DISTANCE_NO_FALLOFF, channel, null, copy, distance_multiplier = INSTRUMENT_DISTANCE_FALLOFF_BUFF)
+ // Could do environment and echo later but not for now
+
+/datum/song/proc/terminate_all_sounds(clear_channels = TRUE)
+ for(var/i in hearing_mobs)
+ terminate_sound_mob(i)
+ if(clear_channels)
+ channels_playing.len = 0
+ channels_idle.len = 0
+ SSinstruments.current_instrument_channels -= using_sound_channels
+ using_sound_channels = 0
+ SSsounds.free_datum_channels(src)
+
+/datum/song/proc/terminate_sound_mob(mob/M)
+ for(var/channel in channels_playing)
+ M.stop_sound_channel(text2num(channel))
+
+/datum/song/proc/pop_channel()
+ if(length(channels_idle)) //just pop one off of here if we have one available
+ . = text2num(channels_idle[1])
+ channels_idle.Cut(1,2)
+ return
+ if(using_sound_channels >= max_sound_channels)
+ return
+ . = SSinstruments.reserve_instrument_channel(src)
+ if(!isnull(.))
+ using_sound_channels++
+
+/datum/song/proc/process_decay(wait_ds)
+ var/linear_dropoff = cached_linear_dropoff * wait_ds
+ var/exponential_dropoff = cached_exponential_dropoff ** wait_ds
+ for(var/channel in channels_playing)
+ if(full_sustain_held_note && (channel == last_channel_played))
+ continue
+ var/current_volume = channels_playing[channel]
+ switch(sustain_mode)
+ if(SUSTAIN_LINEAR)
+ current_volume -= linear_dropoff
+ if(SUSTAIN_EXPONENTIAL)
+ current_volume /= exponential_dropoff
+ channels_playing[channel] = current_volume
+ var/dead = current_volume <= sustain_dropoff_volume
+ var/channelnumber = text2num(channel)
+ if(dead)
+ channels_playing -= channel
+ channels_idle += channel
+ for(var/i in hearing_mobs)
+ var/mob/M = i
+ M.stop_sound_channel(channelnumber)
+ else
+ for(var/i in hearing_mobs)
+ var/mob/M = i
+ M.set_sound_channel_volume(channelnumber, current_volume)
diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm
index 810dd778de..892cc3f13b 100644
--- a/code/modules/mob/living/silicon/pai/software.dm
+++ b/code/modules/mob/living/silicon/pai/software.dm
@@ -283,9 +283,7 @@
T.visible_message("A port on [src] opens to reveal [cable], which promptly falls to the floor.", "You hear the soft click of something light and hard falling to the ground.")
if("loudness")
if(subscreen == 1) // Open Instrument
- internal_instrument.interact(src)
- if(subscreen == 2) // Change Instrument type
- internal_instrument.selectInstrument()
+ internal_instrument.ui_interact(src)
//updateUsrDialog() We only need to account for the single mob this is intended for, and he will *always* be able to call this window
paiInterface() // So we'll just call the update directly rather than doing some default checks
diff --git a/sound/instruments/synthesis_samples/brass/crisis_brass/c2.ogg b/sound/instruments/synthesis_samples/brass/crisis_brass/c2.ogg
new file mode 100644
index 0000000000..9808431b25
Binary files /dev/null and b/sound/instruments/synthesis_samples/brass/crisis_brass/c2.ogg differ
diff --git a/sound/instruments/synthesis_samples/brass/crisis_brass/c3.ogg b/sound/instruments/synthesis_samples/brass/crisis_brass/c3.ogg
new file mode 100644
index 0000000000..4118cff81c
Binary files /dev/null and b/sound/instruments/synthesis_samples/brass/crisis_brass/c3.ogg differ
diff --git a/sound/instruments/synthesis_samples/brass/crisis_brass/c4.ogg b/sound/instruments/synthesis_samples/brass/crisis_brass/c4.ogg
new file mode 100644
index 0000000000..ae9e1361d2
Binary files /dev/null and b/sound/instruments/synthesis_samples/brass/crisis_brass/c4.ogg differ
diff --git a/sound/instruments/synthesis_samples/brass/crisis_brass/c5.ogg b/sound/instruments/synthesis_samples/brass/crisis_brass/c5.ogg
new file mode 100644
index 0000000000..f19ddd9300
Binary files /dev/null and b/sound/instruments/synthesis_samples/brass/crisis_brass/c5.ogg differ
diff --git a/sound/instruments/synthesis_samples/brass/crisis_trombone/C2.ogg b/sound/instruments/synthesis_samples/brass/crisis_trombone/C2.ogg
new file mode 100644
index 0000000000..d2c872d5a3
Binary files /dev/null and b/sound/instruments/synthesis_samples/brass/crisis_trombone/C2.ogg differ
diff --git a/sound/instruments/synthesis_samples/brass/crisis_trombone/C3.ogg b/sound/instruments/synthesis_samples/brass/crisis_trombone/C3.ogg
new file mode 100644
index 0000000000..e1e3fc8635
Binary files /dev/null and b/sound/instruments/synthesis_samples/brass/crisis_trombone/C3.ogg differ
diff --git a/sound/instruments/synthesis_samples/brass/crisis_trombone/C4.ogg b/sound/instruments/synthesis_samples/brass/crisis_trombone/C4.ogg
new file mode 100644
index 0000000000..42c4aa755d
Binary files /dev/null and b/sound/instruments/synthesis_samples/brass/crisis_trombone/C4.ogg differ
diff --git a/sound/instruments/synthesis_samples/brass/crisis_trombone/C5.ogg b/sound/instruments/synthesis_samples/brass/crisis_trombone/C5.ogg
new file mode 100644
index 0000000000..762d2868d0
Binary files /dev/null and b/sound/instruments/synthesis_samples/brass/crisis_trombone/C5.ogg differ
diff --git a/sound/instruments/synthesis_samples/brass/crisis_trumpet/C4.ogg b/sound/instruments/synthesis_samples/brass/crisis_trumpet/C4.ogg
new file mode 100644
index 0000000000..baf71063af
Binary files /dev/null and b/sound/instruments/synthesis_samples/brass/crisis_trumpet/C4.ogg differ
diff --git a/sound/instruments/synthesis_samples/brass/crisis_trumpet/C5.ogg b/sound/instruments/synthesis_samples/brass/crisis_trumpet/C5.ogg
new file mode 100644
index 0000000000..92b4a11018
Binary files /dev/null and b/sound/instruments/synthesis_samples/brass/crisis_trumpet/C5.ogg differ
diff --git a/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C2.ogg b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C2.ogg
new file mode 100644
index 0000000000..5a50a5091c
Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C2.ogg differ
diff --git a/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C3.ogg b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C3.ogg
new file mode 100644
index 0000000000..f086938260
Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C3.ogg differ
diff --git a/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C4.ogg b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C4.ogg
new file mode 100644
index 0000000000..1246bd5341
Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C4.ogg differ
diff --git a/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C5.ogg b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C5.ogg
new file mode 100644
index 0000000000..73680ce627
Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C5.ogg differ
diff --git a/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C6.ogg b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C6.ogg
new file mode 100644
index 0000000000..72841fb189
Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C6.ogg differ
diff --git a/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C7.ogg b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C7.ogg
new file mode 100644
index 0000000000..47797e4c00
Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C7.ogg differ
diff --git a/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C8.ogg b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C8.ogg
new file mode 100644
index 0000000000..ea34703160
Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C8.ogg differ
diff --git a/sound/instruments/synthesis_samples/chromatic/sgmbox/c2.ogg b/sound/instruments/synthesis_samples/chromatic/sgmbox/c2.ogg
new file mode 100644
index 0000000000..4b96dd3dd3
Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/sgmbox/c2.ogg differ
diff --git a/sound/instruments/synthesis_samples/chromatic/sgmbox/c3.ogg b/sound/instruments/synthesis_samples/chromatic/sgmbox/c3.ogg
new file mode 100644
index 0000000000..25daf5372e
Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/sgmbox/c3.ogg differ
diff --git a/sound/instruments/synthesis_samples/chromatic/sgmbox/c4.ogg b/sound/instruments/synthesis_samples/chromatic/sgmbox/c4.ogg
new file mode 100644
index 0000000000..9b989404e7
Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/sgmbox/c4.ogg differ
diff --git a/sound/instruments/synthesis_samples/chromatic/sgmbox/c5.ogg b/sound/instruments/synthesis_samples/chromatic/sgmbox/c5.ogg
new file mode 100644
index 0000000000..7fa99d9278
Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/sgmbox/c5.ogg differ
diff --git a/sound/instruments/synthesis_samples/chromatic/vibraphone1/c2.ogg b/sound/instruments/synthesis_samples/chromatic/vibraphone1/c2.ogg
new file mode 100644
index 0000000000..64578a8fe1
Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/vibraphone1/c2.ogg differ
diff --git a/sound/instruments/synthesis_samples/chromatic/vibraphone1/c3.ogg b/sound/instruments/synthesis_samples/chromatic/vibraphone1/c3.ogg
new file mode 100644
index 0000000000..d97230ce1f
Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/vibraphone1/c3.ogg differ
diff --git a/sound/instruments/synthesis_samples/chromatic/vibraphone1/c4.ogg b/sound/instruments/synthesis_samples/chromatic/vibraphone1/c4.ogg
new file mode 100644
index 0000000000..eb6f80654a
Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/vibraphone1/c4.ogg differ
diff --git a/sound/instruments/synthesis_samples/chromatic/vibraphone1/c5.ogg b/sound/instruments/synthesis_samples/chromatic/vibraphone1/c5.ogg
new file mode 100644
index 0000000000..8f25ee2747
Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/vibraphone1/c5.ogg differ
diff --git a/sound/instruments/synthesis_samples/guitar/crisis_clean/C2.ogg b/sound/instruments/synthesis_samples/guitar/crisis_clean/C2.ogg
new file mode 100644
index 0000000000..17bea8122c
Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_clean/C2.ogg differ
diff --git a/sound/instruments/synthesis_samples/guitar/crisis_clean/C3.ogg b/sound/instruments/synthesis_samples/guitar/crisis_clean/C3.ogg
new file mode 100644
index 0000000000..6dc0d8ad38
Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_clean/C3.ogg differ
diff --git a/sound/instruments/synthesis_samples/guitar/crisis_clean/C4.ogg b/sound/instruments/synthesis_samples/guitar/crisis_clean/C4.ogg
new file mode 100644
index 0000000000..fbbcacb372
Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_clean/C4.ogg differ
diff --git a/sound/instruments/synthesis_samples/guitar/crisis_clean/C5.ogg b/sound/instruments/synthesis_samples/guitar/crisis_clean/C5.ogg
new file mode 100644
index 0000000000..f9bb3f6e92
Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_clean/C5.ogg differ
diff --git a/sound/instruments/synthesis_samples/guitar/crisis_muted/C2.ogg b/sound/instruments/synthesis_samples/guitar/crisis_muted/C2.ogg
new file mode 100644
index 0000000000..777a38664f
Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_muted/C2.ogg differ
diff --git a/sound/instruments/synthesis_samples/guitar/crisis_muted/C3.ogg b/sound/instruments/synthesis_samples/guitar/crisis_muted/C3.ogg
new file mode 100644
index 0000000000..c14aa5ec93
Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_muted/C3.ogg differ
diff --git a/sound/instruments/synthesis_samples/guitar/crisis_muted/C4.ogg b/sound/instruments/synthesis_samples/guitar/crisis_muted/C4.ogg
new file mode 100644
index 0000000000..b2a457b496
Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_muted/C4.ogg differ
diff --git a/sound/instruments/synthesis_samples/guitar/crisis_muted/C5.ogg b/sound/instruments/synthesis_samples/guitar/crisis_muted/C5.ogg
new file mode 100644
index 0000000000..c4f5aac8bf
Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_muted/C5.ogg differ
diff --git a/sound/instruments/synthesis_samples/guitar/crisis_nylon/c2.ogg b/sound/instruments/synthesis_samples/guitar/crisis_nylon/c2.ogg
new file mode 100644
index 0000000000..6a8115f004
Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_nylon/c2.ogg differ
diff --git a/sound/instruments/synthesis_samples/guitar/crisis_nylon/c3.ogg b/sound/instruments/synthesis_samples/guitar/crisis_nylon/c3.ogg
new file mode 100644
index 0000000000..15ec741bb6
Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_nylon/c3.ogg differ
diff --git a/sound/instruments/synthesis_samples/guitar/crisis_nylon/c4.ogg b/sound/instruments/synthesis_samples/guitar/crisis_nylon/c4.ogg
new file mode 100644
index 0000000000..df4ef3fb79
Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_nylon/c4.ogg differ
diff --git a/sound/instruments/synthesis_samples/guitar/crisis_nylon/c5.ogg b/sound/instruments/synthesis_samples/guitar/crisis_nylon/c5.ogg
new file mode 100644
index 0000000000..961f4a6b90
Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_nylon/c5.ogg differ
diff --git a/sound/instruments/synthesis_samples/guitar/crisis_steel/c2.ogg b/sound/instruments/synthesis_samples/guitar/crisis_steel/c2.ogg
new file mode 100644
index 0000000000..06ccf6ae51
Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_steel/c2.ogg differ
diff --git a/sound/instruments/synthesis_samples/guitar/crisis_steel/c3.ogg b/sound/instruments/synthesis_samples/guitar/crisis_steel/c3.ogg
new file mode 100644
index 0000000000..14756fb249
Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_steel/c3.ogg differ
diff --git a/sound/instruments/synthesis_samples/guitar/crisis_steel/c4.ogg b/sound/instruments/synthesis_samples/guitar/crisis_steel/c4.ogg
new file mode 100644
index 0000000000..544b324786
Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_steel/c4.ogg differ
diff --git a/sound/instruments/synthesis_samples/guitar/crisis_steel/c5.ogg b/sound/instruments/synthesis_samples/guitar/crisis_steel/c5.ogg
new file mode 100644
index 0000000000..df2dc8def5
Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_steel/c5.ogg differ
diff --git a/sound/instruments/synthesis_samples/organ/crisis_accordian/c2.ogg b/sound/instruments/synthesis_samples/organ/crisis_accordian/c2.ogg
new file mode 100644
index 0000000000..ba20e3f4b6
Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_accordian/c2.ogg differ
diff --git a/sound/instruments/synthesis_samples/organ/crisis_accordian/c3.ogg b/sound/instruments/synthesis_samples/organ/crisis_accordian/c3.ogg
new file mode 100644
index 0000000000..fcc8715e04
Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_accordian/c3.ogg differ
diff --git a/sound/instruments/synthesis_samples/organ/crisis_accordian/c4.ogg b/sound/instruments/synthesis_samples/organ/crisis_accordian/c4.ogg
new file mode 100644
index 0000000000..aa21d7e230
Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_accordian/c4.ogg differ
diff --git a/sound/instruments/synthesis_samples/organ/crisis_accordian/c5.ogg b/sound/instruments/synthesis_samples/organ/crisis_accordian/c5.ogg
new file mode 100644
index 0000000000..ac45bc6be4
Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_accordian/c5.ogg differ
diff --git a/sound/instruments/synthesis_samples/organ/crisis_church/c2.ogg b/sound/instruments/synthesis_samples/organ/crisis_church/c2.ogg
new file mode 100644
index 0000000000..71b9280e86
Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_church/c2.ogg differ
diff --git a/sound/instruments/synthesis_samples/organ/crisis_church/c3.ogg b/sound/instruments/synthesis_samples/organ/crisis_church/c3.ogg
new file mode 100644
index 0000000000..676a24669f
Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_church/c3.ogg differ
diff --git a/sound/instruments/synthesis_samples/organ/crisis_church/c4.ogg b/sound/instruments/synthesis_samples/organ/crisis_church/c4.ogg
new file mode 100644
index 0000000000..24e3732d7e
Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_church/c4.ogg differ
diff --git a/sound/instruments/synthesis_samples/organ/crisis_church/c5.ogg b/sound/instruments/synthesis_samples/organ/crisis_church/c5.ogg
new file mode 100644
index 0000000000..72dc2ce764
Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_church/c5.ogg differ
diff --git a/sound/instruments/synthesis_samples/organ/crisis_hammond/c2.ogg b/sound/instruments/synthesis_samples/organ/crisis_hammond/c2.ogg
new file mode 100644
index 0000000000..853b7591b4
Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_hammond/c2.ogg differ
diff --git a/sound/instruments/synthesis_samples/organ/crisis_hammond/c3.ogg b/sound/instruments/synthesis_samples/organ/crisis_hammond/c3.ogg
new file mode 100644
index 0000000000..205e190379
Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_hammond/c3.ogg differ
diff --git a/sound/instruments/synthesis_samples/organ/crisis_hammond/c4.ogg b/sound/instruments/synthesis_samples/organ/crisis_hammond/c4.ogg
new file mode 100644
index 0000000000..e90c28f14c
Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_hammond/c4.ogg differ
diff --git a/sound/instruments/synthesis_samples/organ/crisis_hammond/c5.ogg b/sound/instruments/synthesis_samples/organ/crisis_hammond/c5.ogg
new file mode 100644
index 0000000000..a56d3cc3f6
Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_hammond/c5.ogg differ
diff --git a/sound/instruments/synthesis_samples/organ/crisis_harmonica/c3.ogg b/sound/instruments/synthesis_samples/organ/crisis_harmonica/c3.ogg
new file mode 100644
index 0000000000..14c5f35eef
Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_harmonica/c3.ogg differ
diff --git a/sound/instruments/synthesis_samples/organ/crisis_harmonica/c4.ogg b/sound/instruments/synthesis_samples/organ/crisis_harmonica/c4.ogg
new file mode 100644
index 0000000000..e06e733eb2
Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_harmonica/c4.ogg differ
diff --git a/sound/instruments/synthesis_samples/organ/crisis_harmonica/c5.ogg b/sound/instruments/synthesis_samples/organ/crisis_harmonica/c5.ogg
new file mode 100644
index 0000000000..3d21e92d53
Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_harmonica/c5.ogg differ
diff --git a/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c2.ogg b/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c2.ogg
new file mode 100644
index 0000000000..be51b6dcd4
Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c2.ogg differ
diff --git a/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c3.ogg b/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c3.ogg
new file mode 100644
index 0000000000..7426d246a0
Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c3.ogg differ
diff --git a/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c4.ogg b/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c4.ogg
new file mode 100644
index 0000000000..af7b66cf19
Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c4.ogg differ
diff --git a/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c5.ogg b/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c5.ogg
new file mode 100644
index 0000000000..58ff4ff216
Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c5.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c2.ogg b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c2.ogg
new file mode 100644
index 0000000000..4b7feb77a4
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c2.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c3.ogg b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c3.ogg
new file mode 100644
index 0000000000..5e7eefa79a
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c3.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c4.ogg b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c4.ogg
new file mode 100644
index 0000000000..13e49d4f3a
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c4.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c5.ogg b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c5.ogg
new file mode 100644
index 0000000000..6ef00f0700
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c5.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c6.ogg b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c6.ogg
new file mode 100644
index 0000000000..a868249ca8
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c6.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c7.ogg b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c7.ogg
new file mode 100644
index 0000000000..2d08d98dd9
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c7.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c8.ogg b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c8.ogg
new file mode 100644
index 0000000000..50e3cb76a7
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c8.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c2.ogg b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c2.ogg
new file mode 100644
index 0000000000..0b87714166
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c2.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c3.ogg b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c3.ogg
new file mode 100644
index 0000000000..31fd625098
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c3.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c4.ogg b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c4.ogg
new file mode 100644
index 0000000000..0ed309dbb7
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c4.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c5.ogg b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c5.ogg
new file mode 100644
index 0000000000..7fd820d373
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c5.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c6.ogg b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c6.ogg
new file mode 100644
index 0000000000..b4ed66e27c
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c6.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c7.ogg b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c7.ogg
new file mode 100644
index 0000000000..13a5f7d873
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c7.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c8.ogg b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c8.ogg
new file mode 100644
index 0000000000..0169d52c21
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c8.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c2.ogg b/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c2.ogg
new file mode 100644
index 0000000000..16d49cb15e
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c2.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c3.ogg b/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c3.ogg
new file mode 100644
index 0000000000..83e906cd1a
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c3.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c4.ogg b/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c4.ogg
new file mode 100644
index 0000000000..33f766e4aa
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c4.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c5.ogg b/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c5.ogg
new file mode 100644
index 0000000000..6ce06dda56
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c5.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/fluid_harpsi/C2.ogg b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C2.ogg
new file mode 100644
index 0000000000..3941e7af03
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C2.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/fluid_harpsi/C3.ogg b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C3.ogg
new file mode 100644
index 0000000000..4f79ee9987
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C3.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/fluid_harpsi/C4.ogg b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C4.ogg
new file mode 100644
index 0000000000..1d773c62e7
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C4.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/fluid_harpsi/C5.ogg b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C5.ogg
new file mode 100644
index 0000000000..938a557e04
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C5.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/fluid_harpsi/C6.ogg b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C6.ogg
new file mode 100644
index 0000000000..c65f06ce3e
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C6.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/fluid_harpsi/C7.ogg b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C7.ogg
new file mode 100644
index 0000000000..87d4943509
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C7.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/fluid_harpsi/C8.ogg b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C8.ogg
new file mode 100644
index 0000000000..5800b2d0ff
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C8.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/fluid_piano/c2.ogg b/sound/instruments/synthesis_samples/piano/fluid_piano/c2.ogg
new file mode 100644
index 0000000000..9ce5eee76e
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_piano/c2.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/fluid_piano/c3.ogg b/sound/instruments/synthesis_samples/piano/fluid_piano/c3.ogg
new file mode 100644
index 0000000000..4644327476
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_piano/c3.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/fluid_piano/c4.ogg b/sound/instruments/synthesis_samples/piano/fluid_piano/c4.ogg
new file mode 100644
index 0000000000..ab6d8a2a92
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_piano/c4.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/fluid_piano/c5.ogg b/sound/instruments/synthesis_samples/piano/fluid_piano/c5.ogg
new file mode 100644
index 0000000000..0041ea2e13
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_piano/c5.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/fluid_piano/c6.ogg b/sound/instruments/synthesis_samples/piano/fluid_piano/c6.ogg
new file mode 100644
index 0000000000..5d836b9f7c
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_piano/c6.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/fluid_piano/c7.ogg b/sound/instruments/synthesis_samples/piano/fluid_piano/c7.ogg
new file mode 100644
index 0000000000..1981572cf2
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_piano/c7.ogg differ
diff --git a/sound/instruments/synthesis_samples/piano/fluid_piano/c8.ogg b/sound/instruments/synthesis_samples/piano/fluid_piano/c8.ogg
new file mode 100644
index 0000000000..35e5b147bc
Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_piano/c8.ogg differ
diff --git a/sound/instruments/synthesis_samples/tones/Sawtooth.ogg b/sound/instruments/synthesis_samples/tones/Sawtooth.ogg
new file mode 100644
index 0000000000..e46f5eaced
Binary files /dev/null and b/sound/instruments/synthesis_samples/tones/Sawtooth.ogg differ
diff --git a/sound/instruments/synthesis_samples/tones/Sine.ogg b/sound/instruments/synthesis_samples/tones/Sine.ogg
new file mode 100644
index 0000000000..e0b14869e9
Binary files /dev/null and b/sound/instruments/synthesis_samples/tones/Sine.ogg differ
diff --git a/sound/instruments/synthesis_samples/tones/Square.ogg b/sound/instruments/synthesis_samples/tones/Square.ogg
new file mode 100644
index 0000000000..817531bcca
Binary files /dev/null and b/sound/instruments/synthesis_samples/tones/Square.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index 11e0946291..5870935f66 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -51,6 +51,7 @@
#include "code\__DEFINES\food.dm"
#include "code\__DEFINES\footsteps.dm"
#include "code\__DEFINES\hud.dm"
+#include "code\__DEFINES\instruments.dm"
#include "code\__DEFINES\integrated_electronics.dm"
#include "code\__DEFINES\interaction_flags.dm"
#include "code\__DEFINES\inventory.dm"
@@ -290,6 +291,7 @@
#include "code\controllers\subsystem\research.dm"
#include "code\controllers\subsystem\server_maint.dm"
#include "code\controllers\subsystem\shuttle.dm"
+#include "code\controllers\subsystem\sounds.dm"
#include "code\controllers\subsystem\spacedrift.dm"
#include "code\controllers\subsystem\stickyban.dm"
#include "code\controllers\subsystem\sun.dm"
@@ -307,6 +309,7 @@
#include "code\controllers\subsystem\processing\circuit.dm"
#include "code\controllers\subsystem\processing\fastprocess.dm"
#include "code\controllers\subsystem\processing\fields.dm"
+#include "code\controllers\subsystem\processing\instruments.dm"
#include "code\controllers\subsystem\processing\nanites.dm"
#include "code\controllers\subsystem\processing\networks.dm"
#include "code\controllers\subsystem\processing\obj.dm"
@@ -969,7 +972,6 @@
#include "code\game\objects\items\devices\geiger_counter.dm"
#include "code\game\objects\items\devices\glue.dm"
#include "code\game\objects\items\devices\gps.dm"
-#include "code\game\objects\items\devices\instruments.dm"
#include "code\game\objects\items\devices\laserpointer.dm"
#include "code\game\objects\items\devices\lightreplacer.dm"
#include "code\game\objects\items\devices\megaphone.dm"
@@ -1115,7 +1117,6 @@
#include "code\game\objects\structures\mirror.dm"
#include "code\game\objects\structures\mop_bucket.dm"
#include "code\game\objects\structures\morgue.dm"
-#include "code\game\objects\structures\musician.dm"
#include "code\game\objects\structures\noticeboard.dm"
#include "code\game\objects\structures\petrified_statue.dm"
#include "code\game\objects\structures\plasticflaps.dm"
@@ -1987,6 +1988,22 @@
#include "code\modules\hydroponics\grown\tobacco.dm"
#include "code\modules\hydroponics\grown\tomato.dm"
#include "code\modules\hydroponics\grown\towercap.dm"
+#include "code\modules\instruments\instrument_data\_instrument_data.dm"
+#include "code\modules\instruments\instrument_data\_instrument_key.dm"
+#include "code\modules\instruments\instrument_data\brass.dm"
+#include "code\modules\instruments\instrument_data\chromatic_percussion.dm"
+#include "code\modules\instruments\instrument_data\fun.dm"
+#include "code\modules\instruments\instrument_data\guitar.dm"
+#include "code\modules\instruments\instrument_data\hardcoded.dm"
+#include "code\modules\instruments\instrument_data\organ.dm"
+#include "code\modules\instruments\instrument_data\piano.dm"
+#include "code\modules\instruments\instrument_data\synth_tones.dm"
+#include "code\modules\instruments\instruments\item.dm"
+#include "code\modules\instruments\instruments\stationary.dm"
+#include "code\modules\instruments\songs\_song.dm"
+#include "code\modules\instruments\songs\editor.dm"
+#include "code\modules\instruments\songs\play_legacy.dm"
+#include "code\modules\instruments\songs\play_synthesized.dm"
#include "code\modules\integrated_electronics\_defines.dm"
#include "code\modules\integrated_electronics\core\analyzer.dm"
#include "code\modules\integrated_electronics\core\assemblies.dm"