Ports musical instruments from /tg/

This commit is contained in:
Chompstation Bot
2021-06-01 20:29:51 +00:00
parent 151d317f5c
commit eb03a53185
182 changed files with 2552 additions and 65 deletions

View File

@@ -6,6 +6,10 @@
#define SEND_GLOBAL_SIGNAL(sigtype, arguments...) ( SEND_SIGNAL(SSdcs, sigtype, ##arguments) )
/// Signifies that this proc is used to handle signals.
/// Every proc you pass to RegisterSignal must have this.
#define SIGNAL_HANDLER SHOULD_NOT_SLEEP(TRUE)
/// A wrapper for _AddElement that allows us to pretend we're using normal named arguments
#define AddElement(arguments...) _AddElement(list(##arguments))
/// A wrapper for _RemoveElement that allows us to pretend we're using normal named arguments

View File

@@ -0,0 +1,24 @@
#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
/// 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

View File

@@ -423,6 +423,7 @@ GLOBAL_LIST_EMPTY(##LIST_NAME);\
#define VOLUME_CHANNEL_ALARMS "Alarms"
#define VOLUME_CHANNEL_VORE "Vore"
#define VOLUME_CHANNEL_DOORS "Doors"
#define VOLUME_CHANNEL_INSTRUMENTS "Instruments"
// Make sure you update this or clients won't be able to adjust the channel
GLOBAL_LIST_INIT(all_volume_channels, list(
@@ -431,6 +432,7 @@ GLOBAL_LIST_INIT(all_volume_channels, list(
VOLUME_CHANNEL_ALARMS,
VOLUME_CHANNEL_VORE,
VOLUME_CHANNEL_DOORS,
VOLUME_CHANNEL_INSTRUMENTS
))
#define APPEARANCECHANGER_CHANGED_RACE "Race"

View File

@@ -1,3 +1,4 @@
<<<<<<< HEAD
//max channel is 1024. Only go lower from here, because byond tends to pick the first availiable channel to play sounds on
#define CHANNEL_LOBBYMUSIC 1024
#define CHANNEL_ADMIN 1023
@@ -233,3 +234,456 @@
'sound/ambience/engineering/engineering3.ogg',\
'sound/ambience/atmospherics/atmospherics1.ogg'\
)
||||||| parent of db274375dc... Merge pull request #10518 from VOREStation/Arokha/instrumental
//max channel is 1024. Only go lower from here, because byond tends to pick the first availiable channel to play sounds on
#define CHANNEL_LOBBYMUSIC 1024
#define CHANNEL_ADMIN 1023
#define CHANNEL_VOX 1022
#define CHANNEL_JUKEBOX 1021
#define CHANNEL_HEARTBEAT 1020 //sound channel for heartbeats
#define CHANNEL_AMBIENCE_FORCED 1019
#define CHANNEL_AMBIENCE 1018
#define CHANNEL_BUZZ 1017
#define CHANNEL_BICYCLE 1016
#define CHANNEL_PREYLOOP 1015 //VORESTATION ADD - Fancy Sound Loop channel
//THIS SHOULD ALWAYS BE THE LOWEST ONE!
//KEEP IT UPDATED
#define CHANNEL_HIGHEST_AVAILABLE 1014 //VORESTATION EDIT - Fancy Sound Loop channel from 1015
#define SOUND_MINIMUM_PRESSURE 10
#define FALLOFF_SOUNDS 0.5
//Sound environment defines. Reverb preset for sounds played in an area, see sound datum reference for more.
#define GENERIC 0
#define PADDED_CELL 1
#define ROOM 2
#define BATHROOM 3
#define LIVINGROOM 4
#define STONEROOM 5
#define AUDITORIUM 6
#define CONCERT_HALL 7
#define CAVE 8
#define ARENA 9
#define HANGAR 10
#define CARPETED_HALLWAY 11
#define HALLWAY 12
#define STONE_CORRIDOR 13
#define ALLEY 14
#define FOREST 15
#define CITY 16
#define MOUNTAINS 17
#define QUARRY 18
#define PLAIN 19
#define PARKING_LOT 20
#define SEWER_PIPE 21
#define UNDERWATER 22
#define DRUGGED 23
#define DIZZY 24
#define PSYCHOTIC 25
#define STANDARD_STATION STONEROOM
#define LARGE_ENCLOSED HANGAR
#define SMALL_ENCLOSED BATHROOM
#define TUNNEL_ENCLOSED CAVE
#define LARGE_SOFTFLOOR CARPETED_HALLWAY
#define MEDIUM_SOFTFLOOR LIVINGROOM
#define SMALL_SOFTFLOOR ROOM
#define ASTEROID CAVE
#define SPACE UNDERWATER
// Ambience presets.
// All you need to do to make an area play one of these is set their ambience var to one of these lists.
// You can even combine them by adding them together, since they're just lists, however you'd have to do that in initialization.
// For weird alien places like the crashed UFO.
#define AMBIENCE_OTHERWORLDLY list(\
'sound/ambience/otherworldly/otherworldly1.ogg',\
'sound/ambience/otherworldly/otherworldly2.ogg',\
'sound/ambience/otherworldly/otherworldly3.ogg'\
)
// Restricted, military, or mercenary aligned locations like the armory, the merc ship/base, BSD, etc.
#define AMBIENCE_HIGHSEC list(\
'sound/ambience/highsec/highsec1.ogg',\
'sound/ambience/highsec/highsec2.ogg',\
'sound/ambience/highsec/highsec3.ogg',\
'sound/ambience/highsec/highsec4.ogg'\
)
// Ruined structures found on the surface or in the caves.
#define AMBIENCE_RUINS list(\
'sound/ambience/ruins/ruins1.ogg',\
'sound/ambience/ruins/ruins2.ogg',\
'sound/ambience/ruins/ruins3.ogg',\
'sound/ambience/ruins/ruins4.ogg',\
'sound/ambience/ruins/ruins5.ogg',\
'sound/ambience/ruins/ruins6.ogg'\
)
// Similar to the above, but for more technology/signaling based ruins.
#define AMBIENCE_TECH_RUINS list(\
'sound/ambience/tech_ruins/tech_ruins1.ogg',\
'sound/ambience/tech_ruins/tech_ruins2.ogg',\
'sound/ambience/tech_ruins/tech_ruins3.ogg'\
)
// The actual chapel room, and maybe some other places of worship.
#define AMBIENCE_CHAPEL list(\
'sound/ambience/chapel/chapel1.ogg',\
'sound/ambience/chapel/chapel2.ogg',\
'sound/ambience/chapel/chapel3.ogg',\
'sound/ambience/chapel/chapel4.ogg'\
)
// For peaceful, serene areas, distinct from the Chapel.
#define AMBIENCE_HOLY list(\
'sound/ambience/holy/holy1.ogg',\
'sound/ambience/holy/holy2.ogg'\
)
// Generic sounds for less special rooms.
#define AMBIENCE_GENERIC list(\
'sound/ambience/generic/generic1.ogg',\
'sound/ambience/generic/generic2.ogg',\
'sound/ambience/generic/generic3.ogg'\
)
// 'sound/ambience/generic/generic4.ogg'
// Sounds of PA announcements, presumably involving shuttles?
#define AMBIENCE_ARRIVALS list(\
'sound/ambience/arrivals/arrivals1.ogg',\
'sound/ambience/arrivals/arrivals2.ogg',\
'sound/ambience/arrivals/arrivals3.ogg',\
'sound/ambience/arrivals/arrivals4.ogg',\
'sound/ambience/arrivals/arrivals5.ogg',\
'sound/ambience/arrivals/arrivals6.ogg',\
'sound/ambience/arrivals/arrivals7.ogg'\
)
// Sounds suitable for being inside dark, tight corridors in the underbelly of the station.
#define AMBIENCE_MAINTENANCE list(\
'sound/ambience/maintenance/maintenance1.ogg',\
'sound/ambience/maintenance/maintenance2.ogg',\
'sound/ambience/maintenance/maintenance3.ogg',\
'sound/ambience/maintenance/maintenance4.ogg',\
'sound/ambience/maintenance/maintenance5.ogg',\
'sound/ambience/maintenance/maintenance6.ogg',\
'sound/ambience/maintenance/maintenance7.ogg',\
'sound/ambience/maintenance/maintenance8.ogg',\
'sound/ambience/maintenance/maintenance9.ogg'\
)
// Life support machinery at work, keeping everyone breathing.
#define AMBIENCE_ENGINEERING list(\
'sound/ambience/engineering/engineering1.ogg',\
'sound/ambience/engineering/engineering2.ogg',\
'sound/ambience/engineering/engineering3.ogg'\
)
// Creepy AI/borg stuff.
#define AMBIENCE_AI list(\
'sound/ambience/ai/ai1.ogg',\
'sound/ambience/ai/ai2.ogg',\
'sound/ambience/ai/ai3.ogg'\
)
// Peaceful sounds when floating in the void.
#define AMBIENCE_SPACE list(\
'sound/ambience/space/space_serithi.ogg',\
'sound/ambience/space/space1.ogg'\
)
// Vaguely spooky sounds when around dead things.
#define AMBIENCE_GHOSTLY list(\
'sound/ambience/ghostly/ghostly1.ogg',\
'sound/ambience/ghostly/ghostly2.ogg'\
)
// Concerning sounds, for when one discovers something horrible happened in a PoI.
#define AMBIENCE_FOREBODING list(\
'sound/ambience/foreboding/foreboding1.ogg',\
'sound/ambience/foreboding/foreboding2.ogg',\
'sound/ambience/foreboding/foreboding3.ogg',\
'sound/ambience/foreboding/foreboding4.ogg',\
'sound/ambience/foreboding/foreboding5.ogg',\
'sound/ambience/foreboding/foreboding6.ogg'\
)
// Ambience heard when aboveground on Sif and not in a Point of Interest.
#define AMBIENCE_SIF list(\
'sound/ambience/sif/sif1.ogg'\
)
// If we ever add geothermal PoIs or other places that are really hot, this will do.
#define AMBIENCE_LAVA list(\
'sound/ambience/lava/lava1.ogg'\
)
// Cult-y ambience, for some PoIs, and maybe when the cultists darken the world with the ritual.
#define AMBIENCE_UNHOLY list(\
'sound/ambience/unholy/unholy1.ogg'\
)
// For the memes.
#define AMBIENCE_AESTHETIC list(\
'sound/ambience/vaporwave.ogg'\
)
#define AMBIENCE_OUTPOST list(\
'sound/ambience/expoutpost/expoutpost1.ogg',\
'sound/ambience/expoutpost/expoutpost2.ogg',\
'sound/ambience/expoutpost/expoutpost3.ogg',\
'sound/ambience/expoutpost/expoutpost4.ogg'\
)
#define AMBIENCE_SUBSTATION list(\
'sound/ambience/substation/substation1.ogg',\
'sound/ambience/substation/substation2.ogg'\
)
#define AMBIENCE_HANGAR list(\
'sound/ambience/hangar/hangar1.ogg',\
'sound/ambience/hangar/hangar2.ogg',\
'sound/ambience/hangar/hangar3.ogg',\
'sound/ambience/hangar/hangar4.ogg',\
'sound/ambience/hangar/hangar5.ogg',\
'sound/ambience/hangar/hangar6.ogg'\
)
#define AMBIENCE_ATMOS list(\
'sound/ambience/engineering/engineering1.ogg',\
'sound/ambience/engineering/engineering2.ogg',\
'sound/ambience/engineering/engineering3.ogg',\
'sound/ambience/atmospherics/atmospherics1.ogg'\
)
=======
//max channel is 1024. Only go lower from here, because byond tends to pick the first availiable channel to play sounds on
#define CHANNEL_LOBBYMUSIC 1024
#define CHANNEL_ADMIN 1023
#define CHANNEL_VOX 1022
#define CHANNEL_JUKEBOX 1021
#define CHANNEL_HEARTBEAT 1020 //sound channel for heartbeats
#define CHANNEL_AMBIENCE_FORCED 1019
#define CHANNEL_AMBIENCE 1018
#define CHANNEL_BUZZ 1017
#define CHANNEL_BICYCLE 1016
#define CHANNEL_PREYLOOP 1015 //VORESTATION ADD - Fancy Sound Loop channel
//THIS SHOULD ALWAYS BE THE LOWEST ONE!
//KEEP IT UPDATED
#define CHANNEL_HIGHEST_AVAILABLE 1014 //VORESTATION EDIT - Fancy Sound Loop channel from 1015
#define SOUND_MINIMUM_PRESSURE 10
#define FALLOFF_SOUNDS 0.5
#define MAX_INSTRUMENT_CHANNELS (128 * 6)
//default byond sound environments
#define SOUND_ENVIRONMENT_NONE -1
#define SOUND_ENVIRONMENT_GENERIC 0
#define SOUND_ENVIRONMENT_PADDED_CELL 1
#define SOUND_ENVIRONMENT_ROOM 2
#define SOUND_ENVIRONMENT_BATHROOM 3
#define SOUND_ENVIRONMENT_LIVINGROOM 4
#define SOUND_ENVIRONMENT_STONEROOM 5
#define SOUND_ENVIRONMENT_AUDITORIUM 6
#define SOUND_ENVIRONMENT_CONCERT_HALL 7
#define SOUND_ENVIRONMENT_CAVE 8
#define SOUND_ENVIRONMENT_ARENA 9
#define SOUND_ENVIRONMENT_HANGAR 10
#define SOUND_ENVIRONMENT_CARPETED_HALLWAY 11
#define SOUND_ENVIRONMENT_HALLWAY 12
#define SOUND_ENVIRONMENT_STONE_CORRIDOR 13
#define SOUND_ENVIRONMENT_ALLEY 14
#define SOUND_ENVIRONMENT_FOREST 15
#define SOUND_ENVIRONMENT_CITY 16
#define SOUND_ENVIRONMENT_MOUNTAINS 17
#define SOUND_ENVIRONMENT_QUARRY 18
#define SOUND_ENVIRONMENT_PLAIN 19
#define SOUND_ENVIRONMENT_PARKING_LOT 20
#define SOUND_ENVIRONMENT_SEWER_PIPE 21
#define SOUND_ENVIRONMENT_UNDERWATER 22
#define SOUND_ENVIRONMENT_DRUGGED 23
#define SOUND_ENVIRONMENT_DIZZY 24
#define SOUND_ENVIRONMENT_PSYCHOTIC 25
//If we ever make custom ones add them here
#define STANDARD_STATION SOUND_ENVIRONMENT_STONEROOM
#define LARGE_ENCLOSED SOUND_ENVIRONMENT_HANGAR
#define SMALL_ENCLOSED SOUND_ENVIRONMENT_BATHROOM
#define TUNNEL_ENCLOSED SOUND_ENVIRONMENT_CAVE
#define LARGE_SOFTFLOOR SOUND_ENVIRONMENT_CARPETED_HALLWAY
#define MEDIUM_SOFTFLOOR SOUND_ENVIRONMENT_LIVINGROOM
#define SMALL_SOFTFLOOR SOUND_ENVIRONMENT_ROOM
#define ASTEROID SOUND_ENVIRONMENT_CAVE
#define SPACE SOUND_ENVIRONMENT_UNDERWATER
// Ambience presets.
// All you need to do to make an area play one of these is set their ambience var to one of these lists.
// You can even combine them by adding them together, since they're just lists, however you'd have to do that in initialization.
// For weird alien places like the crashed UFO.
#define AMBIENCE_OTHERWORLDLY list(\
'sound/ambience/otherworldly/otherworldly1.ogg',\
'sound/ambience/otherworldly/otherworldly2.ogg',\
'sound/ambience/otherworldly/otherworldly3.ogg'\
)
// Restricted, military, or mercenary aligned locations like the armory, the merc ship/base, BSD, etc.
#define AMBIENCE_HIGHSEC list(\
'sound/ambience/highsec/highsec1.ogg',\
'sound/ambience/highsec/highsec2.ogg',\
'sound/ambience/highsec/highsec3.ogg',\
'sound/ambience/highsec/highsec4.ogg'\
)
// Ruined structures found on the surface or in the caves.
#define AMBIENCE_RUINS list(\
'sound/ambience/ruins/ruins1.ogg',\
'sound/ambience/ruins/ruins2.ogg',\
'sound/ambience/ruins/ruins3.ogg',\
'sound/ambience/ruins/ruins4.ogg',\
'sound/ambience/ruins/ruins5.ogg',\
'sound/ambience/ruins/ruins6.ogg'\
)
// Similar to the above, but for more technology/signaling based ruins.
#define AMBIENCE_TECH_RUINS list(\
'sound/ambience/tech_ruins/tech_ruins1.ogg',\
'sound/ambience/tech_ruins/tech_ruins2.ogg',\
'sound/ambience/tech_ruins/tech_ruins3.ogg'\
)
// The actual chapel room, and maybe some other places of worship.
#define AMBIENCE_CHAPEL list(\
'sound/ambience/chapel/chapel1.ogg',\
'sound/ambience/chapel/chapel2.ogg',\
'sound/ambience/chapel/chapel3.ogg',\
'sound/ambience/chapel/chapel4.ogg'\
)
// For peaceful, serene areas, distinct from the Chapel.
#define AMBIENCE_HOLY list(\
'sound/ambience/holy/holy1.ogg',\
'sound/ambience/holy/holy2.ogg'\
)
// Generic sounds for less special rooms.
#define AMBIENCE_GENERIC list(\
'sound/ambience/generic/generic1.ogg',\
'sound/ambience/generic/generic2.ogg',\
'sound/ambience/generic/generic3.ogg'\
)
// 'sound/ambience/generic/generic4.ogg'
// Sounds of PA announcements, presumably involving shuttles?
#define AMBIENCE_ARRIVALS list(\
'sound/ambience/arrivals/arrivals1.ogg',\
'sound/ambience/arrivals/arrivals2.ogg',\
'sound/ambience/arrivals/arrivals3.ogg',\
'sound/ambience/arrivals/arrivals4.ogg',\
'sound/ambience/arrivals/arrivals5.ogg',\
'sound/ambience/arrivals/arrivals6.ogg',\
'sound/ambience/arrivals/arrivals7.ogg'\
)
// Sounds suitable for being inside dark, tight corridors in the underbelly of the station.
#define AMBIENCE_MAINTENANCE list(\
'sound/ambience/maintenance/maintenance1.ogg',\
'sound/ambience/maintenance/maintenance2.ogg',\
'sound/ambience/maintenance/maintenance3.ogg',\
'sound/ambience/maintenance/maintenance4.ogg',\
'sound/ambience/maintenance/maintenance5.ogg',\
'sound/ambience/maintenance/maintenance6.ogg',\
'sound/ambience/maintenance/maintenance7.ogg',\
'sound/ambience/maintenance/maintenance8.ogg',\
'sound/ambience/maintenance/maintenance9.ogg'\
)
// Life support machinery at work, keeping everyone breathing.
#define AMBIENCE_ENGINEERING list(\
'sound/ambience/engineering/engineering1.ogg',\
'sound/ambience/engineering/engineering2.ogg',\
'sound/ambience/engineering/engineering3.ogg'\
)
// Creepy AI/borg stuff.
#define AMBIENCE_AI list(\
'sound/ambience/ai/ai1.ogg',\
'sound/ambience/ai/ai2.ogg',\
'sound/ambience/ai/ai3.ogg'\
)
// Peaceful sounds when floating in the void.
#define AMBIENCE_SPACE list(\
'sound/ambience/space/space_serithi.ogg',\
'sound/ambience/space/space1.ogg'\
)
// Vaguely spooky sounds when around dead things.
#define AMBIENCE_GHOSTLY list(\
'sound/ambience/ghostly/ghostly1.ogg',\
'sound/ambience/ghostly/ghostly2.ogg'\
)
// Concerning sounds, for when one discovers something horrible happened in a PoI.
#define AMBIENCE_FOREBODING list(\
'sound/ambience/foreboding/foreboding1.ogg',\
'sound/ambience/foreboding/foreboding2.ogg',\
'sound/ambience/foreboding/foreboding3.ogg',\
'sound/ambience/foreboding/foreboding4.ogg',\
'sound/ambience/foreboding/foreboding5.ogg',\
'sound/ambience/foreboding/foreboding6.ogg'\
)
// Ambience heard when aboveground on Sif and not in a Point of Interest.
#define AMBIENCE_SIF list(\
'sound/ambience/sif/sif1.ogg'\
)
// If we ever add geothermal PoIs or other places that are really hot, this will do.
#define AMBIENCE_LAVA list(\
'sound/ambience/lava/lava1.ogg'\
)
// Cult-y ambience, for some PoIs, and maybe when the cultists darken the world with the ritual.
#define AMBIENCE_UNHOLY list(\
'sound/ambience/unholy/unholy1.ogg'\
)
// For the memes.
#define AMBIENCE_AESTHETIC list(\
'sound/ambience/vaporwave.ogg'\
)
#define AMBIENCE_OUTPOST list(\
'sound/ambience/expoutpost/expoutpost1.ogg',\
'sound/ambience/expoutpost/expoutpost2.ogg',\
'sound/ambience/expoutpost/expoutpost3.ogg',\
'sound/ambience/expoutpost/expoutpost4.ogg'\
)
#define AMBIENCE_SUBSTATION list(\
'sound/ambience/substation/substation1.ogg',\
'sound/ambience/substation/substation2.ogg'\
)
#define AMBIENCE_HANGAR list(\
'sound/ambience/hangar/hangar1.ogg',\
'sound/ambience/hangar/hangar2.ogg',\
'sound/ambience/hangar/hangar3.ogg',\
'sound/ambience/hangar/hangar4.ogg',\
'sound/ambience/hangar/hangar5.ogg',\
'sound/ambience/hangar/hangar6.ogg'\
)
#define AMBIENCE_ATMOS list(\
'sound/ambience/engineering/engineering1.ogg',\
'sound/ambience/engineering/engineering2.ogg',\
'sound/ambience/engineering/engineering3.ogg',\
'sound/ambience/atmospherics/atmospherics1.ogg'\
)
>>>>>>> db274375dc... Merge pull request #10518 from VOREStation/Arokha/instrumental

