mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-01-02 21:52:48 +00:00
Port Baystruments PR from tgstation
This commit is contained in:
113
code/modules/instruments/_instrument_data.dm
Normal file
113
code/modules/instruments/_instrument_data.dm
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Get all non admin_only instruments as a list of text ids.
|
||||
*/
|
||||
/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
|
||||
|
||||
/**
|
||||
* # Instrument Datums
|
||||
*
|
||||
* Instrument datums hold the data for any given instrument, as well as data on how to play it and what bounds there are to playing it.
|
||||
*
|
||||
* The datums themselves are kept in SSinstruments in a list by their unique ID. The reason it uses ID instead of typepath is to support the runtime creation of instruments.
|
||||
* Since songs cache them while playing, there isn't realistic issues regarding performance from accessing.
|
||||
*/
|
||||
/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 = 0.33
|
||||
|
||||
/datum/instrument/New()
|
||||
if(isnull(id))
|
||||
id = "[type]"
|
||||
|
||||
/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 ..()
|
||||
|
||||
/**
|
||||
* Initializes the instrument, calculating its samples if necessary.
|
||||
*/
|
||||
/datum/instrument/proc/Initialize()
|
||||
if(instrument_flags & (INSTRUMENT_LEGACY | INSTRUMENT_DO_NOT_AUTOSAMPLE))
|
||||
return
|
||||
calculate_samples()
|
||||
|
||||
/**
|
||||
* Checks if this instrument is ready to play.
|
||||
*/
|
||||
/datum/instrument/proc/is_ready()
|
||||
if(instrument_flags & INSTRUMENT_LEGACY)
|
||||
return legacy_instrument_path && legacy_instrument_ext
|
||||
else if(instrument_flags & INSTRUMENT_DO_NOT_AUTOSAMPLE)
|
||||
return length(samples)
|
||||
return length(samples) >= 128
|
||||
|
||||
/**
|
||||
* For synthesized instruments, this is how the instrument generates the "keys" that a [/datum/song] uses to play notes.
|
||||
* Calculating them on the fly would be unperformant, so we do it during init and keep it all cached in a list.
|
||||
*/
|
||||
/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)
|
||||
33
code/modules/instruments/_instrument_key.dm
Normal file
33
code/modules/instruments/_instrument_key.dm
Normal file
@@ -0,0 +1,33 @@
|
||||
#define KEY_TWELTH (1/12)
|
||||
|
||||
/**
|
||||
* Instrument key datums contain everything needed to know how to play a specific
|
||||
* note of an instrument.*
|
||||
*/
|
||||
/datum/instrument_key
|
||||
/// The numerical key of what this is, from 1 to 127 on a standard piano keyboard.
|
||||
var/key
|
||||
/// The actual sample file that will be loaded when playing.
|
||||
var/sample
|
||||
/// The frequency to play the sample to get our desired note.
|
||||
var/frequency
|
||||
/// Deviation up/down from the pivot point that uses its sample. Used to calculate frequency.
|
||||
var/deviation
|
||||
|
||||
/datum/instrument_key/New(sample, key, deviation, frequency)
|
||||
src.sample = sample
|
||||
src.key = key
|
||||
src.deviation = deviation
|
||||
src.frequency = frequency
|
||||
if(!frequency && deviation)
|
||||
calculate()
|
||||
|
||||
/**
|
||||
* Calculates and stores our deviation.
|
||||
*/
|
||||
/datum/instrument_key/proc/calculate()
|
||||
if(!deviation)
|
||||
CRASH("Invalid calculate call: No deviation or sample in instrument_key")
|
||||
frequency = 2 ** (KEY_TWELTH * deviation)
|
||||
|
||||
#undef KEY_TWELTH
|
||||
26
code/modules/instruments/brass.dm
Normal file
26
code/modules/instruments/brass.dm
Normal file
@@ -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')
|
||||
31
code/modules/instruments/chromatic_percussion.dm
Normal file
31
code/modules/instruments/chromatic_percussion.dm
Normal file
@@ -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')
|
||||
19
code/modules/instruments/fun.dm
Normal file
19
code/modules/instruments/fun.dm
Normal file
@@ -0,0 +1,19 @@
|
||||
/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')
|
||||
36
code/modules/instruments/guitar.dm
Normal file
36
code/modules/instruments/guitar.dm
Normal file
@@ -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')
|
||||
86
code/modules/instruments/hardcoded.dm
Normal file
86
code/modules/instruments/hardcoded.dm
Normal file
@@ -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/accordion
|
||||
name = "Accordion"
|
||||
id = "accordion"
|
||||
legacy_instrument_ext = "mid"
|
||||
legacy_instrument_path = "accordion"
|
||||
|
||||
/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"
|
||||
59
code/modules/instruments/objs/items/_instrument.dm
Normal file
59
code/modules/instruments/objs/items/_instrument.dm
Normal file
@@ -0,0 +1,59 @@
|
||||
//copy pasta of the space piano, don't hurt me -Pete
|
||||
/obj/item/instrument
|
||||
name = "generic instrument"
|
||||
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'
|
||||
/// Our song datum.
|
||||
var/datum/song/handheld/song
|
||||
/// Our allowed list of instrument ids. This is nulled on initialize.
|
||||
var/list/allowed_instrument_ids
|
||||
/// How far away our song datum can be heard.
|
||||
var/instrument_range = 15
|
||||
|
||||
/obj/item/instrument/Initialize(mapload)
|
||||
. = ..()
|
||||
song = new(src, allowed_instrument_ids, instrument_range)
|
||||
allowed_instrument_ids = null //We don't need this clogging memory after it's used.
|
||||
|
||||
/obj/item/instrument/Destroy()
|
||||
QDEL_NULL(song)
|
||||
return ..()
|
||||
|
||||
/obj/item/instrument/suicide_act(mob/user)
|
||||
user.visible_message("<span class='suicide'>[user] begins to play 'Gloomy Sunday'! It looks like [user.p_theyre()] trying to commit suicide!</span>")
|
||||
return (BRUTELOSS)
|
||||
|
||||
/obj/item/instrument/attack_self(mob/user)
|
||||
if(!user.IsAdvancedToolUser())
|
||||
to_chat(user, "<span class='warning'>You don't have the dexterity to do this!</span>")
|
||||
return TRUE
|
||||
interact(user)
|
||||
|
||||
/obj/item/instrument/attack_self(mob/user)
|
||||
tgui_interact(user)
|
||||
|
||||
/obj/item/instrument/tgui_data(mob/user)
|
||||
return song.tgui_data(user)
|
||||
|
||||
/obj/item/instrument/tgui_interact(mob/user)
|
||||
if(!isliving(user) || user.incapacitated())
|
||||
return
|
||||
song.tgui_interact(user)
|
||||
|
||||
/obj/item/instrument/tgui_act(action, params)
|
||||
if(..())
|
||||
return
|
||||
return song.tgui_act(action, params)
|
||||
|
||||
/**
|
||||
* Whether the instrument should stop playing
|
||||
*
|
||||
* Arguments:
|
||||
* * user - The user
|
||||
*/
|
||||
/obj/item/instrument/proc/should_stop_playing(mob/user)
|
||||
return !(src in user) || !isliving(user) || user.incapacitated()
|
||||
206
code/modules/instruments/objs/items/instruments.dm
Normal file
206
code/modules/instruments/objs/items/instruments.dm
Normal file
@@ -0,0 +1,206 @@
|
||||
/obj/item/instrument/violin
|
||||
name = "space violin"
|
||||
desc = "A wooden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\""
|
||||
icon_state = "violin"
|
||||
item_state = "violin"
|
||||
hitsound = "swing_hit"
|
||||
allowed_instrument_ids = "violin"
|
||||
|
||||
/obj/item/instrument/violin/golden
|
||||
name = "golden violin"
|
||||
desc = "A golden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\""
|
||||
icon_state = "golden_violin"
|
||||
item_state = "golden_violin"
|
||||
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
|
||||
|
||||
/obj/item/instrument/piano_synth
|
||||
name = "synthesizer"
|
||||
desc = "An advanced electronic synthesizer that can be used as various instruments."
|
||||
icon_state = "synth"
|
||||
item_state = "synth"
|
||||
allowed_instrument_ids = "piano"
|
||||
|
||||
/obj/item/instrument/piano_synth/Initialize(mapload)
|
||||
. = ..()
|
||||
song.allowed_instrument_ids = SSinstruments.synthesizer_instrument_ids
|
||||
|
||||
/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/instrument/guitar
|
||||
name = "guitar"
|
||||
desc = "It's made of wood and has bronze strings."
|
||||
icon_state = "guitar"
|
||||
item_state = "guitar"
|
||||
attack_verb = list("played metal on", "serenaded", "crashed", "smashed")
|
||||
hitsound = 'sound/weapons/stringsmash.ogg'
|
||||
allowed_instrument_ids = "guitar"
|
||||
|
||||
/obj/item/instrument/eguitar
|
||||
name = "electric guitar"
|
||||
desc = "Makes all your shredding needs possible."
|
||||
icon_state = "eguitar"
|
||||
item_state = "eguitar"
|
||||
force = 12
|
||||
attack_verb = list("played metal on", "shredded", "crashed", "smashed")
|
||||
hitsound = 'sound/weapons/stringsmash.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"
|
||||
allowed_instrument_ids = "glockenspiel"
|
||||
|
||||
/obj/item/instrument/accordion
|
||||
name = "accordion"
|
||||
desc = "Pun-Pun not included."
|
||||
icon_state = "accordion"
|
||||
item_state = "accordion"
|
||||
allowed_instrument_ids = "accordion"
|
||||
|
||||
/obj/item/instrument/trumpet
|
||||
name = "trumpet"
|
||||
desc = "To announce the arrival of the king!"
|
||||
icon_state = "trumpet"
|
||||
item_state = "trumpet"
|
||||
allowed_instrument_ids = "trombone"
|
||||
|
||||
/obj/item/instrument/trumpet/spectral
|
||||
name = "spectral trumpet"
|
||||
desc = "Things are about to get spooky!"
|
||||
icon_state = "spectral_trumpet"
|
||||
item_state = "spectral_trumpet"
|
||||
force = 0
|
||||
attack_verb = list("played", "jazzed", "trumpeted", "mourned", "dooted", "spooked")
|
||||
|
||||
/obj/item/instrument/trumpet/spectral/Initialize()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/spooky)
|
||||
|
||||
/obj/item/instrument/trumpet/spectral/attack(mob/living/carbon/C, mob/user)
|
||||
playsound(src, 'sound/instruments/trombone/En4.mid', 100, 1, -1)
|
||||
..()
|
||||
|
||||
/obj/item/instrument/saxophone
|
||||
name = "saxophone"
|
||||
desc = "This soothing sound will be sure to leave your audience in tears."
|
||||
icon_state = "saxophone"
|
||||
item_state = "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"
|
||||
force = 0
|
||||
attack_verb = list("played", "jazzed", "saxxed", "mourned", "dooted", "spooked")
|
||||
|
||||
/obj/item/instrument/saxophone/spectral/Initialize()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/spooky)
|
||||
|
||||
/obj/item/instrument/saxophone/spectral/attack(mob/living/carbon/C, mob/user)
|
||||
playsound(src, 'sound/instruments/saxophone/En4.mid', 100,1,-1)
|
||||
..()
|
||||
|
||||
/obj/item/instrument/trombone
|
||||
name = "trombone"
|
||||
desc = "How can any pool table ever hope to compete?"
|
||||
icon_state = "trombone"
|
||||
allowed_instrument_ids = "trombone"
|
||||
item_state = "trombone"
|
||||
|
||||
/obj/item/instrument/trombone/spectral
|
||||
name = "spectral trombone"
|
||||
desc = "A skeleton's favorite instrument. Apply directly on the mortals."
|
||||
icon_state = "trombone"
|
||||
item_state = "trombone"
|
||||
force = 0
|
||||
attack_verb = list("played", "jazzed", "tromboned", "mourned", "dooted", "spooked")
|
||||
|
||||
/obj/item/instrument/trombone/spectral/Initialize()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/spooky)
|
||||
|
||||
/obj/item/instrument/trombone/spectral/attack(mob/living/carbon/C, mob/user)
|
||||
playsound (src, 'sound/instruments/trombone/Cn4.mid', 100,1,-1)
|
||||
..()
|
||||
|
||||
/obj/item/instrument/recorder
|
||||
name = "recorder"
|
||||
desc = "Just like in school, playing ability and all."
|
||||
force = 5
|
||||
icon_state = "recorder"
|
||||
item_state = "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"
|
||||
force = 5
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
allowed_instrument_ids = "harmonica"
|
||||
|
||||
/obj/item/instrument/xylophone
|
||||
name = "xylophone"
|
||||
desc = "A percussion instrument with a bright tone."
|
||||
icon_state = "xylophone"
|
||||
item_state = "xylophone"
|
||||
allowed_instrument_ids = "bikehorn"
|
||||
|
||||
/obj/item/instrument/bikehorn
|
||||
name = "gilded bike horn"
|
||||
desc = "An exquisitely decorated bike horn, capable of honking in a variety of notes."
|
||||
icon_state = "bike_horn"
|
||||
item_state = "bike_horn"
|
||||
lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/items_righthand.dmi'
|
||||
attack_verb = list("beautifully honks")
|
||||
w_class = WEIGHT_CLASS_TINY
|
||||
force = 0
|
||||
throw_speed = 3
|
||||
throw_range = 15
|
||||
hitsound = 'sound/items/bikehorn.ogg'
|
||||
allowed_instrument_ids = "bikehorn"
|
||||
|
||||
// Crafting recipes
|
||||
/datum/crafting_recipe/violin
|
||||
name = "Violin"
|
||||
result = /obj/item/instrument/violin
|
||||
reqs = list(/obj/item/stack/sheet/wood = 5,
|
||||
/obj/item/stack/cable_coil = 6,
|
||||
/obj/item/stack/tape_roll = 5)
|
||||
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
|
||||
time = 8 SECONDS
|
||||
category = CAT_MISC
|
||||
|
||||
/datum/crafting_recipe/guitar
|
||||
name = "Guitar"
|
||||
result = /obj/item/instrument/guitar
|
||||
reqs = list(/obj/item/stack/sheet/wood = 5,
|
||||
/obj/item/stack/cable_coil = 6,
|
||||
/obj/item/stack/tape_roll = 5)
|
||||
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
|
||||
time = 8 SECONDS
|
||||
category = CAT_MISC
|
||||
|
||||
/datum/crafting_recipe/eguitar
|
||||
name = "Electric Guitar"
|
||||
result = /obj/item/instrument/eguitar
|
||||
reqs = list(/obj/item/stack/sheet/metal = 5,
|
||||
/obj/item/stack/cable_coil = 6,
|
||||
/obj/item/stack/tape_roll = 5)
|
||||
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
|
||||
time = 8 SECONDS
|
||||
category = CAT_MISC
|
||||
47
code/modules/instruments/objs/structures/_musician.dm
Normal file
47
code/modules/instruments/objs/structures/_musician.dm
Normal file
@@ -0,0 +1,47 @@
|
||||
/obj/structure/musician
|
||||
name = "Not A Piano"
|
||||
desc = "Something broke!"
|
||||
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/attack_hand(mob/user)
|
||||
add_fingerprint(user)
|
||||
tgui_interact(user)
|
||||
|
||||
/obj/structure/musician/tgui_data(mob/user)
|
||||
return song.tgui_data(user)
|
||||
|
||||
/obj/structure/musician/tgui_interact(mob/user)
|
||||
song.tgui_interact(user)
|
||||
|
||||
/obj/structure/musician/tgui_act(action, params)
|
||||
if(..())
|
||||
return
|
||||
return song.tgui_act(action, params)
|
||||
|
||||
/obj/structure/musician/wrench_act(mob/living/user, obj/item/I)
|
||||
default_unfasten_wrench(user, I, 40)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Whether the instrument should stop playing
|
||||
*
|
||||
* Arguments:
|
||||
* * user - The user
|
||||
*/
|
||||
/obj/structure/musician/proc/should_stop_playing(mob/user)
|
||||
if(!(anchored || can_play_unanchored))
|
||||
return TRUE
|
||||
if(!user)
|
||||
return FALSE
|
||||
return !CanUseTopic(user, GLOB.physical_state)
|
||||
22
code/modules/instruments/objs/structures/piano.dm
Normal file
22
code/modules/instruments/objs/structures/piano.dm
Normal file
@@ -0,0 +1,22 @@
|
||||
/obj/structure/piano
|
||||
parent_type = /obj/structure/musician // TODO: Can't edit maps right now due to a freeze, remove and update path when it's done
|
||||
name = "space minimoog"
|
||||
icon = 'icons/obj/musician.dmi'
|
||||
icon_state = "minimoog"
|
||||
anchored = TRUE
|
||||
density = TRUE
|
||||
allowed_instrument_ids = "piano"
|
||||
|
||||
/obj/structure/piano/unanchored
|
||||
anchored = FALSE
|
||||
|
||||
/obj/structure/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"
|
||||
43
code/modules/instruments/organ.dm
Normal file
43
code/modules/instruments/organ.dm
Normal file
@@ -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 Accordion"
|
||||
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 Accordion"
|
||||
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')
|
||||
56
code/modules/instruments/piano.dm
Normal file
56
code/modules/instruments/piano.dm
Normal file
@@ -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')
|
||||
401
code/modules/instruments/songs/_song.dm
Normal file
401
code/modules/instruments/songs/_song.dm
Normal file
@@ -0,0 +1,401 @@
|
||||
#define MUSICIAN_HEARCHECK_MINDELAY 4
|
||||
#define MUSIC_MAXLINES 1000
|
||||
#define MUSIC_MAXLINECHARS 300
|
||||
|
||||
/**
|
||||
* # Song datum
|
||||
*
|
||||
* These are the actual backend behind instruments.
|
||||
* They attach to an atom and provide the editor + playback functionality.
|
||||
*/
|
||||
/datum/song
|
||||
/// 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
|
||||
|
||||
/// How far we can be heard
|
||||
var/instrument_range = 15
|
||||
|
||||
/// 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 = 1
|
||||
/// 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
|
||||
|
||||
//////////// 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 ////////////////
|
||||
/**
|
||||
* Build by compile_chords()
|
||||
* Must be rebuilt on instrument switch.
|
||||
* Compilation happens when we start playing and is cleared after we finish playing.
|
||||
* Format: list of chord lists, with chordlists having (key1, key2, key3, tempodiv)
|
||||
*/
|
||||
var/list/compiled_chords
|
||||
/// Current section of a long chord we're on, so we don't need to make a billion chords, one for every unit ticklag.
|
||||
var/elapsed_delay
|
||||
/// Amount of delay to wait before playing the next chord
|
||||
var/delay_by
|
||||
/// Current chord we're on.
|
||||
var/current_chord
|
||||
/// Channel as text = current volume percentage but it's 0 to 100 instead of 0 to 1.
|
||||
var/list/channels_playing
|
||||
/// 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
|
||||
/// Person playing us
|
||||
var/mob/user_playing
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
/// 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
|
||||
/// 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
|
||||
var/static/list/accent_lookup
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
///////////// !!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
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var/static/list/valid_files[0] // Cache to avoid running fexists() every time
|
||||
|
||||
/datum/song/New(atom/parent, list/instrument_ids, new_range)
|
||||
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)
|
||||
channels_playing = list()
|
||||
channels_idle = list()
|
||||
if(!note_offset_lookup)
|
||||
note_offset_lookup = list(9, 11, 0, 2, 4, 5, 7)
|
||||
accent_lookup = list("b" = -1, "s" = 1, "#" = 1, "n" = 0)
|
||||
update_sustain()
|
||||
if(new_range)
|
||||
instrument_range = new_range
|
||||
|
||||
/datum/song/Destroy()
|
||||
stop_playing()
|
||||
SSinstruments.on_song_del(src)
|
||||
lines = null
|
||||
using_instrument = null
|
||||
allowed_instrument_ids = null
|
||||
parent = null
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Checks and stores which mobs can hear us. Terminates sounds for mobs that leave our range.
|
||||
*/
|
||||
/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 GLOB.player_list)
|
||||
var/dist = get_dist(M, source)
|
||||
if(dist > instrument_range)
|
||||
continue
|
||||
hearing_mobs[M] = dist
|
||||
var/list/exited = old - hearing_mobs
|
||||
for(var/i in exited)
|
||||
terminate_sound_mob(i)
|
||||
|
||||
/**
|
||||
* Sets our instrument, caching anything necessary for faster accessing. Accepts an ID, typepath, or instantiated instrument datum.
|
||||
*/
|
||||
/datum/song/proc/set_instrument(datum/instrument/I)
|
||||
terminate_all_sounds()
|
||||
var/old_legacy
|
||||
if(using_instrument)
|
||||
using_instrument.songs_using -= src
|
||||
old_legacy = (using_instrument.instrument_flags & INSTRUMENT_LEGACY)
|
||||
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 = (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
|
||||
if(isnull(old_legacy) || (old_legacy != instrument_legacy))
|
||||
if(playing)
|
||||
compile_chords()
|
||||
|
||||
/**
|
||||
* Attempts to start playing our song.
|
||||
*/
|
||||
/datum/song/proc/start_playing(mob/user)
|
||||
if(playing)
|
||||
return
|
||||
if(!using_instrument?.is_ready())
|
||||
to_chat(user, "<span class='warning'>An error has occured with [src]. Please reset the instrument.</span>")
|
||||
return
|
||||
compile_chords()
|
||||
if(!length(compiled_chords))
|
||||
to_chat(user, "<span class='warning'>Song is empty.</span>")
|
||||
return
|
||||
playing = TRUE
|
||||
SStgui.update_uis(parent)
|
||||
//we can not afford to runtime, since we are going to be doing sound channel reservations and if we runtime it means we have a channel allocation leak.
|
||||
//wrap the rest of the stuff to ensure stop_playing() is called.
|
||||
do_hearcheck()
|
||||
SEND_SIGNAL(parent, COMSIG_SONG_START)
|
||||
elapsed_delay = 0
|
||||
delay_by = 0
|
||||
current_chord = 1
|
||||
user_playing = user
|
||||
START_PROCESSING(SSinstruments, src)
|
||||
|
||||
/**
|
||||
* Stops playing, terminating all sounds if in synthesized mode. Clears hearing_mobs.
|
||||
*/
|
||||
/datum/song/proc/stop_playing()
|
||||
if(!playing)
|
||||
return
|
||||
playing = FALSE
|
||||
if(!debug_mode)
|
||||
compiled_chords = null
|
||||
STOP_PROCESSING(SSinstruments, src)
|
||||
SEND_SIGNAL(parent, COMSIG_SONG_END)
|
||||
terminate_all_sounds(TRUE)
|
||||
hearing_mobs.len = 0
|
||||
SStgui.update_uis(parent)
|
||||
user_playing = null
|
||||
|
||||
/**
|
||||
* Processes our song.
|
||||
*/
|
||||
/datum/song/proc/process_song(wait)
|
||||
if(!length(compiled_chords) || should_stop_playing(user_playing))
|
||||
stop_playing()
|
||||
return
|
||||
var/list/chord = compiled_chords[current_chord]
|
||||
if(++elapsed_delay >= delay_by)
|
||||
play_chord(chord)
|
||||
elapsed_delay = 0
|
||||
delay_by = tempodiv_to_delay(chord[length(chord)])
|
||||
current_chord++
|
||||
if(current_chord > length(compiled_chords))
|
||||
if(repeat)
|
||||
repeat--
|
||||
current_chord = 1
|
||||
SStgui.update_uis(parent)
|
||||
return
|
||||
else
|
||||
stop_playing()
|
||||
return
|
||||
|
||||
/**
|
||||
* Converts a tempodiv to ticks to elapse before playing the next chord, taking into account our tempo.
|
||||
*/
|
||||
/datum/song/proc/tempodiv_to_delay(tempodiv)
|
||||
return max(1, round((tempo/tempodiv) / world.tick_lag, 1))
|
||||
|
||||
/**
|
||||
* Compiles chords.
|
||||
*/
|
||||
/datum/song/proc/compile_chords()
|
||||
legacy ? compile_legacy() : compile_synthesized()
|
||||
|
||||
/**
|
||||
* Plays a chord.
|
||||
*/
|
||||
/datum/song/proc/play_chord(list/chord)
|
||||
// last value is timing information
|
||||
for(var/i in 1 to (length(chord) - 1))
|
||||
legacy? playkey_legacy(chord[i][1], chord[i][2], chord[i][3], user_playing) : playkey_synth(chord[i], user_playing)
|
||||
|
||||
/**
|
||||
* Checks if we should halt playback.
|
||||
*/
|
||||
/datum/song/proc/should_stop_playing(mob/user)
|
||||
return QDELETED(parent) || !using_instrument || !playing
|
||||
|
||||
/**
|
||||
* Sanitizes tempo to a value that makes sense and fits the current world.tick_lag.
|
||||
*/
|
||||
/datum/song/proc/sanitize_tempo(new_tempo)
|
||||
new_tempo = abs(new_tempo)
|
||||
return clamp(round(new_tempo, world.tick_lag), world.tick_lag, 5 SECONDS)
|
||||
|
||||
/**
|
||||
* Gets our beats per minute based on our tempo.
|
||||
*/
|
||||
/datum/song/proc/get_bpm()
|
||||
return 600 / tempo
|
||||
|
||||
/**
|
||||
* Sets our tempo from a beats-per-minute, sanitizing it to a valid number first.
|
||||
*/
|
||||
/datum/song/proc/set_bpm(bpm)
|
||||
tempo = sanitize_tempo(600 / bpm)
|
||||
|
||||
/datum/song/process(wait)
|
||||
if(!playing)
|
||||
return PROCESS_KILL
|
||||
// it's expected this ticks at every world.tick_lag. if it lags, do not attempt to catch up.
|
||||
process_song(world.tick_lag)
|
||||
process_decay(world.tick_lag)
|
||||
|
||||
/**
|
||||
* Updates our cached linear/exponential falloff stuff, saving calculations down the line.
|
||||
*/
|
||||
/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, 100 - sustain_dropoff_volume)
|
||||
var/volume_decrease_per_decisecond = volume_diff / target_duration
|
||||
cached_linear_dropoff = volume_decrease_per_decisecond
|
||||
|
||||
/**
|
||||
* Setter for setting output volume.
|
||||
*/
|
||||
/datum/song/proc/set_volume(volume)
|
||||
src.volume = clamp(volume, max(0, min_volume), min(100, max_volume))
|
||||
update_sustain()
|
||||
SStgui.update_uis(parent)
|
||||
|
||||
/**
|
||||
* Setter for setting how low the volume has to get before a note is considered "dead" and dropped
|
||||
*/
|
||||
/datum/song/proc/set_dropoff_volume(volume)
|
||||
sustain_dropoff_volume = clamp(volume, INSTRUMENT_MIN_SUSTAIN_DROPOFF, 100)
|
||||
update_sustain()
|
||||
SStgui.update_uis(parent)
|
||||
|
||||
/**
|
||||
* Setter for setting exponential falloff factor.
|
||||
*/
|
||||
/datum/song/proc/set_exponential_drop_rate(drop)
|
||||
sustain_exponential_dropoff = clamp(drop, INSTRUMENT_EXP_FALLOFF_MIN, INSTRUMENT_EXP_FALLOFF_MAX)
|
||||
update_sustain()
|
||||
SStgui.update_uis(parent)
|
||||
|
||||
/**
|
||||
* Setter for setting linear falloff duration.
|
||||
*/
|
||||
/datum/song/proc/set_linear_falloff_duration(duration)
|
||||
sustain_linear_duration = clamp(duration, 0.1, INSTRUMENT_MAX_TOTAL_SUSTAIN)
|
||||
update_sustain()
|
||||
SStgui.update_uis(parent)
|
||||
|
||||
/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/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/should_stop_playing(mob/user)
|
||||
. = ..()
|
||||
if(.)
|
||||
return TRUE
|
||||
var/obj/structure/musician/M = parent
|
||||
return M.should_stop_playing(user)
|
||||
|
||||
183
code/modules/instruments/songs/_song_ui.dm
Normal file
183
code/modules/instruments/songs/_song_ui.dm
Normal file
@@ -0,0 +1,183 @@
|
||||
/datum/song/tgui_data(mob/user)
|
||||
var/data[0]
|
||||
|
||||
// General
|
||||
data["playing"] = playing
|
||||
data["repeat"] = repeat
|
||||
data["maxRepeats"] = max_repeats
|
||||
data["editing"] = editing
|
||||
data["lines"] = lines
|
||||
data["tempo"] = tempo
|
||||
data["minTempo"] = world.tick_lag
|
||||
data["maxTempo"] = 5 SECONDS
|
||||
data["tickLag"] = world.tick_lag
|
||||
data["help"] = help
|
||||
|
||||
// Status
|
||||
var/list/allowed_instrument_names = list()
|
||||
for(var/i in allowed_instrument_ids)
|
||||
var/datum/instrument/I = SSinstruments.get_instrument(i)
|
||||
if(I)
|
||||
allowed_instrument_names += I.name
|
||||
data["allowedInstrumentNames"] = allowed_instrument_names
|
||||
data["instrumentLoaded"] = !isnull(using_instrument)
|
||||
if(using_instrument)
|
||||
data["instrument"] = using_instrument.name
|
||||
data["canNoteShift"] = can_noteshift
|
||||
if(can_noteshift)
|
||||
data["noteShift"] = note_shift
|
||||
data["noteShiftMin"] = note_shift_min
|
||||
data["noteShiftMax"] = note_shift_max
|
||||
data["sustainMode"] = sustain_mode
|
||||
switch(sustain_mode)
|
||||
if(SUSTAIN_LINEAR)
|
||||
data["sustainLinearDuration"] = sustain_linear_duration
|
||||
if(SUSTAIN_EXPONENTIAL)
|
||||
data["sustainExponentialDropoff"] = sustain_exponential_dropoff
|
||||
data["ready"] = using_instrument?.is_ready()
|
||||
data["legacy"] = legacy
|
||||
data["volume"] = volume
|
||||
data["minVolume"] = min_volume
|
||||
data["maxVolume"] = max_volume
|
||||
data["sustainDropoffVolume"] = sustain_dropoff_volume
|
||||
data["sustainHeldNote"] = full_sustain_held_note
|
||||
|
||||
return data
|
||||
|
||||
/datum/song/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
|
||||
ui = SStgui.try_update_ui(user, parent, ui_key, ui, force_open)
|
||||
if(!ui)
|
||||
ui = new(user, parent, ui_key, "Instrument", parent?.name || "Instrument", 700, 500)
|
||||
ui.open()
|
||||
ui.set_autoupdate(FALSE) // NO!!! Don't auto-update this!!
|
||||
|
||||
/datum/song/tgui_act(action, params)
|
||||
switch(action)
|
||||
if("newsong")
|
||||
lines = new()
|
||||
tempo = sanitize_tempo(5) // default 120 BPM
|
||||
name = ""
|
||||
return TRUE
|
||||
if("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)
|
||||
parse_song(t)
|
||||
if("help")
|
||||
help = !help
|
||||
return TRUE
|
||||
if("edit")
|
||||
editing = !editing
|
||||
return TRUE
|
||||
if("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 = clamp(round(text2num(params["new"])), 0, max_repeats)
|
||||
return TRUE
|
||||
if("tempo")
|
||||
tempo = sanitize_tempo(text2num(params["new"]))
|
||||
return TRUE
|
||||
if("play")
|
||||
INVOKE_ASYNC(src, .proc/start_playing, usr)
|
||||
if("newline")
|
||||
var/newline = html_encode(input("Enter your line: ", parent.name) as text|null)
|
||||
if(!newline || !in_range(parent, usr))
|
||||
return
|
||||
if(length(lines) > MUSIC_MAXLINES)
|
||||
return
|
||||
if(length(newline) > MUSIC_MAXLINECHARS)
|
||||
newline = copytext(newline, 1, MUSIC_MAXLINECHARS)
|
||||
lines.Add(newline)
|
||||
return TRUE
|
||||
if("deleteline")
|
||||
var/num = round(text2num(params["line"]))
|
||||
if(num > length(lines) || num < 1)
|
||||
return
|
||||
lines.Cut(num, num + 1)
|
||||
return TRUE
|
||||
if("modifyline")
|
||||
var/num = round(text2num(params["line"]), 1)
|
||||
var/content = stripped_input(usr, "Enter your line: ", parent.name, lines[num], MUSIC_MAXLINECHARS)
|
||||
if(!content || !in_range(parent, usr))
|
||||
return
|
||||
if(num > length(lines) || num < 1)
|
||||
return
|
||||
lines[num] = content
|
||||
return TRUE
|
||||
if("stop")
|
||||
stop_playing()
|
||||
if("setlinearfalloff")
|
||||
set_linear_falloff_duration(round(text2num(params["new"]) * 10, world.tick_lag))
|
||||
return TRUE
|
||||
if("setexpfalloff")
|
||||
set_exponential_drop_rate(round(text2num(params["new"]), 0.00001))
|
||||
return TRUE
|
||||
if("setvolume")
|
||||
set_volume(round(text2num(params["new"]), 1))
|
||||
return TRUE
|
||||
if("setdropoffvolume")
|
||||
set_dropoff_volume(round(text2num(params["new"]), 0.01))
|
||||
return TRUE
|
||||
if("switchinstrument")
|
||||
if(!length(allowed_instrument_ids))
|
||||
return
|
||||
else if(length(allowed_instrument_ids) == 1)
|
||||
set_instrument(allowed_instrument_ids[1])
|
||||
return
|
||||
var/choice = params["name"]
|
||||
for(var/i in allowed_instrument_ids)
|
||||
var/datum/instrument/I = SSinstruments.get_instrument(i)
|
||||
if(I && I.name == choice)
|
||||
set_instrument(I)
|
||||
return TRUE
|
||||
if("setnoteshift")
|
||||
note_shift = clamp(round(text2num(params["new"])), note_shift_min, note_shift_max)
|
||||
return TRUE
|
||||
if("setsustainmode")
|
||||
var/static/list/sustain_modes
|
||||
if(!length(sustain_modes))
|
||||
sustain_modes = list("Linear" = SUSTAIN_LINEAR, "Exponential" = SUSTAIN_EXPONENTIAL)
|
||||
var/choice = params["new"]
|
||||
sustain_mode = sustain_modes[choice] || sustain_mode
|
||||
return TRUE
|
||||
if("togglesustainhold")
|
||||
full_sustain_held_note = !full_sustain_held_note
|
||||
return TRUE
|
||||
else
|
||||
return FALSE
|
||||
parent.add_fingerprint(usr)
|
||||
|
||||
/**
|
||||
* Parses a song the user has input into lines and stores them.
|
||||
*/
|
||||
/datum/song/proc/parse_song(text)
|
||||
set waitfor = FALSE
|
||||
//split into lines
|
||||
lines = splittext(text, "\n")
|
||||
if(length(lines))
|
||||
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(length(lines) > 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++
|
||||
SStgui.update_uis(parent)
|
||||
95
code/modules/instruments/songs/play_legacy.dm
Normal file
95
code/modules/instruments/songs/play_legacy.dm
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Compiles our lines into "chords" with filenames for legacy playback. This makes there have to be a bit of lag at the beginning of the song, but repeats will not have to parse it again, and overall playback won't be impacted by as much lag.
|
||||
*/
|
||||
/datum/song/proc/compile_legacy()
|
||||
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[++compiled_chord.len] = list(key, accents[key], octaves[key])
|
||||
compiled_chord += tempodiv //this goes last
|
||||
if(length(compiled_chord))
|
||||
compiled_chords[++compiled_chords.len] = compiled_chord
|
||||
|
||||
/**
|
||||
* Proc to play a legacy note. Just plays the sound to hearing mobs (and does hearcheck if necessary), no fancy channel/sustain/management.
|
||||
*
|
||||
* Arguments:
|
||||
* * note - number from 1-7 for A-G
|
||||
* * acc - either "b", "n", or "#"
|
||||
* * oct - 1-8 (or 9 for C)
|
||||
*/
|
||||
/datum/song/proc/playkey_legacy(note, acc as text, oct, mob/user)
|
||||
// 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/filename = "sound/instruments/[cached_legacy_dir]/[ascii2text(note + 64)][acc][oct].[cached_legacy_ext]"
|
||||
var/soundfile = file(filename)
|
||||
// make sure the note exists
|
||||
var/cached_fexists = valid_files[filename]
|
||||
if(!isnull(cached_fexists))
|
||||
if(!cached_fexists)
|
||||
return
|
||||
else if(!fexists(soundfile))
|
||||
valid_files[filename] = FALSE
|
||||
return
|
||||
else
|
||||
valid_files[filename] = TRUE
|
||||
// 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
|
||||
if(!(M?.client?.prefs?.toggles & SOUND_INSTRUMENTS))
|
||||
continue
|
||||
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
|
||||
134
code/modules/instruments/songs/play_synthesized.dm
Normal file
134
code/modules/instruments/songs/play_synthesized.dm
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Compiles our lines into "chords" with numbers. This makes there have to be a bit of lag at the beginning of the song, but repeats will not have to parse it again, and overall playback won't be impacted by as much lag.
|
||||
*/
|
||||
/datum/song/proc/compile_synthesized()
|
||||
if(!length(src.lines))
|
||||
return
|
||||
var/list/lines = src.lines //cache for hyepr speed!
|
||||
compiled_chords = list()
|
||||
var/list/octaves = list(3, 3, 3, 3, 3, 3, 3)
|
||||
var/list/accents = list("n", "n", "n", "n", "n", "n", "n")
|
||||
for(var/line in lines)
|
||||
var/list/chords = splittext(lowertext(line), ",")
|
||||
for(var/chord in chords)
|
||||
var/list/compiled_chord = list()
|
||||
var/tempodiv = 1
|
||||
var/list/notes_tempodiv = splittext(chord, "/")
|
||||
var/len = length(notes_tempodiv)
|
||||
if(len >= 2)
|
||||
tempodiv = text2num(notes_tempodiv[2])
|
||||
if(len) //some dunkass is going to do ,,,, to make 3 rests instead of ,/1 because there's no standardization so let's be prepared for that.
|
||||
var/list/notes = splittext(notes_tempodiv[1], "-")
|
||||
for(var/note in notes)
|
||||
if(length(note) == 0)
|
||||
continue
|
||||
// 1-7, A-G
|
||||
var/key = text2ascii(note) - 96
|
||||
if((key < 1) || (key > 7))
|
||||
continue
|
||||
for(var/i in 2 to length(note))
|
||||
var/oct_acc = copytext(note, i, i + 1)
|
||||
var/num = text2num(oct_acc)
|
||||
if(!num) //it's an accidental
|
||||
accents[key] = oct_acc //if they misspelled it/fucked up that's on them lmao, no safety checks.
|
||||
else //octave
|
||||
octaves[key] = clamp(num, octave_min, octave_max)
|
||||
compiled_chord += clamp((note_offset_lookup[key] + octaves[key] * 12 + accent_lookup[accents[key]]), key_min, key_max)
|
||||
compiled_chord += tempodiv //this goes last
|
||||
if(length(compiled_chord))
|
||||
compiled_chords[++compiled_chords.len] = compiled_chord
|
||||
|
||||
/**
|
||||
* Plays a specific numerical key from our instrument to anyone who can hear us.
|
||||
* Does a hearing check if enough time has passed.
|
||||
*/
|
||||
/datum/song/proc/playkey_synth(key, mob/user)
|
||||
if(can_noteshift)
|
||||
key = clamp(key + note_shift, key_min, key_max)
|
||||
if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
|
||||
do_hearcheck()
|
||||
var/datum/instrument_key/K = using_instrument.samples[num2text(key)] //See how fucking easy it is to make a number text? You don't need a complicated 9 line proc!
|
||||
//Should probably add channel limiters here at some point but I don't care right now.
|
||||
var/channel = pop_channel()
|
||||
if(isnull(channel))
|
||||
return FALSE
|
||||
. = TRUE
|
||||
var/sound/copy = sound(K.sample)
|
||||
var/volume = src.volume * using_instrument.volume_multiplier
|
||||
copy.frequency = K.frequency
|
||||
copy.volume = volume
|
||||
var/channel_text = num2text(channel)
|
||||
channels_playing[channel_text] = 100
|
||||
last_channel_played = channel_text
|
||||
for(var/i in hearing_mobs)
|
||||
var/mob/M = i
|
||||
if(!(M?.client?.prefs?.toggles & SOUND_INSTRUMENTS))
|
||||
continue
|
||||
M.playsound_local(get_turf(parent), null, volume, FALSE, K.frequency, INSTRUMENT_DISTANCE_NO_FALLOFF, channel, null, copy, distance_multiplier = INSTRUMENT_DISTANCE_FALLOFF_BUFF)
|
||||
// Could do environment and echo later but not for now
|
||||
|
||||
/**
|
||||
* Stops all sounds we are "responsible" for. Only works in synthesized mode.
|
||||
*/
|
||||
/datum/song/proc/terminate_all_sounds(clear_channels = TRUE)
|
||||
for(var/i in hearing_mobs)
|
||||
terminate_sound_mob(i)
|
||||
if(clear_channels && channels_playing)
|
||||
channels_playing.len = 0
|
||||
channels_idle.len = 0
|
||||
SSinstruments.current_instrument_channels -= using_sound_channels
|
||||
using_sound_channels = 0
|
||||
SSsounds.free_datum_channels(src)
|
||||
|
||||
/**
|
||||
* Stops all sounds we are responsible for in a given person. Only works in synthesized mode.
|
||||
*/
|
||||
/datum/song/proc/terminate_sound_mob(mob/M)
|
||||
for(var/channel in channels_playing)
|
||||
M.stop_sound_channel(text2num(channel))
|
||||
|
||||
/**
|
||||
* Pops a channel we have reserved so we don't have to release and re-request them from SSsounds every time we play a note. This is faster.
|
||||
*/
|
||||
/datum/song/proc/pop_channel()
|
||||
if(length(channels_idle)) //just pop one off of here if we have one available
|
||||
. = text2num(channels_idle[1])
|
||||
channels_idle.Cut(1, 2)
|
||||
return
|
||||
if(using_sound_channels >= max_sound_channels)
|
||||
return
|
||||
. = SSinstruments.reserve_instrument_channel(src)
|
||||
if(!isnull(.))
|
||||
using_sound_channels++
|
||||
|
||||
/**
|
||||
* Decays our channels and updates their volumes to mobs who can hear us.
|
||||
*
|
||||
* Arguments:
|
||||
* * wait_ds - the deciseconds we should decay by. This is to compensate for any lag, as otherwise songs would get pretty nasty during high time dilation.
|
||||
*/
|
||||
/datum/song/proc/process_decay(wait_ds)
|
||||
var/linear_dropoff = cached_linear_dropoff * wait_ds
|
||||
var/exponential_dropoff = cached_exponential_dropoff ** wait_ds
|
||||
for(var/channel in channels_playing)
|
||||
if(full_sustain_held_note && (channel == last_channel_played))
|
||||
continue
|
||||
var/current_volume = channels_playing[channel]
|
||||
switch(sustain_mode)
|
||||
if(SUSTAIN_LINEAR)
|
||||
current_volume -= linear_dropoff
|
||||
if(SUSTAIN_EXPONENTIAL)
|
||||
current_volume /= exponential_dropoff
|
||||
channels_playing[channel] = current_volume
|
||||
var/dead = current_volume <= sustain_dropoff_volume
|
||||
var/channelnumber = text2num(channel)
|
||||
if(dead)
|
||||
channels_playing -= channel
|
||||
channels_idle += channel
|
||||
for(var/i in hearing_mobs)
|
||||
var/mob/M = i
|
||||
M.stop_sound_channel(channelnumber)
|
||||
else
|
||||
for(var/i in hearing_mobs)
|
||||
var/mob/M = i
|
||||
M.set_sound_channel_volume(channelnumber, (current_volume * 0.01) * volume * using_instrument.volume_multiplier)
|
||||
19
code/modules/instruments/synth_tones.dm
Normal file
19
code/modules/instruments/synth_tones.dm
Normal file
@@ -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')
|
||||
@@ -296,7 +296,7 @@
|
||||
return
|
||||
var/sound/S = sound(mysound)
|
||||
S.wait = 0 //No queue
|
||||
S.channel = open_sound_channel()
|
||||
S.channel = SSsounds.random_available_channel()
|
||||
S.volume = 50
|
||||
for(var/mob/M in passengers | pilot)
|
||||
M << S
|
||||
|
||||
Reference in New Issue
Block a user