main.lua 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  1. #/usr/bin/env love
  2. -- love .
  3. local sfxr = require("sfxr")
  4. -- Global stuff
  5. local source
  6. local sound
  7. local sounddata
  8. local seed
  9. local playbutton
  10. local playing = false
  11. local wavecanvas
  12. local statistics = {
  13. generation = 0,
  14. transfer = 0,
  15. waveview = 0,
  16. duration = 0
  17. }
  18. -- This will hold all sliders and the wave form box
  19. local guiparams = {}
  20. -- The parameter list is built from this
  21. -- {{"Text", "table name", {{"Text", "table paramter", min, max}, ...}}, ...}
  22. local guicategories = {
  23. {
  24. "Envelope",
  25. "envelope",
  26. {
  27. {"Attack Time", "attack", 0, 1},
  28. {"Sustain Time", "sustain", 0, 1},
  29. {"Sustain Punch", "punch", 0, 1},
  30. {"Decay Time", "decay", 0, 1}
  31. }
  32. },
  33. {
  34. "Frequency",
  35. "frequency",
  36. {
  37. {"Start", "start", 0, 1},
  38. {"Minimum", "min", 0, 1},
  39. {"Slide", "slide", -1, 1},
  40. {"Delta Slide", "dslide", -1, 1}
  41. }
  42. },
  43. {
  44. "Vibrato",
  45. "vibrato",
  46. {
  47. {"Depth", "depth", 0, 1},
  48. {"Speed", "speed", 0, 1}
  49. }
  50. },
  51. {
  52. "Change",
  53. "change",
  54. {
  55. {"Amount", "amount", -1, 1},
  56. {"Speed", "speed", 0, 1}
  57. }
  58. },
  59. {
  60. "Square Duty",
  61. "duty",
  62. {
  63. {"Ratio", "ratio", 0, 1},
  64. {"Sweep", "sweep", -1, 1}
  65. }
  66. },
  67. {
  68. "Phaser",
  69. "phaser",
  70. {
  71. {"Offset", "offset", -1, 1},
  72. {"Sweep", "sweep", -1, 1}
  73. }
  74. },
  75. {
  76. "Low Pass",
  77. "lowpass",
  78. {
  79. {"Cutoff", "cutoff", 0, 1},
  80. {"Sweep", "sweep", -1, 1},
  81. {"Resonance", "resonance", 0, 1}
  82. }
  83. },
  84. {
  85. "High Pass",
  86. "highpass",
  87. {
  88. {"Cutoff", "cutoff", 0, 1},
  89. {"Sweep", "sweep", -1, 1}
  90. }
  91. }
  92. }
  93. -- Easy lookup of wave forms
  94. local waveFormList = {
  95. ["Square"] = 0,
  96. ["Sawtooth"] = 1,
  97. ["Sine"] = 2,
  98. ["Noise"] = 3,
  99. [0] = "Square",
  100. [1] = "Sawtooth",
  101. [2] = "Sine",
  102. [3] = "Noise"
  103. }
  104. function stopSound()
  105. playbutton:SetText("Play")
  106. if source then
  107. source:stop()
  108. end
  109. end
  110. function playSound()
  111. -- Stop the currently playing source
  112. if source then
  113. source:stop()
  114. end
  115. local t = love.timer.getTime()
  116. local tab = sound:generateTable(sfxr.FREQ_44100, sfxr.BITS_FLOAT)
  117. t = love.timer.getTime() - t
  118. statistics.generation = math.floor(t * 10000) / 10
  119. if #tab == 0 then
  120. return nil
  121. end
  122. sounddata = love.sound.newSoundData(#tab, 44100, 16, 1)
  123. statistics.duration = math.floor(sounddata:getDuration() * 10000) / 10
  124. -- Stuff for the wave view
  125. local waveview = {}
  126. local j = 0
  127. local max = -1
  128. local min = 1
  129. local avg = 0.5
  130. local t = love.timer.getTime()
  131. for i = 0, #tab - 1 do
  132. local v = tab[i + 1]
  133. -- Copy the sample over to the SoundData
  134. sounddata:setSample(i, v)
  135. -- Add the minimal and maximal sample to the wave view
  136. -- every 256 samples. This is how Audacity does it, actually.
  137. j = j + 1
  138. min = math.min(v, min)
  139. max = math.max(v, max)
  140. if j >= 256 then
  141. waveview[#waveview + 1] = min
  142. waveview[#waveview + 1] = max
  143. j = 0
  144. min, max = 1, -1
  145. end
  146. end
  147. t = love.timer.getTime() - t
  148. statistics.transfer = math.floor(t * 10000) / 10
  149. updateWaveCanvas(waveview)
  150. updateStatistics()
  151. if sounddata then
  152. source = love.audio.newSource(sounddata)
  153. source:play()
  154. playbutton:SetText("Stop Playing")
  155. playing = true
  156. end
  157. end
  158. function createSeedBox()
  159. local f = lf.Create("form"):SetName("Random Seed")
  160. seed = lf.Create("numberbox")
  161. :SetValue(math.floor(love.timer.getTime()))
  162. :SetMax(math.huge)
  163. :SetMin(-math.huge)
  164. :SetWidth(100)
  165. f:AddItem(seed):SetPos(5, 240)
  166. end
  167. function createPresetGenerators()
  168. local f = lf.Create("form")
  169. :SetName("Preset Generators")
  170. local generators = {
  171. {"Pickup/Coin", sound.randomPickup},
  172. {"Laser/Shoot", sound.randomLaser},
  173. {"Explosion", sound.randomExplosion},
  174. {"Powerup", sound.randomPowerup},
  175. {"Hit/Hurt", sound.randomHit},
  176. {"Jump", sound.randomJump},
  177. {"Blip/Select", sound.randomBlip}
  178. }
  179. for i, v in ipairs(generators) do
  180. local b = lf.Create("button")
  181. :SetText(v[1])
  182. :SetWidth(100)
  183. f:AddItem(b)
  184. b.OnClick = function(self)
  185. v[2](sound, seed:GetValue())
  186. seed:SetValue(seed:GetValue() + 1)
  187. updateParameters()
  188. playSound()
  189. end
  190. end
  191. f:SetPos(5, 5):SetWidth(110)
  192. end
  193. function createRandomizers()
  194. local f = lf.Create("form"):SetName("Randomizers")
  195. local b = lf.Create("button")
  196. :SetText("Mutate")
  197. :SetWidth(100)
  198. f:AddItem(b)
  199. b.OnClick = function(self)
  200. sound:mutate()
  201. updateParameters()
  202. playSound()
  203. end
  204. local b = lf.Create("button")
  205. :SetText("Randomize")
  206. :SetWidth(100)
  207. f:AddItem(b)
  208. b.OnClick = function(self)
  209. sound:randomize(seed:GetValue())
  210. updateParameters()
  211. seed:SetValue(seed:GetValue() + 1)
  212. playSound()
  213. end
  214. f:SetPos(5, 515):SetSize(110, 80)
  215. end
  216. function createParameters()
  217. local f = lf.Create("form"):SetName("Parameters")
  218. local l = lf.Create("list")
  219. :SetSpacing(5)
  220. :SetPadding(5)
  221. :SetSize(340, 565)
  222. f:AddItem(l)
  223. -- Waveforms
  224. l:AddItem(lf.Create("text"):SetPos(0, pheight):SetText("Wave Form"))
  225. local m = lf.Create("multichoice")
  226. for i = 0, #waveFormList do
  227. m:AddChoice(waveFormList[i])
  228. end
  229. m:SetChoice("Square")
  230. m.OnChoiceSelected = function(o, c)
  231. sound.wavetype = waveFormList[c]
  232. end
  233. l:AddItem(m)
  234. guiparams.waveform = m
  235. -- Repeat speed
  236. local t = lf.Create("text")
  237. :SetText("Repeat Speed 0")
  238. :SetPos(0, pheight)
  239. local s = lf.Create("slider")
  240. :SetWidth(120)
  241. :SetMinMax(0, 1)
  242. :SetValue(sound.repeatspeed)
  243. s.OnValueChanged = function(o)
  244. local v = o:GetValue()
  245. if v <= 0.02 and v >= -0.02 and v ~= 0 then
  246. o:SetValue(0)
  247. sound.repeatspeed = 0
  248. t:SetText("Repeat Speed 0")
  249. else
  250. sound.repeatspeed = v
  251. t:SetText("Repeat Speed " .. tostring(math.floor(v * 100) / 100))
  252. end
  253. end
  254. l:AddItem(t):AddItem(s)
  255. guiparams.repeatspeed = {s, t}
  256. for i1, v1 in ipairs(guicategories) do
  257. local c = lf.Create("collapsiblecategory"):SetText(v1[1])
  258. l:AddItem(c)
  259. local p = lf.Create("panel")
  260. local pheight = 0
  261. p.Draw = function() end
  262. c:SetObject(p)
  263. guiparams[v1[2]] = {}
  264. for i2, v2 in ipairs(v1[3]) do
  265. lf.Create("text", p)
  266. :SetText(v2[1])
  267. :SetPos(0, pheight)
  268. local t = lf.Create("text", p)
  269. :SetText("0")
  270. :SetPos(95, pheight)
  271. local s = lf.Create("slider", p)
  272. :SetPos(130, pheight - 3)
  273. :SetWidth(170)
  274. :SetMinMax(v2[3], v2[4])
  275. :SetValue(sound[v1[2]][v2[2]])
  276. s.OnValueChanged = function(o)
  277. local v = o:GetValue()
  278. if v <= 0.02 and v >= -0.02 and v ~= 0 then
  279. o:SetValue(0)
  280. sound[v1[2]][v2[2]] = 0
  281. t:SetText("0")
  282. else
  283. sound[v1[2]][v2[2]] = v
  284. t:SetText(math.floor(v * 100) / 100)
  285. end
  286. end
  287. guiparams[v1[2]][v2[2]] = {s, t}
  288. pheight = pheight + 30
  289. end
  290. p:SetHeight(pheight - 10)
  291. end
  292. f:SetPos(125, 5):SetSize(350, 590)
  293. end
  294. function createActionButtons()
  295. local f = lf.Create("form"):SetName("Actions")
  296. local b = lf.Create("button")
  297. :SetText("Generate and Play")
  298. :SetWidth(140)
  299. b.OnClick = function(o)
  300. if not playing then
  301. playSound()
  302. else
  303. stopSound()
  304. end
  305. end
  306. playbutton = b
  307. f:AddItem(b)
  308. local fr = lf.Create("frame")
  309. :SetName("File Picker")
  310. :SetSize(400, 300)
  311. :Center()
  312. :SetVisible(false)
  313. :SetModal(false)
  314. local frl = lf.Create("columnlist", fr)
  315. :SetPos(5, 30)
  316. :SetSize(390, 235)
  317. :AddColumn("Name")
  318. local frt = lf.Create("textinput", fr)
  319. :SetPos(5, 270)
  320. :SetWidth(300)
  321. local frb = lf.Create("button", fr)
  322. :SetPos(315, 270)
  323. frl.OnRowSelected = function(p, row, data)
  324. frt:SetText(data[1])
  325. end
  326. fr.OnClose = function(o)
  327. frl:Clear()
  328. fr:SetVisible(false):SetModal(false)
  329. return false
  330. end
  331. local function saveHandler(type, cb)
  332. return function()
  333. fr:SetName("Save to ." .. type)
  334. frt:SetText("sound." .. type)
  335. frb:SetText("Save")
  336. love.filesystem.getDirectoryItems("sounds", function(name)
  337. if name:find(type, #type-#name+1, true) then
  338. frl:AddRow(name)
  339. end
  340. end)
  341. frb.OnClick = function(o)
  342. local name = frt:GetText()
  343. if (#name > 0) then
  344. local f = love.filesystem.newFile("sounds/" .. name, "w")
  345. if f then
  346. cb(f)
  347. frl:Clear()
  348. fr:SetVisible(false):SetModal(false)
  349. end
  350. end
  351. end
  352. frt.OnEnter = frb.OnClick
  353. fr:SetVisible(true)
  354. :SetModal(true)
  355. :Center()
  356. end
  357. end
  358. local function loadHandler(type, cb)
  359. return function()
  360. fr:SetName("Load from ." .. type)
  361. frt:SetText("sound." .. type)
  362. frb:SetText("Load")
  363. love.filesystem.getDirectoryItems("sounds", function(name)
  364. if name:find(type, #type-#name+1, true) then
  365. frl:AddRow(name)
  366. end
  367. end)
  368. frb.OnClick = function(o)
  369. local name = frt:GetText()
  370. if (#name > 0) then
  371. local f = love.filesystem.newFile("sounds/" .. name, "r")
  372. if f then
  373. cb(f)
  374. frl:Clear()
  375. fr:SetVisible(false):SetModal(false)
  376. end
  377. end
  378. end
  379. frt.OnEnter = frb.OnClick
  380. fr:SetVisible(true)
  381. :SetModal(true)
  382. :Center()
  383. end
  384. end
  385. local sb = lf.Create("button")
  386. :SetText("Save Lua")
  387. :SetWidth(67)
  388. sb.OnClick = saveHandler("lua", function(f) sound:save(f, true) end)
  389. f:AddItem(sb)
  390. local lb = lf.Create("button")
  391. :SetText("Load Lua")
  392. :SetWidth(67)
  393. lb.OnClick = loadHandler("lua", function(f) sound:load(f) end)
  394. f:AddItem(lb)
  395. local bsb = lf.Create("button")
  396. :SetText("Save binary")
  397. :SetWidth(67)
  398. bsb.OnClick = saveHandler("sfs", function(f) sound:saveBinary(f) end)
  399. f:AddItem(bsb)
  400. local blb = lf.Create("button")
  401. :SetText("Load binary")
  402. :SetWidth(67)
  403. blb.OnClick = loadHandler("sfs", function(f) sound:loadBinary(f) end)
  404. f:AddItem(blb)
  405. local eb = lf.Create("button")
  406. :SetText("Export WAV")
  407. :SetWidth(140)
  408. eb.OnClick = saveHandler("wav", function(f) sound:exportWAV(f) end)
  409. f:AddItem(eb)
  410. f:SetPos(485, 455):SetSize(150, 140)
  411. lb:SetPos(78, 47)
  412. bsb:SetY(77)
  413. blb:SetPos(78, 77)
  414. eb:SetY(107)
  415. end
  416. function createOther()
  417. local f = lf.Create("form")
  418. :SetName("Wave View")
  419. :SetPos(485, 5)
  420. :SetSize(150, 170)
  421. local draw = function(o)
  422. if source then
  423. love.graphics.setColor(255, 255, 255)
  424. love.graphics.draw(wavecanvas, 495, 25)
  425. -- Draw a fancy position cursor
  426. local pos = source:tell("samples")
  427. local max = sounddata:getSampleCount()
  428. local x = 495 + (pos / max) * 125
  429. love.graphics.setColor(255, 153, 0)
  430. love.graphics.line(x, 25, x, 165)
  431. end
  432. lf.skins.available["Orange"].DrawForm(o)
  433. end
  434. f.Draw = draw
  435. local f = lf.Create("form"):SetName("Volume")
  436. local t = lf.Create("text"):SetText("Master 0.5")
  437. f:AddItem(t)
  438. local s = lf.Create("slider")
  439. :SetMinMax(0, 1)
  440. :SetSize(135, 20)
  441. s.OnValueChanged = function(o)
  442. local v = o:GetValue()
  443. if v <= 0.52 and v >= 0.48 and v ~= 0.5 then
  444. o:SetValue(0.5)
  445. v = 0.5
  446. end
  447. sound.volume.master = v
  448. t:SetText("Master " .. tostring(math.floor(v * 100) / 100))
  449. end
  450. s:SetValue(sound.volume.master)
  451. f:AddItem(s)
  452. local t = lf.Create("text"):SetText("Sound 0.5")
  453. f:AddItem(t)
  454. local s = lf.Create("slider")
  455. :SetMinMax(0, 1)
  456. :SetSize(135, 20)
  457. s.OnValueChanged = function(o)
  458. local v = o:GetValue()
  459. if v <= 0.52 and v >= 0.48 and v ~= 0.5 then
  460. o:SetValue(0.5)
  461. v = 0.5
  462. end
  463. sound.volume.sound = v
  464. t:SetText("Sound " .. tostring(math.floor(v * 100) / 100))
  465. end
  466. s:SetValue(sound.volume.sound)
  467. f:AddItem(s)
  468. f:SetPos(485, 340):SetWidth(150)
  469. local f = lf.Create("form"):SetName("Times / Duration")
  470. local t = lf.Create("text"):SetText("Generation: 0ms")
  471. f:AddItem(t)
  472. statistics.generationtext = t
  473. local t = lf.Create("text"):SetText("Transfer: 0ms")
  474. f:AddItem(t)
  475. statistics.transfertext = t
  476. local t = lf.Create("text"):SetText("Wave View: 0ms")
  477. f:AddItem(t)
  478. statistics.waveviewtext = t
  479. local t = lf.Create("text"):SetText("Duration: 0ms")
  480. f:AddItem(t)
  481. statistics.durationtext = t
  482. f:SetPos(485, 185):SetWidth(150)
  483. end
  484. function updateParameters()
  485. -- Iterate through the list of parameters and update all of them
  486. for i1, v1 in ipairs(guicategories) do
  487. for i2, v2 in ipairs(v1[3]) do
  488. local v = sound[v1[2]][v2[2]]
  489. local s, t = unpack(guiparams[v1[2]][v2[2]])
  490. s:SetValue(v)
  491. t:SetText(math.floor(v * 100) / 100)
  492. end
  493. end
  494. local s, t = unpack(guiparams.repeatspeed)
  495. local v = sound.repeatspeed
  496. s:SetValue(v)
  497. t:SetText("Repeat Speed " .. tostring(math.floor(v * 100) / 100))
  498. guiparams.waveform:SetChoice(waveFormList[sound.wavetype])
  499. end
  500. function updateWaveCanvas(waveview)
  501. local t = love.timer.getTime()
  502. wavecanvas:clear()
  503. love.graphics.setCanvas(wavecanvas)
  504. love.graphics.setColor(255, 255, 255)
  505. love.graphics.setLineStyle("rough")
  506. -- Iterate through the passed table and draw all lines to the canvas
  507. local step = 125 / #waveview
  508. local last = 70
  509. for i, v in ipairs(waveview) do
  510. local x = (i * step)
  511. local y = (-v + 1) * 70
  512. love.graphics.line(x - step, last, x, y)
  513. last = y
  514. end
  515. -- Draw the zero line
  516. love.graphics.setColor(255, 80, 51, 200)
  517. love.graphics.line(0, 70, 125, 70)
  518. love.graphics.setCanvas()
  519. t = love.timer.getTime() - t
  520. statistics.waveview = math.floor(t * 10000) / 10
  521. end
  522. function updateStatistics()
  523. statistics.durationtext:SetText("Duration: " .. statistics.duration .. " ms")
  524. statistics.transfertext:SetText("Transfer: " .. statistics.transfer .. " ms")
  525. statistics.waveviewtext:SetText("Wave View: " .. statistics.waveview .. " ms")
  526. statistics.generationtext:SetText("Generation: " .. statistics.generation .. " ms")
  527. end
  528. function love.load()
  529. require("loveframes")
  530. lf = loveframes
  531. lf.util.SetActiveSkin("Orange")
  532. love.graphics.setBackgroundColor(200, 200, 200)
  533. if not love.filesystem.isDirectory("sounds") then
  534. love.filesystem.createDirectory("sounds")
  535. end
  536. sound = sfxr.newSound()
  537. createSeedBox()
  538. createPresetGenerators()
  539. createRandomizers()
  540. createParameters()
  541. createActionButtons()
  542. createOther()
  543. wavecanvas = love.graphics.newCanvas(125, 140)
  544. love.mousepressed = lf.mousepressed
  545. love.mousereleased = lf.mousereleased
  546. love.keyreleased = lf.keyreleased
  547. love.textinput = lf.textinput
  548. end
  549. function love.update(dt)
  550. lf.update(dt)
  551. if source then
  552. if playing and not source:isPlaying() then
  553. playing = false
  554. playbutton:SetText("Generate and Play")
  555. end
  556. end
  557. end
  558. function love.draw()
  559. lf.draw()
  560. end
  561. function love.keypressed(key)
  562. if key == " " then
  563. playSound()
  564. elseif key == "escape" then
  565. love.event.push("quit")
  566. end
  567. lf.keypressed(key)
  568. end