var Clip = function(twine, data, parent) { var clip = this; var loaded = false; var waveformClip; var waveformEdit; var datatable; this.channel = null; var minWidth = 10; if (!data) { var id = twine.getNewID(); var data = { name: "Clip " + id, channel: -1, id: id, clipindex: null, playLength: 1, colour: "#" + (Math.random() * 0xFFFFFF << 0).toString(16), position: 0, // debugs: duration: 1, warp: false, loop: false }; } else { loaded = true; } this.data = data; Object.defineProperty(this, "colour", { get: function() { return data.colour; }, set: function(x) { data.colour = x; clip.element.css("background-color", data.colour); } }); this.exportData = function() { return data; }; this.destroy = function(onComplete) { var cbid = app.createCallback(function(ndata){ clip.element.remove(); clip.channel.removeClip(clip); if (onComplete) { onComplete(ndata); } }); app.insertScore("twine_removeclip", [0, 1, cbid, clip.data.clipindex]); }; var dataMode = { fnL: 0, fnR: 1, divisionsPerBeat: 2, duration: 3, beatsLength: 4, utilisedLength: 5, warpMode: 6, pitch: 7, amp: 8, fftSize: 9, txtWinSize: 10, txtRandom: 11, txtOverlap: 12, loop: 13, warp: 14, txtWinType: 15, // warp points are 16 + in table position: -1, name: -2, channel: -3, clipindex: -4, playLength: -5 }; this.element = $("
").addClass("twine_clip").css({ "background-color": data.colour }).click(function(e){ if (e.ctrlKey) { twine.timeline.selectedClips.push(clip); } else { $(".twine_clip").css("outline", "none"); twine.timeline.selectedClips = [clip]; } console.log("ch", clip.data); clip.element.css("outline", "1px dashed white"); var cui = twine.ui.clip; cui.name.setValue(data.name); cui.colour.setValue(data.colour); cui.amp.setValue(data.amp); cui.warp.setValue(data.warp); cui.loop.setValue(data.loop); cui.readType.setValue(data.warpMode); cui.pitch.setValue(Math.round((Math.log(data.pitch) / Math.log(2)) * 12)); cui.fftSize.setValue(data.fftSize); cui.winSize.setValue(data.txtWinSize); cui.winRandom.setValue(data.txtRandom); cui.winOverlap.setValue(data.txtOverlap); cui.winType.setValue(data.txtWinType); showEditWaveform($("#twine_clipdetailsright")); twine.ui.showPane(twine.ui.pane.CLIP); }); var elWaveClip = $("").css({position: "absolute", width: "100%", height: "100%", top: "0px", left: "0px"}).appendTo(clip.element); var elWaveText = $("").css({position: "absolute", width: "100%", height: "100%", top: "0px", left: "0px", "font-size": "var(--fontSizeSmall)", color: "var(--fgColor1)"}).text(data.name).appendTo(clip.element); var elResizeLeft = $("").addClass("twine_clip_edge_left").appendTo(clip.element); var elResizeRight = $("").addClass("twine_clip_edge_right").appendTo(clip.element); var elMove = $("").addClass("twine_clip_centre").appendTo(clip.element); var elWaveEdit = $("").css({width: "100%", height: "100%", top: "0px", left: "0px"}); async function getDataFromTable(key) { async function setFromKey(key) { if (dataMode[key] < 0) return; var value = await app.getCsound().tableGet(datatable, dataMode[key]) data[key] = value; } for (var k in dataMode) { setFromKey(k); } } function setClipAudioUnique(onComplete) { var cbid = app.createCallback(async function(ndata){ await getDataFromTable(); if (onComplete) onComplete(); }); app.insertScore("twine_setclipaudiounique", [0, 1, cbid]); } function reloadAfterEdit(tables) { twirl.loading.show("Loading"); var cbid = app.createCallback(async function(ndata){ datatable = ndata.datatable; await getDataFromTable(); clip.redraw(); twine.setVisible(true); twigs.setVisible(false); twist.setVisible(false); twirl.loading.hide(); }); var call = [0, 1, cbid, data.clipindex]; for (let t of tables) { call.push(t); } app.insertScore("twine_clipreplacetables", call); } this.editInTwist = function(asUnique) { if (!window.twist) return twirl.prompt.show("twist is unavailable in this session"); function edit() { twist.boot(twine); twist.bootAudio(twine); var tables = [data.fnL]; if (data.fnR) tables.push(data.fnR); twist.loadFileFromFtable(data.name, tables, function(ndata){ if (ndata.status > 0) { twine.setVisible(false); twist.setVisible(true); } }, reloadAfterEdit); } if (asUnique) { setClipAudioUnique(edit); } else { edit(); } }; this.editInTwigs = function(asUnique) { if (!window.twigs) return twirl.prompt.show("twigs is unavailable in this session"); function edit() { twigs.boot(twine); var tables = [data.fnL]; if (data.fnR) tables.push(data.fnR); twigs.loadFileFromFtable(data.name, tables, function(ndata){ if (ndata.status > 0) { twine.setVisible(false); twigs.setVisible(true); } }, reloadAfterEdit); } if (asUnique) { setClipAudioUnique(edit); } else { edit(); } }; this.setData = function(modeString, v, onComplete) { data[modeString] = v; if (dataMode[modeString] < 0) { if (modeString == "name") { elWaveText.text(data.name); } return; } function doSetData() { if (!twirl.debug) app.getCsound().tableSet(datatable, dataMode[modeString], v); } doSetData(); if (onComplete) onComplete(); }; this.getPlaybackArgs = function(cbid, time) { return [(time) ? time: 0, data.playLength, cbid, data.clipindex, data.playLength, clip.channel.getCsChannelName()]; }; this.play = function(onCallback) { var cbid = app.createCallback(function(ndata) { if (onCallback) onCallback(ndata); }, true); app.insertScore("ecp_playback_tst", clip.getPlaybackArgs(cbid)); } async function getSourceTableData() { var wavedata = []; var tbL = await app.getTable(data.fnL); wavedata.push(tbL); if (data.hasOwnProperty("fnR") && data.fnR > 0) { var tbR = await app.getTable(data.fnR); wavedata.push(tbR); } return wavedata; } async function setClipWaveform(noRedraw) { if (!waveformClip) { waveformClip = new Waveform({ target: elWaveClip, allowSelect: false, showGrid: false, bgColor: "rgb(255, 255, 255, 0)", fgColor: "#000000" }); setTimeout(async function(){ var sourceTables = await getSourceTableData(); waveformClip.setData(sourceTables, data.duration); }, 100); } else { if (!noRedraw) waveformClip.redraw(); } } async function showEditWaveform(target) { target.empty().append(elWaveEdit); if (!waveformEdit) { waveformEdit = new Waveform({ target: elWaveEdit, allowSelect: true, showGrid: true, latencyCorrection: twirl.latencyCorrection // , markers: }); setTimeout(async function(){ var sourceTables = await getSourceTableData(); waveformEdit.setData(sourceTables, data.duration); }, 100); } else { waveformEdit.redraw(); } } this.setWarp = function(v) { clip.setData("warp", v); if (!data.warp && !data.loop && data.playLength > data.duration) { data.playLength = data.duration; clip.setSize(); } }; this.setLoop = function(v) { clip.setData("loop", v); if (!data.warp && !data.loop && data.playLength > data.duration) { data.playLength = data.duration; clip.setSize(); } }; this.setPitch = function(semitones) { var pitchRatio = Math.pow(2, (semitones / 12)); clip.setData("pitch", pitchRatio); if (data.warpMode == 0 && data.loop == 0 && data.warp == 0) { data.playLength = data.duration / pitchRatio; clip.setSize(); } }; this.setWarpMode = function(v) { var prevMode = data.warpMode; clip.setData("warpMode", v); if (prevMode == 0 && data.warpMode != 0 && !data.loop && !data.warp) { data.playLength = data.duration; clip.setSize(); } }; this.setSize = function(noWaveRedraw) { var width = data.playLength * twine.timeline.pixelsPerBeat; clip.element.css("width", width + "px"); setClipWaveform(noWaveRedraw); } this.redraw = function(noWaveRedraw) { if (!loaded) return; var b = twine.timeline.beatRegion; clip.setSize(noWaveRedraw); if ((data.position + data.playLength) < b[0] || data.position > b[1]) { return clip.element.hide(); } var css = { height: clip.channel.height + "px", left: (data.position - b[0]) * twine.timeline.pixelsPerBeat + "px" }; if (data.position < b[0]) { css.left = "0px"; css.width = (data.playLength - (b[0] - data.position)) * twine.timeline.pixelsPerBeat + "px"; } clip.element.show().css(css); }; this.clone = function() { var newData = Object.assign({}, data); newData.id = twine.getNewID(); var c = new Clip(twine, newData, clip); clip.channel.addClip(c); app.insertScore("twine_cloneclip", [0, 1, app.createCallback(function(ndata) { datatable = ndata.datatable; c.loadFromDataTable(ndata.datatable, ndata.clipindex); }), clip.data.clipindex]); }; async function loadData(ndata, name, colour, defaultLength) { twirl.loading.show("Loading"); if (ndata.status == -1) { return twirl.errorHandler("File not valid"); } else if (ndata.status == -2) { return twirl.errorHandler("File too large"); } datatable = ndata.data.datatable; await getDataFromTable(); data.clipindex = ndata.data.clipindex; if (name) { data.name = name; } if (defaultLength) { data.playLength = data.duration; } elWaveText.text(data.name); if (!colour) colour = twine.randomColour(); data.colour = colour; loaded = true; clip.redraw(); }; this.loadFromDataTable = async function(newDatatable, clipindex) { datatable = newDatatable; await getDataFromTable(); data.clipindex = clipindex; loaded = true; clip.redraw(); }; this.loadFromFtables = function(name, tables, colour) { twirl.loading.show("Loading"); var cbid = app.createCallback(async function(ndata){ await loadData(ndata, name, colour); twirl.loading.hide(); }); var call = [0, 1, cbid]; for (let t of tables) { call.push(t); } app.insertScore("twine_loadftables", call); }; this.loadFromPath = function(path, colour) { twirl.loading.show("Loading"); var cbid = app.createCallback(async function(ndata){ await loadData(ndata, path, colour, true); twirl.loading.hide(); }); app.insertScore("twine_loadpath", [0, 1, cbid, path]); }; function getMaxClipWidth() { var maxWidth = 9999; if (!data.warp && !data.loop) { maxWidth = data.duration * twine.timeline.pixelsPerBeat; } return maxWidth; } clip.movement = { pos1: 0, pos2: 0, pos3: 0, pos4: 0, offset: 0, startX: 0, startY: 0, clipWidth: 0, clipLeft: 0, clipTop: 0, lastLeft: 0, isCopying: false, mouseDown: function(e, dragType) { e.preventDefault(); twine.timeline.selectedClips.forEach(function(c){ c.movement.mouseDownInner(e, dragType); }); }, mouseDownInner: function(e, dragType) { e.preventDefault(); this.isCopying = false; this.offset = clip.element.offset().left this.clipWidth = parseFloat(clip.element.css("width")); this.clipTop = parseFloat(clip.element.css("top")); this.clipLeft = parseFloat(clip.element.css("left")); this.startX = e.clientX - e.target.getBoundingClientRect().left; this.startY = e.clientY - e.target.getBoundingClientRect().top; this.pos3 = e.clientX; this.pos4 = e.drag; this.lastLeft = (this.pos3 - this.startX - clip.channel.offset.left); $("html").on("mousemove", function(e){ clip.movement.doDrag(e, dragType); }).on("mouseup", this.endDrag); $("#container").css("cursor", "e-resize"); }, endDrag: function(e) { e.preventDefault(); this.isCopying = false; $("html").off("mouseup", this.endDrag).off("mousemove"); //, this.doDrag); $("#container").css("cursor", "pointer"); }, doDrag: function(e, dragType) { e.preventDefault(); twine.timeline.selectedClips.forEach(function(c){ c.movement.doDragInner(e, dragType); }); }, doDragInner: function (e, dragType) { e.preventDefault(); this.pos1 = this.pos3 - e.clientX; this.pos2 = this.pos4 - e.clientY; this.pos3 = e.clientX; this.pos4 = e.clientY; if (dragType == "right") { var maxWidth = getMaxClipWidth(); var newWidth = (this.pos3 - this.startX - clip.channel.offset.left - this.clipLeft); newWidth = twine.timeline.roundToGrid(newWidth); if (newWidth > maxWidth) newWidth = maxWidth; if (newWidth < minWidth) newWidth = minWidth; data.playLength = newWidth / twine.timeline.pixelsPerBeat; clip.element.css("width", newWidth + "px"); } else if (dragType == "left") { var maxWidth = getMaxClipWidth(); var left = (this.pos3 - this.startX - clip.channel.offset.left); left = twine.timeline.roundToGrid(left); if (left < 0) left = 0; var newWidth = (clipWidth - left) + this.clipLeft; var cWidth, cLeft; if (newWidth < minWidth) { cWidth = minWidth, this.clipLeft + minWidth; //(minWidth - left) + clipLeft; } else if (newWidth > maxWidth) { cWidth = maxWidth, cLeft = this.lastLeft; } else { lastLeft = left; cWidth = newWidth, cLeft = left; } clip.element.css({width: cWidth + "px", left: cLeft + "px"}); data.playLength = newWidth / twine.timeline.pixelsPerBeat; data.position = Math.min(0, (left / twine.timeline.pixelsPerBeat) + twine.timeline.beatRegion[0]); } else { if (e.ctrlKey && !this.isCopying) { this.isCopying = true; clip.clone(); } //var left = (this.pos3 - this.startX - clip.channel.offset.left); var left = (this.pos3 - this.clipLeft - clip.channel.offset.left); left = twine.timeline.roundToGrid(left); //var top = (this.pos4 - this.startY - clip.channel.offset.top); var top = (this.pos4 - this.clipTop - clip.channel.offset.top); if (top > clip.channel.height) { if (clip.channel.index + 1 < twine.timeline.channels.length) { clip.channel.removeClip(clip); twine.timeline.channels[clip.channel.index + 1].addClip(clip); } } else if (top < 0) { if (clip.channel.index -1 >= 0) { clip.channel.removeClip(clip); twine.timeline.channels[clip.channel.index - 1].addClip(clip); } } if (left < 0) left = 0; //if (left > clip.channel.width - clipWidth) left = clip.channel.width - clipWidth; if (left > clip.channel.width) left = clip.channel.width; data.position = (left / twine.timeline.pixelsPerBeat) + twine.timeline.beatRegion[0]; clip.element.css("left", left + "px"); } } // end doDragInner }; elMove.mousedown(clip.movement.mouseDown); elResizeRight.mousedown(function(e){ clip.movement.mouseDown(e, "right"); }); elResizeLeft.mousedown(function(e){ clip.movement.mouseDown(e, "left"); }); };