View File

@@ -58,6 +58,8 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
#define INIT_ORDER_CHEMISTRY 35
#define INIT_ORDER_SKYBOX 30
#define INIT_ORDER_MAPPING 25
#define INIT_ORDER_SOUNDS 23
#define INIT_ORDER_INSTRUMENTS 22
#define INIT_ORDER_DECALS 20
#define INIT_ORDER_PLANTS 19 // Must initialize before atoms.
#define INIT_ORDER_PLANETS 18
@@ -92,8 +94,9 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
#define FIRE_PRIORITY_SUPPLY 5
#define FIRE_PRIORITY_NIGHTSHIFT 5
#define FIRE_PRIORITY_PLANTS 5
#define FIRE_PRIORITY_ORBIT 8
#define FIRE_PRIORITY_VOTE 9
#define FIRE_PRIORITY_ORBIT 7
#define FIRE_PRIORITY_VOTE 8
#define FIRE_PRIORITY_INSTRUMENTS 9
#define FIRE_PRIORITY_AI 10
#define FIRE_PRIORITY_GARBAGE 15
#define FIRE_PRIORITY_ALARM 20

View File

@@ -0,0 +1,56 @@
PROCESSING_SUBSYSTEM_DEF(instruments)
name = "Instruments"
wait = 0.5
init_order = INIT_ORDER_INSTRUMENTS
flags = SS_KEEP_TIMING
priority = FIRE_PRIORITY_INSTRUMENTS
/// List of all instrument data, associative id = datum
var/static/list/datum/instrument/instrument_data = list()
/// List of all song datums.
var/static/list/datum/song/songs = list()
/// Max lines in songs
var/static/musician_maxlines = 600
/// Max characters per line in songs
var/static/musician_maxlinechars = 300
/// Deciseconds between hearchecks. Too high and instruments seem to lag when people are moving around in terms of who can hear it. Too low and the server lags from this.
var/static/musician_hearcheck_mindelay = 5
/// Maximum instrument channels total instruments are allowed to use. This is so you don't have instruments deadlocking all sound channels.
var/static/max_instrument_channels = MAX_INSTRUMENT_CHANNELS
/// Current number of channels allocated for instruments
var/static/current_instrument_channels = 0
/// Single cached list for synthesizer instrument ids, so you don't have to have a new list with every synthesizer.
var/static/list/synthesizer_instrument_ids
/datum/controller/subsystem/processing/instruments/Initialize()
initialize_instrument_data()
synthesizer_instrument_ids = get_allowed_instrument_ids()
return ..()
/datum/controller/subsystem/processing/instruments/proc/on_song_new(datum/song/S)
songs += S
/datum/controller/subsystem/processing/instruments/proc/on_song_del(datum/song/S)
songs -= S
/datum/controller/subsystem/processing/instruments/proc/initialize_instrument_data()
for(var/path in subtypesof(/datum/instrument))
var/datum/instrument/I = path
if(initial(I.abstract_type) == path)
continue
I = new path
I.Initialize()
if(!I.id)
qdel(I)
continue
instrument_data[I.id] = I
CHECK_TICK
/datum/controller/subsystem/processing/instruments/proc/get_instrument(id_or_path)
return instrument_data["[id_or_path]"]
/datum/controller/subsystem/processing/instruments/proc/reserve_instrument_channel(datum/instrument/I)
if(current_instrument_channels > max_instrument_channels)
return
. = SSsounds.reserve_sound_channel(I)
if(!isnull(.))
current_instrument_channels++

