From ca6c8285ec79889d7d1e5f1002df724ad9ea3b63 Mon Sep 17 00:00:00 2001 From: NovaBot <154629622+NovaBot13@users.noreply.github.com> Date: Thu, 14 Mar 2024 21:35:24 -0400 Subject: [PATCH] [MIRROR] Instrument editor now uses TGUI (#1446) * Instrument editor now uses TGUI (#81923) ## About The Pull Request Instruments now use TGUI as their editor which is pretty cool. It's mostly a 1:1 remake of the HTML UI except I did make a change to make the playback options a little more compact, leaving some more space for the editor before you have to scroll, and some other minor things that were made to make the UI hopefully nicer to look at and mess with. When there's a song to play - While playing, Repeat section can't be edited ![image](https://github.com/tgstation/tgstation/assets/53777086/33f21ca3-98d8-4147-83e7-74e7611463e6) Help section and UI when there's no song put in ![image](https://github.com/tgstation/tgstation/assets/53777086/babd30ab-9551-448b-9fe6-24e0b0535caf) ## Why It's Good For The Game It is yet another step in finishing up https://hackmd.io/XLt5MoRvRxuhFbwtk4VAUA?view Instruments especially were in a poor spot because they didn't respect things like ``IN_USE`` to not refresh if it's not the "UI" you are on, and such. ## Changelog :cl: refactor: Instruments now use TGUI. /:cl: --------- Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> * Instrument editor now uses TGUI --------- Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> --- code/__DEFINES/instruments.dm | 6 +- .../subsystem/processing/instruments.dm | 5 +- code/datums/ai/generic/generic_behaviors.dm | 2 +- code/modules/instruments/items.dm | 58 ++- code/modules/instruments/piano_synth.dm | 6 +- code/modules/instruments/songs/_song.dm | 26 +- code/modules/instruments/songs/editor.dm | 401 ++++++++---------- .../instruments/songs/play_synthesized.dm | 3 +- tgui/packages/tgui/components/Collapsible.tsx | 5 +- .../tgui/interfaces/InstrumentEditor.tsx | 341 +++++++++++++++ 10 files changed, 567 insertions(+), 286 deletions(-) create mode 100644 tgui/packages/tgui/interfaces/InstrumentEditor.tsx diff --git a/code/__DEFINES/instruments.dm b/code/__DEFINES/instruments.dm index fa09eee0dd7..bf54ddf1b90 100644 --- a/code/__DEFINES/instruments.dm +++ b/code/__DEFINES/instruments.dm @@ -6,6 +6,8 @@ /// Max number of playing notes per instrument. #define CHANNELS_PER_INSTRUMENT 128 +/// Minimum length a note should ever go for +#define INSTRUMENT_MIN_TOTAL_SUSTAIN 0.1 /// Maximum length a note should ever go for #define INSTRUMENT_MAX_TOTAL_SUSTAIN (5 SECONDS) @@ -16,8 +18,8 @@ /// Minimum volume for when the sound is considered dead. #define INSTRUMENT_MIN_SUSTAIN_DROPOFF 0 -#define SUSTAIN_LINEAR 1 -#define SUSTAIN_EXPONENTIAL 2 +#define SUSTAIN_LINEAR "Linear" +#define SUSTAIN_EXPONENTIAL "Exponential" // /datum/instrument instrument_flags #define INSTRUMENT_LEGACY (1<<0) //Legacy instrument. Implies INSTRUMENT_DO_NOT_AUTOSAMPLE diff --git a/code/controllers/subsystem/processing/instruments.dm b/code/controllers/subsystem/processing/instruments.dm index acee4480b94..1cfbb144e5f 100644 --- a/code/controllers/subsystem/processing/instruments.dm +++ b/code/controllers/subsystem/processing/instruments.dm @@ -20,7 +20,10 @@ PROCESSING_SUBSYSTEM_DEF(instruments) var/static/current_instrument_channels = 0 /// Single cached list for synthesizer instrument ids, so you don't have to have a new list with every synthesizer. var/static/list/synthesizer_instrument_ids - var/static/list/note_sustain_modes = list("Linear" = SUSTAIN_LINEAR, "Exponential" = SUSTAIN_EXPONENTIAL) + var/static/list/note_sustain_modes = list( + SUSTAIN_LINEAR, + SUSTAIN_EXPONENTIAL, + ) /datum/controller/subsystem/processing/instruments/Initialize() initialize_instrument_data() diff --git a/code/datums/ai/generic/generic_behaviors.dm b/code/datums/ai/generic/generic_behaviors.dm index 43e37f66e8c..b70375ef393 100644 --- a/code/datums/ai/generic/generic_behaviors.dm +++ b/code/datums/ai/generic/generic_behaviors.dm @@ -333,7 +333,7 @@ //just in case- it won't do anything if the instrument isn't playing song.stop_playing() - song.ParseSong(song_lines) + song.ParseSong(new_song = song_lines) song.repeat = 10 song.volume = song.max_volume - 10 finish_action(controller, TRUE) diff --git a/code/modules/instruments/items.dm b/code/modules/instruments/items.dm index d9a7e2f3e8b..dcc4ef8daba 100644 --- a/code/modules/instruments/items.dm +++ b/code/modules/instruments/items.dm @@ -34,20 +34,7 @@ user.visible_message(span_suicide("[user] begins to play 'Gloomy Sunday'! It looks like [user.p_theyre()] trying to commit suicide!")) return BRUTELOSS -/obj/item/instrument/attack_self(mob/user) - if(!ISADVANCEDTOOLUSER(user)) - to_chat(user, span_warning("You don't have the dexterity to do this!")) - return TRUE - interact(user) - -/obj/item/instrument/interact(mob/user) - ui_interact(user) - -/obj/item/instrument/ui_interact(mob/living/user) - if(!isliving(user) || user.stat != CONSCIOUS || (HAS_TRAIT(user, TRAIT_HANDS_BLOCKED) && !ispAI(user))) - return - - user.set_machine(src) +/obj/item/instrument/ui_interact(mob/user, datum/tgui/ui) song.ui_interact(user) /obj/item/instrument/violin @@ -130,9 +117,9 @@ . = ..() AddElement(/datum/element/spooky) -/obj/item/instrument/trumpet/spectral/attack(mob/living/carbon/C, mob/user) - playsound (src, 'sound/runtime/instruments/trombone/En4.mid', 100,1,-1) - ..() +/obj/item/instrument/trumpet/spectral/attack(mob/living/target_mob, mob/living/user, params) + playsound(src, 'sound/runtime/instruments/trombone/En4.mid', 1000, 1, -1) + return ..() /obj/item/instrument/saxophone name = "saxophone" @@ -154,9 +141,9 @@ . = ..() AddElement(/datum/element/spooky) -/obj/item/instrument/saxophone/spectral/attack(mob/living/carbon/C, mob/user) - playsound (src, 'sound/runtime/instruments/saxophone/En4.mid', 100,1,-1) - ..() +/obj/item/instrument/saxophone/spectral/attack(mob/living/target_mob, mob/living/user, params) + playsound(src, 'sound/runtime/instruments/trombone/En4.mid', 1000, 1, -1) + return ..() /obj/item/instrument/trombone name = "trombone" @@ -178,9 +165,9 @@ . = ..() AddElement(/datum/element/spooky) -/obj/item/instrument/trombone/spectral/attack(mob/living/carbon/C, mob/user) - playsound (src, 'sound/runtime/instruments/trombone/Cn4.mid', 100,1,-1) - ..() +/obj/item/instrument/trombone/spectral/attack(mob/living/target_mob, mob/living/user, params) + playsound(src, 'sound/runtime/instruments/trombone/Cn4.mid', 1000, 1, -1) + return ..() /obj/item/instrument/recorder name = "recorder" @@ -201,19 +188,24 @@ w_class = WEIGHT_CLASS_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_warning("You stop playing the harmonica to talk...")) - song.playing = FALSE - -/obj/item/instrument/harmonica/equipped(mob/M, slot) +/obj/item/instrument/harmonica/equipped(mob/user, slot, initial = FALSE) . = ..() - RegisterSignal(M, COMSIG_MOB_SAY, PROC_REF(handle_speech)) + if(!(slot & slot_flags)) + return + RegisterSignal(user, COMSIG_MOB_SAY, PROC_REF(handle_speech)) -/obj/item/instrument/harmonica/dropped(mob/M) +/obj/item/instrument/harmonica/dropped(mob/user, silent = FALSE) . = ..() - UnregisterSignal(M, COMSIG_MOB_SAY) + UnregisterSignal(user, COMSIG_MOB_SAY) + +/obj/item/instrument/harmonica/proc/handle_speech(datum/source, list/speech_args) + SIGNAL_HANDLER + if(!song.playing) + return + if(!ismob(loc)) + CRASH("[src] was still registered to listen in on [source] but was not found to be on their mob.") + to_chat(loc, span_warning("You stop playing the harmonica to talk...")) + song.playing = FALSE /datum/action/item_action/instrument name = "Use Instrument" diff --git a/code/modules/instruments/piano_synth.dm b/code/modules/instruments/piano_synth.dm index 71d0d96ef56..8e107d494c7 100644 --- a/code/modules/instruments/piano_synth.dm +++ b/code/modules/instruments/piano_synth.dm @@ -148,7 +148,7 @@ stopped_playing.set_output(COMPONENT_SIGNAL) /obj/item/circuit_component/synth/proc/import_song() - synth.song.ParseSong(song.value) + synth.song.ParseSong(new_song = song.value) /obj/item/circuit_component/synth/proc/set_repetitions() synth.song.set_repeats(repetitions.value) @@ -169,7 +169,9 @@ synth.song.note_shift = clamp(note_shift.value, synth.song.note_shift_min, synth.song.note_shift_max) /obj/item/circuit_component/synth/proc/set_sustain_mode() - synth.song.sustain_mode = SSinstruments.note_sustain_modes[sustain_mode.value] + if(!(sustain_mode.value in SSinstruments.note_sustain_modes)) + return + synth.song.sustain_mode = sustain_mode.value /obj/item/circuit_component/synth/proc/set_sustain_value() switch(synth.song.sustain_mode) diff --git a/code/modules/instruments/songs/_song.dm b/code/modules/instruments/songs/_song.dm index 68039df2146..fb0e4f08744 100644 --- a/code/modules/instruments/songs/_song.dm +++ b/code/modules/instruments/songs/_song.dm @@ -23,11 +23,6 @@ /// Are we currently playing? var/playing = FALSE - /// Are we currently editing? - var/editing = TRUE - /// Is the help screen open? - var/help = FALSE - /// Repeats left var/repeat = 0 /// Maximum times we can repeat @@ -107,7 +102,6 @@ var/note_shift = 0 var/note_shift_min = -100 var/note_shift_max = 100 - var/can_noteshift = TRUE /// The kind of sustain we're using var/sustain_mode = SUSTAIN_LINEAR /// When a note is considered dead if it is below this in volume @@ -129,7 +123,7 @@ tempo = sanitize_tempo(tempo, TRUE) src.parent = parent if(instrument_ids) - allowed_instrument_ids = islist(instrument_ids)? instrument_ids : list(instrument_ids) + allowed_instrument_ids = islist(instrument_ids) ? instrument_ids : list(instrument_ids) if(length(allowed_instrument_ids)) set_instrument(allowed_instrument_ids[1]) hearing_mobs = list() @@ -217,8 +211,6 @@ delay_by = 0 current_chord = 1 music_player = user - if(ismob(music_player)) - updateDialog(music_player) START_PROCESSING(SSinstruments, src) /** @@ -328,12 +320,6 @@ /datum/song/proc/set_bpm(bpm) tempo = sanitize_tempo(600 / bpm) -/** - * Updates the window for our users. Override down the line. - */ -/datum/song/proc/updateDialog(mob/user) - ui_interact(user) - /datum/song/process(wait) if(!playing) return PROCESS_KILL @@ -359,7 +345,6 @@ /datum/song/proc/set_volume(volume) src.volume = clamp(round(volume, 1), max(0, min_volume), min(100, max_volume)) update_sustain() - updateDialog() /** * Setter for setting how low the volume has to get before a note is considered "dead" and dropped @@ -367,7 +352,6 @@ /datum/song/proc/set_dropoff_volume(volume) sustain_dropoff_volume = clamp(round(volume, 0.01), INSTRUMENT_MIN_SUSTAIN_DROPOFF, 100) update_sustain() - updateDialog() /** * Setter for setting exponential falloff factor. @@ -375,7 +359,6 @@ /datum/song/proc/set_exponential_drop_rate(drop) sustain_exponential_dropoff = clamp(round(drop, 0.00001), INSTRUMENT_EXP_FALLOFF_MIN, INSTRUMENT_EXP_FALLOFF_MAX) update_sustain() - updateDialog() /** * Setter for setting linear falloff duration. @@ -383,7 +366,6 @@ /datum/song/proc/set_linear_falloff_duration(duration) sustain_linear_duration = clamp(round(duration * 10, world.tick_lag), world.tick_lag, INSTRUMENT_MAX_TOTAL_SUSTAIN) update_sustain() - updateDialog() /datum/song/vv_edit_var(var_name, var_value) . = ..() @@ -401,9 +383,6 @@ // subtype for handheld instruments, like violin /datum/song/handheld -/datum/song/handheld/updateDialog(mob/user) - parent.ui_interact(user || usr) - /datum/song/handheld/should_stop_playing(atom/player) . = ..() if(. == STOP_PLAYING || . == IGNORE_INSTRUMENT_CHECKS) @@ -414,9 +393,6 @@ // subtype for stationary structures, like pianos /datum/song/stationary -/datum/song/stationary/updateDialog(mob/user) - parent.ui_interact(user || usr) - /datum/song/stationary/should_stop_playing(atom/player) . = ..() if(. == STOP_PLAYING || . == IGNORE_INSTRUMENT_CHECKS) diff --git a/code/modules/instruments/songs/editor.dm b/code/modules/instruments/songs/editor.dm index 58c0562c9b0..927e03d055d 100644 --- a/code/modules/instruments/songs/editor.dm +++ b/code/modules/instruments/songs/editor.dm @@ -1,96 +1,189 @@ -/** - * Returns the HTML for the status UI for this song datum. - */ -/datum/song/proc/instrument_status_ui() - . = list() - . += "
" - . += "Current instrument: " - if(!using_instrument) - . += "[span_danger("No instrument loaded!")]
" - else - . += "[using_instrument.name]
" - . += "Playback Settings:
" - if(can_noteshift) - . += "Note Shift/Note Transpose: [note_shift] keys / [round(note_shift / 12, 0.01)] octaves
" - var/smt - var/modetext = "" +/datum/song/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if (!ui) + ui = new(user, src, "InstrumentEditor", parent.name) + ui.open() + +/datum/song/ui_host(mob/user) + return parent + +/datum/song/ui_data(mob/user) + var/list/data = ..() + data["using_instrument"] = using_instrument?.name || "No instrument loaded!" + data["note_shift"] = note_shift + data["octaves"] = round(note_shift / 12, 0.01) + data["sustain_mode"] = sustain_mode switch(sustain_mode) if(SUSTAIN_LINEAR) - smt = "Linear" - modetext = "Linear Sustain Duration: [sustain_linear_duration / 10] seconds
" + data["sustain_mode_button"] = "Linear Sustain Duration (in seconds)" + data["sustain_mode_duration"] = sustain_linear_duration / 10 + data["sustain_mode_min"] = INSTRUMENT_MIN_TOTAL_SUSTAIN + data["sustain_mode_max"] = INSTRUMENT_MAX_TOTAL_SUSTAIN if(SUSTAIN_EXPONENTIAL) - smt = "Exponential" - modetext = "Exponential Falloff Factor: [sustain_exponential_dropoff]% per decisecond
" - . += "Sustain Mode: [smt]
" - . += modetext - . += using_instrument?.ready()? "Status: Ready
" : "Status: !Instrument Definition Error!
" - . += "Instrument Type: [legacy? "Legacy" : "Synthesized"]
" - . += "Volume: [volume]
" - . += "Volume Dropoff Threshold: [sustain_dropoff_volume]
" - . += "Sustain indefinitely last held note: [full_sustain_held_note? "Enabled" : "Disabled"].
" - . += "
" - -/datum/song/ui_interact(mob/user) - var/list/dat = list() - - dat += instrument_status_ui() - - if(lines.len > 0) - dat += "

Playback

" - if(!playing) - dat += "Play Stop

" - dat += "Repeat Song: " - dat += repeat > 0 ? "--" : "--" - dat += " [repeat] times " - dat += repeat < max_repeats ? "++" : "++" - dat += "
" - else - dat += "Play Stop
" - dat += "Repeats left: [repeat]
" - if(!editing) - dat += "
Show Editor
" - else - dat += "

Editing

" - dat += "Hide Editor" - dat += " Start a New Song" - dat += " Import a Song

" - var/bpm = round(600 / tempo) - dat += "Tempo: - [bpm] BPM +

" - var/linecount = 0 - for(var/line in lines) - linecount += 1 - dat += "Line [linecount]: Edit X [line]
" - dat += "Add Line

" - if(help) - dat += "Hide Help
" - dat += {" - Lines are a series of chords, separated by commas (,), each with notes separated by hyphens (-).
- Every note in a chord will play together, with chord timed by the tempo.
-
- Notes are played by the names of the note, and optionally, the accidental, and/or the octave number.
- By default, every note is natural and in octave 3. Defining otherwise is remembered for each note.
- Example: C,D,E,F,G,A,B will play a C major scale.
- After a note has an accidental placed, it will be remembered: C,C4,C,C3 is C3,C4,C4,C3
- Chords can be played simply by seperating each note with a hyphon: A-C#,Cn-E,E-G#,Gn-B
- A pause may be denoted by an empty chord: C,E,,C,G
- To make a chord be a different time, end it with /x, where the chord length will be length
- defined by tempo / x: C,G/2,E/4
- Combined, an example is: E-E4/4,F#/2,G#/8,B/8,E3-E4/4 -
- Lines may be up to [MUSIC_MAXLINECHARS] characters.
- A song may only contain up to [MUSIC_MAXLINES] lines.
- "} - else - dat += "Show Help
" - - var/datum/browser/popup = new(user, "instrument", parent?.name || "instrument", 700, 500) - popup.set_content(dat.Join("")) - popup.open() + data["sustain_mode_button"] = "Exponential Falloff Factor (% per decisecond)" + data["sustain_mode_duration"] = sustain_exponential_dropoff + data["sustain_mode_min"] = INSTRUMENT_EXP_FALLOFF_MIN + data["sustain_mode_max"] = INSTRUMENT_EXP_FALLOFF_MAX + data["instrument_ready"] = using_instrument?.ready() + data["volume"] = volume + data["volume_dropoff_threshold"] = sustain_dropoff_volume + data["sustain_indefinitely"] = full_sustain_held_note + data["playing"] = playing + data["repeat"] = repeat + data["bpm"] = round(60 SECONDS / tempo) + data["lines"] = list() + var/linecount + for(var/line in lines) + linecount++ + data["lines"] += list(list( + "line_count" = linecount, + "line_text" = line, + )) + return data + +/datum/song/ui_static_data(mob/user) + var/list/data = ..() + data["can_switch_instrument"] = (length(allowed_instrument_ids) > 1) + data["possible_instruments"] = list() + for(var/instrument in allowed_instrument_ids) + UNTYPED_LIST_ADD(data["possible_instruments"], list("name" = SSinstruments.instrument_data[instrument], "id" = instrument)) + data["sustain_modes"] = SSinstruments.note_sustain_modes + data["max_repeats"] = max_repeats + data["min_volume"] = min_volume + data["max_volume"] = max_volume + data["note_shift_min"] = note_shift_min + data["note_shift_max"] = note_shift_max + data["max_line_chars"] = MUSIC_MAXLINECHARS + data["max_lines"] = MUSIC_MAXLINES + return data + +/datum/song/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + var/mob/user = ui.user + if(!istype(user)) + return FALSE + + switch(action) + //SETTINGS + if("play_music") + if(!playing) + INVOKE_ASYNC(src, PROC_REF(start_playing), user) + else + stop_playing() + return TRUE + if("change_instrument") + var/new_instrument = params["new_instrument"] + //only one instrument, so no need to bother changing it. + if(!length(allowed_instrument_ids)) + return FALSE + if(!(new_instrument in allowed_instrument_ids)) + return FALSE + set_instrument(new_instrument) + return TRUE + if("tempo") + var/move_direction = params["tempo_change"] + var/tempo_diff + if(move_direction == "increase_speed") + tempo_diff = world.tick_lag + else + tempo_diff = -world.tick_lag + tempo = sanitize_tempo(tempo + tempo_diff) + return TRUE + + //SONG MAKING + if("import_song") + var/song_text = "" + do + song_text = tgui_input_text(user, "Please paste the entire song, formatted:", name, max_length = (MUSIC_MAXLINES * MUSIC_MAXLINECHARS)) + if(!in_range(parent, user)) + return + if(length_char(song_text) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS) + var/should_continue = tgui_alert(user, "Your message is too long! Would you like to continue editing it?", "Warning", list("Yes", "No")) + if(should_continue != "Yes") + break + while(length_char(song_text) > MUSIC_MAXLINES * MUSIC_MAXLINECHARS) + ParseSong(user, song_text) + return TRUE + if("start_new_song") + name = "" + lines = new() + tempo = sanitize_tempo(5) // default 120 BPM + return TRUE + if("add_new_line") + var/newline = tgui_input_text(user, "Enter your line", parent.name) + if(!newline || !in_range(parent, user)) + return + if(lines.len > MUSIC_MAXLINES) + return + if(length(newline) > MUSIC_MAXLINECHARS) + newline = copytext(newline, 1, MUSIC_MAXLINECHARS) + lines.Add(newline) + if("delete_line") + var/line_to_delete = params["line_deleted"] + if(line_to_delete > lines.len || line_to_delete < 1) + return FALSE + lines.Cut(line_to_delete, line_to_delete + 1) + return TRUE + if("modify_line") + var/line_to_edit = params["line_editing"] + if(line_to_edit > lines.len || line_to_edit < 1) + return FALSE + var/new_line_text = tgui_input_text(user, "Enter your line ", parent.name, lines[line_to_edit], MUSIC_MAXLINECHARS) + if(isnull(new_line_text) || !in_range(parent, user)) + return FALSE + lines[line_to_edit] = new_line_text + return TRUE + + //MODE STUFF + if("set_sustain_mode") + var/new_mode = params["new_mode"] + if(isnull(new_mode) || !(new_mode in SSinstruments.note_sustain_modes)) + return FALSE + sustain_mode = new_mode + return TRUE + if("set_note_shift") + var/amount = params["amount"] + if(!isnum(amount)) + return FALSE + note_shift = clamp(amount, note_shift_min, note_shift_max) + return TRUE + if("set_volume") + var/new_volume = params["amount"] + if(!isnum(new_volume)) + return FALSE + set_volume(new_volume) + return TRUE + if("set_dropoff_volume") + var/dropoff_threshold = params["amount"] + if(!isnum(dropoff_threshold)) + return FALSE + set_dropoff_volume(dropoff_threshold) + return TRUE + if("toggle_sustain_hold_indefinitely") + full_sustain_held_note = !full_sustain_held_note + return TRUE + if("set_repeat_amount") + if(playing) + return + var/repeat_amount = params["amount"] + if(!isnum(repeat_amount)) + return FALSE + set_repeats(repeat_amount) + return TRUE + if("edit_sustain_mode") + var/sustain_amount = params["amount"] + if(isnull(sustain_amount) || !isnum(sustain_amount)) + return + switch(sustain_mode) + if(SUSTAIN_LINEAR) + set_linear_falloff_duration(sustain_amount) + if(SUSTAIN_EXPONENTIAL) + set_exponential_drop_rate(sustain_amount) /** * Parses a song the user has input into lines and stores them. */ -/datum/song/proc/ParseSong(new_song) +/datum/song/proc/ParseSong(mob/user, new_song) set waitfor = FALSE //split into lines lines = islist(new_song) ? new_song : splittext(new_song, "\n") @@ -103,142 +196,14 @@ else tempo = sanitize_tempo(5) // default 120 BPM if(lines.len > MUSIC_MAXLINES) - to_chat(usr, "Too many lines!") + if(user) + to_chat(user, "Too many lines!") lines.Cut(MUSIC_MAXLINES + 1) var/linenum = 1 for(var/l in lines) if(length_char(l) > MUSIC_MAXLINECHARS) - to_chat(usr, "Line [linenum] too long!") + if(user) + to_chat(user, "Line [linenum] too long!") lines.Remove(l) else linenum++ - updateDialog(usr) // make sure updates when complete - -/datum/song/Topic(href, href_list) - if(!usr.can_perform_action(parent, ALLOW_RESTING)) - 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:", name, t) as message) - if(!in_range(parent, usr)) - return - - if(length_char(t) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS) - var/cont = tgui_alert(usr, "Your message is too long! Would you like to continue editing it?", "Warning", list("Yes", "No")) - if(cont != "Yes") - break - while(length_char(t) > MUSIC_MAXLINES * MUSIC_MAXLINECHARS) - ParseSong(t) - - else if(href_list["help"]) - help = text2num(href_list["help"]) - 1 - - else if(href_list["edit"]) - editing = text2num(href_list["edit"]) - 1 - - if(href_list["repeat"]) //Changing this from a toggle to a number of repeats to avoid infinite loops. - set_repeats(repeat + text2num(href_list["repeat"])) - - else if(href_list["tempo"]) - tempo = sanitize_tempo(tempo + text2num(href_list["tempo"])) - - else if(href_list["play"]) - INVOKE_ASYNC(src, PROC_REF(start_playing), usr) - - else if(href_list["newline"]) - var/newline = tgui_input_text(usr, "Enter your line ", parent.name) - if(!newline || !in_range(parent, usr)) - return - if(lines.len > MUSIC_MAXLINES) - return - if(length(newline) > MUSIC_MAXLINECHARS) - newline = copytext(newline, 1, MUSIC_MAXLINECHARS) - lines.Add(newline) - - else if(href_list["deleteline"]) - var/num = round(text2num(href_list["deleteline"])) - if(num > lines.len || num < 1) - return - lines.Cut(num, num+1) - - else if(href_list["modifyline"]) - var/num = round(text2num(href_list["modifyline"]),1) - var/content = tgui_input_text(usr, "Enter your line ", parent.name, lines[num], MUSIC_MAXLINECHARS) - if(!content || !in_range(parent, usr)) - return - if(num > lines.len || num < 1) - return - lines[num] = content - - else if(href_list["stop"]) - stop_playing() - - else if(href_list["setlinearfalloff"]) - var/amount = tgui_input_number(usr, "Set linear sustain duration in seconds", "Linear Sustain Duration", 0.1, INSTRUMENT_MAX_TOTAL_SUSTAIN, 0.1, round_value = FALSE) - if(!isnull(amount)) - set_linear_falloff_duration(amount) - - else if(href_list["setexpfalloff"]) - var/amount = tgui_input_number(usr, "Set exponential sustain factor", "Exponential sustain factor", INSTRUMENT_EXP_FALLOFF_MIN, INSTRUMENT_EXP_FALLOFF_MAX, INSTRUMENT_EXP_FALLOFF_MIN, round_value = FALSE) - if(!isnull(amount)) - set_exponential_drop_rate(amount) - - else if(href_list["setvolume"]) - var/amount = tgui_input_number(usr, "Set volume", "Volume", 1, 75, 1) - if(!isnull(amount)) - set_volume(amount) - - else if(href_list["setdropoffvolume"]) - var/amount = tgui_input_number(usr, "Set dropoff threshold", "Dropoff Volume", max_value = 100) - if(!isnull(amount)) - set_dropoff_volume(amount) - - else if(href_list["switchinstrument"]) - if(!length(allowed_instrument_ids)) - return - else if(length(allowed_instrument_ids) == 1) - set_instrument(allowed_instrument_ids[1]) - return - var/list/categories = list() - for(var/i in allowed_instrument_ids) - var/datum/instrument/I = SSinstruments.get_instrument(i) - if(I) - LAZYSET(categories[I.category || "ERROR CATEGORY"], I.name, I.id) - var/cat = tgui_input_list(usr, "Select Category", "Instrument Category", categories) - if(isnull(cat)) - return - var/list/instruments = categories[cat] - var/choice = tgui_input_list(usr, "Select Instrument", "Instrument Selection", instruments) - if(isnull(choice)) - return - if(isnull(instruments[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 = tgui_input_list(usr, "Choose a sustain mode", "Sustain Mode", SSinstruments.note_sustain_modes) - if(choice) - sustain_mode = SSinstruments.note_sustain_modes[choice] - - else if(href_list["togglesustainhold"]) - full_sustain_held_note = !full_sustain_held_note - - updateDialog(usr) diff --git a/code/modules/instruments/songs/play_synthesized.dm b/code/modules/instruments/songs/play_synthesized.dm index 3b5f3d5b9f4..836c2fdd86b 100644 --- a/code/modules/instruments/songs/play_synthesized.dm +++ b/code/modules/instruments/songs/play_synthesized.dm @@ -43,8 +43,7 @@ * Does a hearing check if enough time has passed. */ /datum/song/proc/playkey_synth(key, atom/player) - if(can_noteshift) - key = clamp(key + note_shift, key_min, key_max) + 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! diff --git a/tgui/packages/tgui/components/Collapsible.tsx b/tgui/packages/tgui/components/Collapsible.tsx index b470ed5ce6d..9f5f944b0ec 100644 --- a/tgui/packages/tgui/components/Collapsible.tsx +++ b/tgui/packages/tgui/components/Collapsible.tsx @@ -13,11 +13,12 @@ type Props = Partial<{ buttons: ReactNode; open: boolean; title: ReactNode; + icon: string; }> & BoxProps; export function Collapsible(props: Props) { - const { children, color, title, buttons, ...rest } = props; + const { children, color, title, buttons, icon, ...rest } = props; const [open, setOpen] = useState(props.open); return ( @@ -27,7 +28,7 @@ export function Collapsible(props: Props) { + + )} + + Repeats Left: + + act('set_repeat_amount', { + amount: value, + }) + } + /> + + + {!!can_switch_instrument && ( + + Instrument Using + + instrument.name, + )} + onSelected={(value) => + act('change_instrument', { + new_instrument: instrument_id_by_name(value), + }) + } + /> + + + )} + + + + Playback Settings: + + + act('set_note_shift', { + amount: value, + }) + } + /> + keys / {octaves} octaves + + + Mode: + + + act('set_sustain_mode', { + new_mode: value, + }) + } + /> + + + + {sustain_mode_button}: + + act('edit_sustain_mode', { + amount: value, + }) + } + /> + + + + + + Status: + {instrument_ready ? ( + Ready + ) : ( + + {' '} + Instrument Definition Error! + + )} + + + Volume: + + act('set_volume', { + amount: value, + }) + } + /> + + + Volume Dropoff Threshold: + + act('set_dropoff_volume', { + amount: value, + }) + } + /> + + + + + + + + ); +}; + +const EditingSettings = (props) => { + const { act, data } = useBackend(); + const { bpm, lines } = data; + + return ( +
+ + + + + + Tempo:{' '} + {' '} + {bpm} BPM{' '} + + + + {lines.map((line, index) => ( + + Line {index}: + + + {line.line_text} + + ))} + + + + +
+ ); +}; + +const HelpSection = (props) => { + const { data } = useBackend(); + const { max_line_chars, max_lines } = data; + + return ( +
+ + Lines are a series of chords, separated by commas (,), each with notes + separated by hyphens (-). +
+ Every note in a chord will play together, with chord timed by the tempo. +
+ Notes are played by the names of the note, and optionally, the + accidental, and/or the octave number. +
+ By default, every note is natural and in octave 3. Defining otherwise is + remembered for each note. +
+ Example: C,D,E,F,G,A,B will play a C major scale. +
+ After a note has an accidental placed, it will be remembered:{' '} + C,C4,C,C3 is C3,C4,C4,C3 +
+ Chords can be played simply by seperating each note with a hyphon:{' '} + A-C#,Cn-E,E-G#,Gn-B +
A pause may be denoted by an empty chord: C,E,,C,G +
+ To make a chord be a different time, end it with /x, where the chord + length will be length +
+ defined by tempo / x: C,G/2,E/4 +
+ Combined, an example is: E-E4/4,F#/2,G#/8,B/8,E3-E4/4 +
+ Lines may be up to {max_line_chars} characters. +
A song may only contain up to {max_lines} lines. +
+
+
+ ); +};