ctunethread.lua 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. --Chiptune 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, sin, abs, random = math.min, math.max, math.floor, 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 = 0 --How much to increase/decrease the amplitude to reach the target amplitude.
  27. local amp_slide_samples = floor(rate*0.02) --How many samples to reach the wanted amp.
  28. local amp_slide_time = amp_slide_samples/rate --How much time does it take to slide the amp
  29. local wave = 0 --The wave to generate.
  30. local freq = 440 --The frequency to generate the sound at.
  31. local generator --An iterator which generates samples.
  32. local generated_time = 0 --How long the sound has been generated for.
  33. --The waveforms generators list.
  34. local waveforms = {}
  35. --Sin
  36. waveforms[0] = function(samples)
  37. local r = 0
  38. local hs = samples/2
  39. local pi = math.pi
  40. local dpi = pi*2
  41. return function()
  42. r = (r + pi/hs)%dpi
  43. return sin(r)*amp
  44. end
  45. end
  46. --Square
  47. waveforms[1] = function(samples)
  48. local c = 0
  49. local hs = samples/2
  50. return function()
  51. c = c + 1
  52. if c == samples then c = 0 end
  53. if c < hs then
  54. return amp
  55. else
  56. return -amp
  57. end
  58. end
  59. end
  60. --Pulse
  61. waveforms[2] = function(samples)
  62. local r = 0
  63. local hs = samples/2
  64. local pi = math.pi
  65. local dpi = pi*2
  66. return function()
  67. r = (r + pi/hs)%dpi
  68. if sin(r) > 0.5 then
  69. return amp
  70. else
  71. return -amp
  72. end
  73. end
  74. end
  75. --Sawtooth
  76. waveforms[3] = function(samples)
  77. local c = 0
  78. local inc = 2/samples
  79. return function()
  80. c = (c+inc)%2
  81. return (c-1)*amp
  82. end
  83. end
  84. --Triangle
  85. waveforms[4] = function(samples)
  86. local inc = 4/samples
  87. local c = 3
  88. return function()
  89. c = (c+inc)%4
  90. return (abs(c-2)-1)*amp
  91. end
  92. end
  93. --Noise
  94. waveforms[5] = function(samples)
  95. local v = random()
  96. local hs = floor(samples/2)
  97. local c = hs
  98. local mn = love.math.noise
  99. return function()
  100. c = c - 1
  101. if c == 0 then
  102. v = random()
  103. c = hs
  104. end
  105. return (mn(v)*2-1)*2*amp
  106. end
  107. end
  108. --Pull a new command from the channel
  109. local function pullParams()
  110. if amp > 0 or tamp > 0 then
  111. return chIn:pop()
  112. else
  113. local arg = chIn:demand()
  114. love.timer.step() --Don't cound the time spent while waiting for a new command.
  115. return arg
  116. end
  117. end
  118. love.timer.step() --Start counting the delta time.
  119. --The thread while true do loop !
  120. while true do
  121. --Check for new commands
  122. for params in pullParams do
  123. if type(params) == "string" and params == "stop" then
  124. return --It's time to shutdown the thread.
  125. else
  126. --New waveform parameters
  127. local nwave, nfreq, namp = unpack(params)
  128. nwave, nfreq, namp = nwave or wave, nfreq or 0, namp or 0
  129. if nfreq == 0 then nfreq, namp = freq, 0 end
  130. --The sound generator is already off
  131. if namp == 0 and amp == 0 then
  132. break --There's nothing to do
  133. end
  134. local owave = wave --The old waveform id.
  135. local ofreq = freq --The old frequency.
  136. --Stop the sound
  137. if namp == 0 then
  138. wave = nwave --Incase if the waveform has been changed.
  139. tamp = 0 --Set the target amplitude to 0 because we want to stop the generator.
  140. freq = nfreq == 0 and freq or nfreq --The frequency to use while sliding down with the amplitude.
  141. amp_slide = amp/amp_slide_samples --Calculate the amplitude decrease amount each sample.
  142. else --New waveform to generate.
  143. wave = nwave --Incase if the waveform has been changed.
  144. freq = nfreq --Incase if the frequency has been changed.
  145. tamp = namp --The target amplitude
  146. amp_slide = tamp/amp_slide_samples --Calculate the amplitude increase/decrease amount each sample.
  147. generated_time = 0
  148. end
  149. --If the waveform changed, or the frequency changed we will have to create a new generator.
  150. if owave ~= wave or ofreq ~= freq then
  151. gen = waveforms[wave](floor(rate/freq)) --The new generator.
  152. end
  153. --We have to recalculate the buffer size inorder to make sure that each buffer ends with a cycle end.
  154. if ofreq ~= freq then
  155. buffer_size = floor(rate/freq) * floor(freq/buffers_rate)
  156. buffer_time = buffer_size/rate
  157. buffers_cache = {} --Clear the buffers cache
  158. buffers_cache_id = 1 --Reset the buffers cache id
  159. end
  160. end
  161. end
  162. local skip_generation = (tamp > 0) and (#buffers_cache == buffers_cache_amount) and (generated_time > max(buffer_time,amp_slide_time)*buffers_cache_amount) and (wave ~= 5)
  163. --Generate audio.
  164. if amp > 0 or tamp > 0 then
  165. --If there're any free buffer slots, then we have to fill it.
  166. for i=1, qs:getFreeBufferCount() do
  167. local sounddata --The sounddata to work on.
  168. --Get the sounddata out from the buffers cache.
  169. if #buffers_cache == buffers_cache_amount then
  170. sounddata = buffers_cache[ buffers_cache_id ]
  171. else
  172. sounddata = love.sound.newSoundData(buffer_size, rate, bitdepth, 1)
  173. buffers_cache[ buffers_cache_id ] = sounddata
  174. end
  175. buffers_cache_id = buffers_cache_id + 1 --Increase the id
  176. if buffers_cache_id > buffers_cache_amount then buffers_cache_id = 1 end
  177. if not skip_generation then
  178. local setSample = sounddata.setSample
  179. for i=0,buffer_size-1 do
  180. setSample(sounddata,i,gen())
  181. if tamp > amp then --We have to increase the amplitude.
  182. amp = min(amp + amp_slide,1)
  183. elseif tamp < amp then --We have to decrease the amplitude
  184. amp = max(amp - amp_slide,0)
  185. end
  186. end
  187. generated_time = generated_time + buffer_time
  188. end
  189. qs:queue(sounddata) --Insert the new sounddata into the queue.
  190. qs:play() --Make sure that the QueueableSource is playing.
  191. end
  192. end
  193. love.timer.step()--Calculate the time spent while generating.
  194. local dt = love.timer.getDelta()
  195. local sleeptime = (buffer_time - dt*2)*0.6 --Calculate the remaining time that we can sleep.
  196. --There's time to sleep
  197. if sleeptime > 0 then
  198. love.timer.sleep(sleeptime) --ZzZzZzZzZzZzZzZzZzzzz.
  199. love.timer.step() --Skip the time spent while sleeping..
  200. else --Well, we're not generating enough
  201. --TODO: Lower the sample rate.
  202. end
  203. --REPEAT !
  204. end