View File

@@ -0,0 +1,135 @@
#define DATUMLESS "NO_DATUM"
SUBSYSTEM_DEF(sounds)
name = "Sounds"
flags = SS_NO_FIRE
init_order = INIT_ORDER_SOUNDS
var/static/using_channels_max = CHANNEL_HIGHEST_AVAILABLE //BYOND max channels
/// 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/static/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 ..()
/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.
/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.
/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
LAZYINITLIST(using_channels_by_datum[DATUMLESS])
using_channels_by_datum[DATUMLESS] += .
/// Reserves a channel for a datum. Automatic cleanup only when the datum is deleted. Returns an integer for channel.
/datum/controller/subsystem/sounds/proc/reserve_sound_channel(datum/D)
if(!D) //i don't like typechecks but someone will fuck it up
CRASH("Attempted to reserve sound channel without datum using the managed proc.")
.= reserve_channel()
if(!.)
return FALSE
var/text_channel = num2text(.)
using_channels[text_channel] = D
LAZYINITLIST(using_channels_by_datum[D])
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

View File

@@ -80,7 +80,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]

View File

@@ -0,0 +1,39 @@
/datum/supply_pack/musical/strings
contains = list(
/obj/item/instrument/violin,
/obj/item/instrument/banjo,
/obj/item/instrument/guitar,
/obj/item/instrument/eguitar,
)
name = "string instruments"
cost = 50
containertype = /obj/structure/closet/crate
containername = "string instrument crate"
/datum/supply_pack/musical/wind
contains = list(
/obj/item/instrument/accordion,
/obj/item/instrument/trumpet,
/obj/item/instrument/saxophone,
/obj/item/instrument/trombone,
/obj/item/instrument/recorder,
/obj/item/instrument/harmonica,
/obj/item/instrument/bikehorn,
)
name = "wind instruments"
cost = 50
containertype = /obj/structure/closet/crate
containername = "wind instrument crate"
/datum/supply_pack/musical/keys
contains = list(
/obj/item/instrument/piano_synth,
/obj/item/instrument/glockenspiel, // cough
/obj/item/instrument/musicalmoth
)
name = "keyed instruments"
cost = 50
containertype = /obj/structure/closet/crate
containername = "keyed instruments crate"
// /obj/item/instrument/piano_synth/headphones

