local sfxr = require("sfxr") -- Global stuff local source local sound local sounddata local seed local playbutton local playing = false local wavecanvas local statistics = { generation = 0, transfer = 0, waveview = 0, duration = 0 } -- This will hold all sliders and the wave form box local guiparams = {} -- The parameter list is built from this local guicategories = { { "Envelope", "envelope", { {"Attack Time", "attack", 0, 1}, {"Sustain Time", "sustain", 0, 1}, {"Sustain Punch", "punch", 0, 1}, {"Decay Time", "decay", 0, 1} } }, { "Frequency", "frequency", { {"Start", "start", 0, 1}, {"Minimum", "min", 0, 1}, {"Slide", "slide", -1, 1}, {"Delta Slide", "dslide", -1, 1} } }, { "Vibrato", "vibrato", { {"Depth", "depth", 0, 1}, {"Speed", "speed", 0, 1} } }, { "Change", "change", { {"Amount", "amount", -1, 1}, {"Speed", "speed", 0, 1} } }, { "Square Duty", "duty", { {"Ratio", "ratio", 0, 1}, {"Sweep", "sweep", -1, 1} } }, { "Phaser", "phaser", { {"Offset", "offset", -1, 1}, {"Sweep", "sweep", -1, 1} } }, { "Low Pass", "lowpass", { {"Cutoff", "cutoff", 0, 1}, {"Sweep", "sweep", -1, 1}, {"Resonance", "resonance", 0, 1} } }, { "High Pass", "highpass", { {"Cutoff", "cutoff", 0, 1}, {"Sweep", "sweep", -1, 1} } } } -- Easy lookup of wave forms local waveFormList = { ["Square"] = 0, ["Sawtooth"] = 1, ["Sine"] = 2, ["Noise"] = 3, "Square", "Sawtooth", "Sine", "Noise" } function stopSound() playbutton:SetText("Play") if source then source:stop() end end function playSound() -- Stop the currently playing source if source then source:stop() end local t = love.timer.getTime() local tab = sound:generateTable(sfxr.FREQ_44100, sfxr.BITS_FLOAT) t = love.timer.getTime() - t statistics.generation = math.floor(t * 10000) / 10 if #tab == 0 then return nil end sounddata = love.sound.newSoundData(#tab, 44100, 16, 1) statistics.duration = math.floor(sounddata:getDuration() * 10000) / 10 -- Stuff for the wave view local waveview = {} local j = 0 local max = -1 local min = 1 local avg = 0.5 local t = love.timer.getTime() for i = 0, #tab - 1 do local v = tab[i + 1] -- Copy the sample over to the SoundData sounddata:setSample(i, v) -- Add the minimal and maximal sample to the wave view -- every 256 samples. This is how Audacity does it, actually. j = j + 1 min = math.min(v, min) max = math.max(v, max) if j >= 256 then waveview[#waveview + 1] = min waveview[#waveview + 1] = max j = 0 min, max = 1, -1 end end t = love.timer.getTime() - t statistics.transfer = math.floor(t * 10000) / 10 updateWaveCanvas(waveview) updateStatistics() if sounddata then source = love.audio.newSource(sounddata) source:play() playbutton:SetText("Stop Playing") playing = true end end function createSeedBox() local f = lf.Create("form") f:SetName("Random Seed") seed = lf.Create("numberbox") seed:SetValue(math.floor(love.timer.getTime())) seed:SetMax(math.huge) seed:SetMin(-math.huge) seed:SetWidth(100) f:AddItem(seed) f:SetPos(5, 240) end function createPresetGenerators() local f = lf.Create("form") f:SetName("Preset Generators") local generators = { {"Pickup/Coin", sound.randomPickup}, {"Laser/Shoot", sound.randomLaser}, {"Explosion", sound.randomExplosion}, {"Powerup", sound.randomPowerup}, {"Hit/Hurt", sound.randomHit}, {"Jump", sound.randomJump}, {"Blip/Select", sound.randomBlip} } for i, v in ipairs(generators) do local b = lf.Create("button") b:SetText(v[1]) b:SetWidth(100) f:AddItem(b) b.OnClick = function(self) v[2](sound, seed:GetValue()) seed:SetValue(seed:GetValue() + 1) updateParameters() playSound() end end f:SetPos(5, 5) f:SetWidth(110) end function createRandomizers() local f = lf.Create("form") f:SetName("Randomizers") local b = lf.Create("button") b:SetText("Mutate") b:SetWidth(100) f:AddItem(b) b.OnClick = function(self) sound:mutate() updateParameters() playSound() end local b = lf.Create("button") b:SetText("Randomize") b:SetWidth(100) f:AddItem(b) b.OnClick = function(self) sound:randomize(seed:GetValue()) updateParameters() seed:SetValue(seed:GetValue() + 1) playSound() end f:SetPos(5, 515) f:SetSize(110, 80) end function createParameters() local f = lf.Create("form") f:SetName("Parameters") local l = lf.Create("list") l:SetSpacing(5) l:SetPadding(5) l:SetSize(340, 565) f:AddItem(l) -- Waveforms l:AddItem(lf.Create("text"):SetPos(0, pheight):SetText("Wave Form")) local m = lf.Create("multichoice") m:AddChoice("Square") m:AddChoice("Sawtooth") m:AddChoice("Sine") m:AddChoice("Noise") m:SetChoice("Square") m.OnChoiceSelected = function(o, c) sound.wavetype = waveFormList[c] end l:AddItem(m) guiparams.waveform = m -- Repeat speed local t = lf.Create("text"):SetPos(0, pheight) t:SetText("Repeat Speed 0.00") local s = lf.Create("slider") s:SetWidth(120) s:SetMinMax(0, 1) s:SetValue(sound.repeatspeed) s.Update = function(o) local v = math.floor(s:GetValue() * 100) / 100 if v <= 0.02 and v >= -0.02 then s:SetValue(0) sound.repeatspeed = 0 t:SetText("Repeat Speed 0.00") else sound.repeatspeed = v t:SetText("Repeat Speed " .. tostring(math.floor(v * 100) / 100)) end end l:AddItem(t) l:AddItem(s) guiparams.repeatspeed = {s, t} for i1, v1 in ipairs(guicategories) do local c = lf.Create("collapsiblecategory") c:SetText(v1[1]) l:AddItem(c) local p = lf.Create("panel") local pheight = 0 p.Draw = function() end c:SetObject(p) guiparams[v1[2]] = {} for i2, v2 in ipairs(v1[3]) do lf.Create("text", p):SetPos(0, pheight):SetText(v2[1]) local t = lf.Create("text", p):SetPos(95, pheight):SetText("0.00") local s = lf.Create("slider", p):SetPos(130, pheight - 3):SetWidth(170) s:SetMinMax(v2[3], v2[4]) s:SetValue(sound[v1[2]][v2[2]]) s.Update = function(o) local v = s:GetValue() if v <= 0.02 and v >= -0.02 then s:SetValue(0) sound[v1[2]][v2[2]] = 0 t:SetText("0.00") else sound[v1[2]][v2[2]] = v t:SetText(math.floor(v * 100) / 100) end end guiparams[v1[2]][v2[2]] = {s, t} pheight = pheight + 30 end p:SetHeight(pheight - 10) end f:SetPos(125, 5) f:SetSize(350, 590) end function createActionButtons() local f = lf.Create("form") f:SetName("Actions") local b = lf.Create("button") b:SetText("Generate and Play") b:SetWidth(140) b.OnClick = function(o) if not playing then playSound() else stopSound() end end playbutton = b f:AddItem(b) local fr = lf.Create("frame") fr:SetName("Alright") fr:SetSize(400, 105) fr:Center() fr:SetVisible(false) fr:SetModal(false) fr.OnClose = function(o) fr:SetVisible(false):SetModal(false) return false end local frb = lf.Create("button", fr) frb:SetText("Okay") frb:SetPos(5, 70) frb.OnClick = function(o) fr:SetVisible(false):SetModal(false) end local frt = lf.Create("text", fr) frt:SetPos(5, 30) local sb = lf.Create("button") sb:SetText("Save") sb:SetWidth(67) sb.OnClick = function(o) local p = love.filesystem.getSaveDirectory() .. "/" .. "sound.lua" sound:save(p, true) frt:SetText("Saved to\n" .. p) fr:SetVisible(true):SetModal(true):Center() end f:AddItem(sb) local lb = lf.Create("button") lb:SetText("Load") lb:SetWidth(67) lb.OnClick = function(o) local p = love.filesystem.getSaveDirectory() .. "/" .. "sound.lua" sound:load(p) frt:SetText("Loaded from\n" .. p) fr:SetVisible(true):SetModal(true):Center() end f:AddItem(lb) local eb = lf.Create("button") eb:SetText("Export WAV") eb:SetWidth(140) eb.OnClick = function(o) local p = love.filesystem.getSaveDirectory() .. "/" .. "sound.wav" sound:exportWAV(p) frt:SetText("Exported WAV to\n" .. p) fr:SetVisible(true):SetModal(true):Center() end f:AddItem(eb) f:SetPos(485, 485) f:SetSize(150, 110) -- well ugh lb:SetPos(78, 47) eb:SetY(77) end function createOther() local f = lf.Create("form") f:SetName("Wave View") f:SetPos(485, 5) f:SetSize(150, 170) local f = lf.Create("form") f:SetName("Volume") local t = lf.Create("text") t:SetText("Master 0.5") f:AddItem(t) local s = lf.Create("slider") s:SetMinMax(0, 1) s:SetSize(135, 20) s.Update = function(o) local v = s:GetValue() if v <= 0.52 and v >= 0.48 then s:SetValue(0.5) v = 0.5 end sound.volume.master = v t:SetText("Master " .. tostring(math.floor(v * 100) / 100)) end s:SetValue(sound.volume.master) f:AddItem(s) local t = lf.Create("text") t:SetText("Sound 0.5") f:AddItem(t) local s = lf.Create("slider") s:SetMinMax(0, 1) s:SetSize(135, 20) s.Update = function(o) local v = s:GetValue() if v <= 0.52 and v >= 0.48 then s:SetValue(0.5) v = 0.5 end sound.volume.sound = v t:SetText("Sound " .. tostring(math.floor(v * 100) / 100)) end s:SetValue(sound.volume.sound) f:AddItem(s) f:SetPos(485, 370) f:SetWidth(150) local f = lf.Create("form") f:SetName("Times / Duration") local t = lf.Create("text") t:SetText("Generation: 0ms") f:AddItem(t) statistics.generationtext = t local t = lf.Create("text") t:SetText("Transfer: 0ms") f:AddItem(t) statistics.transfertext = t local t = lf.Create("text") t:SetText("Wave View: 0ms") f:AddItem(t) statistics.waveviewtext = t local t = lf.Create("text") t:SetText("Duration: 0ms") f:AddItem(t) statistics.durationtext = t f:SetPos(485, 185) f:SetWidth(150) end function updateParameters() -- Iterate through the list of parameters and update all of them for i1, v1 in ipairs(guicategories) do for i2, v2 in ipairs(v1[3]) do local v = sound[v1[2]][v2[2]] local s, t = unpack(guiparams[v1[2]][v2[2]]) s:SetValue(v) t:SetText(math.floor(v * 100) / 100) end end local s, t = unpack(guiparams.repeatspeed) local v = sound.repeatspeed s:SetValue(v) t:SetText("Repeat Speed " .. tostring(math.floor(v * 100) / 100)) guiparams.waveform:SetChoice(waveFormList[sound.wavetype + 1]) end function updateWaveCanvas(waveview) local t = love.timer.getTime() wavecanvas:clear() love.graphics.setCanvas(wavecanvas) love.graphics.setColor(255, 255, 255) love.graphics.setLineStyle("rough") -- Iterate through the passed table and draw all lines to the canvas local step = 125 / #waveview local last = 70 for i, v in ipairs(waveview) do local x = (i * step) local y = (v + 1) * 70 love.graphics.line(x - step, last, x, y) last = y end -- Draw the zero line love.graphics.setColor(255, 80, 51, 200) love.graphics.line(0, 70, 125, 70) love.graphics.setCanvas() t = love.timer.getTime() - t statistics.waveview = math.floor(t * 10000) / 10 end function drawWaveView() if source then love.graphics.setColor(255, 255, 255) love.graphics.draw(wavecanvas, 495, 25) -- Draw a fancy position cursor local pos = source:tell("samples") local max = sounddata:getSampleCount() local x = 495 + (pos / max) * 125 love.graphics.setColor(255, 153, 0) love.graphics.line(x, 25, x, 165) end end function updateStatistics() statistics.durationtext:SetText("Duration: " .. statistics.duration .. " ms") statistics.transfertext:SetText("Transfer: " .. statistics.transfer .. " ms") statistics.waveviewtext:SetText("Wave View: " .. statistics.waveview .. " ms") statistics.generationtext:SetText("Generation: " .. statistics.generation .. " ms") end function love.load() require("loveframes") lf = loveframes lf.util.SetActiveSkin("Orange") love.graphics.setBackgroundColor(200, 200, 200) sound = sfxr.newSound() createSeedBox() createPresetGenerators() createRandomizers() createParameters() createActionButtons() createOther() wavecanvas = love.graphics.newCanvas(125, 140) love.mousepressed = lf.mousepressed love.mousereleased = lf.mousereleased love.keyreleased = lf.keyreleased love.textinput = lf.textinput end function love.update(dt) lf.update(dt) if source then if playing and not source:isPlaying() then playing = false playbutton:SetText("Generate and Play") end end end function love.draw() lf.draw() drawWaveView() end function love.keypressed(key) if key == " " or key == "return" then playSound() elseif key == "escape" then love.event.push("quit") end lf.keypressed(key) end