From 9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22 Mon Sep 17 00:00:00 2001 From: Richard Date: Sun, 13 Apr 2025 18:48:02 +0100 Subject: initial --- site/app/twine/channel.js | 499 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 499 insertions(+) create mode 100644 site/app/twine/channel.js (limited to 'site/app/twine/channel.js') diff --git a/site/app/twine/channel.js b/site/app/twine/channel.js new file mode 100644 index 0000000..18f1b3a --- /dev/null +++ b/site/app/twine/channel.js @@ -0,0 +1,499 @@ +var Insert = function(channel, options) { + var insert = this; +}; + + +var Channel = function(timeline, index) { + var channel = this; + this.clips = {}; + this.width = null; + this.offset = null; + var heights = [25, 100]; + this.height = heights[0]; + this.index = index; + this.name = "Channel " + (index + 1); + var elChannel = $("
").addClass("twine_channel").css({ + height: channel.height + "px" + }).appendTo(timeline.element); + this.inserts = []; + var splines = {}; + + var heightState = 0; + var elChannelControl = $("
").addClass("twine_channelcontrol").text(channel.name).appendTo(elChannel).dblclick(function(){ + heightState = 1 - heightState; + channel.setHeight(heights[heightState]); + }).click(function(){ + channel.showDetails(); + }).on("contextmenu", function(e){ + return twirl.contextMenu.show(e, [ + {name: "Rename", click: function(){ + var el = $("
"); + $("

").text("Rename channel").appendTo(el); + var ti = $("").val(channel.name).appendTo(el); + twirl.prompt.show(el, function(){ + channel.setName(ti.val()); + }); + }} + ]); + }); + + var elChannelClips = $("
").addClass("twine_channelclips").appendTo(elChannel).mousedown(twine.timeline.dragSelection); + + var elChannelSpline = $("
").css({ + position: "absolute", + width: "100%", + height: "75%", + left: "0px", + top: "25%", + "z-index": 31, + opacity: 0.57, + "background-color": "var(--bgColor1)", + display: "none" + }).appendTo(elChannelClips); + + + channel.defaultChannels = { + amp: "twine_camp" + channel.index, + pan: "twine_cpan" + channel.index, + mute: "twine_cmute" + channel.index, + solo: twine.mixer.soloChannel + }; + + + this.setName = function(name) { + channel.name = name; + elChannelControl.text(channel.name); + }; + + this.exportData = async function() { + var saveData = { + clips: [], + inserts: [], + name: channel.name, + amp: (await app.getControlChannel(channel.defaultChannels.amp)), + pan: (await app.getControlChannel(channel.defaultChannels.pan)) + }; + + for (var ins in channel.inserts) { + + } + for (var i in channel.clips) { + if (channel.clips[i]) { + saveData.clips.push(await channel.clips[i].exportData()); + } + } + return saveData; + }; + + this.remove = function(noredraw) { + elChannel.remove(); + console.log("remove channel"); + for (let c in channel.clips) { + if (channel.clips[c]) channel.clips[c].destroy(); + } + for (let c in timeline.channels) { + if (timeline.channels[c] == channel) { + timeline.channels.splice(c, 1); + } + } + if (!noredraw) timeline.drawGrid(); + }; + + this.importData = async function(loadData, ftMap) { + await app.setControlChannel(channel.defaultChannels.amp, loadData.amp); + await app.setControlChannel(channel.defaultChannels.pan, loadData.pan); + channel.setName(loadData.name); + for (var i in channel.clips) { + channel.clips[i].remove(); + delete channel.clips[i]; + } + + for (let cl of loadData.clips) { + if (cl.table[0]) { + cl.table[0] = ftMap[cl.table[0]]; + } + if (cl.table[1]) { + cl.table[1] = ftMap[cl.table[1]]; + } + var c = new Clip(twine); + c.importData(cl); + channel.addClip(c); + } + }; + + this.addClip = function(clip) { + channel.clips[clip.data.id] = clip; + clip.channel = channel; + elChannelClips.append(clip.element); + clip.redraw(); + }; + + this.removeClip = function(clip) { + //clip.element.detach(); + //clip.channel = null; + channel.clips[clip.data.id] = null; + //delete channel.clips[clip.data.id]; + }; + + this.changeBeatEnd = function(original, newBeatEnd, noredraw) { + var ratio = original / newBeatEnd; + for (let s in splines) { + splines[s].spline.resize(ratio, noredraw); + } + }; + + + this.getCsChannelName = function() { + return "mxchan" + channel.index; + }; + + var elAddInsert; + var elInsertsArea; + var insertID = 0; + function removeInsert(id, noCompile) { + var index; + for (let ci in channel.inserts) { + if (channel.inserts[ci].id == id) { + index = ci; + break; + } + } + if (index == null) return; + var i = channel.inserts[index]; + i.element.remove(); + i.transform.remove(); + channel.inserts.splice(index, 1); + refreshInserts(); + if (!noCompile) channel.compileInstr(); + } + + + async function createDefaultSplines() { + var splineColour = getComputedStyle(document.body).getPropertyValue("--fgColor2"); + var ampVal, panVal; + if (twine.offline) { + ampVal = 1; + panVal = 0; + } else { + ampVal = await app.getControlChannel(channel.defaultChannels.amp); + panVal = await app.getControlChannel(channel.defaultChannels.pan); + } + var def = [ + {name: "default_amp", dfault: 1, constraints: [0, 2, ampVal, 0.00001], channel: channel.defaultChannels.amp}, + {name: "default_pan", dfault: 0, constraints: [-1, 1, panVal, 0.00001], channel: channel.defaultChannels.pan} + ]; + + for (let d of def) { + splines[d.name] = { + element: $("
").addClass("twca" + channel.index).addClass("twine_spline").attr("id", "twca" + channel.index + d.name).hide().appendTo(elChannelSpline), + show: function() { + $(".twca" + channel.index).hide(); + splines[d.name].element.show(); + splines[d.name].spline.redraw(); + }, + hide: function() { + splines[d.name].element.hide(); + }, + channel: d.channel + }; + } + for (let d of def) { + splines[d.name].spline = new SplineEdit( + splines[d.name].element, splineColour, + twine.timeline.getTotalBeatDuration, + d.constraints, d.name + ) + } + } + + + function addInsert(definition, noCompile) { + var container = $("
").addClass("twine_channeldetails_insert").appendTo(elInsertsArea); + container.css("left", (channel.inserts.length * 500) + "px"); + var id = insertID ++; + var uniqueID = parseInt(channel.index.toString() + id.toString()); + var t = new twirl.transform.Transform({ + uniqueID: uniqueID, + element: container, + definition: definition, + splineElement: elChannelSpline, + getRegionFunc: function() { + return [0, 1]; + }, + getDurationFunc: timeline.getTotalBeatDuration, + onClose: function() { + removeInsert(id); + }, + onAutomationClick: function(state) { + if (state && channel.height == heights[0]) { + channel.expand(); // TODO: set selected thing in dropdown + } + }, + unmanagedAutomation: true, + unmanagedModulation: true, + host: twine + }); + channel.inserts.push({ + transform: t, + element: container, + id: id + }); + + for (let i in t.parameters) { + let paramID = (channel.inserts.length - 1) + "_" + i; + let tp = t.parameters[i]; + splines[paramID] = { + element: $("
").addClass("twca" + channel.index).addClass("twine_spline").attr("id", "twca" + channel.index + paramID).hide().appendTo(elChannelSpline), + show: function() { + $(".twca" + channel.index).hide(); + splines[paramID].element.show(); + splines[paramID].spline.redraw(); + }, + hide: function() { + splines[paramID].element.hide(); + }, + channel: tp.sendChannel + }; + tp.createAutomationSpline(splines[paramID].element); + splines[paramID].spline = tp.automation; + } + refreshAutomationSelectors(); + if (!noCompile) channel.compileInstr(); + } + + this.hasOverlap = function(clip, proposedBeat, proposedLength) { + if (!proposedBeat) proposedBeat = clip.data.position; + if (!proposedLength) proposedLength = clip.data.playLength; + var clipEnd; + var proposedEnd; + for (var i in channel.clips) { + var c = channel.clips[i]; + if (c && c != clip) { + clipEnd = c.data.position + c.data.playLength; + proposedEnd = proposedBeat + proposedLength; + if ((proposedBeat < clipEnd && proposedEnd > c.data.position) || (c.data.position > proposedBeat && clipEnd < proposedEnd)) { + return true; + } + } + } + return false; + }; + + function refreshInserts() { + elInsertsArea.empty(); + for (let i in channel.inserts) { + var el = channel.inserts[i].element; + el.css("left", (i * 500) + "px"); + elInsertsArea.append(el); + } + } + + this.showDetails = function() { + if (!elAddInsert) { + elAddInsert = $("
").addClass("twine_channeldetails_insertnew"); + elInsertsArea = $("
").addClass("twine_channeldetails_inserts"); + var data = {}; + for (var c of twirl.appdata.transforms) { + var catName = c.name; + for (var t of c.contents) { + if (t.twine) { + if (!data[c.name]) { + data[c.name] = { + name: c.name, + description: c.description, + contents: [] + }; + } + data[c.name].contents.push(t); + } + } + } + var ttv = new twirl.transform.TreeView({ + element: elAddInsert, + items: Object.values(data), + click: addInsert + }); + } + if (twine.timeline.selectedChannel != channel) { + twine.timeline.selectedChannel = channel; + $(".twine_channelcontrol").css("background-color", "var(--bgColor2)"); + elChannelControl.css("background-color", "var(--bgColor1)"); + $("#twine_channeldetails").append(elAddInsert).append(elInsertsArea).show(); + } + twine.ui.showPane(twine.ui.pane.CHANNEL); + }; + + this.compileInstr = async function() { + if (twine.offline) return; + var instrName = "twine_channel" + channel.index; + var instr = "instr " + instrName + "\n" + + "aL, aR bus_read \"" + channel.getCsChannelName() + "\"\n"; + for (let i of channel.inserts) { + instr += "chnset aL, \"twstfeedL\"\n" + + "chnset aR, \"twstfeedR\"\n" + + "aoutL, aoutR subinstr \"" + i.transform.instr + "\", " + i.transform.uniqueID + "\n" + + "aL, aR twst_setapplymode chnget:i(\"applymode" + i.transform.uniqueID + "\"), aL, aR, aoutL, aoutR\n" + } + instr += "kamp chnget \"" + channel.defaultChannels.amp + "\"\n" + + "kpan chnget \"" + channel.defaultChannels.pan + "\"\n" + + "kmute chnget \"" + channel.defaultChannels.mute + "\"\n" + + "ksolo chnget \"" + channel.defaultChannels.solo + "\"\n" + + "kaudible = (1 - kmute) * ((ksolo == -1 || ksolo == " + channel.index + ") ? 1 : 0)\n" + + "aL *= kamp * (1 - kpan) * kaudible\naR *= kamp * kpan * kaudible\n" + instr += "bus_mix \"twine_master\", aL, aR\n"; + instr += "endin"; + await app.compileOrc(instr); + }; + + this.automationChanged = function() { + var changed = false; + for (let i in splines) { + if (splines[i].spline && splines[i].spline.changed) { + console.log("automation changed"); + return true; + } + } + return changed; + }; + + this.getAutomationData = function(start, end) { + var data = []; + for (let i in splines) { + var linsegData = splines[i].spline.getLinsegData(start, end, true); + if (linsegData) { + data.push("chnset linseg:k(" + linsegData + "), \"" + splines[i].channel + "\""); + } + } + return data; + }; + + this.refreshOffset = function() { + channel.offset = elChannelClips.offset(); + }; + + this.redraw = function(noClipWaveRedraw) { + channel.width = parseFloat(elChannelClips.css("width")); + channel.refreshOffset(); + refreshAutomationSelectors(); + for (let c in channel.clips) { + if (channel.clips[c]) channel.clips[c].redraw(noClipWaveRedraw); + } + + if (channel.height > heights[0]) { + for (var d in splines) { + console.log(splines[d]); + splines[d].spline.setRange(timeline.data.regionStart, timeline.data.regionEnd); + } + } + }; + + this.showAutomation = function(groupIndex, nameIndex) { + elAutomationSelectGroup.val(groupIndex).change(); + elAutomationSelector.val(nameIndex).change(); + }; + + var elAutomationSelectGroup; + var elAutomationSelector; + function refreshAutomationSelectors() { + if (channel.height <= heights[0]) { + if (elAutomationSelectGroup && elAutomationSelector) { + elAutomationSelectGroup.hide(); + elAutomationSelector.hide(); + } + return; + } + + var valGroup = (elAutomationSelectGroup) ? elAutomationSelectGroup.val() : 0; + var valItem = (elAutomationSelector) ? elAutomationSelector.val() : 0; + + var items = [ + {name: "Mixer", items: [ + {name: "Gain", onselect: function(){ + splines.default_amp.show(); + }}, + {name: "Pan", onselect: function(){ + splines.default_pan.show(); + }} + ]} + ]; + + for (let i in channel.inserts) { + let ci = channel.inserts[i]; + var obj = {name: ci.transform.name, items: []}; + items.push(obj); + for (let it in ci.transform.parameters) + obj.items.push({ + name: ci.transform.parameters[it].definition.name, + onselect: function() { + splines[i + "_" + it].show(); + } + }); + } + + if (!elAutomationSelectGroup && !elAutomationSelector) { + var el = $("
").addClass("twine_automationselectors").appendTo(elChannelControl); + elAutomationSelectGroup = $("").addClass("twine_automationselect").appendTo(el).on("change", changeAutomationItem); + + for (let i in items) { + $("