mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-02-03 05:09:17 +00:00
Port Baystruments PR from tgstation
This commit is contained in:
29
code/__DEFINES/instruments.dm
Normal file
29
code/__DEFINES/instruments.dm
Normal file
@@ -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 0
|
||||
|
||||
#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
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#define CHANNEL_HIGHEST_AVAILABLE 1017
|
||||
|
||||
#define MAX_INSTRUMENT_CHANNELS (128 * 6)
|
||||
|
||||
#define SOUND_MINIMUM_PRESSURE 10
|
||||
#define FALLOFF_SOUNDS 0.5
|
||||
|
||||
@@ -45,11 +45,13 @@
|
||||
// Subsystems shutdown in the reverse of the order they initialize in
|
||||
// The numbers just define the ordering, they are meaningless otherwise.
|
||||
#define INIT_ORDER_TITLE 100 // This **MUST** load first or people will se blank lobby screens
|
||||
#define INIT_ORDER_GARBAGE 19
|
||||
#define INIT_ORDER_DBCORE 18
|
||||
#define INIT_ORDER_BLACKBOX 17
|
||||
#define INIT_ORDER_SERVER_MAINT 16
|
||||
#define INIT_ORDER_INPUT 15
|
||||
#define INIT_ORDER_GARBAGE 21
|
||||
#define INIT_ORDER_DBCORE 20
|
||||
#define INIT_ORDER_BLACKBOX 19
|
||||
#define INIT_ORDER_SERVER_MAINT 18
|
||||
#define INIT_ORDER_INPUT 17
|
||||
#define INIT_ORDER_SOUNDS 16
|
||||
#define INIT_ORDER_INSTRUMENTS 15
|
||||
#define INIT_ORDER_RESEARCH 14
|
||||
#define INIT_ORDER_EVENTS 13
|
||||
#define INIT_ORDER_JOBS 12
|
||||
|
||||
86
code/controllers/subsystem/processing/instruments.dm
Normal file
86
code/controllers/subsystem/processing/instruments.dm
Normal file
@@ -0,0 +1,86 @@
|
||||
PROCESSING_SUBSYSTEM_DEF(instruments)
|
||||
name = "Instruments"
|
||||
init_order = INIT_ORDER_INSTRUMENTS
|
||||
wait = 0.5
|
||||
flags = SS_KEEP_TIMING
|
||||
offline_implications = "Instruments will no longer play. No immediate action is needed."
|
||||
stat_tag = "Instruments"
|
||||
|
||||
/// List of all instrument data, associative id = datum
|
||||
var/list/datum/instrument/instrument_data
|
||||
/// List of all song datums.
|
||||
var/list/datum/song/songs
|
||||
/// Max lines in songs
|
||||
var/musician_maxlines = 600
|
||||
/// Max characters per line in songs
|
||||
var/musician_maxlinechars = 300
|
||||
/// Deciseconds between hearchecks. Too high and instruments seem to lag when people are moving around in terms of who can hear it. Too low and the server lags from this.
|
||||
var/musician_hearcheck_mindelay = 5
|
||||
/// Maximum instrument channels total instruments are allowed to use. This is so you don't have instruments deadlocking all sound channels.
|
||||
var/max_instrument_channels = MAX_INSTRUMENT_CHANNELS
|
||||
/// Current number of channels allocated for instruments
|
||||
var/current_instrument_channels = 0
|
||||
/// Single cached list for synthesizer instrument ids, so you don't have to have a new list with every synthesizer.
|
||||
var/list/synthesizer_instrument_ids
|
||||
|
||||
/datum/controller/subsystem/processing/instruments/Initialize()
|
||||
initialize_instrument_data()
|
||||
synthesizer_instrument_ids = get_allowed_instrument_ids()
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Initializes all instrument datums
|
||||
*/
|
||||
/datum/controller/subsystem/processing/instruments/proc/initialize_instrument_data()
|
||||
instrument_data = list()
|
||||
for(var/path in subtypesof(/datum/instrument))
|
||||
var/datum/instrument/I = path
|
||||
if(initial(I.abstract_type) == path)
|
||||
continue
|
||||
I = new path
|
||||
I.Initialize()
|
||||
if(!I.id)
|
||||
qdel(I)
|
||||
continue
|
||||
instrument_data[I.id] = I
|
||||
CHECK_TICK
|
||||
|
||||
/**
|
||||
* Reserves a sound channel for a given instrument datum
|
||||
*
|
||||
* Arguments:
|
||||
* * I - The instrument datum
|
||||
*/
|
||||
/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++
|
||||
|
||||
/**
|
||||
* Called when a datum/song is created
|
||||
*
|
||||
* Arguments:
|
||||
* * S - The created datum/song
|
||||
*/
|
||||
/datum/controller/subsystem/processing/instruments/proc/on_song_new(datum/song/S)
|
||||
LAZYADD(songs, S)
|
||||
|
||||
/**
|
||||
* Called when a datum/song is deleted
|
||||
*
|
||||
* Arguments:
|
||||
* * S - The deleted datum/song
|
||||
*/
|
||||
/datum/controller/subsystem/processing/instruments/proc/on_song_del(datum/song/S)
|
||||
LAZYREMOVE(songs, S)
|
||||
|
||||
/**
|
||||
* Returns the instrument datum at the given ID or path
|
||||
*
|
||||
* Arguments:
|
||||
* * id_or_path - The ID or path of the instrument
|
||||
*/
|
||||
/datum/controller/subsystem/processing/instruments/proc/get_instrument(id_or_path)
|
||||
return instrument_data["[id_or_path]"]
|
||||
165
code/controllers/subsystem/sounds.dm
Normal file
165
code/controllers/subsystem/sounds.dm
Normal file
@@ -0,0 +1,165 @@
|
||||
#define DATUMLESS "NO_DATUM"
|
||||
|
||||
SUBSYSTEM_DEF(sounds)
|
||||
name = "Sounds"
|
||||
init_order = INIT_ORDER_SOUNDS
|
||||
flags = SS_NO_FIRE
|
||||
offline_implications = "Sounds may not play correctly. Shuttle call recommended."
|
||||
|
||||
var/using_channels_max = CHANNEL_HIGHEST_AVAILABLE // BYOND max channels
|
||||
/// Amount of channels to reserve for random usage rather than reservations being allowed to reserve all channels. Also a nice safeguard for when someone screws up.
|
||||
var/random_channels_min = 50
|
||||
// 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
|
||||
/// Assoc list datum = list(channel1, channel2, ...) for what channels something reserved.
|
||||
var/list/using_channels_by_datum
|
||||
// Special datastructure for fast channel management
|
||||
/// List of all channels as numbers
|
||||
var/list/channel_list
|
||||
/// Associative list of all reserved channels associated to their position. "[channel_number]" = index as number
|
||||
var/list/reserved_channels
|
||||
/// lower iteration position - Incremented and looped to get "random" sound channels for normal sounds. The channel at this index is returned when asking for a random channel.
|
||||
var/channel_random_low
|
||||
/// higher reserve position - decremented and incremented to reserve sound channels, anything above this is reserved. The channel at this index is the highest unreserved channel.
|
||||
var/channel_reserve_high
|
||||
|
||||
/datum/controller/subsystem/sounds/Initialize()
|
||||
setup_available_channels()
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Sets up all available sound channels
|
||||
*/
|
||||
/datum/controller/subsystem/sounds/proc/setup_available_channels()
|
||||
channel_list = list()
|
||||
reserved_channels = list()
|
||||
using_channels = list()
|
||||
using_channels_by_datum = list()
|
||||
for(var/i in 1 to using_channels_max)
|
||||
channel_list += i
|
||||
channel_random_low = 1
|
||||
channel_reserve_high = length(channel_list)
|
||||
|
||||
/**
|
||||
* Removes a channel from using list
|
||||
*
|
||||
* Arguments:
|
||||
* * channel - The channel number
|
||||
*/
|
||||
/datum/controller/subsystem/sounds/proc/free_sound_channel(channel)
|
||||
var/text_channel = num2text(channel)
|
||||
var/using = using_channels[text_channel]
|
||||
using_channels -= text_channel
|
||||
if(using != TRUE) // datum channel
|
||||
using_channels_by_datum[using] -= channel
|
||||
if(!length(using_channels_by_datum[using]))
|
||||
using_channels_by_datum -= using
|
||||
free_channel(channel)
|
||||
|
||||
/**
|
||||
* Frees all the channels a datum is using
|
||||
*
|
||||
* Arguments:
|
||||
* * D - The datum
|
||||
*/
|
||||
/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 -= num2text(channel)
|
||||
free_channel(channel)
|
||||
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()
|
||||
. = reserve_channel()
|
||||
if(!.) // oh no..
|
||||
return FALSE
|
||||
var/text_channel = num2text(.)
|
||||
using_channels[text_channel] = DATUMLESS
|
||||
LAZYADD(using_channels_by_datum[DATUMLESS], .)
|
||||
|
||||
/**
|
||||
* Reserves a channel for a datum. Automatic cleanup only when the datum is deleted.
|
||||
*
|
||||
* Returns an integer for channel
|
||||
* Arguments:
|
||||
* * D - The datum
|
||||
*/
|
||||
/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.")
|
||||
. = reserve_channel()
|
||||
if(!.)
|
||||
return FALSE
|
||||
var/text_channel = num2text(.)
|
||||
using_channels[text_channel] = D
|
||||
LAZYADD(using_channels_by_datum[D], .)
|
||||
|
||||
/**
|
||||
* Reserves a channel and updates the datastructure. Private proc.
|
||||
*/
|
||||
/datum/controller/subsystem/sounds/proc/reserve_channel()
|
||||
PRIVATE_PROC(TRUE)
|
||||
if(channel_reserve_high <= random_channels_min) // out of channels
|
||||
return
|
||||
var/channel = channel_list[channel_reserve_high]
|
||||
reserved_channels[num2text(channel)] = channel_reserve_high--
|
||||
return channel
|
||||
|
||||
/**
|
||||
* Frees a channel and updates the datastructure. Private proc.
|
||||
*/
|
||||
/datum/controller/subsystem/sounds/proc/free_channel(number)
|
||||
PRIVATE_PROC(TRUE)
|
||||
var/text_channel = num2text(number)
|
||||
var/index = reserved_channels[text_channel]
|
||||
if(!index)
|
||||
CRASH("Attempted to (internally) free a channel that wasn't reserved.")
|
||||
reserved_channels -= text_channel
|
||||
// push reserve index up, which makes it now on a channel that is reserved
|
||||
channel_reserve_high++
|
||||
// swap the reserved channel wtih the unreserved channel so the reserve index is now on an unoccupied channel and the freed channel is next to be used.
|
||||
channel_list.Swap(channel_reserve_high, index)
|
||||
// now, an existing reserved channel will likely (exception: unreserving last reserved channel) be at index
|
||||
// get it, and update position.
|
||||
var/text_reserved = num2text(channel_list[index])
|
||||
if(!reserved_channels[text_reserved]) // if it isn't already reserved make sure we don't accidently mistakenly put it on reserved list!
|
||||
return
|
||||
reserved_channels[text_reserved] = index
|
||||
|
||||
/**
|
||||
* Random available channel, returns text
|
||||
*/
|
||||
/datum/controller/subsystem/sounds/proc/random_available_channel_text()
|
||||
if(channel_random_low > channel_reserve_high)
|
||||
channel_random_low = 1
|
||||
. = "[channel_list[channel_random_low++]]"
|
||||
|
||||
/**
|
||||
* Random available channel, returns number
|
||||
*/
|
||||
/datum/controller/subsystem/sounds/proc/random_available_channel()
|
||||
if(channel_random_low > channel_reserve_high)
|
||||
channel_random_low = 1
|
||||
. = channel_list[channel_random_low++]
|
||||
|
||||
/**
|
||||
* How many channels we have left
|
||||
*/
|
||||
/datum/controller/subsystem/sounds/proc/available_channels_left()
|
||||
return length(channel_list) - random_channels_min
|
||||
|
||||
#undef DATUMLESS
|
||||
@@ -232,19 +232,6 @@
|
||||
button.name = name
|
||||
..()
|
||||
|
||||
/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
|
||||
var/chosen = input("Choose the type of instrument you want to use", "Instrument Selection", "piano") as null|anything in synth.insTypes
|
||||
if(!synth.insTypes[chosen])
|
||||
return
|
||||
return synth.changeInstrument(chosen)
|
||||
return ..()
|
||||
|
||||
/datum/action/item_action/vortex_recall
|
||||
name = "Vortex Recall"
|
||||
desc = "Recall yourself, and anyone nearby, to an attuned hierophant beacon at any time.<br>If the beacon is still attached, will detach it."
|
||||
|
||||
58
code/datums/components/spooky.dm
Normal file
58
code/datums/components/spooky.dm
Normal file
@@ -0,0 +1,58 @@
|
||||
/datum/component/spooky
|
||||
var/too_spooky = TRUE //will it spawn a new instrument?
|
||||
|
||||
/datum/component/spooky/Initialize()
|
||||
RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/spectral_attack)
|
||||
|
||||
/datum/component/spooky/proc/spectral_attack(datum/source, mob/living/carbon/C, mob/user)
|
||||
if(ishuman(user)) //this weapon wasn't meant for mortals.
|
||||
var/mob/living/carbon/human/U = user
|
||||
if(!istype(U.dna.species, /datum/species/skeleton))
|
||||
U.adjustStaminaLoss(35) //Extra Damage
|
||||
U.Jitter(35)
|
||||
U.stuttering = 20
|
||||
if(U.getStaminaLoss() > 95)
|
||||
to_chat(U, "<font color='red' size='4'><b>Your ears weren't meant for this spectral sound.</b></font>")
|
||||
spectral_change(U)
|
||||
return
|
||||
|
||||
if(ishuman(C))
|
||||
var/mob/living/carbon/human/H = C
|
||||
if(istype(H.dna.species, /datum/species/skeleton))
|
||||
return //undeads are unaffected by the spook-pocalypse.
|
||||
C.Jitter(35)
|
||||
C.stuttering = 20
|
||||
if(!istype(H.dna.species, /datum/species/diona) && !istype(H.dna.species, /datum/species/machine) && !istype(H.dna.species, /datum/species/slime) && !istype(H.dna.species, /datum/species/golem) && !istype(H.dna.species, /datum/species/plasmaman) && !istype(H.dna.species, /datum/species/skeleton))
|
||||
C.adjustStaminaLoss(25) //boneless humanoids don't lose the will to live
|
||||
to_chat(C, "<font color='red' size='4'><B>DOOT</B></font>")
|
||||
spectral_change(H)
|
||||
|
||||
else //the sound will spook monkeys.
|
||||
C.Jitter(15)
|
||||
C.stuttering = 20
|
||||
|
||||
/datum/component/spooky/proc/spectral_change(mob/living/carbon/human/H, mob/user)
|
||||
if((H.getStaminaLoss() > 95) && (!istype(H.dna.species, /datum/species/diona) && !istype(H.dna.species, /datum/species/machine) && !istype(H.dna.species, /datum/species/slime) && !istype(H.dna.species, /datum/species/golem) && !istype(H.dna.species, /datum/species/plasmaman) && !istype(H.dna.species, /datum/species/skeleton)))
|
||||
H.Stun(20)
|
||||
H.set_species(/datum/species/skeleton)
|
||||
H.visible_message("<span class='warning'>[H] has given up on life as a mortal.</span>")
|
||||
var/T = get_turf(H)
|
||||
if(too_spooky)
|
||||
if(prob(30))
|
||||
new/obj/item/instrument/saxophone/spectral(T)
|
||||
else if(prob(30))
|
||||
new/obj/item/instrument/trumpet/spectral(T)
|
||||
else if(prob(30))
|
||||
new/obj/item/instrument/trombone/spectral(T)
|
||||
else
|
||||
to_chat(H, "<span class='boldwarning'>The spooky gods forgot to ship your instrument. Better luck next unlife.</span>")
|
||||
to_chat(H, "<span class='boldnotice'>You are the spooky skeleton!</span>")
|
||||
to_chat(H, "<span class='boldnotice'>A new life and identity has begun. Help your fellow skeletons into bringing out the spooky-pocalypse. You haven't forgotten your past life, and are still beholden to past loyalties.</span>")
|
||||
change_name(H) //time for a new name!
|
||||
|
||||
/datum/component/spooky/proc/change_name(mob/living/carbon/human/H)
|
||||
var/t = stripped_input(H, "Enter your new skeleton name", H.real_name, null, MAX_NAME_LEN)
|
||||
if(!t)
|
||||
t = "spooky skeleton"
|
||||
H.real_name = t
|
||||
H.name = t
|
||||
@@ -71,7 +71,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]
|
||||
|
||||
@@ -1,374 +0,0 @@
|
||||
#define SONG_HEAR_DISTANCE 15
|
||||
#define SONG_FALLOFF 5
|
||||
#define SONG_MAX_TEMPO 10
|
||||
#define SONG_MAX_LENGTH 60
|
||||
#define SONG_MAX_LINE_LENGTH 200
|
||||
|
||||
/datum/song
|
||||
var/name = "Untitled"
|
||||
var/list/lines
|
||||
var/tempo = 5 // delay between notes
|
||||
|
||||
var/playing = FALSE // if we're playing
|
||||
var/help = FALSE // if help is open
|
||||
var/repeat = 1 // number of times remaining to repeat
|
||||
var/max_repeat = 10 // maximum times we can repeat
|
||||
|
||||
var/instrument_folder = "piano" // the folder with the sounds
|
||||
var/instrument_extension = "ogg" // the file extension
|
||||
var/obj/instrument_obj = null // the associated obj playing the sound
|
||||
|
||||
var/static/list/valid_files[0] // Cache to avoid running fexists() every time
|
||||
|
||||
/datum/song/New(dir, obj, ext = "ogg")
|
||||
lines = new()
|
||||
tempo = sanitize_tempo(tempo)
|
||||
instrument_folder = dir
|
||||
instrument_obj = obj
|
||||
instrument_extension = ext
|
||||
|
||||
/datum/song/Destroy()
|
||||
instrument_obj = null
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Plays a note with the given accent and octave
|
||||
*
|
||||
* Longer detailed paragraph about the proc
|
||||
* including any relevant detail
|
||||
* Arguments:
|
||||
* * note - Number from 1 to 7 for A to G
|
||||
* * acc - Either "b", "n" or "#"
|
||||
* * oct - Number between 1 to 8 (or 9 for C)
|
||||
*/
|
||||
/datum/song/proc/play_note(note, acc, 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/filename = "sound/instruments/[instrument_folder]/[ascii2text(note+64)][acc][oct].[instrument_extension]"
|
||||
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(instrument_obj)
|
||||
var/sound/music_played = sound(soundfile)
|
||||
for(var/A in GLOB.player_list)
|
||||
if(get_dist(A, source) > SONG_HEAR_DISTANCE)
|
||||
continue
|
||||
var/mob/M = A
|
||||
if(!(M.client.prefs.sound & SOUND_INSTRUMENTS))
|
||||
continue
|
||||
M.playsound_local(source, null, 100, falloff = SONG_FALLOFF, S = music_played)
|
||||
|
||||
/**
|
||||
* Returns whether the instrument should play or not
|
||||
*
|
||||
* Arguments:
|
||||
* * user - The current user
|
||||
*/
|
||||
/datum/song/proc/should_stop_playing(mob/user)
|
||||
if(instrument_obj)
|
||||
//if(!user.canUseTopic(instrument_obj))
|
||||
//return 1
|
||||
return !instrument_obj.anchored // add special cases to stop in subclasses
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Plays the song, duh.
|
||||
*
|
||||
* Arguments:
|
||||
* * user - The current user
|
||||
*/
|
||||
/datum/song/proc/play_song(mob/user)
|
||||
while(repeat)
|
||||
var/cur_oct[7]
|
||||
var/cur_acc[7]
|
||||
for(var/i in 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 || should_stop_playing(user)) //If the instrument is playing, or special case
|
||||
playing = FALSE
|
||||
return
|
||||
if(!length(note))
|
||||
continue
|
||||
var/cur_note = text2ascii(note) - 96
|
||||
if(cur_note < 1 || cur_note > 7)
|
||||
continue
|
||||
for(var/i in 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)
|
||||
play_note(cur_note, cur_acc[cur_note], cur_oct[cur_note])
|
||||
if(length(notes) >= 2 && text2num(notes[2]))
|
||||
sleep(sanitize_tempo(tempo / text2num(notes[2])))
|
||||
else
|
||||
sleep(tempo)
|
||||
repeat--
|
||||
playing = FALSE
|
||||
repeat = 0
|
||||
|
||||
/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)
|
||||
if(!instrument_obj)
|
||||
return
|
||||
|
||||
ui = SStgui.try_update_ui(user, instrument_obj, ui_key, ui, force_open)
|
||||
if(!ui)
|
||||
ui = new(user, instrument_obj, ui_key, "Song", instrument_obj.name, 700, 500)
|
||||
ui.open()
|
||||
ui.set_autoupdate(FALSE) // NO!!! Don't auto-update this!!
|
||||
|
||||
/datum/song/tgui_data(mob/user)
|
||||
var/data[0]
|
||||
|
||||
data["lines"] = lines
|
||||
data["tempo"] = tempo
|
||||
|
||||
data["playing"] = playing
|
||||
data["help"] = help
|
||||
data["repeat"] = repeat
|
||||
data["maxRepeat"] = max_repeat
|
||||
data["minTempo"] = world.tick_lag
|
||||
data["maxTempo"] = SONG_MAX_TEMPO
|
||||
data["tickLag"] = world.tick_lag
|
||||
|
||||
return data
|
||||
|
||||
/datum/song/tgui_act(action, params)
|
||||
// We can't check ..() here because src isn't an actual object
|
||||
if(!in_range(instrument_obj, usr) || (issilicon(usr) && instrument_obj.loc != usr) || !isliving(usr) || usr.incapacitated())
|
||||
return
|
||||
|
||||
. = TRUE
|
||||
switch(action)
|
||||
if("newsong")
|
||||
playing = FALSE
|
||||
lines = new()
|
||||
tempo = sanitize_tempo(5) // default 120 BPM
|
||||
name = ""
|
||||
if("import")
|
||||
playing = FALSE
|
||||
var/t = ""
|
||||
do
|
||||
t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message)
|
||||
if(!in_range(instrument_obj, usr))
|
||||
return
|
||||
|
||||
if(length(t) >= SONG_MAX_LENGTH * SONG_MAX_LINE_LENGTH)
|
||||
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(t) > SONG_MAX_LENGTH * SONG_MAX_LINE_LENGTH)
|
||||
|
||||
INVOKE_ASYNC(src, .proc/process_import, t)
|
||||
if("help")
|
||||
help = !help
|
||||
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(repeat + round(text2num(params["num"])), 0, max_repeat)
|
||||
if("tempo")
|
||||
var/new_tempo = text2num(params["new"])
|
||||
if(new_tempo <= 0)
|
||||
return
|
||||
tempo = sanitize_tempo(new_tempo)
|
||||
if("play")
|
||||
if(playing)
|
||||
return
|
||||
playing = TRUE
|
||||
INVOKE_ASYNC(src, .proc/play_song, usr)
|
||||
if("insertline")
|
||||
var/num = round(text2num(params["line"]))
|
||||
if(num <= 0)
|
||||
return
|
||||
if(length(lines) > SONG_MAX_LINE_LENGTH)
|
||||
return
|
||||
var/newline = html_encode(input("Enter your line: ", instrument_obj.name) as text|null)
|
||||
if(!newline || !in_range(instrument_obj, usr))
|
||||
return
|
||||
if(length(newline) > SONG_MAX_LINE_LENGTH)
|
||||
newline = copytext(newline, 1, SONG_MAX_LINE_LENGTH)
|
||||
lines.Insert(num, newline)
|
||||
if("deleteline")
|
||||
var/num = round(text2num(params["line"]))
|
||||
if(num <= 0 || num > length(lines))
|
||||
return
|
||||
lines.Cut(num, num + 1)
|
||||
if("modifyline")
|
||||
var/num = round(text2num(params["line"]))
|
||||
var/content = html_encode(input("Enter your line: ", instrument_obj.name, lines[num]) as text|null)
|
||||
if(!content || !in_range(instrument_obj, usr))
|
||||
return
|
||||
if(num <= 0 || num > length(lines))
|
||||
return
|
||||
if(length(content) > SONG_MAX_LINE_LENGTH)
|
||||
content = copytext(content, 1, SONG_MAX_LINE_LENGTH)
|
||||
lines[num] = content
|
||||
if("stop")
|
||||
playing = FALSE
|
||||
else
|
||||
return FALSE
|
||||
instrument_obj.add_fingerprint(usr)
|
||||
|
||||
/**
|
||||
* Processes a multi-line text into playable lines
|
||||
*
|
||||
* Arguments:
|
||||
* * text - Text to process
|
||||
*/
|
||||
/datum/song/proc/process_import(text)
|
||||
lines = splittext(text, "\n")
|
||||
if(!length(lines))
|
||||
return
|
||||
if(copytext(lines[1], 1, 6) == "BPM: ")
|
||||
tempo = sanitize_tempo(600 / text2num(copytext(lines[1], 6)))
|
||||
lines.Cut(1, 2)
|
||||
else
|
||||
tempo = sanitize_tempo(5) // default 120 BPM
|
||||
if(length(lines) > SONG_MAX_LINE_LENGTH)
|
||||
to_chat(usr, "Too many lines!")
|
||||
lines.Cut(201)
|
||||
var/linenum = 1
|
||||
for(var/l in lines)
|
||||
if(length(l) > SONG_MAX_LINE_LENGTH)
|
||||
to_chat(usr, "Line [linenum] too long!")
|
||||
lines.Remove(l)
|
||||
else
|
||||
linenum++
|
||||
SStgui.update_uis(instrument_obj)
|
||||
|
||||
/**
|
||||
* Sanitizes a tempo in accordance with world.tick_lag
|
||||
*
|
||||
* Arguments:
|
||||
* * new_tempo - The tempo to sanitize
|
||||
*/
|
||||
/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/should_stop_playing()
|
||||
if(instrument_obj)
|
||||
return !isliving(instrument_obj.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/New()
|
||||
..()
|
||||
song = new("piano", src)
|
||||
|
||||
if(prob(50))
|
||||
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_NULL(song)
|
||||
return ..()
|
||||
|
||||
/obj/structure/piano/Initialize()
|
||||
if(song)
|
||||
song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded
|
||||
..()
|
||||
|
||||
/obj/structure/piano/attack_hand(mob/user)
|
||||
tgui_interact(user)
|
||||
|
||||
/obj/structure/piano/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)
|
||||
if(!isliving(user) || user.incapacitated() || !anchored)
|
||||
return
|
||||
|
||||
song.tgui_interact(user, ui_key, ui, force_open)
|
||||
|
||||
/obj/structure/piano/tgui_data(mob/user)
|
||||
return song.tgui_data(user)
|
||||
|
||||
/obj/structure/piano/tgui_act(action, params)
|
||||
if(..())
|
||||
return
|
||||
return song.tgui_act(action, params)
|
||||
|
||||
/obj/structure/piano/wrench_act(mob/user, obj/item/I)
|
||||
. = TRUE
|
||||
if(!I.tool_use_check(user, 0))
|
||||
return
|
||||
if(!anchored && !isinspace())
|
||||
WRENCH_ANCHOR_MESSAGE
|
||||
if(!I.use_tool(src, user, 20, volume = I.tool_volume))
|
||||
return
|
||||
user.visible_message( \
|
||||
"[user] tightens [src]'s casters.", \
|
||||
"<span class='notice'> You have tightened [src]'s casters. Now it can be played again.</span>", \
|
||||
"You hear ratchet.")
|
||||
anchored = TRUE
|
||||
else if(anchored)
|
||||
to_chat(user, "<span class='notice'> You begin to loosen [src]'s casters...</span>")
|
||||
if(!I.use_tool(src, user, 40, volume = I.tool_volume))
|
||||
return
|
||||
user.visible_message( \
|
||||
"[user] loosens [src]'s casters.", \
|
||||
"<span class='notice'> You have loosened [src]. Now it can be pulled somewhere else.</span>", \
|
||||
"You hear ratchet.")
|
||||
anchored = FALSE
|
||||
else
|
||||
to_chat(user, "<span class='warning'>[src] needs to be bolted to the floor!</span>")
|
||||
|
||||
#undef SONG_HEAR_DISTANCE
|
||||
#undef SONG_FALLOFF
|
||||
#undef SONG_MAX_TEMPO
|
||||
#undef SONG_MAX_LENGTH
|
||||
#undef SONG_MAX_LINE_LENGTH
|
||||
@@ -4,12 +4,13 @@
|
||||
return
|
||||
|
||||
var/turf/turf_source = get_turf(source)
|
||||
|
||||
if(!turf_source)
|
||||
return
|
||||
if(!SSsounds.channel_list) // Not ready yet
|
||||
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))
|
||||
@@ -33,7 +34,7 @@
|
||||
if(distance <= maxdistance)
|
||||
M.playsound_local(turf_source, soundin, vol, vary, frequency, falloff, channel, pressure_affected, S)
|
||||
|
||||
/mob/proc/playsound_local(turf/turf_source, soundin, vol as num, vary, frequency, falloff, channel = 0, pressure_affected = TRUE, sound/S)
|
||||
/mob/proc/playsound_local(turf/turf_source, soundin, vol as num, vary, frequency, falloff, channel = 0, pressure_affected = TRUE, sound/S, distance_multiplier = 1)
|
||||
if(!client || !can_hear())
|
||||
return
|
||||
|
||||
@@ -41,7 +42,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
|
||||
|
||||
if(vary)
|
||||
@@ -55,6 +56,7 @@
|
||||
|
||||
//sound volume falloff with distance
|
||||
var/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.
|
||||
|
||||
@@ -81,9 +83,9 @@
|
||||
return //No sound
|
||||
|
||||
var/dx = turf_source.x - T.x // Hearing from the right/left
|
||||
S.x = dx
|
||||
S.x = dx * distance_multiplier
|
||||
var/dz = turf_source.y - T.y // Hearing from infront/behind
|
||||
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)
|
||||
@@ -98,15 +100,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()
|
||||
if(!SSticker || !SSticker.login_music || config.disable_lobby_music)
|
||||
return
|
||||
|
||||
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()
|
||||
@@ -1,57 +1,10 @@
|
||||
//copy pasta of the space piano, don't hurt me -Pete
|
||||
/obj/item/instrument
|
||||
name = "generic instrument"
|
||||
icon = 'icons/obj/musician.dmi'
|
||||
lefthand_file = 'icons/mob/inhands/equipment/instruments_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/equipment/instruments_righthand.dmi'
|
||||
resistance_flags = FLAMMABLE
|
||||
max_integrity = 100
|
||||
var/datum/song/handheld/song
|
||||
var/instrument_id = "generic"
|
||||
var/instrument_extension = "mid"
|
||||
|
||||
/obj/item/instrument/New()
|
||||
song = new(instrument_id, src, instrument_extension)
|
||||
..()
|
||||
|
||||
/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/Initialize(mapload)
|
||||
song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded
|
||||
..()
|
||||
|
||||
/obj/item/instrument/attack_self(mob/user)
|
||||
tgui_interact(user)
|
||||
|
||||
/obj/item/instrument/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)
|
||||
if(!isliving(user) || user.incapacitated())
|
||||
return
|
||||
|
||||
song.tgui_interact(user, ui_key, ui, force_open)
|
||||
|
||||
/obj/item/instrument/tgui_data(mob/user)
|
||||
return song.tgui_data(user)
|
||||
|
||||
/obj/item/instrument/tgui_act(action, params)
|
||||
if(..())
|
||||
return
|
||||
return song.tgui_act(action, params)
|
||||
|
||||
/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"
|
||||
instrument_extension = "ogg"
|
||||
force = 10
|
||||
hitsound = "swing_hit"
|
||||
instrument_id = "violin"
|
||||
allowed_instrument_ids = "violin"
|
||||
|
||||
/obj/item/instrument/violin/golden
|
||||
name = "golden violin"
|
||||
@@ -65,87 +18,146 @@
|
||||
desc = "An advanced electronic synthesizer that can be used as various instruments."
|
||||
icon_state = "synth"
|
||||
item_state = "synth"
|
||||
instrument_id = "piano"
|
||||
instrument_extension = "ogg"
|
||||
var/static/list/insTypes = list("accordion" = "mid", "glockenspiel" = "mid", "guitar" = "ogg", "eguitar" = "ogg", "harmonica" = "mid", "piano" = "ogg", "recorder" = "mid", "saxophone" = "mid", "trombone" = "mid", "violin" = "ogg", "xylophone" = "mid")
|
||||
actions_types = list(/datum/action/item_action/synthswitch)
|
||||
allowed_instrument_ids = "piano"
|
||||
|
||||
/obj/item/instrument/piano_synth/proc/changeInstrument(name = "piano")
|
||||
song.instrument_folder = name
|
||||
song.instrument_extension = insTypes[name]
|
||||
/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"
|
||||
instrument_extension = "ogg"
|
||||
force = 10
|
||||
attack_verb = list("played metal on", "serenaded", "crashed", "smashed")
|
||||
hitsound = 'sound/effects/guitarsmash.ogg'
|
||||
instrument_id = "guitar"
|
||||
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"
|
||||
instrument_extension = "ogg"
|
||||
force = 12
|
||||
attack_verb = list("played metal on", "shredded", "crashed", "smashed")
|
||||
hitsound = 'sound/weapons/stringsmash.ogg'
|
||||
instrument_id = "eguitar"
|
||||
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"
|
||||
instrument_id = "glockenspiel"
|
||||
allowed_instrument_ids = "glockenspiel"
|
||||
|
||||
/obj/item/instrument/accordion
|
||||
name = "accordion"
|
||||
desc = "Pun-Pun not included."
|
||||
icon_state = "accordion"
|
||||
item_state = "accordion"
|
||||
instrument_id = "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"
|
||||
instrument_id = "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"
|
||||
instrument_id = "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"
|
||||
instrument_id = "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"
|
||||
instrument_id = "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."
|
||||
desc = "A percussion instrument with a bright tone."
|
||||
icon_state = "xylophone"
|
||||
item_state = "xylophone"
|
||||
instrument_id = "xylophone"
|
||||
allowed_instrument_ids = "bikehorn"
|
||||
|
||||
/obj/item/instrument/bikehorn
|
||||
name = "gilded bike horn"
|
||||
@@ -155,14 +167,14 @@
|
||||
lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/items_righthand.dmi'
|
||||
attack_verb = list("beautifully honks")
|
||||
instrument_id = "bikehorn"
|
||||
instrument_extension = "ogg"
|
||||
w_class = WEIGHT_CLASS_TINY
|
||||
force = 0
|
||||
throw_speed = 3
|
||||
throw_range = 7
|
||||
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
|
||||
@@ -170,7 +182,7 @@
|
||||
/obj/item/stack/cable_coil = 6,
|
||||
/obj/item/stack/tape_roll = 5)
|
||||
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
|
||||
time = 80
|
||||
time = 8 SECONDS
|
||||
category = CAT_MISC
|
||||
|
||||
/datum/crafting_recipe/guitar
|
||||
@@ -180,7 +192,7 @@
|
||||
/obj/item/stack/cable_coil = 6,
|
||||
/obj/item/stack/tape_roll = 5)
|
||||
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
|
||||
time = 80
|
||||
time = 8 SECONDS
|
||||
category = CAT_MISC
|
||||
|
||||
/datum/crafting_recipe/eguitar
|
||||
@@ -190,5 +202,5 @@
|
||||
/obj/item/stack/cable_coil = 6,
|
||||
/obj/item/stack/tape_roll = 5)
|
||||
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
|
||||
time = 80
|
||||
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
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 23 KiB |
24
paradise.dme
24
paradise.dme
@@ -40,6 +40,7 @@
|
||||
#include "code\__DEFINES\genetics.dm"
|
||||
#include "code\__DEFINES\hud.dm"
|
||||
#include "code\__DEFINES\hydroponics.dm"
|
||||
#include "code\__DEFINES\instruments.dm"
|
||||
#include "code\__DEFINES\inventory.dm"
|
||||
#include "code\__DEFINES\is_helpers.dm"
|
||||
#include "code\__DEFINES\job.dm"
|
||||
@@ -241,6 +242,7 @@
|
||||
#include "code\controllers\subsystem\parallax.dm"
|
||||
#include "code\controllers\subsystem\radio.dm"
|
||||
#include "code\controllers\subsystem\shuttles.dm"
|
||||
#include "code\controllers\subsystem\sounds.dm"
|
||||
#include "code\controllers\subsystem\spacedrift.dm"
|
||||
#include "code\controllers\subsystem\statistics.dm"
|
||||
#include "code\controllers\subsystem\sun.dm"
|
||||
@@ -252,6 +254,7 @@
|
||||
#include "code\controllers\subsystem\vote.dm"
|
||||
#include "code\controllers\subsystem\weather.dm"
|
||||
#include "code\controllers\subsystem\processing\fastprocess.dm"
|
||||
#include "code\controllers\subsystem\processing\instruments.dm"
|
||||
#include "code\controllers\subsystem\processing\obj.dm"
|
||||
#include "code\controllers\subsystem\processing\processing.dm"
|
||||
#include "code\controllers\subsystem\tickets\mentor_tickets.dm"
|
||||
@@ -305,6 +308,7 @@
|
||||
#include "code\datums\components\paintable.dm"
|
||||
#include "code\datums\components\slippery.dm"
|
||||
#include "code\datums\components\spawner.dm"
|
||||
#include "code\datums\components\spooky.dm"
|
||||
#include "code\datums\components\squeak.dm"
|
||||
#include "code\datums\components\swarming.dm"
|
||||
#include "code\datums\diseases\_disease.dm"
|
||||
@@ -879,7 +883,6 @@
|
||||
#include "code\game\objects\items\devices\flashlight.dm"
|
||||
#include "code\game\objects\items\devices\floor_painter.dm"
|
||||
#include "code\game\objects\items\devices\handheld_defib.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\machineprototype.dm"
|
||||
@@ -1084,7 +1087,6 @@
|
||||
#include "code\game\objects\structures\misc.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\plasticflaps.dm"
|
||||
#include "code\game\objects\structures\reflector.dm"
|
||||
@@ -1636,6 +1638,24 @@
|
||||
#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.dm"
|
||||
#include "code\modules\instruments\_instrument_key.dm"
|
||||
#include "code\modules\instruments\brass.dm"
|
||||
#include "code\modules\instruments\chromatic_percussion.dm"
|
||||
#include "code\modules\instruments\fun.dm"
|
||||
#include "code\modules\instruments\guitar.dm"
|
||||
#include "code\modules\instruments\hardcoded.dm"
|
||||
#include "code\modules\instruments\organ.dm"
|
||||
#include "code\modules\instruments\piano.dm"
|
||||
#include "code\modules\instruments\synth_tones.dm"
|
||||
#include "code\modules\instruments\objs\items\_instrument.dm"
|
||||
#include "code\modules\instruments\objs\items\instruments.dm"
|
||||
#include "code\modules\instruments\objs\structures\_musician.dm"
|
||||
#include "code\modules\instruments\objs\structures\piano.dm"
|
||||
#include "code\modules\instruments\songs\_song.dm"
|
||||
#include "code\modules\instruments\songs\_song_ui.dm"
|
||||
#include "code\modules\instruments\songs\play_legacy.dm"
|
||||
#include "code\modules\instruments\songs\play_synthesized.dm"
|
||||
#include "code\modules\karma\karma.dm"
|
||||
#include "code\modules\keybindings\bindings_admin.dm"
|
||||
#include "code\modules\keybindings\bindings_ai.dm"
|
||||
|
||||
BIN
sound/instruments/banjo/Ab3.ogg
Normal file
BIN
sound/instruments/banjo/Ab3.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Ab4.ogg
Normal file
BIN
sound/instruments/banjo/Ab4.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Ab5.ogg
Normal file
BIN
sound/instruments/banjo/Ab5.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/An3.ogg
Normal file
BIN
sound/instruments/banjo/An3.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/An4.ogg
Normal file
BIN
sound/instruments/banjo/An4.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/An5.ogg
Normal file
BIN
sound/instruments/banjo/An5.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Bb3.ogg
Normal file
BIN
sound/instruments/banjo/Bb3.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Bb4.ogg
Normal file
BIN
sound/instruments/banjo/Bb4.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Bb5.ogg
Normal file
BIN
sound/instruments/banjo/Bb5.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Bn2.ogg
Normal file
BIN
sound/instruments/banjo/Bn2.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Bn3.ogg
Normal file
BIN
sound/instruments/banjo/Bn3.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Bn4.ogg
Normal file
BIN
sound/instruments/banjo/Bn4.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Bn5.ogg
Normal file
BIN
sound/instruments/banjo/Bn5.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Cn3.ogg
Normal file
BIN
sound/instruments/banjo/Cn3.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Cn4.ogg
Normal file
BIN
sound/instruments/banjo/Cn4.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Cn5.ogg
Normal file
BIN
sound/instruments/banjo/Cn5.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Cn6.ogg
Normal file
BIN
sound/instruments/banjo/Cn6.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Db3.ogg
Normal file
BIN
sound/instruments/banjo/Db3.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Db4.ogg
Normal file
BIN
sound/instruments/banjo/Db4.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Db5.ogg
Normal file
BIN
sound/instruments/banjo/Db5.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Db6.ogg
Normal file
BIN
sound/instruments/banjo/Db6.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Dn3.ogg
Normal file
BIN
sound/instruments/banjo/Dn3.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Dn4.ogg
Normal file
BIN
sound/instruments/banjo/Dn4.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Dn5.ogg
Normal file
BIN
sound/instruments/banjo/Dn5.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Dn6.ogg
Normal file
BIN
sound/instruments/banjo/Dn6.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Eb3.ogg
Normal file
BIN
sound/instruments/banjo/Eb3.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Eb4.ogg
Normal file
BIN
sound/instruments/banjo/Eb4.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Eb5.ogg
Normal file
BIN
sound/instruments/banjo/Eb5.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/En3.ogg
Normal file
BIN
sound/instruments/banjo/En3.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/En4.ogg
Normal file
BIN
sound/instruments/banjo/En4.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/En5.ogg
Normal file
BIN
sound/instruments/banjo/En5.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Fn3.ogg
Normal file
BIN
sound/instruments/banjo/Fn3.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Fn4.ogg
Normal file
BIN
sound/instruments/banjo/Fn4.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Fn5.ogg
Normal file
BIN
sound/instruments/banjo/Fn5.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Gb3.ogg
Normal file
BIN
sound/instruments/banjo/Gb3.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Gb4.ogg
Normal file
BIN
sound/instruments/banjo/Gb4.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Gb5.ogg
Normal file
BIN
sound/instruments/banjo/Gb5.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Gn3.ogg
Normal file
BIN
sound/instruments/banjo/Gn3.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Gn4.ogg
Normal file
BIN
sound/instruments/banjo/Gn4.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/banjo/Gn5.ogg
Normal file
BIN
sound/instruments/banjo/Gn5.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/synthesis_samples/brass/crisis_brass/c2.ogg
Normal file
BIN
sound/instruments/synthesis_samples/brass/crisis_brass/c2.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/synthesis_samples/brass/crisis_brass/c3.ogg
Normal file
BIN
sound/instruments/synthesis_samples/brass/crisis_brass/c3.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/synthesis_samples/brass/crisis_brass/c4.ogg
Normal file
BIN
sound/instruments/synthesis_samples/brass/crisis_brass/c4.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/synthesis_samples/brass/crisis_brass/c5.ogg
Normal file
BIN
sound/instruments/synthesis_samples/brass/crisis_brass/c5.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/synthesis_samples/brass/crisis_trombone/C2.ogg
Normal file
BIN
sound/instruments/synthesis_samples/brass/crisis_trombone/C2.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/synthesis_samples/brass/crisis_trombone/C3.ogg
Normal file
BIN
sound/instruments/synthesis_samples/brass/crisis_trombone/C3.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/synthesis_samples/brass/crisis_trombone/C4.ogg
Normal file
BIN
sound/instruments/synthesis_samples/brass/crisis_trombone/C4.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/synthesis_samples/brass/crisis_trombone/C5.ogg
Normal file
BIN
sound/instruments/synthesis_samples/brass/crisis_trombone/C5.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/synthesis_samples/brass/crisis_trumpet/C4.ogg
Normal file
BIN
sound/instruments/synthesis_samples/brass/crisis_trumpet/C4.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/synthesis_samples/brass/crisis_trumpet/C5.ogg
Normal file
BIN
sound/instruments/synthesis_samples/brass/crisis_trumpet/C5.ogg
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
sound/instruments/synthesis_samples/chromatic/sgmbox/c2.ogg
Normal file
BIN
sound/instruments/synthesis_samples/chromatic/sgmbox/c2.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/synthesis_samples/chromatic/sgmbox/c3.ogg
Normal file
BIN
sound/instruments/synthesis_samples/chromatic/sgmbox/c3.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/synthesis_samples/chromatic/sgmbox/c4.ogg
Normal file
BIN
sound/instruments/synthesis_samples/chromatic/sgmbox/c4.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/synthesis_samples/chromatic/sgmbox/c5.ogg
Normal file
BIN
sound/instruments/synthesis_samples/chromatic/sgmbox/c5.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/synthesis_samples/chromatic/vibraphone1/c2.ogg
Normal file
BIN
sound/instruments/synthesis_samples/chromatic/vibraphone1/c2.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/synthesis_samples/chromatic/vibraphone1/c3.ogg
Normal file
BIN
sound/instruments/synthesis_samples/chromatic/vibraphone1/c3.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/synthesis_samples/chromatic/vibraphone1/c4.ogg
Normal file
BIN
sound/instruments/synthesis_samples/chromatic/vibraphone1/c4.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/synthesis_samples/chromatic/vibraphone1/c5.ogg
Normal file
BIN
sound/instruments/synthesis_samples/chromatic/vibraphone1/c5.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/synthesis_samples/guitar/crisis_clean/C2.ogg
Normal file
BIN
sound/instruments/synthesis_samples/guitar/crisis_clean/C2.ogg
Normal file
Binary file not shown.
BIN
sound/instruments/synthesis_samples/guitar/crisis_clean/C3.ogg
Normal file
BIN
sound/instruments/synthesis_samples/guitar/crisis_clean/C3.ogg
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user