sfxthread.lua 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. --Chiptune SFX Thread
  2. require("love.system")
  3. --Are we running on mobile ?
  4. local onMobile = (love.system.getOS() == "Android")
  5. --Store math functions in locals to optamize speed.
  6. local min, max, floor, ceil, sin, abs, random = math.min, math.max, math.floor, math.ceil, math.sin, math.abs, math.random
  7. --The chunnel to recieve orders from
  8. local chIn = ...
  9. --Require love modules
  10. require("love.audio")
  11. require("love.sound")
  12. require("love.timer")
  13. require("love.math")
  14. local bitdepth = 8 --Bits per sample (8 or 16)
  15. local rate = onMobile and 22050 or 44100 --The samples rate.
  16. local buffers_rate = 20 --The number of buffers created each second.
  17. local buffer_size = rate/buffers_rate --The size of sounddatas generated and inserted to the QueueableSource
  18. local buffer_time = buffer_size/rate --The time of the buffer in seconds, used when putting the thread into sleep.
  19. local buffers_cache = {} --Put in the sounddatas
  20. local buffers_cache_id = 1 --The current active sounddata from the cache
  21. local buffers_cache_amount = onMobile and 4 or 2 --The number of sounddatas to create
  22. --Create a new QueueableSource, with 2 buffer slots.
  23. local qs = love.audio.newQueueableSource(rate,bitdepth,1,buffers_cache_amount)
  24. local amp = 0 --The soundwave cycle amplitude.
  25. local tamp = 0 --The target amplitude.
  26. local amp_slide_samples = floor(rate*0.02) --How many samples to reach the wanted amp.
  27. local amp_slide_time = amp_slide_samples/rate --How much time does it take to slide the amp
  28. local amp_slide = 1/amp_slide_samples --How much to increase/decrease the amplitude to reach the target amplitude.
  29. local wave = 0 --The wave to generate.
  30. local freq = 440 --The frequency to generate the sound at.
  31. local gen --An iterator which generates samples.
  32. local sfxdata = {} --The list of waves to play and for how long.
  33. local samplesToNextWave = 0
  34. --The waveforms generators list.
  35. local waveforms = {}
  36. --No sound
  37. waveforms[-1] = function(samples)
  38. return function()
  39. return 0
  40. end
  41. end
  42. --Sin
  43. waveforms[0] = function(samples)
  44. local r = 0
  45. local hs = samples/2
  46. local pi = math.pi
  47. local dpi = pi*2
  48. return function()
  49. r = r + pi/hs
  50. if r >= dpi then
  51. r = r - dpi
  52. end
  53. return sin(r)*amp
  54. end
  55. end
  56. --Square
  57. waveforms[1] = function(samples)
  58. local c = 0
  59. local hs = samples/2
  60. return function()
  61. c = c + 1
  62. if c == samples then c = 0 end
  63. if c < hs then
  64. return amp
  65. else
  66. return -amp
  67. end
  68. end
  69. end
  70. --Pulse
  71. waveforms[2] = function(samples)
  72. local c = 0
  73. local qs = samples/4
  74. return function()
  75. c = c + 1
  76. if c == samples then c = 0 end
  77. if c < qs then
  78. return amp
  79. else
  80. return -amp
  81. end
  82. end
  83. end
  84. --Sawtooth
  85. waveforms[3] = function(samples)
  86. local c = 0
  87. local inc = 2/samples
  88. return function()
  89. c = (c+inc)%2
  90. return (c-1)*amp
  91. end
  92. end
  93. --Triangle
  94. waveforms[4] = function(samples)
  95. local inc = 4/samples
  96. local c = 3
  97. return function()
  98. c = (c+inc)%4
  99. return (abs(c-2)-1)*amp
  100. end
  101. end
  102. --Noise
  103. waveforms[5] = function(samples)
  104. local v = random()
  105. local hs = floor(samples/2)
  106. local c = hs
  107. local mn = love.math.noise
  108. return function()
  109. c = c - 1
  110. if c == 0 then
  111. v = random()
  112. c = hs
  113. end
  114. return (mn(v)*2-1)*2*amp
  115. end
  116. end
  117. local wavepos = -3
  118. local function nextWave()
  119. wavepos = wavepos + 4
  120. local ntime = sfxdata[wavepos]
  121. local nwave = sfxdata[wavepos+1]
  122. local nfreq = sfxdata[wavepos+2]
  123. local namp = sfxdata[wavepos+3]
  124. if ntime then
  125. samplesToNextWave = ntime
  126. else
  127. samplesToNextWave = buffer_size
  128. wave = -1
  129. tamp = 0
  130. return
  131. end
  132. if nwave == -1 then --No sound
  133. tamp = 0 --Set the target amplitude to 0 because we want to stop the generator.
  134. else --New wave
  135. gen = waveforms[nwave](nfreq)
  136. wave = nwave
  137. freq = nfreq
  138. tamp = namp
  139. end
  140. end
  141. --Pull a new command from the channel
  142. local function pullParams()
  143. if gen then
  144. return chIn:pop()
  145. else
  146. local arg = chIn:demand()
  147. love.timer.step() --Don't count the time spent while waiting for a new command.
  148. return arg
  149. end
  150. end
  151. love.timer.step() --Start counting the delta time.
  152. --The thread while true do loop !
  153. while true do
  154. --Check for new commands
  155. for params in pullParams do
  156. if type(params) == "string" and params == "stop" then
  157. return --It's time to shutdown the thread.
  158. else
  159. --Convert the frequency from Hz to samples per cycle.
  160. for i=3,#params,4 do
  161. if params[i-1] == -1 then
  162. params[i] = 1
  163. else
  164. params[i] = floor(rate/params[i])
  165. end
  166. end
  167. --Convert the time from seconds to samples, and make sure it ends with a cycle end.
  168. for i=1,#params,4 do
  169. local samples = floor(params[i]*rate)
  170. local freq = params[i+2]
  171. params[i] = max(floor(samples/freq+0.5)*freq,1)
  172. end
  173. sfxdata = params
  174. wavepos = -3
  175. nextWave()
  176. end
  177. end
  178. --Generate audio.
  179. if gen then
  180. --If there're any free buffer slots, then we have to fill it.
  181. for i=1, qs:getFreeBufferCount() do
  182. local sounddata --The sounddata to work on.
  183. --Get the sounddata out from the buffers cache.
  184. if #buffers_cache == buffers_cache_amount then
  185. sounddata = buffers_cache[ buffers_cache_id ]
  186. else
  187. sounddata = love.sound.newSoundData(buffer_size, rate, bitdepth, 1)
  188. buffers_cache[ buffers_cache_id ] = sounddata
  189. end
  190. buffers_cache_id = buffers_cache_id + 1 --Increase the id
  191. if buffers_cache_id > buffers_cache_amount then buffers_cache_id = 1 end
  192. local setSample = sounddata.setSample
  193. local sfxEnd = false
  194. for i=0,buffer_size-1 do
  195. setSample(sounddata,i,gen())
  196. samplesToNextWave = samplesToNextWave - 1
  197. if samplesToNextWave == 0 then
  198. nextWave()
  199. if wave == -1 then
  200. sfxEnd = true
  201. end
  202. end
  203. if tamp > amp then --We have to increase the amplitude.
  204. amp = min(amp + amp_slide,1)
  205. elseif tamp < amp then --We have to decrease the amplitude
  206. amp = max(amp - amp_slide,0)
  207. end
  208. end
  209. qs:queue(sounddata) --Insert the new sounddata into the queue.
  210. qs:play() --Make sure that the QueueableSource is playing.
  211. if sfxEnd then
  212. gen = nil
  213. break
  214. end
  215. end
  216. end
  217. love.timer.step()--Calculate the time spent while generating.
  218. local dt = love.timer.getDelta()
  219. local sleeptime = (buffer_time - dt*2)*0.6 --Calculate the remaining time that we can sleep.
  220. --There's time to sleep
  221. if sleeptime > 0 then
  222. love.timer.sleep(sleeptime) --ZzZzZzZzZzZzZzZzZzzzz.
  223. love.timer.step() --Skip the time spent while sleeping..
  224. else --Well, we're not generating enough
  225. --TODO: Lower the sample rate.
  226. end
  227. --REPEAT !
  228. end