Skip to content

Commit

Permalink
New instrument with microphone-recorded sample
Browse files Browse the repository at this point in the history
  • Loading branch information
jmiskovic committed Jun 13, 2020
1 parent 0d9f6df commit 39e0ac6
Show file tree
Hide file tree
Showing 2 changed files with 341 additions and 0 deletions.
1 change: 1 addition & 0 deletions conf.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ function love.conf(t)
t.window.fullscreen = true
t.window.resizable = true
t.window.vsync = false
t.audio.mic = true
end
340 changes: 340 additions & 0 deletions patches/soundbyte/soundbyte.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,340 @@
local patch = {}
patch.__index = patch

local l = require('lume')
local hexgrid = require('hexgrid')
local sampler = require('sampler')
local fretboard = require('fretboard')
local hexpad = require('hexpad')
local freeform = require('freeform')
local efx = require('efx')

local colorScheme = {
background = {l.rgba(0xffb762ff)},
highlight = {l.rgba(0xff823bff)},
surface = {l.rgba(0xffffe4ff)},
black = {l.rgba(0x041528ff)},
outline = {l.rgba(0x604b46ff)},
}

-- Possible combination testing
local samplingFrequencies = {48000, 44100, 32000, 22050, 16000, 8000}
local bitDepths = {16, 8}
local bufferSize = 16384
-- on pixel 2 valid combinations are 22050 (8 & 16) and 48000 (8 & 16)

function patch.load()
local self = setmetatable({}, patch)
self.hexpad = hexpad.new(false, 0, 3)
self.fretboard = fretboard.new(false, {-12})
self.fretboard.neckHeight = 0.25
self.fretboard.fretWidth = 0.15

self.pitchboard = fretboard.new(false, {-48})
self.pitchboard.neckHeight = 0.2
self.pitchboard.fretWidth = 0.04

patch.triggers = {
{type='whitekey', note= -12, x=-0.054, y= 0.492, r= 0.20},
{type='whitekey', note= -10, x= 0.220, y= 0.481, r= 0.20},
{type='whitekey', note= -8, x= 0.493, y= 0.507, r= 0.20},
{type='whitekey', note= -7, x= 0.769, y= 0.484, r= 0.20},
{type='whitekey', note= -5, x= 1.043, y= 0.484, r= 0.20},
{type='whitekey', note= -3, x= 1.319, y= 0.475, r= 0.20},
{type='whitekey', note= -1, x= 1.596, y= 0.488, r= 0.20},
{type='blackkey', note= -11, x= 0.059, y= 0.270, r= 0.16},
{type='blackkey', note= -9, x= 0.396, y= 0.256, r= 0.16},
{type='blackkey', note= -6, x= 0.880, y= 0.244, r= 0.16},
{type='blackkey', note= -4, x= 1.193, y= 0.263, r= 0.16},
{type='blackkey', note= -2, x= 1.509, y= 0.261, r= 0.16},
{type='whitekey', note= 0, x=-0.072, y=-0.129, r= 0.20},
{type='whitekey', note= 2, x= 0.200, y=-0.151, r= 0.20},
{type='whitekey', note= 4, x= 0.467, y=-0.138, r= 0.20},
{type='whitekey', note= 5, x= 0.744, y=-0.153, r= 0.20},
{type='whitekey', note= 7, x= 1.011, y=-0.190, r= 0.20},
{type='whitekey', note= 9, x= 1.283, y=-0.210, r= 0.20},
{type='whitekey', note= 11, x= 1.556, y=-0.219, r= 0.20},
{type='blackkey', note= 1, x= 0.037, y=-0.378, r= 0.16},
{type='blackkey', note= 3, x= 0.346, y=-0.372, r= 0.16},
{type='blackkey', note= 6, x= 0.857, y=-0.415, r= 0.16},
{type='blackkey', note= 8, x= 1.165, y=-0.443, r= 0.16},
{type='blackkey', note= 10, x= 1.444, y=-0.443, r= 0.16},
}
patch.freeform = freeform.new(patch.triggers)

self.sampler = sampler.new({
{path='patches/chromakey/sustain.ogg', note= 0},
looped = true,
envelope = { attack = 0, decay = 0.40, sustain = 1, release = 0.2 },
})

self.efx = efx.load()

self.button = {
placement = {-0.25, -0.88},
pressed = nil,
}
local devices = love.audio.getRecordingDevices()
self.recorder = devices[1]
self.parameters = self:findRecordingParams()
self.recording = false -- currently storing buffers being recorded
self.recordingLength = 0 -- collected recording length in number of samples
self.collectedSamples = {} -- table holding recorded buffers
self.crossBuffer = nil -- store first buffer separately to crossfade

