From 52b835a825025c41aaf87fc14cf7538850040475 Mon Sep 17 00:00:00 2001 From: Josip Miskovic Date: Sun, 29 Mar 2020 11:15:52 +0300 Subject: [PATCH] Refactor tapes and recorder handling of time --- freeform.lua | 1 - main.lua | 9 ++-- recorder.lua | 148 +++++++++++++++++++++++++++++---------------------- 3 files changed, 88 insertions(+), 70 deletions(-) diff --git a/freeform.lua b/freeform.lua index a4087a0..e3d938b 100644 --- a/freeform.lua +++ b/freeform.lua @@ -54,7 +54,6 @@ function freeform:interpret(s) element.oscA = l.angle(x, y, element.x, element.y) element.oscM = l.distance(x, y, element.x, element.y) end - print(element.note) end touch.note = element.note touch.location = {x * 0.7, 0.5} diff --git a/main.lua b/main.lua index e8bc0a3..ea8839b 100755 --- a/main.lua +++ b/main.lua @@ -10,7 +10,6 @@ local time = 0 local sw, sh, dpi local patch = selector local stream = {} -local tape1 function love.resize() sw, sh = love.graphics.getDimensions() @@ -24,10 +23,8 @@ function love.load() require('toolset') -- import module only after love.draw is defined controls.load() selector.load('patches') - tape1 = recorder.addTape() - tape2 = recorder.addTape() - tape1.position = {-1.65, -0.88} - tape2.position = {-1.30, -0.88} + recorder.addTape() + recorder.addTape() mock.load() love.audio.setPosition(0, 0, 0) love.audio.setVolume(1) @@ -66,7 +63,7 @@ function love.update(dt) patch:process(stream) recorder.process(stream) efx.process(stream) - love.timer.sleep(0.053) + love.timer.sleep(0.003) --stream is garbage collected end diff --git a/recorder.lua b/recorder.lua index 534b087..8454ba5 100644 --- a/recorder.lua +++ b/recorder.lua @@ -13,48 +13,71 @@ local colorScheme = { note = {l.hsl(0, 0.6, 0.4)}, } +states = { + off = {}, + armed = {}, + recording = {}, + playing = {} +} + -- global for all tapes +recorder.state = states.armed +recorder.length = 0 recorder.speed = 1 recorder.time = 0 +recorder.timePrev = -1 recorder.tapes = {} -recorder.loadedPatch = nil - ---[[ -TODO: - per-tape volume: low mid high - changing tempo - syncing between length multiples (4 bars against 1 bar) - adding echo track with dotted eighth delay - adjustable track icon position (on main screen) - saving and loading to storage - more than 2 tracks, adding and removing -]] +recorder.currentPatch = nil -- patch is preloaded here so it can be quickly transfered to tape function recorder.addTape() local self = setmetatable({}, tape) - self.position = {-1.5, -0.85} - self.recording = false - self.notes = {} - self.touchId = nil + -- tape properties + self.content = {} -- the actual recording of notes, array of {time, stream} tables + self.startTime = 0 -- time at which the used part of tape + self.endTime = 0 -- end of used part of tape + self.placement = {-1.65 + 0.35 * #recorder.tapes, -0.88} -- on-screen location + self.dragging = false -- being dragged around the screen + self.recording = false -- currently recording + self.doneRecording = false -- recording just stopped, needs processing + self.touchId = nil -- last touch id, to distinguish new touch from held touch self.patch = nil -- a copy of patch is instantiated just for tape playback - self.time = 0 - self.timePrev = 0 - self.length = math.huge -- huge! - self.recordingStop = false - self.canvas = love.graphics.newCanvas(200, 200) + self.headOnNote = false -- note being recorded or played currently + self.canvas = love.graphics.newCanvas(200, 200) -- tape visualization self:drawVinyl() table.insert(recorder.tapes, self) return self end + function recorder.patchChanged(patch) - recorder.loadedPatch = patch.load() + if recorder.state ~= states.off then + recorder.currentPatch = patch.load() + end end + function recorder.interpret(s, inSelector) for i,tape in ipairs(recorder.tapes) do + tape.doneRecording = false -- clean info from previous frame tape:interpret(s, inSelector) end + + if recorder.tapes[1] and recorder.tapes[1].recording and recorder.state ~= states.recording then + recorder.state = states.recording + recorder.time = 0 + end + if recorder.tapes[1] and recorder.tapes[1].doneRecording then + recorder.state = states.playing + recorder.length = recorder.tapes[1].endTime + end + + --[[ on recorder + if y - self.placement[2] > 0 then + recorder.speed = recorder.speed * 0.998 + else + recorder.speed = recorder.speed * 1.002 + end + --]] end @@ -62,6 +85,15 @@ function recorder.process(s) for i,tape in ipairs(recorder.tapes) do tape:process(s) end + + if recorder.state == states.recording or recorder.state == states.playing then + recorder.time = recorder.time + s.dt * recorder.speed + end + if recorder.state == states.playing then + recorder.time = recorder.time % recorder.length -- loop tape back to start + end + recorder.timePrev = recorder.time - s.dt * recorder.speed + track("%1.2f", recorder.time) end @@ -80,27 +112,21 @@ function tape:interpret(s, inSelector) for id,touch in pairs(s.touches) do cnt = cnt + 1 local x, y = love.graphics.inverseTransformPoint(touch[1], touch[2]) - if (x - self.position[1])^2 + (y - self.position[2])^2 < 0.03 then - if inSelector then - if y - self.position[2] > 0 then - recorder.speed = recorder.speed * 0.998 - else - recorder.speed = recorder.speed * 1.002 - end - else -- on new touch toggle recording + if (x - self.placement[1])^2 + (y - self.placement[2])^2 < 0.03 then + if not inSelector then + -- on new touch toggle recording if id ~= self.touchId then - if not self.recording then - self.patch = recorder.loadedPatch + if not self.recording then -- start recording + self.patch = recorder.currentPatch self.recording = true - self.time = 0 - self.notes = {} - else + self.content = {} + else -- stop recording self.recording = false - self.recordingStop = true - self.length = self.time - self.masterVolume = 0.5 + self.doneRecording = true + self.endTime = recorder.time + self.patch.sampler.masterVolume = 0.5 end - self.touchId = id -- mark id to distinguish new touch from held touch + self.touchId = id end end s.touches[id] = nil -- sneakily remove touch from stream to prevent unintended notes @@ -113,28 +139,25 @@ end function tape:process(s) - -- tape recording - if self.recording then - table.insert(self.notes, {self.time, s}) + local next = next + if self.recording then -- tape recording + table.insert(self.content, {recorder.time, s}) + self.headOnNote = next(s.touches) ~= nil + elseif self.doneRecording then -- tape post-processing + print('recorded', recorder.length, 'seconds of sweet sweet music') end - -- tape playback - if not self.recording and #self.notes > 0 and self.patch then - for i, rec in ipairs(self.notes) do + if not self.recording and self.patch then -- tape playback + for i, rec in ipairs(self.content) do local noteTime, stream = unpack(rec) - if noteTime < math.max(self.time, self.timePrev) and noteTime > math.min(self.time, self.timePrev) then - self.patch.sampler:processTouches(s.dt, stream.touches) + if noteTime < math.max(recorder.time, recorder.timePrev) and noteTime > math.min(recorder.time, recorder.timePrev) then + self.patch.sampler:processTouches(s.dt / recorder.length, stream.touches) + self.headOnNote = next(stream.touches) ~= nil end end - self.timePrev = self.time - -- loop tape back to start - if self.time > self.length then - self.time = 0 - self.timePrev = -s.dt - end end - self.time = self.time + s.dt * recorder.speed end + function tape:drawVinyl() love.graphics.origin() love.graphics.setCanvas(self.canvas) @@ -159,6 +182,7 @@ function tape:drawVinyl() love.graphics.setCanvas() end + function tape:drawNotes() love.graphics.origin() love.graphics.setCanvas(self.canvas) @@ -167,11 +191,11 @@ function tape:drawNotes() love.graphics.scale(100) love.graphics.setColor(colorScheme.note) -- notes - for i, rec in ipairs(self.notes) do + for i, rec in ipairs(self.content) do noteTime, stream = unpack(rec) for id, touch in pairs(stream.touches) do - if touch.note and noteTime < self.length then - local angle = 2 * math.pi * noteTime / self.length + if touch.note and noteTime < recorder.length then + local angle = 2 * math.pi * noteTime / recorder.length love.graphics.push() love.graphics.rotate(angle) love.graphics.translate(0.6 + touch.note * 0.01, 0) @@ -186,16 +210,15 @@ end function tape:draw() -- when recording stops, map recorded notes onto circle - if self.recordingStop then + if self.doneRecording then love.graphics.push() self:drawVinyl() self:drawNotes() - self.recordingStop = false love.graphics.pop() end -- vinyl record love.graphics.push() - love.graphics.translate(unpack(self.position)) + love.graphics.translate(unpack(self.placement)) love.graphics.setColor(colorScheme.background) love.graphics.circle("fill", 0, 0, 0.1) if self.recording then @@ -207,7 +230,7 @@ function tape:draw() love.graphics.setColor(1,1,1) love.graphics.setBlendMode("alpha", "premultiplied") love.graphics.scale(1/1000) - love.graphics.rotate(-2 * math.pi * self.time / self.length) + love.graphics.rotate(-2 * math.pi * recorder.time / recorder.length) love.graphics.draw(self.canvas, -100, -100) love.graphics.setBlendMode("alpha") love.graphics.pop() @@ -215,8 +238,7 @@ function tape:draw() -- playing head love.graphics.setColor(colorScheme.background) love.graphics.arc('fill', 0.05, 0, 0.065, math.pi * 1/12, -math.pi * 1/12) - love.graphics.setColor(colorScheme.head) - --love.graphics.rectangle('fill', 0.07, -0.013, 0.05, 0.028, 0.02, 0.02) + love.graphics.setColor(self.headOnNote and colorScheme.note or colorScheme.head) love.graphics.arc('fill', 0.06, 0, 0.05, math.pi * 1/12, -math.pi * 1/12) love.graphics.pop() end