vsynth_mojo.monkey2 8.5 KB

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