| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- '***** HIGHLY EXPERIMENTAL WIP TEST OF OPENAL STREAMING *****
- #Import "<std>"
- #Import "<mojo>"
- Using std..
- Using mojo..
- Global AppTitle:String="VSynth 0.01 Big Ben Release"
- Global Contact:="Latest Source=github.com/nitrologic/m2"
- Global About:="VSynth Control"
- Global Octave1:= "Sharps= W E T Y U "
- Global Octave0:= "Notes=A S D F G H J K"
- Global Controls:="Reset Keys=Space,Quit=Escape"
- Global OscillatorNames:=New String[]("Square","Sine","Sawtooth","Triangle","Noise")
- Global EnvelopeNames:=New String[]("Plain","Punchy","SlowOut","SlowIn")
- Global ArpNames:=New String[]("None","Natural","Ascending","Descending","UpDown","Random")
- Alias V:Double ' Voltage(volts)
- Alias F:Double ' Frequency(hz)
- Alias T:Double ' Time(seconds)
- Alias Note:Int
- Alias K:Key
- Public
- Global instance:AppInstance
- Global vsynth:VSynth
- Global Duration:=0
- Global FragmentSize:=1024
- Global AudioFrequency:=44100
- Const MaxPolyphony:=16
- Class Envelope
- Field p:V
- Method On:V() Virtual
- Return 1.0
- End
- Method Off:V() Virtual
- Return 0.0
- End
- End
- Class ADSR Extends Envelope
- Field attack:T
- Field decay:T
- Field sustain:V
- Field release:T
-
- Method New(a:T,d:T,s:V,r:T)
- attack=a
- decay=d
- sustain=s
- release=r
- End
-
- Field t:T
- Field noteOn:Bool
- Method On:V() Override
- If Not noteOn
- t=0
- noteOn=True
- Endif
- t+=1.0/AudioFrequency
- If t<attack Return t/attack
- If t-attack<decay Return 1.0-((1-sustain)*(t-attack)/decay)
- Return sustain
- End
- Method Off:V() Override
- noteOn=False
- t+=1.0/AudioFrequency
- If t<release
- Return 1.0-t/release
- Endif
- Return 0.0
- End
- End
- Class Oscillator
- Field delta:T
- Method Sample:V(hz:F) Virtual
- Return 0
- End
- End
- Class Sine Extends Oscillator
- Method Sample:V(hz:F) Override
- Local t:T=hz/AudioFrequency
- delta+=t
- Return Sin(Pi*delta)
- End
- End
- Class Sawtooth Extends Oscillator
- Method Sample:V(hz:F) Override
- Local t:T=hz/AudioFrequency
- delta+=t
- Return ((delta+1) Mod 2)-1
- End
- End
- Class Triangle Extends Oscillator
- Method Sample:V(hz:F) Override
- Local t:T=2*hz/AudioFrequency
- delta+=t
- Return (Abs(delta Mod 4)-2)-1
- End
- End
- Class Square Extends Oscillator
- Method Sample:V(hz:F) Override
- Local t:T=hz/AudioFrequency
- delta+=t
- Return -1+2*(Int(delta)&1)
- End
- End
- Class Noise Extends Oscillator
- Field a:V
- Method Sample:V(hz:F) Override
- Local t:T=hz/AudioFrequency
- Local delta0:=delta
- delta+=t
- Local f:=delta Mod 1
- If Int(delta0)<>Int(delta)
- a=Rnd()
- Endif
- Return 1-2*a '(a+f*(b-a)
- End
- End
- Class Voice
- Field oscillator:Oscillator
- Field envelope:Envelope
- Field noteOn:Bool
- Field hz:F
- Field pan:V
- Field gain:V=0.12
-
- Method SetOscillator(osc:Int)
- Select osc
- Case 0 oscillator=New Square
- Case 1 oscillator=New Sine
- Case 2 oscillator=New Sawtooth
- Case 3 oscillator=New Triangle
- Case 4 oscillator=New Noise
- End
- End
-
- Method SetEnvelope(env:Int)
- Select env
- Case 0
- envelope=New ADSR(0.05,1.5,0.2,0.3)
- Case 1
- envelope=New ADSR(0.06,0.01,0.92,0.2)
- Case 2
- envelope=New ADSR(0.06,2.0,0.2,1.2)
- Case 3
- envelope=New ADSR(0.2,0.2,0.92,0.4)
- End
- End
- Method SetPan(value:V)
- pan=value
- End
-
- Method SetGain(value:V)
- gain=value
- End
- Method Stop()
- NoteOff()
- envelope.Off()
- End
-
- Method NoteOn(note:Int)
- hz=440.0*Pow(2.0,(note-67.0)/12)
- noteOn=True
- End
-
- Method NoteOff()
- noteOn=False
- End
-
- Method Mix(buffer:Double[],samples:Int,detune:V)
- Local left:=1.0
- Local right:=1.0
- If pan<0 right+=pan
- If pan>0 left-=pan
- For Local i:=0 Until samples
- Local v:=oscillator.Sample(hz*detune)
- Local e:V
- If noteOn e=envelope.On() Else e=envelope.Off()
- e*=gain
- buffer[i*2+0]+=e*left*v
- buffer[i*2+1]+=e*right*v
- Next
- End
- End
- Class VSynth
- Field buffer:=New Double[FragmentSize*2]
-
- Field voices:=New Stack<Voice>
- Field polyList:=New List<Voice>
- Field polyMap:=New Map<Int,Voice>
- Field detune:V
- Method New()
- OpenAudio()
- End
-
- Method Detune(bend:V)
- detune=bend
- End
-
- Method ClearKeys()
- voices.Clear()
- End
- Method FillAudioBuffer:Double[](samples:Int)
- For Local i:=0 Until samples
- buffer[i*2+0]=0
- buffer[i*2+1]=0
- Next
- For Local voice:=Eachin voices
- voice.Mix(buffer,samples,detune)
- Next
-
- Duration+=samples
- Return buffer
- End
-
- Method OpenAudio()
-
- For Local i:=0 Until MaxPolyphony
- Local tone:=New Voice
- tone.SetOscillator(0)
- tone.SetEnvelope(0)
- polyList.AddLast(tone)
- Next
-
- New Fiber( Lambda()
-
- Local channel:=New Channel
- Local data:=New AudioData( FragmentSize,AudioFormat.Stereo16,AudioFrequency )
- Local datap:=Cast<Short Ptr>( data.Data )
-
- Repeat
-
- Local samples:=FillAudioBuffer( FragmentSize )
- For Local i:=0 Until FragmentSize*2
- datap[i]=Clamp( samples[i],Double(-1.0),Double(1.0) ) * 32767.0
-
- Next
- channel.WaitQueued( 3 ) 'waits until <=3 chunks are queued
- channel.Queue( data ) 'queues another chunk...
-
- Forever
-
- End )
-
- End
- Method NoteOn(note:Int,oscillator:Int,envelope:Int)
- NoteOff(note)
- If polyList.Empty Return
- Local voice:=polyList.RemoveFirst()
- voice.SetEnvelope(envelope)
- voice.SetOscillator(oscillator)
- voice.NoteOn(note)
- polyMap[note]=voice
- polyList.Remove(voice)
- If Not voices.Contains(voice)
- voices.Add(voice)
- Endif
- End
- Method NoteOff(note:Int)
- Local voice:=polyMap[note]
- If voice
- voice.Stop()
- polyMap.Remove(note)
- polyList.AddLast(voice)
- Endif
- End
- End
- Class VSynthWindow Extends Window
- 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 )
- Field frame:Int
- Field tick:Int
- Field mousex:Int
- Field mousey:Int
-
- Field oscillator:Int
- Field envelope:Int
- Field octave:Int=5
-
- Field pitchbend:V
-
- Field keyNoteMap:=New Map<Key,Int>
- Method New(title:String)
- Super.New(title,720,560,WindowFlags.Resizable)
- For Local i:=0 Until MusicKeys.Length
- keyNoteMap.Set(MusicKeys[i],i-1)
- Next
- vsynth=New VSynth
- End
-
- Method OnRender( display:Canvas ) Override
- App.RequestRender()
- vsynth.Detune(Pow(2,pitchbend))
- Local text:String = About+",,"+Octave1+","+Octave0+","
- text+="Octave=< >="+octave
- text+=",Oscillator=1-5="+OscillatorNames[oscillator]
- text+=",Envelope=[]="+EnvelopeNames[envelope]
- text+=",PitchBend=Mouse Wheel="+FloatString(pitchbend)
- text+=",,"+Controls+",,"+Contact
- Local cy:=40
- For Local line:=Eachin text.Split(",")
- Local cx:=50
- For Local tab:=Eachin line.Split("=")
- display.DrawText(tab,cx,cy)
- cx+=100
- Next
- cy+=20
- Next
- End
-
- Method KeyDown(key:Key)
- Local note:=keyNoteMap[key]+octave*12
- vsynth.NoteOn(note,oscillator,envelope)
- End
- Method KeyUp(key:Key)
- Local note:=keyNoteMap[key]+octave*12
- vsynth.NoteOff(note)
- End
- Method UpdateSequence()
- frame+=1
- Local t:Int=(frame/20)
- If t<>tick
- Local note:=((t Shr 1)&15)*3+40
- If t&1
- vsynth.NoteOn(note,oscillator,envelope)
- Else
- vsynth.NoteOff(note)
- Endif
- tick=t
- Endif
- ' Print "tick d="+d
- End
-
- Function Limit:Int(value:Int, lo:Int, hi:Int)
- If value<lo Return lo
- If value>hi Return hi
- Return value
- End
-
- Method OnKeyEvent( event:KeyEvent ) Override
- Select event.Type
- Case EventType.KeyDown
- Select event.Key
- Case Key.Key1
- oscillator=0
- Case Key.Key2
- oscillator=1
- Case Key.Key3
- oscillator=2
- Case Key.Key4
- oscillator=3
- Case Key.Key5
- oscillator=4
- Case Key.Escape
- instance.Terminate()
- Case Key.LeftBracket
- envelope=Wrap(envelope-1,0,EnvelopeNames.Length)
- Case Key.RightBracket
- envelope=Wrap(envelope+1,0,EnvelopeNames.Length)
- Case Key.Comma
- octave=Clamp(octave-1,0,12)
- Case Key.Period
- octave=Clamp(octave+1,0,12)
- Case Key.Space
- vsynth.ClearKeys()
- Default
- KeyDown(event.Key)
- End
- Case EventType.KeyUp
- Select event.Key
- Case Key.Escape
- Default
- KeyUp(event.Key)
- End
- End
- End
- Method OnMouseEvent( event:MouseEvent ) Override
- mousex=event.Location.X
- mousey=event.Location.Y
- pitchbend+=event.Wheel.Y/24.0
- End
-
- End
- Function FloatString:String(value:Float,dp:Int=2)
- Local sign:String
- If value<0
- sign="-"
- value=-value
- Endif
- Local a:String=Int(value*(Pow(10,dp)))
- Local l:=dp+1-a.Length
- If l>0 a="000000".Slice(0,l)+a
- Local r:=a.Length
- Return sign+a.Slice(0,r-dp)+"."+a.Slice(r-dp)
- End
- Function Wrap:Int(value:Int,lower:Int,upper:Int)
- If value<lower value=upper-1
- If value>=upper value=lower
- Return value
- End
- Function Main()
- instance = New AppInstance
- New VSynthWindow(AppTitle)
- App.Run()
- End
|