var Locators = function(timeline, elTimebar, elChannelOverlay) { var locators = this; var items = { start: { elMain: $("
").addClass("twine_timeline_timebar_locatorhead").appendTo(elTimebar), elLine: $("").addClass("twine_timeline_timebar_locatorline").appendTo(elChannelOverlay), setLeft: function(px){ items.start.elMain.show().css("left", px - (items.start.elMain.width() / 2)); items.start.elLine.show().css("left", px); }, hide: function() { items.start.elMain.hide(); items.start.elLine.hide(); } }, regionStart: { elMain: $("").addClass("twine_timeline_timebar_regionstart").appendTo(elTimebar), elLine: $("").addClass("twine_timeline_timebar_regionstart").appendTo(elChannelOverlay), setLeft: function(px){ items.regionStart.elMain.show().css("left", px - (items.regionStart.elMain.width() / 2)); items.regionStart.elLine.show().css("left", px); }, hide: function() { items.regionStart.elMain.hide(); items.regionStart.elLine.hide(); } }, regionEnd: { elMain: $("").addClass("twine_timeline_timebar_regionstart").appendTo(elTimebar), elLine: $("").addClass("twine_timeline_timebar_regionstart").appendTo(elChannelOverlay), setLeft: function(px){ items.regionEnd.elMain.show().css("left", px - (items.regionEnd.elMain.width() / 2)); items.regionEnd.elLine.show().css("left", px); }, hide: function() { items.regionEnd.elMain.hide(); items.regionEnd.elLine.hide(); } } }; items.regionStart.elMain.on("mousedown", function(es) { es.preventDefault(); es.stopPropagation(); var offset = elTimebar.offset().left; var max = elTimebar.width(); var startLeft = parseFloat(items.regionStart.elLine.css("left")); function mouseup() { $("body").off("mousemove", mousemove).off("mouseup", mouseup); } function mousemove(e) { var newPos = (e.clientX - es.clientX) + startLeft; newPos = timeline.roundToGrid(newPos); if (newPos < 0) { newPos = 0; } else if (newPos > max) { newPos = max; } var offset = elTimebar.offset().left; var beats = timeline.beatRegion[1] - timeline.beatRegion[0]; var beat = timeline.beatRegion[0] + (Math.round((newPos / (max - offset)) * beats * 100) / 100); if (beat > timeline.data.beatEnd - 1) { return; } items.regionStart.setLeft(newPos); timeline.data.beatStart = beat; } $("body").on("mousemove", mousemove).on("mouseup", mouseup); }); items.regionEnd.elMain.on("mousedown", function(es) { es.preventDefault(); es.stopPropagation(); var offset = elTimebar.offset().left; var max = elTimebar.width(); var startLeft = parseFloat(items.regionEnd.elLine.css("left")); function mouseup() { $("body").off("mousemove", mousemove).off("mouseup", mouseup); } function mousemove(e) { var newPos = (e.clientX - es.clientX) + startLeft; newPos = timeline.roundToGrid(newPos); if (newPos < 0) { newPos = 0; } else if (newPos > max) { newPos = max; } var offset = elTimebar.offset().left; var beats = timeline.beatRegion[1] - timeline.beatRegion[0]; var beat = timeline.beatRegion[0] + (Math.round((newPos / (max - offset)) * beats * 100) / 100); if (beat < timeline.data.beatStart + 1) { return; } items.regionEnd.setLeft(newPos); timeline.data.beatEnd = beat; } $("body").on("mousemove", mousemove).on("mouseup", mouseup); }); items.start.elMain.on("mousedown", function(es) { es.preventDefault(); es.stopPropagation(); var offset = elTimebar.offset().left; var max = elTimebar.width(); var startLeft = parseFloat(items.start.elLine.css("left")); function mouseup() { $("body").off("mousemove", mousemove).off("mouseup", mouseup); } function mousemove(e) { var newPos = (e.clientX - es.clientX) + startLeft; newPos = timeline.roundToGrid(newPos); if (newPos < 0) { newPos = 0; } else if (newPos > max) { newPos = max; } items.start.setLeft(newPos); var offset = elTimebar.offset().left; var beats = timeline.beatRegion[1] - timeline.beatRegion[0]; var beat = timeline.beatRegion[0] + (Math.round((newPos / (max - offset)) * beats * 100) / 100); timeline.startLocation = beat; } $("body").on("mousemove", mousemove).on("mouseup", mouseup); }); this.redrawStart = function() { if (timeline.startLocation >= timeline.beatRegion[1] || timeline.startLocation < timeline.beatRegion[0]) { items.start.hide(); return; } var ratio = (timeline.startLocation - timeline.beatRegion[0]) / (timeline.beatRegion[1] - timeline.beatRegion[0]); var eltWidth = elTimebar.width() - elTimebar.offset().left; var px = ratio * eltWidth; items.start.setLeft(px); } }; // end locators var Timeline = function(twine, data) { var timeline = this; var elDragSelection; timeline.selectedClips = []; timeline.selectedChannel = null; timeline.startLocation = 0; Object.defineProperty(timeline, "selectedClip", { get: function() { return timeline.selectedClips[timeline.selectedClips.length - 1]; }, set: function(c) { timeline.selectedClips = [c]; } }); var container = $("#twine_timeline"); var elTimebarContainer = $("").attr("id", "twine_timeline_timebar").appendTo(container); var elTimebar = $("").attr("id", "twine_timeline_timebar_inner").appendTo(elTimebarContainer); this.element = $("").attr("id", "twine_timeline_inner").appendTo(container); $("").attr("id", "twine_timelineoverlay").appendTo(timeline.element); var elChannelOverlay = $("").attr("id", "twine_timeline_channeloverlay").appendTo(container); var elScrollOuter = $("").attr("id", "twine_timeline_scroll_outer").appendTo(container); var elScrollInner = $("").attr("id", "twine_timeline_scroll_inner").appendTo(elScrollOuter); var elIcons = $("").attr("id", "twine_timeline_scroll_filler").appendTo(container); var elPlayhead = $("").attr("id", "twine_timeline_playposition").appendTo(elChannelOverlay); var locators = new Locators(timeline, elTimebar, elChannelOverlay); elIcons.append(twirl.createIcon({ label: "Zoom in", size: 20, icon: "zoomIn", click: function() { timeline.zoomIn(); } }).el); elIcons.append(twirl.createIcon({ label: "Zoom out", size: 20, icon: "zoomOut", click: function() { timeline.zoomOut(); } }).el); elIcons.append(twirl.createIcon({ label: "Show all", size: 20, icon: "showAll", click: function() { timeline.showAll(); } }).el); var channelIndex = 0; this.channels = []; this.gridPixels = 40; this.pixelsPerBeat = 10; this.beatRegion = []; this.playbackBeatStart = 0; if (data) { this.data = data; } else { this.data = { name: "New arrangement", snapToGrid: 4, gridVisible: true, beatStart: 0, beatEnd: 16, bpm: 120, timeSigMarker: 4, regionStart: 0, regionEnd: 1 }; } elTimebar.click(function(e) { var offset = elTimebar.offset().left; var beats = timeline.beatRegion[1] - timeline.beatRegion[0]; var px = timeline.roundToGrid(e.clientX - offset); var width = elTimebar.width() - offset; // ???? var beat = timeline.beatRegion[0] + (Math.round((px / width) * beats * 100) / 100); timeline.startLocation = beat; locators.redrawStart(); twine.stopAndPlay(beat); }); this.getTotalBeatDuration = function() { return timeline.data.beatEnd * (60 / timeline.data.bpm); }; this.changeBeatEnd = function(newBeatEnd, noredraw) { var original = timeline.data.beatEnd; timeline.data.beatEnd = newBeatEnd; for (let c of timeline.channels) { c.changeBeatEnd(original, newBeatEnd, noredraw); } }; this.extend = function(newBeatEnd, noredraw) { if (newBeatEnd > timeline.data.beatEnd) { timeline.changeBeatEnd(newBeatEnd, noredraw); } }; this.reduce = function() { var end = 0; for (let ch of timeline.channels) { for (let i in ch.clips) { if (ch.clips[i]) { var e = ch.clips[i].data.position + ch.clips[i].data.playLength; if (e > end) { end = e; } } } } end += 8; timeline.data.beatEnd = end; }; this.exportData = async function() { var saveData = { channels: [], data: timeline.data, masterAmp: (await app.getControlChannel(twine.mixer.masterAmpChannel)), ftables: {} }; for (let c of timeline.channels) { saveData.channels.push(await c.exportData()); } for (let ch of saveData.channels) { for (let cl of ch.clips) { var fnL = cl.table[0]; var fnR = cl.table[1]; if (!saveData.ftables[fnL] && fnL > 0) { saveData.ftables[fnL] = { length: await app.getCsound().tableLength(fnL), data: (await app.getCsound().tableCopyOut(fnL)).values().toArray() }; } if (!saveData.ftables[fnR] && fnR > 0) { saveData.ftables[fnR] = { length: await app.getCsound().tableLength(fnR), data: (await app.getCsound().tableCopyOut(fnR)).values().toArray() }; } } } return saveData; }; this.createFtable = async function(length) { return new Promise(function(resolve, reject) { var cbid = app.createCallback(function(ndata){ resolve(ndata.table); }); app.insertScore("twine_createtable", [0, 1, cbid, length]); }); }; this.copyNewTableIn = async function(data) { var ftable = await timeline.createFtable(data.length); await app.getCsound().tableCopyIn(ftable, data); return ftable; }; this.importData = async function(loadData) { timeline.data = loadData.data; await app.setControlChannel(twine.mixer.masterAmpChannel, loadData.masterAmp); var ftMap = {}; console.log("load ftables", loadData.ftables); for (let i in loadData.ftables) { var fn = await timeline.copyNewTableIn(loadData.ftables[i].data); console.log("copy into", fn, loadData.ftables[i]); window.fn = loadData.ftables[i]; ftMap[i] = fn; } while (timeline.channels.length > 0) { timeline.channels[0].remove(); } timeline.channels = []; var channelIndex = 0; for (let c of loadData.channels) { var channel = new Channel(timeline, channelIndex ++); timeline.channels.push(channel); await channel.importData(c, ftMap); } timeline.redraw(); }; var playheadInterval; this.setPlaying = function(state) { twine.ui.head.play.setValue(state); if (playheadInterval) { clearInterval(playheadInterval); } if (state) { playheadInterval = setInterval(async function(){ var val = await app.getControlChannel("twine_playpos"); var beat = (val * (timeline.data.bpm / 60)) + timeline.playbackBeatStart; if (beat > timeline.beatRegion[1]) { clearInterval(playheadInterval); elPlayhead.hide(); } var pos = beat * timeline.pixelsPerBeat; elPlayhead.css("left", pos + "px"); }, 50); elPlayhead.show(); } else { elPlayhead.hide(); } }; this.zoomIn = function() { timeline.setRegion( timeline.data.regionStart * 1.1, timeline.data.regionEnd * 0.9 ); }; this.zoomOut = function() { timeline.setRegion( timeline.data.regionStart * 0.9, timeline.data.regionEnd * 1.1 ); timeline.redraw(); }; this.showAll = function() { timeline.setRegion(0, 1); }; this.compileAutomationData = function(onready) { var changed = false; for (let c of timeline.channels) { if (c.automationChanged()) { changed = true; break; } } if (!changed) { return onready(1); } var start = timeline.data.beatStart / (timeline.data.beatEnd - timeline.data.beatStart); var instr = "instr twine_automaterun\n"; for (let c of timeline.channels) { for (let a of c.getAutomationData(start, 1)) { instr += a + "\n" } } instr += "a_ init 0\nout a_\nendin\n"; console.log(instr); app.compileOrc(instr).then(function(status){ if (status < 0) { self.errorHandler("Cannot parse automation data"); } else { onready(1); } }); }; function determineChannelForAdd() { var channel; if (!timeline.selectedChannel) { if (timeline.channels.length == 0) { channel = timeline.addChannel(); } else { channel = timeline.channels[0]; } } else { channel = timeline.selectedChannel; } return channel; } this.addScriptClip = function() { var channel = determineChannelForAdd(); var clip = new Clip(twine); clip.data.position = timeline.playbackBeatStart; //timeline.beatRegion[0]; channel.addClip(clip); clip.initScript(); clip.redraw(); }; this.addBlankClip = function() { var el = $(""); el.append($("").text("New blank clip")); let pStereo = new twirl.transform.Parameter({ definition: { name: "Stereo", description: "Whether the clip should be stereo or mono", fireChanges: false, automatable: false, min: 0, max: 1, step: 1, dfault: 1 }, host: twine }); let pDuration = new twirl.transform.Parameter({ definition: { name: "Duration", description: "Duration of the clip in seconds", fireChanges: false, automatable: false, min: 0.1, max: 30, step: 0.01, dfault: 10 }, host: twine }); var tb = $("").appendTo($("