self.hexpad.drawCell = function(self, q, r, s, touch)
love.graphics.scale(touch and 0.8 or 0.75)
love.graphics.setColor(touch and colorScheme.highlight or colorScheme.surface)
love.graphics.polygon('fill', hexgrid.roundhex)
love.graphics.setLineWidth(0.05)
love.graphics.scale(not touch and 1 or 1 + 0.02 * math.sin(s.time * 50))
love.graphics.setColor(colorScheme.outline)
love.graphics.polygon('line', hexgrid.roundhex)
end
self.fretboard.colorScheme.neck = {0, 0, 0, 0}
self.fretboard.colorScheme.fret = {0, 0, 0, 0}
self.fretboard.colorScheme.dot = {0, 0, 0, 0}
self.fretboard.colorScheme.shade = {0, 0, 0, 0}
self.fretboard.colorScheme.string = colorScheme.background
self.fretboard.colorScheme.light = colorScheme.outline
self.pitchboard.colorScheme.neck = {0, 0, 0, 0}
self.pitchboard.colorScheme.fret = {0, 0, 0, 0}
self.pitchboard.colorScheme.dot = {0, 0, 0, 0}
self.pitchboard.colorScheme.shade = {0, 0, 0, 0}
self.pitchboard.colorScheme.string = colorScheme.surface
self.pitchboard.colorScheme.light = {0, 0, 0, 0}
self.freeform.colorScheme.whitekey = colorScheme.surface
self.freeform.colorScheme.blackkey = colorScheme.black
love.graphics.setBackgroundColor(colorScheme.background)
return self
end


function patch:findRecordingParams()
local success = false -- success locally before you hit it big
-- test all possible combos
for _, samplingFrequency in ipairs(samplingFrequencies) do
for _, bitDepth in ipairs(bitDepths) do
success = self.recorder:start(bufferSize, samplingFrequency, bitDepth, 1)
if success then
return {
samplingFrequency = samplingFrequency,
bitDepth = bitDepth
}
else
self.recorder:stop()
end
end
end
log('Please enable microphone permission')
return {}
end


local function withFretboard(fun, ...)
love.graphics.push()
love.graphics.translate(0, 0.95)
love.graphics.scale(1, 0.6)
fun(...)
love.graphics.pop()
end


local function withPitchboard(fun, ...)
love.graphics.push()
love.graphics.translate(0.95, -0.85)
love.graphics.scale(0.3, 1)
fun(...)
love.graphics.pop()
end


local function withHexpad(fun, ...)
love.graphics.push()
love.graphics.translate(-1, -0.1)
love.graphics.scale(0.6)
fun(...)
love.graphics.pop()
end


local function waveformInit()
patch.waveVisualization = {-1, 0}
end


local function waveformAdd(timeFraction, amplitude)
table.insert(patch.waveVisualization, -1 + 2 * timeFraction)
table.insert(patch.waveVisualization, math.log(1 + amplitude / 50))
end


local function waveformEnd()
table.insert(patch.waveVisualization, 1)
table.insert(patch.waveVisualization, 0)
end


local function waveformDraw()
if patch.waveVisualization then
love.graphics.setColor(colorScheme.highlight)
love.graphics.polygon('line', patch.waveVisualization)
end
end


function patch:process(s)
-- while recording constantly collect samples
if self.recording then
local data = self.recorder:getData()
if data then
if not self.crossBuffer then
self.crossBuffer = data
else
self.recordingLength = self.recordingLength + data:getSampleCount()
table.insert(self.collectedSamples, data)
end
end
end
-- recording stopped, assemble all collected samples
if self.button.touchId and not next(s.touches) then
self.button.touchId = nil
self.recording = false
if self.recordingLength > 0 then
local recording = love.sound.newSoundData(self.recordingLength, self.parameters.samplingFrequency, self.parameters.bitDepth, 1)
local sampleIndex = 0
local crossfade = math.min(self.crossBuffer:getSampleCount(), self.recordingLength/2)
--waveformInit()
for _, v in ipairs(self.collectedSamples) do
for i = 0, v:getSampleCount() - 1 do
if sampleIndex < self.recordingLength - crossfade then
recording:setSample(sampleIndex, v:getSample(i))
else
local fadeout = l.remap(sampleIndex, self.recordingLength - crossfade, self.recordingLength, 1, 0, 'clamp')
local fadein = 1 - fadeout
local crossSample = self.crossBuffer:getSample(self.crossBuffer:getSampleCount() - (self.recordingLength - sampleIndex))
recording:setSample(sampleIndex, v:getSample(i) * fadeout + crossSample * fadein)
end
--waveformAdd(sampleIndex / self.recordingLength, recording:getSample(sampleIndex))
sampleIndex = sampleIndex + 1
end
v:release()
end
self.sampler.samples[1].soundData = recording
--waveformEnd()
end
end
-- recording start when button is pressed
for id,touch in pairs(s.touches) do
local x, y = love.graphics.inverseTransformPoint(touch[1], touch[2])
if (x - self.button.placement[1])^2 + (y - self.button.placement[2])^2 < 0.03 then
if not self.button.touchId then
self.recorder:getData() -- clear data captured so far
self.recording = true
self.crossBuffer = nil
self.recordingLength = 0
self.collectedSamples = {}
end
self.button.touchId = id
s.touches[id] = nil -- sneakily remove touch from stream to prevent unintended notes
end
end
withHexpad(self.hexpad.interpret, self.hexpad, s)
withFretboard(self.fretboard.interpret, self.fretboard, s)
withPitchboard(self.pitchboard.interpret, self.pitchboard, s)
patch.freeform:interpret(s)
self.efx:process()
self.sampler:processTouches(s.dt, s.touches, self.efx)
end


