Browse Source

Added highly experimental mojo openal version of vsynth.

Mark Sibly 9 years ago
parent
commit
fe11a9e828
1 changed files with 454 additions and 0 deletions
  1. 454 0
      bananas/vsynth/vsynth_mojo.monkey2

+ 454 - 0
bananas/vsynth/vsynth_mojo.monkey2

@@ -0,0 +1,454 @@
+
+'***** 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