View File

@@ -339,7 +339,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
icon_state = "thunder"
requires_power = 0
dynamic_lighting = 0
sound_env = ARENA
sound_env = SOUND_ENVIRONMENT_ARENA
flags = AREA_FLAG_IS_NOT_PERSISTENT
/area/tdome/tdome1
@@ -1289,28 +1289,28 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/holodeck/source_emptycourt
name = "\improper Holodeck - Empty Court"
sound_env = ARENA
sound_env = SOUND_ENVIRONMENT_ARENA
/area/holodeck/source_boxingcourt
name = "\improper Holodeck - Boxing Court"
sound_env = ARENA
sound_env = SOUND_ENVIRONMENT_ARENA
/area/holodeck/source_basketball
name = "\improper Holodeck - Basketball Court"
sound_env = ARENA
sound_env = SOUND_ENVIRONMENT_ARENA
/area/holodeck/source_thunderdomecourt
name = "\improper Holodeck - Thunderdome Court"
requires_power = 0
sound_env = ARENA
sound_env = SOUND_ENVIRONMENT_ARENA
/area/holodeck/source_courtroom
name = "\improper Holodeck - Courtroom"
sound_env = AUDITORIUM
sound_env = SOUND_ENVIRONMENT_AUDITORIUM
/area/holodeck/source_beach
name = "\improper Holodeck - Beach"
sound_env = PLAIN
sound_env = SOUND_ENVIRONMENT_PLAIN
/area/holodeck/source_burntest
name = "\improper Holodeck - Atmospheric Burn Test"
@@ -1320,23 +1320,23 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/holodeck/source_meetinghall
name = "\improper Holodeck - Meeting Hall"
sound_env = AUDITORIUM
sound_env = SOUND_ENVIRONMENT_AUDITORIUM
/area/holodeck/source_theatre
name = "\improper Holodeck - Theatre"
sound_env = CONCERT_HALL
sound_env = SOUND_ENVIRONMENT_CONCERT_HALL
/area/holodeck/source_picnicarea
name = "\improper Holodeck - Picnic Area"
sound_env = PLAIN
sound_env = SOUND_ENVIRONMENT_PLAIN
/area/holodeck/source_snowfield
name = "\improper Holodeck - Snow Field"
sound_env = FOREST
sound_env = SOUND_ENVIRONMENT_FOREST
/area/holodeck/source_desert
name = "\improper Holodeck - Desert"
sound_env = PLAIN
sound_env = SOUND_ENVIRONMENT_PLAIN
/area/holodeck/source_space
name = "\improper Holodeck - Space"

View File

@@ -669,3 +669,6 @@
/atom/proc/get_visible_gender()
return gender
/atom/proc/interact(mob/user)
return

View File

@@ -16,9 +16,6 @@
clicksound = "keyboard"
/obj/machinery/computer/New()
..()
/obj/machinery/computer/Initialize()
. = ..()
power_change()

View File

@@ -1054,7 +1054,9 @@
/obj/item/device/radio/headset = 10,
/obj/item/device/flashlight = 5,
/obj/item/device/laser_pointer = 3,
/obj/item/clothing/glasses/omnihud = 10)
/obj/item/clothing/glasses/omnihud = 10,
/obj/item/instrument/piano_synth/headphones = 2, // You're making a subsystem do work, I don't want it TOO busy
/obj/item/instrument/piano_synth/headphones/spacepods = 2)
prices = list(/obj/item/clothing/suit/circuitry = 100,
/obj/item/clothing/head/circuitry = 100,
/obj/item/clothing/shoes/circuitry = 100,
@@ -1074,7 +1076,9 @@
/obj/item/device/radio/headset = 50,
/obj/item/device/flashlight = 100,
/obj/item/device/laser_pointer = 200,
/obj/item/clothing/glasses/omnihud = 100)
/obj/item/clothing/glasses/omnihud = 100,
/obj/item/instrument/piano_synth/headphones = 200,
/obj/item/instrument/piano_synth/headphones/spacepods = 600)
premium = list(/obj/item/device/perfect_tele/one_beacon = 1)
contraband = list(/obj/item/weapon/disk/nifsoft/compliance = 1)

View File

@@ -3,8 +3,8 @@
icon = 'icons/effects/effects_vr.dmi'
icon_state = "confetti"
/obj/effect/effect/sparks/New()
..()
/obj/effect/effect/sparks/Initialize()
. = ..()
playsound(src, "sounds/items/confetti.ogg ", 100, 1)
/datum/effect/effect/system/confetti_spread

View File

@@ -99,15 +99,12 @@ steam.start() -- spawns the effect
anchored = 1.0
mouse_opacity = 0
/obj/effect/effect/sparks/New()
..()
/obj/effect/effect/sparks/Initialize()
. = ..()
playsound(src, "sparks", 100, 1)
var/turf/T = src.loc
if (istype(T, /turf))
T.hotspot_expose(1000,100)
/obj/effect/effect/sparks/Initialize()
. = ..()
QDEL_IN(src, 5 SECONDS)
/obj/effect/effect/sparks/Destroy()

View File

@@ -104,7 +104,7 @@
/obj/item/weapon/reagent_containers/food/snacks/grown/ambrosiadeus,
/obj/item/weapon/reagent_containers/food/snacks/grown/ambrosiavulgaris,
/obj/item/device/paicard,
/obj/item/device/instrument/violin,
/obj/item/instrument/violin,
/obj/item/weapon/storage/belt/utility/full,
/obj/item/clothing/accessory/tie/horrible)

View File

@@ -120,9 +120,6 @@
tgui_interact(user)
..()
/obj/proc/interact(mob/user)
return
/mob/proc/unset_machine()
machine?.remove_visual(src)
src.machine = null

View File

@@ -9,7 +9,7 @@
var/area/area_source = turf_source.loc
//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))
@@ -43,7 +43,7 @@
S = sound(get_sfx(soundin))
S.wait = 0 //No queue
S.channel = channel || open_sound_channel()
S.channel = channel || SSsounds.random_available_channel()
// I'm not sure if you can modify S.volume, but I'd rather not try to find out what
// horrible things lurk in BYOND's internals, so we're just gonna do vol *=
@@ -109,15 +109,14 @@
var/mob/MO = M
MO.playsound_local(get_turf(MO), sound, volume, vary, pressure_affected = FALSE)
/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)
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
src << S
/proc/get_rand_frequency()
return rand(32000, 55000) //Frequency stuff only works with 45kbps oggs.

View 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]"
/**
* 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/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)
/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 ..()
/**
* 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)

View File

@@ -0,0 +1,31 @@
/**
* 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 = src.sample, key = src.key, deviation = src.deviation, frequency = src.frequency)
src.sample = sample
src.key = key
src.deviation = deviation
src.frequency = frequency
if(!frequency && deviation)
calculate()
/**
* Calculates and stores our deviation.
*/
/datum/instrument_key/proc/calculate()
if(!deviation)
CRASH("Invalid calculate call: No deviation or sample in instrument_key")
#define KEY_TWELTH (1/12)
frequency = 2 ** (KEY_TWELTH * deviation)
#undef KEY_TWELTH

View 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')

View 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')

View File

@@ -0,0 +1,25 @@
/datum/instrument/fun
name = "Generic Fun Instrument"
category = "Fun"
abstract_type = /datum/instrument/fun
/datum/instrument/fun/honk
name = "!!HONK!!"
id = "honk"
real_samples = list("74"='sound/items/bikehorn.ogg') // Cluwne Heaven
/datum/instrument/fun/signal
name = "Ping"
id = "ping"
real_samples = list("79"='sound/machines/ping.ogg')
/datum/instrument/fun/chime
name = "Chime"
id = "chime"
real_samples = list("79"='sound/machines/chime.ogg')
/datum/instrument/fun/mothscream
name = "Moth Scream"
id = "mothscream"
real_samples = list("60"='sound/voice/moth/scream_moth.ogg')
admin_only = TRUE

View 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')

View 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"

