Skip to content

Commit

Permalink
Refactor tapes and recorder handling of time
Browse files Browse the repository at this point in the history
  • Loading branch information
jmiskovic committed Mar 29, 2020
1 parent 3f3d71d commit 52b835a
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 70 deletions.
1 change: 0 additions & 1 deletion freeform.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
9 changes: 3 additions & 6 deletions main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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)
Expand Down Expand Up @@ -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

Expand Down
148 changes: 85 additions & 63 deletions recorder.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,55 +13,87 @@ 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


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


Expand All @@ -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
Expand All @@ -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)
Expand All @@ -159,6 +182,7 @@ function tape:drawVinyl()
love.graphics.setCanvas()
end


function tape:drawNotes()
love.graphics.origin()
love.graphics.setCanvas(self.canvas)
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -207,16 +230,15 @@ 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()
end
-- 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
Expand Down

0 comments on commit 52b835a

Please sign in to comment.