function patch.drawMike(time)
-- stand
love.graphics.setColor(colorScheme.black)
love.graphics.rectangle('fill', -0.15, -1.3, 0.3, 0.5)
-- mike
love.graphics.setStencilTest("greater", 0)
local mikeStencil = function()
love.graphics.setColorMask() -- enable drawing inside stencil function
love.graphics.setColor(colorScheme.outline)
love.graphics.rectangle('fill', -0.6, -1, 1.2, 2, 0.4, 0.6)
end
love.graphics.stencil(mikeStencil, "replace", 1)
-- shading
love.graphics.setColor(colorScheme.surface)
love.graphics.circle('fill', -2.5, -0.7 + 0.2 * math.sin(time), 2.7)
-- gills
for i = -0.6, 0.8, 0.3 do
love.graphics.setColor(colorScheme.outline)
love.graphics.rectangle('fill', -0.65, i, 0.53, 0.1, 0.1)
love.graphics.setColor(colorScheme.black)
love.graphics.rectangle('fill', 0.25, i, 0.45, 0.1, 0.1)
end
-- mount
love.graphics.setColor(colorScheme.outline)
love.graphics.push()
love.graphics.translate(0, -1)
love.graphics.rotate(math.pi/4)
love.graphics.rectangle('fill', -0.25, -.25, 0.5, 0.5, 0.2)
love.graphics.pop()
love.graphics.setStencilTest() -- disable stencil
end


function patch:draw(s)
withHexpad(self.hexpad.draw, self.hexpad, s)
withFretboard(self.fretboard.draw, self.fretboard, s)
withPitchboard(self.pitchboard.draw, self.pitchboard, s)
love.graphics.setColor(colorScheme.highlight)
love.graphics.circle('fill', 0.35, -0.85, 0.05)
patch.freeform:draw(s)
--waveformDraw()
love.graphics.translate(self.button.placement[1], self.button.placement[2])
love.graphics.scale(0.1)
self.drawMike(s.time)
if self.recording then
love.graphics.push()
love.graphics.scale(0.03)
love.graphics.translate(30, -20)
love.graphics.setFont(hexpad.font)
love.graphics.setColor(colorScheme.highlight)
love.graphics.print('REC')
love.graphics.pop()
love.graphics.translate(0, 0.4)
love.graphics.scale(1.5)
end
end


function patch.icon(time)
love.graphics.setColor(colorScheme.highlight)
love.graphics.rectangle('fill', -1, -1, 2, 2)
love.graphics.scale(0.8 + 0.03 * math.sin(time)^10)
love.graphics.setColor(colorScheme.background)
love.graphics.circle('fill', -3, -1 + 0.2 * math.cos(time), 4)
patch.drawMike(time)
end

--[[
function love.keypressed(key)
if key == 'tab' then
freeform.editing = true
patch.freeform.selected = (patch.freeform.selected % #patch.triggers) + 1
end
if key == '`' then
patch.freeform.selected = ((patch.freeform.selected - 1) % #patch.triggers)
end
if key == 'return' then
for i,v in ipairs(patch.triggers) do
print(string.format('x=% 1.3f, y=% 1.3f, r=% 1.2f},',v.x, v.y, v.r))
end
end
if key == '=' then
patch.triggers[patch.freeform.selected].r = patch.triggers[patch.freeform.selected].r * 1.02
elseif key == '-' then
patch.triggers[patch.freeform.selected].r = patch.triggers[patch.freeform.selected].r / 1.02
end
end
--]]

return patch

0 comments on commit 39e0ac6

Please sign in to comment.