View 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 Accordian"
id = "crack"
real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_accordian/c2.ogg',
"48"='sound/instruments/synthesis_samples/organ/crisis_accordian/c3.ogg',
"60"='sound/instruments/synthesis_samples/organ/crisis_accordian/c4.ogg',
"72"='sound/instruments/synthesis_samples/organ/crisis_accordian/c5.ogg')
/datum/instrument/organ/crisis_harmonica
name = "Crisis Harmonica"
id = "crharmony"
real_samples = list("48"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c3.ogg',
"60"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c4.ogg',
"72"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c5.ogg')
/datum/instrument/organ/crisis_tango_accordian
name = "Crisis Tango Accordian"
id = "crtango"
real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c2.ogg',
"48"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c3.ogg',
"60"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c4.ogg',
"72"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c5.ogg')

View 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')

View 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')

View File

@@ -0,0 +1,312 @@
//copy pasta of the space piano, don't hurt me -Pete
/obj/item/instrument
name = "generic instrument"
force = 10
health = 100
//resistance_flags = FLAMMABLE
icon = 'icons/obj/musician.dmi'
item_icons = list(
slot_l_hand_str = 'icons/mob/items/lefthand_instruments.dmi',
slot_r_hand_str = 'icons/mob/items/righthand_instruments.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/proc/should_stop_playing(mob/user)
return user.incapacitated() || !((loc == user) || (isturf(loc) && Adjacent(user))) // sorry, no more TK playing.
/obj/item/instrument/suicide_act(mob/user)
var/datum/gender/T = gender_datums[user.get_visible_gender()]
user.visible_message("<span class='suicide'>[user] begins to play 'Gloomy Sunday'! It looks like [T.hes] 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/interact(mob/living/user)
if(!isliving(user) || user.incapacitated())
return
user.set_machine(src)
song.interact(user)
/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"
hitsound = "swing_hit"
allowed_instrument_ids = "violin"
/obj/item/instrument/violin/golden
name = "golden violin"
desc = "A golden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\""
icon_state = "golden_violin"
/obj/item/instrument/piano_synth
name = "synthesizer"
desc = "An advanced electronic synthesizer that can be used as various instruments."
icon_state = "synth"
allowed_instrument_ids = "piano"
/obj/item/instrument/piano_synth/Initialize(mapload)
. = ..()
song.allowed_instrument_ids = SSinstruments.synthesizer_instrument_ids
/obj/item/instrument/piano_synth/headphones
name = "headphones"
desc = "Unce unce unce unce. Boop!"
icon_state = "headphones"
slot_flags = SLOT_EARS | SLOT_HEAD
force = 0
w_class = ITEMSIZE_SMALL
instrument_range = 1
/obj/item/instrument/piano_synth/headphones/Initialize()
. = ..()
RegisterSignal(src, COMSIG_SONG_START, .proc/start_playing)
RegisterSignal(src, COMSIG_SONG_END, .proc/stop_playing)
/**
* Called by a component signal when our song starts playing.
*/
/obj/item/instrument/piano_synth/headphones/proc/start_playing()
SIGNAL_HANDLER
icon_state = "[initial(icon_state)]_on"
if(ishuman(loc))
var/mob/living/carbon/human/H = loc
if(H.l_ear == src || H.r_ear == src)
H.update_inv_ears()
else if(H.head == src)
H.update_inv_head()
/**
* Called by a component signal when our song stops playing.
*/
/obj/item/instrument/piano_synth/headphones/proc/stop_playing()
SIGNAL_HANDLER
icon_state = "[initial(icon_state)]"
if(ishuman(loc))
var/mob/living/carbon/human/H = loc
if(H.l_ear == src || H.r_ear == src)
H.update_inv_ears()
else if(H.head == src)
H.update_inv_head()
/obj/item/instrument/piano_synth/headphones/spacepods
name = "\improper Nanotrasen space pods"
desc = "Flex your money, AND ignore what everyone else says, all at once!"
icon_state = "spacepods"
slot_flags = SLOT_EARS
//strip_delay = 100 //air pods don't fall out
instrument_range = 0 //you're paying for quality here
/obj/item/instrument/banjo
name = "banjo"
desc = "A 'Mura' brand banjo. It's pretty much just a drum with a neck and strings."
icon_state = "banjo"
attack_verb = list("scruggs-styled", "hum-diggitied", "shin-dug", "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"
attack_verb = list("played metal on", "serenaded", "crashed", "smashed")
hitsound = 'sound/weapons/stringsmash.ogg'
allowed_instrument_ids = list("guitar","csteelgt","cnylongt", "ccleangt", "cmutedgt")
/obj/item/instrument/eguitar
name = "electric guitar"
desc = "Makes all your shredding needs possible."
icon_state = "eguitar"
force = 12
attack_verb = list("played metal on", "shreded", "crashed", "smashed")
hitsound = 'sound/weapons/stringsmash.ogg'
allowed_instrument_ids = "eguitar"
/obj/item/instrument/glockenspiel
name = "glockenspiel"
desc = "Smooth metal bars perfect for any marching band."
icon_state = "glockenspiel"
allowed_instrument_ids = list("glockenspiel","crvibr", "sgmmbox", "r3celeste")
/obj/item/instrument/accordion
name = "accordion"
desc = "Pun-Pun not included."
icon_state = "accordion"
allowed_instrument_ids = list("crack", "crtango", "accordion")
/obj/item/instrument/trumpet
name = "trumpet"
desc = "To announce the arrival of the king!"
icon_state = "trumpet"
allowed_instrument_ids = "crtrumpet"
/obj/item/instrument/trumpet/spectral
name = "spectral trumpet"
desc = "Things are about to get spooky!"
icon_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"
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"
force = 0
attack_verb = list("played", "jazzed", "saxed", "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 = list("crtrombone", "crbrass", "trombone")
/obj/item/instrument/trombone/spectral
name = "spectral trombone"
desc = "A skeleton's favorite instrument. Apply directly on the mortals."
icon_state = "trombone"
force = 0
attack_verb = list("played", "jazzed", "tromboneed", "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"
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"
allowed_instrument_ids = list("crharmony", "harmonica")
slot_flags = SLOT_MASK
force = 5
w_class = ITEMSIZE_SMALL
/*
actions_types = list(/datum/action/item_action/instrument)
/obj/item/instrument/harmonica/proc/handle_speech(datum/source, list/speech_args)
SIGNAL_HANDLER
if(song.playing && ismob(loc))
to_chat(loc, "<span class='warning'>You stop playing the harmonica to talk...</span>")
song.playing = FALSE
/obj/item/instrument/harmonica/equipped(mob/M, slot)
. = ..()
RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech)
/obj/item/instrument/harmonica/dropped(mob/M)
. = ..()
UnregisterSignal(M, COMSIG_MOB_SAY)
*/
/obj/item/instrument/bikehorn
name = "gilded bike horn"
desc = "An exquisitely decorated bike horn, capable of honking in a variety of notes."
icon_state = "bike_horn"
item_icons = list(
slot_l_hand_str = 'icons/mob/items/lefthand_horns.dmi',
slot_r_hand_str = 'icons/mob/items/righthand_horns.dmi',
)
allowed_instrument_ids = list("bikehorn", "honk")
attack_verb = list("beautifully honked")
w_class = ITEMSIZE_SMALL
force = 0
throw_speed = 3
throw_range = 15
hitsound = 'sound/items/bikehorn.ogg'
/*
/obj/item/choice_beacon/music
name = "instrument delivery beacon"
desc = "Summon your tool of art."
icon_state = "gangtool-red"
/obj/item/choice_beacon/music/generate_display_names()
var/static/list/instruments
if(!instruments)
instruments = list()
var/list/templist = list(/obj/item/instrument/violin,
/obj/item/instrument/piano_synth,
/obj/item/instrument/banjo,
/obj/item/instrument/guitar,
/obj/item/instrument/eguitar,
/obj/item/instrument/glockenspiel,
/obj/item/instrument/accordion,
/obj/item/instrument/trumpet,
/obj/item/instrument/saxophone,
/obj/item/instrument/trombone,
/obj/item/instrument/recorder,
/obj/item/instrument/harmonica,
/obj/item/instrument/piano_synth/headphones
)
for(var/V in templist)
var/atom/A = V
instruments[initial(A.name)] = A
return instruments
*/
/obj/item/instrument/musicalmoth
name = "musical moth"
desc = "Despite its popularity, this controversial musical toy was eventually banned due to its unethically sampled sounds of moths screaming in agony."
icon_state = "mothsician"
allowed_instrument_ids = "mothscream"
attack_verb = list("fluttered", "flaped")
w_class = ITEMSIZE_SMALL
force = 0
hitsound = 'sound/voice/moth/scream_moth.ogg'

View File

@@ -0,0 +1,405 @@
#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 = 0
/// Maximum times we can repeat
var/max_repeats = 10
/// Our volume
var/volume = 35
/// Max volume
var/max_volume = 75
/// Min volume - This is so someone doesn't decide it's funny to set it to 0 and play invisible songs.
var/min_volume = 1
/// What instruments our built in picker can use. The picker won't show unless this is longer than one.
var/list/allowed_instrument_ids = list("r3grand")
//////////// Cached instrument variables /////////////
/// Instrument we are currently using
var/datum/instrument/using_instrument
/// Cached legacy ext for legacy instruments
var/cached_legacy_ext
/// Cached legacy dir for legacy instruments
var/cached_legacy_dir
/// Cached list of samples, referenced directly from the instrument for synthesized instruments
var/list/cached_samples
/// Are we operating in legacy mode (so if the instrument is a legacy instrument)
var/legacy = FALSE
//////////////////////////////////////////////////////
/////////////////// Playing variables ////////////////
/**
* 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()
/// List of channels that aren't being used, as text. This is to prevent unnecessary freeing and reallocations from SSsounds/SSinstruments.
var/list/channels_idle = list()
/// Person playing us
var/mob/user_playing
//////////////////////////////////////////////////////
/// 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 = list(9, 11, 0, 2, 4, 5, 7)
var/static/list/accent_lookup = list("b" = -1, "s" = 1, "#" = 1, "n" = 0)
//////////////////////////////////////////////////////////////
///////////// !!FUN!! - Only works in synthesized mode! /////////////////
/// Note numbers to shift.
var/note_shift = 0
var/note_shift_min = -100
var/note_shift_max = 100
var/can_noteshift = TRUE
/// The kind of sustain we're using
var/sustain_mode = SUSTAIN_LINEAR
/// When a note is considered dead if it is below this in volume
var/sustain_dropoff_volume = 0
/// Total duration of linear sustain for 100 volume note to get to SUSTAIN_DROPOFF
var/sustain_linear_duration = 5
/// Exponential sustain dropoff rate per decisecond
var/sustain_exponential_dropoff = 1.4
////////// DO NOT DIRECTLY SET THESE!
/// Do not directly set, use update_sustain()
var/cached_linear_dropoff = 10
/// Do not directly set, use update_sustain()
var/cached_exponential_dropoff = 1.045
/////////////////////////////////////////////////////////////////////////
/datum/song/New(atom/parent, list/instrument_ids, 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)
update_sustain()
if(new_range)
instrument_range = new_range
/datum/song/Destroy()
stop_playing()
SSinstruments.on_song_del(src)
lines = null
if(using_instrument)
using_instrument.songs_using -= src
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)
var/list/in_range = get_mobs_and_objs_in_view_fast(source, instrument_range)
for(var/mob/M in in_range["mobs"])
hearing_mobs[M] = get_dist(M, source)
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?.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
updateDialog(user_playing)
//we can not afford to runtime, since we are going to be doing sound channel reservations and if we runtime it means we have a channel allocation leak.
//wrap the rest of the stuff to ensure stop_playing() is called.
do_hearcheck()
SEND_SIGNAL(parent, COMSIG_SONG_START)
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
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
return
else
stop_playing()
return
/**
* Converts a tempodiv to ticks to elapse before playing the next chord, taking into account our tempo.
*/
/datum/song/proc/tempodiv_to_delay(tempodiv)
if(!tempodiv)
tempodiv = 1 // no division by 0. some song converters tend to use 0 for when it wants to have no div, for whatever reason.
return max(1, round((tempo/tempodiv) / world.tick_lag, 1))
/**
* Compiles chords.
*/
/datum/song/proc/compile_chords()
legacy ? compile_legacy() : compile_synthesized()
/**
* Plays a chord.
*/
/datum/song/proc/play_chord(list/chord)
// last value is timing information
for(var/i in 1 to (length(chord) - 1))
legacy? playkey_legacy(chord[i][1], chord[i][2], chord[i][3], user_playing) : playkey_synth(chord[i], user_playing)
/**
* Checks if we should halt playback.
*/
/datum/song/proc/should_stop_playing(mob/user)
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)
/**
* Updates the window for our users. Override down the line.
*/
/datum/song/proc/updateDialog(mob/user)
interact(user)
/datum/song/process(wait)
if(!playing)
return PROCESS_KILL
// 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()
updateDialog()
/**
* Setter for setting how low the volume has to get before a note is considered "dead" and dropped
*/
/datum/song/proc/set_dropoff_volume(volume)
sustain_dropoff_volume = clamp(volume, INSTRUMENT_MIN_SUSTAIN_DROPOFF, 100)
update_sustain()
updateDialog()
/**
* Setter for setting exponential falloff factor.
*/
/datum/song/proc/set_exponential_drop_rate(drop)
sustain_exponential_dropoff = clamp(drop, INSTRUMENT_EXP_FALLOFF_MIN, INSTRUMENT_EXP_FALLOFF_MAX)
update_sustain()
updateDialog()
/**
* Setter for setting linear falloff duration.
*/
/datum/song/proc/set_linear_falloff_duration(duration)
sustain_linear_duration = clamp(duration, 0.1, INSTRUMENT_MAX_TOTAL_SUSTAIN)
update_sustain()
updateDialog()
/datum/song/vv_edit_var(var_name, var_value)
. = ..()
if(.)
switch(var_name)
if(NAMEOF(src, volume))
set_volume(var_value)
if(NAMEOF(src, sustain_dropoff_volume))
set_dropoff_volume(var_value)
if(NAMEOF(src, sustain_exponential_dropoff))
set_exponential_drop_rate(var_value)
if(NAMEOF(src, sustain_linear_duration))
set_linear_falloff_duration(var_value)
// subtype for handheld instruments, like violin
/datum/song/handheld
/datum/song/handheld/updateDialog(mob/user)
parent.interact(user || usr)
/datum/song/handheld/should_stop_playing(mob/user)
. = ..()
if(.)
return TRUE
var/obj/item/instrument/I = parent
return I.should_stop_playing(user)
// subtype for stationary structures, like pianos
/datum/song/stationary
/datum/song/stationary/updateDialog(mob/user)
parent.interact(user || usr)
/datum/song/stationary/should_stop_playing(mob/user)
. = ..()
if(.)
return TRUE
var/obj/structure/musician/M = parent
return M.should_stop_playing(user)

View File

@@ -0,0 +1,251 @@
/**
* Returns the HTML for the status UI for this song datum.
*/
/datum/song/proc/instrument_status_ui()
. = list()
. += "<div class='statusDisplay'>"
. += "<b><a href='?src=[REF(src)];switchinstrument=1'>Current instrument</a>:</b> "
if(!using_instrument)
. += "<span class='danger'>No instrument loaded!</span><br>"
else
. += "[using_instrument.name]<br>"
. += "Playback Settings:<br>"
if(can_noteshift)
. += "<a href='?src=[REF(src)];setnoteshift=1'>Note Shift/Note Transpose</a>: [note_shift] keys / [round(note_shift / 12, 0.01)] octaves<br>"
var/smt
var/modetext = ""
switch(sustain_mode)
if(SUSTAIN_LINEAR)
smt = "Linear"
modetext = "<a href='?src=[REF(src)];setlinearfalloff=1'>Linear Sustain Duration</a>: [sustain_linear_duration / 10] seconds<br>"
if(SUSTAIN_EXPONENTIAL)
smt = "Exponential"
modetext = "<a href='?src=[REF(src)];setexpfalloff=1'>Exponential Falloff Factor</a>: [sustain_exponential_dropoff]% per decisecond<br>"
. += "<a href='?src=[REF(src)];setsustainmode=1'>Sustain Mode</a>: [smt]<br>"
. += modetext
. += using_instrument?.ready()? "Status: <span class='good'>Ready</span><br>" : "Status: <span class='bad'>!Instrument Definition Error!</span><br>"
. += "Instrument Type: [legacy? "Legacy" : "Synthesized"]<br>"
. += "<a href='?src=[REF(src)];setvolume=1'>Volume</a>: [volume]<br>"
. += "<a href='?src=[REF(src)];setdropoffvolume=1'>Volume Dropoff Threshold</a>: [sustain_dropoff_volume]<br>"
. += "<a href='?src=[REF(src)];togglesustainhold=1'>Sustain indefinitely last held note</a>: [full_sustain_held_note? "Enabled" : "Disabled"].<br>"
. += "</div>"
/datum/song/proc/interact(mob/user)
var/list/dat = list()
dat += instrument_status_ui()
if(lines.len > 0)
dat += "<H3>Playback</H3>"
if(!playing)
dat += "<A href='?src=[REF(src)];play=1'>Play</A> <SPAN CLASS='linkOn'>Stop</SPAN><BR><BR>"
dat += "Repeat Song: "
dat += repeat > 0 ? "<A href='?src=[REF(src)];repeat=-10'>-</A><A href='?src=[REF(src)];repeat=-1'>-</A>" : "<SPAN CLASS='linkOff'>-</SPAN><SPAN CLASS='linkOff'>-</SPAN>"
dat += " [repeat] times "
dat += repeat < max_repeats ? "<A href='?src=[REF(src)];repeat=1'>+</A><A href='?src=[REF(src)];repeat=10'>+</A>" : "<SPAN CLASS='linkOff'>+</SPAN><SPAN CLASS='linkOff'>+</SPAN>"
dat += "<BR>"
else
dat += "<SPAN CLASS='linkOn'>Play</SPAN> <A href='?src=[REF(src)];stop=1'>Stop</A><BR>"
dat += "Repeats left: <B>[repeat]</B><BR>"
if(!editing)
dat += "<BR><B><A href='?src=[REF(src)];edit=2'>Show Editor</A></B><BR>"
else
dat += "<H3>Editing</H3>"
dat += "<B><A href='?src=[REF(src)];edit=1'>Hide Editor</A></B>"
dat += " <A href='?src=[REF(src)];newsong=1'>Start a New Song</A>"
dat += " <A href='?src=[REF(src)];import=1'>Import a Song</A><BR><BR>"
var/bpm = round(600 / tempo)
dat += "Tempo: <A href='?src=[REF(src)];tempo=[world.tick_lag]'>-</A> [bpm] BPM <A href='?src=[REF(src)];tempo=-[world.tick_lag]'>+</A><BR><BR>"
var/linecount = 0
for(var/line in lines)
linecount += 1
dat += "Line [linecount]: <A href='?src=[REF(src)];modifyline=[linecount]'>Edit</A> <A href='?src=[REF(src)];deleteline=[linecount]'>X</A> [line]<BR>"
dat += "<A href='?src=[REF(src)];newline=1'>Add Line</A><BR><BR>"
if(help)
dat += "<B><A href='?src=[REF(src)];help=1'>Hide Help</A></B><BR>"
dat += {"
Lines are a series of chords, separated by commas (,), each with notes separated by hyphens (-).<br>
Every note in a chord will play together, with chord timed by the tempo.<br>
<br>
Notes are played by the names of the note, and optionally, the accidental, and/or the octave number.<br>
By default, every note is natural and in octave 3. Defining otherwise is remembered for each note.<br>
Example: <i>C,D,E,F,G,A,B</i> will play a C major scale.<br>
After a note has an accidental placed, it will be remembered: <i>C,C4,C,C3</i> is <i>C3,C4,C4,C3</i><br>
Chords can be played simply by seperating each note with a hyphon: <i>A-C#,Cn-E,E-G#,Gn-B</i><br>
A pause may be denoted by an empty chord: <i>C,E,,C,G</i><br>
To make a chord be a different time, end it with /x, where the chord length will be length<br>
defined by tempo / x: <i>C,G/2,E/4</i><br>
Combined, an example is: <i>E-E4/4,F#/2,G#/8,B/8,E3-E4/4</i>
<br>
Lines may be up to [MUSIC_MAXLINECHARS] characters.<br>
A song may only contain up to [MUSIC_MAXLINES] lines.<br>
"}
else
dat += "<B><A href='?src=[REF(src)];help=2'>Show Help</A></B><BR>"
var/datum/browser/popup = new(user, "instrument", parent?.name || "instrument", 700, 500)
popup.set_content(dat.Join(""))
popup.open()
/**
* Parses a song the user has input into lines and stores them.
*/
/datum/song/proc/ParseSong(text)
set waitfor = FALSE
//split into lines
lines = splittext(text, "\n")
if(lines.len)
var/bpm_string = "BPM: "
if(findtext(lines[1], bpm_string, 1, length(bpm_string) + 1))
var/divisor = text2num(copytext(lines[1], length(bpm_string) + 1)) || 120 // default
tempo = sanitize_tempo(600 / round(divisor, 1))
lines.Cut(1, 2)
else
tempo = sanitize_tempo(5) // default 120 BPM
if(lines.len > MUSIC_MAXLINES)
to_chat(usr, "Too many lines!")
lines.Cut(MUSIC_MAXLINES + 1)
var/linenum = 1
for(var/l in lines)
if(length_char(l) > MUSIC_MAXLINECHARS)
to_chat(usr, "Line [linenum] too long!")
lines.Remove(l)
else
linenum++
updateDialog(usr) // make sure updates when complete
/datum/song/Topic(href, href_list)
if(!parent.CanUseTopic(usr))
usr << browse(null, "window=instrument")
usr.unset_machine()
return
parent.add_fingerprint(usr)
if(href_list["newsong"])
lines = new()
tempo = sanitize_tempo(5) // default 120 BPM
name = ""
else if(href_list["import"])
var/t = ""
do
t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message)
if(!in_range(parent, usr))
return
if(length_char(t) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no")
if(cont == "no")
break
while(length_char(t) > MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
ParseSong(t)
else if(href_list["help"])
help = text2num(href_list["help"]) - 1
else if(href_list["edit"])
editing = text2num(href_list["edit"]) - 1
if(href_list["repeat"]) //Changing this from a toggle to a number of repeats to avoid infinite loops.
if(playing)
return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing.
repeat += round(text2num(href_list["repeat"]))
if(repeat < 0)
repeat = 0
if(repeat > max_repeats)
repeat = max_repeats
else if(href_list["tempo"])
tempo = sanitize_tempo(tempo + text2num(href_list["tempo"]))
else if(href_list["play"])
INVOKE_ASYNC(src, .proc/start_playing, usr)
else if(href_list["newline"])
var/newline = html_encode(input("Enter your line: ", parent.name) as text|null)
if(!newline || !in_range(parent, usr))
return
if(lines.len > MUSIC_MAXLINES)
return
if(length(newline) > MUSIC_MAXLINECHARS)
newline = copytext(newline, 1, MUSIC_MAXLINECHARS)
lines.Add(newline)
else if(href_list["deleteline"])
var/num = round(text2num(href_list["deleteline"]))
if(num > lines.len || num < 1)
return
lines.Cut(num, num+1)
else if(href_list["modifyline"])
var/num = round(text2num(href_list["modifyline"]),1)
var/content = stripped_input(usr, "Enter your line: ", parent.name, lines[num], MUSIC_MAXLINECHARS)
if(!content || !in_range(parent, usr))
return
if(num > lines.len || num < 1)
return
lines[num] = content
else if(href_list["stop"])
stop_playing()
else if(href_list["setlinearfalloff"])
var/amount = input(usr, "Set linear sustain duration in seconds", "Linear Sustain Duration") as null|num
if(!isnull(amount))
set_linear_falloff_duration(round(amount * 10, world.tick_lag))
else if(href_list["setexpfalloff"])
var/amount = input(usr, "Set exponential sustain factor", "Exponential sustain factor") as null|num
if(!isnull(amount))
set_exponential_drop_rate(round(amount, 0.00001))
else if(href_list["setvolume"])
var/amount = input(usr, "Set volume", "Volume") as null|num
if(!isnull(amount))
set_volume(round(amount, 1))
else if(href_list["setdropoffvolume"])
var/amount = input(usr, "Set dropoff threshold", "Dropoff Threshold Volume") as null|num
if(!isnull(amount))
set_dropoff_volume(round(amount, 0.01))
else if(href_list["switchinstrument"])
if(!length(allowed_instrument_ids))
return
else if(length(allowed_instrument_ids) == 1)
set_instrument(allowed_instrument_ids[1])
return
var/list/categories = list()
for(var/i in allowed_instrument_ids)
var/datum/instrument/I = SSinstruments.get_instrument(i)
if(I)
LAZYSET(categories[I.category || "ERROR CATEGORY"], I.name, I.id)
var/cat = input(usr, "Select Category", "Instrument Category") as null|anything in categories
if(!cat)
return
var/list/instruments = categories[cat]
var/choice = input(usr, "Select Instrument", "Instrument Selection") as null|anything in instruments
if(!choice)
return
choice = instruments[choice] //get id
if(choice)
set_instrument(choice)
else if(href_list["setnoteshift"])
var/amount = input(usr, "Set note shift", "Note Shift") as null|num
if(!isnull(amount))
note_shift = clamp(amount, note_shift_min, note_shift_max)
else if(href_list["setsustainmode"])
var/choice = input(usr, "Choose a sustain mode", "Sustain Mode") as null|anything in list("Linear", "Exponential")
switch(choice)
if("Linear")
sustain_mode = SUSTAIN_LINEAR
if("Exponential")
sustain_mode = SUSTAIN_EXPONENTIAL
else if(href_list["togglesustainhold"])
full_sustain_held_note = !full_sustain_held_note
updateDialog()

View File

@@ -0,0 +1,91 @@
/**
* 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 is a number from 1-7 for A-G
* * acc is either "b", "n", or "#"
* * oct is 1-8 (or 9 for C)
*/
/datum/song/proc/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/soundfile = "sound/instruments/[cached_legacy_dir]/[ascii2text(note+64)][acc][oct].[cached_legacy_ext]"
soundfile = file(soundfile)
// make sure the note exists
if(!fexists(soundfile))
return
// and play
var/turf/source = get_turf(parent)
if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
do_hearcheck()
var/sound/music_played = sound(soundfile)
for(var/i in hearing_mobs)
var/mob/M = i
/* Would be nice
if(user && HAS_TRAIT(user, TRAIT_MUSICIAN) && isliving(M))
var/mob/living/L = M
L.apply_status_effect(STATUS_EFFECT_GOOD_MUSIC)
*/
M.playsound_local(source, null, volume * using_instrument.volume_multiplier, S = music_played, preference = /datum/client_preference/instrument_toggle, volume_channel = VOLUME_CHANNEL_INSTRUMENTS)
// Could do environment and echo later but not for now

View File

@@ -0,0 +1,150 @@
/**
* 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
/* Maybe someday
if(user && HAS_TRAIT(user, TRAIT_MUSICIAN) && isliving(M))
var/mob/living/L = M
L.apply_status_effect(STATUS_EFFECT_GOOD_MUSIC)
*/
// Jeez
M.playsound_local(
turf_source = get_turf(parent),
soundin = null,
vol = volume,
vary = FALSE,
frequency = K.frequency,
falloff = null,
is_global = null,
channel = channel,
pressure_affected = null,
S = copy,
preference = /datum/client_preference/instrument_toggle,
volume_channel = VOLUME_CHANNEL_INSTRUMENTS)
// 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.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)

View File

@@ -0,0 +1,59 @@
/obj/structure/musician
name = "Not A Piano"
desc = "Something broke, contact coderbus."
var/can_play_unanchored = FALSE
var/list/allowed_instrument_ids = list("r3grand","r3harpsi","crharpsi","crgrand1","crbright1", "crichugan", "crihamgan","piano")
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/M)
if(!M.IsAdvancedToolUser())
return
interact(M)
/obj/structure/musician/proc/should_stop_playing(mob/user)
if(!(anchored || can_play_unanchored))
return TRUE
if(!user)
return FALSE
return !CanUseTopic(user)
/obj/structure/musician/interact(mob/user)
. = ..()
song.interact(user)
/*
/obj/structure/musician/wrench_act(mob/living/user, obj/item/I)
default_unfasten_wrench(user, I, 40)
return TRUE
*/
/obj/structure/musician/piano
name = "space minimoog"
icon = 'icons/obj/musician.dmi'
icon_state = "minimoog"
anchored = TRUE
density = TRUE
/obj/structure/musician/piano/unanchored
anchored = FALSE
/obj/structure/musician/piano/Initialize(mapload)
. = ..()
if(prob(50) && icon_state == initial(icon_state))
name = "space minimoog"
desc = "This is a minimoog, like a space piano, but more spacey!"
icon_state = "minimoog"
else
name = "space piano"
desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't."
icon_state = "piano"

View File

@@ -1059,15 +1059,15 @@
/mob/living/get_sound_env(var/pressure_factor)
if (hallucination)
return PSYCHOTIC
return SOUND_ENVIRONMENT_PSYCHOTIC
else if (druggy)
return DRUGGED
return SOUND_ENVIRONMENT_DRUGGED
else if (drowsyness)
return DIZZY
return SOUND_ENVIRONMENT_DIZZY
else if (confused)
return DIZZY
return SOUND_ENVIRONMENT_DIZZY
else if (sleeping)
return UNDERWATER
return SOUND_ENVIRONMENT_UNDERWATER
else
return ..()

View File

@@ -189,9 +189,7 @@ GLOBAL_LIST_EMPTY(apcs)
pixel_x = (dir & 3)? 0 : (dir == 4 ? 26 : -26) //VOREStation Edit -> 24 to 26
pixel_y = (dir & 3)? (dir ==1 ? 26 : -26) : 0 //VOREStation Edit -> 24 to 26
if(building==0)
init()
else
if(building)
area = get_area(src)
area.apc = src
opened = 1
@@ -200,6 +198,16 @@ GLOBAL_LIST_EMPTY(apcs)
stat |= MAINT
update_icon()
/obj/machinery/power/apc/Initialize(mapload, ndir, building)
. = ..()
if(!building)
init()
return INITIALIZE_HINT_LATELOAD
/obj/machinery/power/apc/LateInitialize()
. = ..()
update()
/obj/machinery/power/apc/Destroy()
GLOB.apcs -= src
update()
@@ -268,9 +276,6 @@ GLOBAL_LIST_EMPTY(apcs)
make_terminal()
spawn(5)
update()
/obj/machinery/power/apc/examine(mob/user)
. = ..()
if(Adjacent(user))

View File

@@ -288,8 +288,8 @@ var/global/list/light_type_cache = list()
shows_alerts = FALSE //VOREStation Edit
var/lamp_shade = 1
/obj/machinery/light/flamp/New(atom/newloc, obj/machinery/light_construct/construct = null)
..(newloc, construct)
/obj/machinery/light/flamp/Initialize(mapload, obj/machinery/light_construct/construct = null)
. = ..()
if(construct)
start_with_cell = FALSE
lamp_shade = 0
@@ -322,15 +322,13 @@ var/global/list/light_type_cache = list()
auto_flicker = TRUE
//VOREStation Add - Shadeless!
/obj/machinery/light/flamp/noshade/New()
/obj/machinery/light/flamp/noshade
lamp_shade = 0
update(0)
..()
//VOREStation Add End
// create a new lighting fixture
/obj/machinery/light/New(atom/newloc, obj/machinery/light_construct/construct = null)
..(newloc)
/obj/machinery/light/Initialize(mapload, obj/machinery/light_construct/construct = null)
. =..()
if(start_with_cell && !no_emergency)
cell = new/obj/item/weapon/cell/emergency_light(src)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -63,8 +63,18 @@
"Fe" = (/obj/random/outcrop,/turf/simulated/floor/tiled/asteroid_steel,/area/submap/Chasm)
"HE" = (/obj/effect/decal/cleanable/dirt,/obj/effect/decal/cleanable/dirt,/turf/simulated/floor/outdoors/rocks{outdoors = 0},/area/submap/Chasm)
"HF" = (/obj/structure/cliff/automatic{dir = 5},/turf/simulated/floor/outdoors/rocks{outdoors = 0},/area/submap/Chasm)
<<<<<<< HEAD
"Ji" = (/obj/item/stolenpackage,/turf/simulated/floor/outdoors/rocks{outdoors = 0},/area/submap/Chasm)
"JJ" = (/obj/structure/loot_pile/surface/bones,/obj/effect/floor_decal/rust,/obj/effect/decal/cleanable/dirt,/obj/effect/decal/cleanable/dirt,/turf/simulated/floor/tiled/asteroid_steel,/area/submap/Chasm)
||||||| parent of db274375dc... Merge pull request #10518 from VOREStation/Arokha/instrumental
"Ie" = (/obj/random/tool/power,/turf/simulated/floor/outdoors/rocks{outdoors = 0},/area/submap/Chasm)
"Ji" = (/obj/item/device/instrument/violin,/turf/simulated/floor/outdoors/rocks{outdoors = 0},/area/submap/Chasm)
"JJ" = (/obj/structure/loot_pile/surface/bones,/obj/effect/floor_decal/rust,/obj/effect/decal/cleanable/dirt,/obj/effect/decal/cleanable/dirt,/obj/effect/spider/stickyweb,/turf/simulated/floor/tiled/asteroid_steel,/area/submap/Chasm)
=======
"Ie" = (/obj/random/tool/power,/turf/simulated/floor/outdoors/rocks{outdoors = 0},/area/submap/Chasm)
"Ji" = (/obj/item/instrument/violin,/turf/simulated/floor/outdoors/rocks{outdoors = 0},/area/submap/Chasm)
"JJ" = (/obj/structure/loot_pile/surface/bones,/obj/effect/floor_decal/rust,/obj/effect/decal/cleanable/dirt,/obj/effect/decal/cleanable/dirt,/obj/effect/spider/stickyweb,/turf/simulated/floor/tiled/asteroid_steel,/area/submap/Chasm)
>>>>>>> db274375dc... Merge pull request #10518 from VOREStation/Arokha/instrumental
"Kv" = (/turf/simulated/floor/outdoors/dirt{outdoors = 0},/area/submap/Chasm)
"Kz" = (/obj/random/trash,/turf/simulated/floor/outdoors/dirt{outdoors = 0},/area/submap/Chasm)
"KX" = (/obj/structure/loot_pile/surface/bones,/turf/simulated/floor/outdoors/rocks{outdoors = 0},/area/submap/Chasm)

View File

@@ -20880,10 +20880,10 @@
/turf/simulated/floor/tiled/white,
/area/crew_quarters/kitchen)
"aHz" = (
/obj/structure/device/piano,
/obj/effect/floor_decal/spline/plain{
dir = 1
},
/obj/structure/musician/piano,
/turf/simulated/floor/lino,
/area/tether/surfacebase/entertainment/stage)
"aHA" = (
@@ -32556,10 +32556,12 @@
dir = 4
},
/obj/structure/table/rack/steel,
/obj/item/device/instrument/violin,
/obj/effect/floor_decal/spline/plain{
dir = 1
},
/obj/item/instrument/violin,
/obj/item/instrument/piano_synth,
/obj/item/instrument/recorder,
/turf/simulated/floor/lino,
/area/tether/surfacebase/entertainment/stage)
"bcW" = (

View File

@@ -142,7 +142,7 @@
/area/tether/surfacebase/outside
name = "Outside - Surface"
sound_env = MOUNTAINS
sound_env = SOUND_ENVIRONMENT_MOUNTAINS
/area/tether/surfacebase/outside/outside1
icon_state = "outside1"
/area/tether/surfacebase/outside/outside2
@@ -541,7 +541,7 @@
/area/tether/surfacebase/security/solitary
name = "\improper Surface Security Solitary Confinement"
lightswitch = 0
sound_env =PADDED_CELL
sound_env = SOUND_ENVIRONMENT_PADDED_CELL
/area/tether/surfacebase/security/gasstorage
name = "\improper Surface Security Gas Storage"
lightswitch = 0
@@ -639,7 +639,7 @@
/area/engineering/atmos_intake
name = "\improper Atmospherics Intake"
icon_state = "atmos"
sound_env = MOUNTAINS
sound_env = SOUND_ENVIRONMENT_MOUNTAINS
/area/engineering/atmos/hallway
name = "\improper Atmospherics Main Hallway"
@@ -1360,7 +1360,7 @@
name = "\improper Virology Maintenance"
/area/maintenance/station/ai
name = "\improper AI Maintenance"
sound_env = SEWER_PIPE
sound_env = SOUND_ENVIRONMENT_SEWER_PIPE
/area/maintenance/station/exploration
name = "\improper Exploration Maintenance"
/area/maintenance/abandonedlibrary
@@ -1372,7 +1372,7 @@
/area/maintenance/station/spacecommandmaint
name = "\improper Secondary Command Maintenance"
icon_state = "bridge"
sound_env = SEWER_PIPE
sound_env = SOUND_ENVIRONMENT_SEWER_PIPE
/area/maintenance/substation/spacecommand
name = "\improper Secondary Command Substation"
icon_state = "substation"

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.

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.

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.

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.

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.

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