/** * Returns the HTML for the status UI for this song datum. */ /datum/song/proc/instrument_status_ui() . = list() . += "
" . += "Current instrument: " if(!using_instrument) . += "No instrument loaded!
" else . += "[using_instrument.name]
" . += "Playback Settings:
" if(can_noteshift) . += "Note Shift/Note Transpose: [note_shift] keys / [round(note_shift / 12, 0.01)] octaves
" var/smt var/modetext = "" switch(sustain_mode) if(SUSTAIN_LINEAR) smt = "Linear" modetext = "Linear Sustain Duration: [sustain_linear_duration / 10] seconds
" if(SUSTAIN_EXPONENTIAL) smt = "Exponential" modetext = "Exponential Falloff Factor: [sustain_exponential_dropoff]% per decisecond
" . += "Sustain Mode: [smt]
" . += modetext . += using_instrument?.ready()? "Status: Ready
" : "Status: !Instrument Definition Error!
" . += "Instrument Type: [legacy? "Legacy" : "Synthesized"]
" . += "Volume: [volume]
" . += "Volume Dropoff Threshold: [sustain_dropoff_volume]
" . += "Sustain indefinitely last held note: [full_sustain_held_note? "Enabled" : "Disabled"].
" . += "
" /datum/song/proc/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 ? "--" : span_linkOff("--") dat += " [repeat] times " dat += repeat < max_repeats ? "++" : span_linkOff("++") 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 separating each note with a hyphen: 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() /** * 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(tgui_input_text(usr, "Please paste the entire song, formatted:", text("[]", name), t, multiline = TRUE, prevent_enter = TRUE)) 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?", "Too long!", 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. 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_REF(start_playing), usr) else if(href_list["newline"]) var/newline = html_encode(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 = 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 = tgui_input_number(usr, "Set linear sustain duration in seconds", "Linear Sustain Duration", round_value=FALSE) if(!isnull(amount)) set_linear_falloff_duration(round(amount * 10, world.tick_lag)) else if(href_list["setexpfalloff"]) var/amount = tgui_input_number(usr, "Set exponential sustain factor", "Exponential sustain factor", round_value=FALSE) if(!isnull(amount)) set_exponential_drop_rate(round(amount, 0.00001)) else if(href_list["setvolume"]) var/amount = tgui_input_number(usr, "Set volume", "Volume") if(!isnull(amount)) set_volume(round(amount, 1)) else if(href_list["setdropoffvolume"]) var/amount = tgui_input_number(usr, "Set dropoff threshold", "Dropoff Threshold Volume", round_value=FALSE) 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 = tgui_input_list(usr, "Select Category", "Instrument Category", categories) if(!cat) return var/list/instruments = categories[cat] var/choice = tgui_input_list(usr, "Select Instrument", "Instrument Selection", instruments) if(!choice) return choice = instruments[choice] //get id if(choice) set_instrument(choice) else if(href_list["setnoteshift"]) var/amount = tgui_input_number(usr, "Set note shift", "Note Shift", null, note_shift_max, note_shift_min) 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", 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()