vsynth.monkey2 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. #Import "<std>"
  2. #Import "<mojo>"
  3. #Import "<sdl2>"
  4. #import "<sdl2-mixer>"
  5. #Import "audiopipe.monkey2"
  6. Using std..
  7. Using mojo..
  8. Using sdl2..
  9. Global AppTitle:String="VSynth 0.01 Big Ben Release"
  10. Global Contact:="Latest Source=github.com/nitrologic/m2"
  11. Global About:="VSynth Control"
  12. Global Octave1:= "Sharps= W E T Y U "
  13. Global Octave0:= "Notes=A S D F G H J K"
  14. Global Controls:="Reset Keys=Space,Quit=Escape"
  15. Global OscillatorNames:=New String[]("Square","Sine","Sawtooth","Triangle","Noise")
  16. Global EnvelopeNames:=New String[]("Plain","Punchy","SlowOut","SlowIn")
  17. Global ArpNames:=New String[]("None","Natural","Ascending","Descending","UpDown","Random")
  18. Alias V:Double ' Voltage(volts)
  19. Alias F:Double ' Frequency(hz)
  20. Alias T:Double ' Time(seconds)
  21. Alias Note:Int
  22. Alias K:Key
  23. Public
  24. Global instance:AppInstance
  25. Global vsynth:VSynth
  26. Global Duration:=0
  27. Global FragmentSize:=1024
  28. Global WriteAhead:=8192
  29. Global AudioFrequency:=44100
  30. Const MaxPolyphony:=16
  31. Class Envelope
  32. Field p:V
  33. Method On:V() Virtual
  34. Return 1.0
  35. End
  36. Method Off:V() Virtual
  37. Return 0.0
  38. End
  39. End
  40. Class ADSR Extends Envelope
  41. Field attack:T
  42. Field decay:T
  43. Field sustain:V
  44. Field release:T
  45. Method New(a:T,d:T,s:V,r:T)
  46. attack=a
  47. decay=d
  48. sustain=s
  49. release=r
  50. End
  51. Field t:T
  52. Field noteOn:Bool
  53. Method On:V() Override
  54. If Not noteOn
  55. t=0
  56. noteOn=True
  57. Endif
  58. t+=1.0/AudioFrequency
  59. If t<attack Return t/attack
  60. If t-attack<decay Return 1.0-((1-sustain)*(t-attack)/decay)
  61. Return sustain
  62. End
  63. Method Off:V() Override
  64. noteOn=False
  65. t+=1.0/AudioFrequency
  66. If t<release
  67. Return 1.0-t/release
  68. Endif
  69. Return 0.0
  70. End
  71. End
  72. Class Oscillator
  73. Field delta:T
  74. Method Sample:V(hz:F) Virtual
  75. Return 0
  76. End
  77. End
  78. Class Sine Extends Oscillator
  79. Method Sample:V(hz:F) Override
  80. Local t:T=hz/AudioFrequency
  81. delta+=t
  82. Return Sin(Pi*delta)
  83. End
  84. End
  85. Class Sawtooth Extends Oscillator
  86. Method Sample:V(hz:F) Override
  87. Local t:T=hz/AudioFrequency
  88. delta+=t
  89. Return ((delta+1) Mod 2)-1
  90. End
  91. End
  92. Class Triangle Extends Oscillator
  93. Method Sample:V(hz:F) Override
  94. Local t:T=2*hz/AudioFrequency
  95. delta+=t
  96. Return (Abs(delta Mod 4)-2)-1
  97. End
  98. End
  99. Class Square Extends Oscillator
  100. Method Sample:V(hz:F) Override
  101. Local t:T=hz/AudioFrequency
  102. delta+=t
  103. Return -1+2*(Int(delta)&1)
  104. End
  105. End
  106. Class Noise Extends Oscillator
  107. Field a:V
  108. Method Sample:V(hz:F) Override
  109. Local t:T=hz/AudioFrequency
  110. Local delta0:=delta
  111. delta+=t
  112. Local f:=delta Mod 1
  113. If Int(delta0)<>Int(delta)
  114. a=Rnd()
  115. Endif
  116. Return 1-2*a '(a+f*(b-a)
  117. End
  118. End
  119. Class Voice
  120. Field oscillator:Oscillator
  121. Field envelope:Envelope
  122. Field noteOn:Bool
  123. Field hz:F
  124. Field pan:V
  125. Field gain:V=0.12
  126. Method SetOscillator(osc:Int)
  127. Select osc
  128. Case 0 oscillator=New Square
  129. Case 1 oscillator=New Sine
  130. Case 2 oscillator=New Sawtooth
  131. Case 3 oscillator=New Triangle
  132. Case 4 oscillator=New Noise
  133. End
  134. End
  135. Method SetEnvelope(env:Int)
  136. Select env
  137. Case 0
  138. envelope=New ADSR(0.05,1.5,0.2,0.3)
  139. Case 1
  140. envelope=New ADSR(0.06,0.01,0.92,0.2)
  141. Case 2
  142. envelope=New ADSR(0.06,2.0,0.2,1.2)
  143. Case 3
  144. envelope=New ADSR(0.2,0.2,0.92,0.4)
  145. End
  146. End
  147. Method SetPan(value:V)
  148. pan=value
  149. End
  150. Method SetGain(value:V)
  151. gain=value
  152. End
  153. Method Stop()
  154. NoteOff()
  155. envelope.Off()
  156. End
  157. Method NoteOn(note:Int)
  158. hz=440.0*Pow(2.0,(note-67.0)/12)
  159. noteOn=True
  160. End
  161. Method NoteOff()
  162. noteOn=False
  163. End
  164. Method Mix(buffer:Double[],samples:Int,detune:V)
  165. Local left:=1.0
  166. Local right:=1.0
  167. If pan<0 right+=pan
  168. If pan>0 left-=pan
  169. For Local i:=0 Until samples
  170. Local v:=oscillator.Sample(hz*detune)
  171. Local e:V
  172. If noteOn e=envelope.On() Else e=envelope.Off()
  173. e*=gain
  174. buffer[i*2+0]+=e*left*v
  175. buffer[i*2+1]+=e*right*v
  176. Next
  177. End
  178. End
  179. Class VSynth
  180. Field audioSpec:SDL_AudioSpec
  181. Field buffer:=New Double[FragmentSize*2]
  182. Field audioPipe:=AudioPipe.Create()
  183. Field voices:=New Stack<Voice>
  184. Field polyList:=New List<Voice>
  185. Field polyMap:=New Map<Int,Voice>
  186. Field detune:V
  187. Method New()
  188. OpenAudio()
  189. End
  190. Method Detune(bend:V)
  191. detune=bend
  192. End
  193. Method ClearKeys()
  194. voices.Clear()
  195. End
  196. Method UpdateAudio()
  197. While True
  198. Local buffered:=audioPipe.writePointer-audioPipe.readPointer
  199. If buffered>=WriteAhead Exit
  200. Local samples:=FragmentSize
  201. Local buffer:=vsynth.FillAudioBuffer(samples)
  202. Local pointer:=Varptr buffer[0]
  203. audioPipe.WriteSamples(pointer,samples*2)
  204. Wend
  205. End
  206. Method FillAudioBuffer:Double[](samples:Int)
  207. For Local i:=0 Until samples
  208. buffer[i*2+0]=0
  209. buffer[i*2+1]=0
  210. Next
  211. For Local voice:=Eachin voices
  212. voice.Mix(buffer,samples,detune)
  213. Next
  214. Duration+=samples
  215. Return buffer
  216. End
  217. Method OpenAudio()
  218. Local spec:SDL_AudioSpec
  219. spec.freq=AudioFrequency
  220. spec.format = AUDIO_S16
  221. spec.channels = 2
  222. spec.samples = FragmentSize
  223. spec.callback = AudioPipe.Callback
  224. spec.userdata = audioPipe.Handle()
  225. Mix_CloseAudio()
  226. Local error:Int = SDL_OpenAudio(Varptr spec,Varptr audioSpec)
  227. If error
  228. Print "error="+error+" "+String.FromCString(SDL_GetError())
  229. Else
  230. Print "Audio Open freq="+audioSpec.freq
  231. AudioFrequency=audioSpec.freq
  232. Endif
  233. For Local i:=0 Until MaxPolyphony
  234. Local tone:=New Voice
  235. tone.SetOscillator(0)
  236. tone.SetEnvelope(0)
  237. polyList.AddLast(tone)
  238. Next
  239. SDL_PauseAudio(0)
  240. End
  241. Method NoteOn(note:Int,oscillator:Int,envelope:Int)
  242. NoteOff(note)
  243. If polyList.Empty Return
  244. Local voice:=polyList.RemoveFirst()
  245. voice.SetEnvelope(envelope)
  246. voice.SetOscillator(oscillator)
  247. voice.NoteOn(note)
  248. polyMap[note]=voice
  249. polyList.Remove(voice)
  250. If Not voices.Contains(voice)
  251. voices.Add(voice)
  252. Endif
  253. End
  254. Method NoteOff(note:Int)
  255. Local voice:=polyMap[note]
  256. If voice
  257. voice.Stop()
  258. polyMap.Remove(note)
  259. polyList.AddLast(voice)
  260. Endif
  261. End
  262. End
  263. Class VSynthWindow Extends Window
  264. Const MusicKeys:=New Key[]( Key.Q,Key.A,Key.W,Key.S,Key.E,Key.D, Key.F,Key.T,Key.G,Key.Y,Key.H,Key.U,Key.J, Key.K,Key.O,Key.L,Key.P,Key.Semicolon)',Key.Apostrophe )
  265. Field frame:Int
  266. Field tick:Int
  267. Field mousex:Int
  268. Field mousey:Int
  269. Field oscillator:Int
  270. Field envelope:Int
  271. Field octave:Int=5
  272. Field pitchbend:V
  273. Field keyNoteMap:=New Map<Key,Int>
  274. Method New(title:String)
  275. Super.New(title,720,560,WindowFlags.Resizable)
  276. For Local i:=0 Until MusicKeys.Length
  277. keyNoteMap.Set(MusicKeys[i],i-1)
  278. Next
  279. vsynth=New VSynth
  280. End
  281. Method OnRender( display:Canvas ) Override
  282. App.RequestRender()
  283. vsynth.Detune(Pow(2,pitchbend))
  284. vsynth.UpdateAudio()
  285. Local text:String = About+",,"+Octave1+","+Octave0+","
  286. text+="Octave=< >="+octave
  287. text+=",Oscillator=1-5="+OscillatorNames[oscillator]
  288. text+=",Envelope=[]="+EnvelopeNames[envelope]
  289. text+=",PitchBend=Mouse Wheel="+FloatString(pitchbend)
  290. text+=",,"+Controls+",,"+Contact
  291. Local cy:=40
  292. For Local line:=Eachin text.Split(",")
  293. Local cx:=50
  294. For Local tab:=Eachin line.Split("=")
  295. display.DrawText(tab,cx,cy)
  296. cx+=100
  297. Next
  298. cy+=20
  299. Next
  300. End
  301. Method KeyDown(key:Key)
  302. Local note:=keyNoteMap[key]+octave*12
  303. vsynth.NoteOn(note,oscillator,envelope)
  304. End
  305. Method KeyUp(key:Key)
  306. Local note:=keyNoteMap[key]+octave*12
  307. vsynth.NoteOff(note)
  308. End
  309. Method UpdateSequence()
  310. frame+=1
  311. Local t:Int=(frame/20)
  312. If t<>tick
  313. Local note:=((t Shr 1)&15)*3+40
  314. If t&1
  315. vsynth.NoteOn(note,oscillator,envelope)
  316. Else
  317. vsynth.NoteOff(note)
  318. Endif
  319. tick=t
  320. Endif
  321. ' Print "tick d="+d
  322. End
  323. Function Limit:Int(value:Int, lo:Int, hi:Int)
  324. If value<lo Return lo
  325. If value>hi Return hi
  326. Return value
  327. End
  328. Method OnKeyEvent( event:KeyEvent ) Override
  329. Select event.Type
  330. Case EventType.KeyDown
  331. Select event.Key
  332. Case Key.Key1
  333. oscillator=0
  334. Case Key.Key2
  335. oscillator=1
  336. Case Key.Key3
  337. oscillator=2
  338. Case Key.Key4
  339. oscillator=3
  340. Case Key.Key5
  341. oscillator=4
  342. Case Key.Escape
  343. instance.Terminate()
  344. Case Key.LeftBracket
  345. envelope=Wrap(envelope-1,0,EnvelopeNames.Length)
  346. Case Key.RightBracket
  347. envelope=Wrap(envelope+1,0,EnvelopeNames.Length)
  348. Case Key.Comma
  349. octave=Clamp(octave-1,0,12)
  350. Case Key.Period
  351. octave=Clamp(octave+1,0,12)
  352. Case Key.Space
  353. vsynth.ClearKeys()
  354. Default
  355. KeyDown(event.Key)
  356. End
  357. Case EventType.KeyUp
  358. Select event.Key
  359. Case Key.Escape
  360. Default
  361. KeyUp(event.Key)
  362. End
  363. End
  364. End
  365. Method OnMouseEvent( event:MouseEvent ) Override
  366. mousex=event.Location.X
  367. mousey=event.Location.Y
  368. pitchbend+=event.Wheel.Y/24.0
  369. End
  370. End
  371. Function FloatString:String(value:Float,dp:Int=2)
  372. Local sign:String
  373. If value<0
  374. sign="-"
  375. value=-value
  376. Endif
  377. Local a:String=Int(value*(Pow(10,dp)))
  378. Local l:=dp+1-a.Length
  379. If l>0 a="000000".Slice(0,l)+a
  380. Local r:=a.Length
  381. Return sign+a.Slice(0,r-dp)+"."+a.Slice(r-dp)
  382. End
  383. Function Wrap:Int(value:Int,lower:Int,upper:Int)
  384. If value<lower value=upper-1
  385. If value>=upper value=lower
  386. Return value
  387. End
  388. Function Main()
  389. instance = New AppInstance
  390. New VSynthWindow(AppTitle)
  391. App.Run()
  392. End