I implemented a couple of routines which seem go a long way towards eliminating the clicking, and I made use of your filtering idea in the approach.
a) Even when previous sound had already decayed to 0, press of a new note resulted in a click before the new sound played
b) If a new note was pressed before previous sound had decayed, a click resulted
To address these issues, this is what the routines do: Let's say that a current sound has amplitude = X when a new note is pressed. The first routine linearly decays the sound from amplitude X to amplitude 0 over 60 samples, and Low Pass filters the sound over that decay period. The second routine then over the next 100 samples exponentially increases the sound amplitude from 0 to whatever the set Attack level is at 100 samples, from which point the normal ADSR behaviour takes over.
Re the 60 and 100 sample period, I needed to make the period short enough (no more than a few milliseconds) so that a delay between a keypress and onset of the Attack would not be perceived, yet long enough for the sound to be force decayed to 0 and made to rise from 0 without a sharp discontinuity being perceived.
Code: Select all
;Quad-Klanger v102 a monophonic bezier-curve based sound synthesizer
;by doctornash
;midi procedures and some optimization by infratec
XIncludeFile "PortAudio.pb"
XIncludeFile "CurveGadget.pbi"
;for curvegadgets
Global Dim CurvePoint.POINT(1)
Global No2
Global No3
Global No4
Global No5
;end curvegadget
;initial points for single cycle of bezier waveform
Global ptA.f = 0.5
Global ptB.f = 0.06
Global ptC.f = 0.75
Global ptD.f = 0.95
#SampleRate = 44100
#PI2 = 6.28318530717958
Global BezierEnvelopeSamples.l
Global Pee.l
Global InitializeTheBezier.l=0
Global ModX1Freq.f
Global ModX2Freq.f
Global ModY1Freq.f
Global ModY2Freq.f
Global X1HOOKA.f
Global X2HOOKA.f
Global Y1HOOKA.f
Global Y2HOOKA.f
;for triangle wave modulation method of bezier points
Global NewX1Level.f
Global X1Sign.l
Global TheX1Level.f
Global UX1.f
Global VX1.f
Global FinalCurveGadgetValX1.f
Global NewY1Level.f
Global Y1Sign.l
Global TheY1Level.f
Global UY1.f
Global VY1.f
Global FinalCurveGadgetValY1.f
Global NewX2Level.f
Global X2Sign.l
Global TheX2Level.f
Global UX2.f
Global VX2.f
Global FinalCurveGadgetValX2.f
Global NewY2Level.f
Global Y2Sign.l
Global TheY2Level.f
Global UY2.f
Global VY2.f
Global FinalCurveGadgetValY2.f
Global TheVolume.f
Global Kata.s
Enumeration
#WIN_MAIN
#PlayPauseButton
#MIDIDevice
#Trackbar_FilterFreq
#Trackbar_FilterReso
#Trackbar_Attack
#Trackbar_Decay
#Trackbar_Sustain
#Trackbar_Release
#Text_FilterFreq
#Text_FilterReso
#Text_Attack
#Text_Sustain
#Text_Decay
#Text_Release
;for the bezier:
#GADGET_Canvas2
#GADGET_TrackA
#GADGET_TrackB
#GADGET_TrackC
#GADGET_TrackD
#GADGET_TrackVolume
#GADGET_TrackDuration
#GADGET_WaveGrapher
#GADGET_MODAMOUNTX1
#GADGET_MODFREQCOARSEX1
#GADGET_MODFREQFINEX1
#GADGET_MODAMOUNTY1
#GADGET_MODFREQCOARSEY1
#GADGET_MODFREQFINEY1
#GADGET_MODAMOUNTX2
#GADGET_MODFREQCOARSEX2
#GADGET_MODFREQFINEX2
#GADGET_MODAMOUNTY2
#GADGET_MODFREQCOARSEY2
#GADGET_MODFREQFINEY2
#GADGET_TextX1
#GADGET_TextY1
#GADGET_TextX2
#GADGET_TextY2
#GADGET_TextX1ModPercentage
#GADGET_TextY1ModPercentage
#GADGET_TextX2ModPercentage
#GADGET_TextY2ModPercentage
#GADGET_TextX1CoarseModFreq
#GADGET_TextX1FineModFreq
#GADGET_TextY1CoarseModFreq
#GADGET_TextY1FineModFreq
#GADGET_TextX2CoarseModFreq
#GADGET_TextX2FineModFreq
#GADGET_TextY2CoarseModFreq
#GADGET_TextY2FineModFreq
#GADGET_TextMasterVolume
#GADGET_TextDuration
#GADGET_ModDurationIncrement
#GADGET_FrameAmpEG
#GADGET_FrameFilter
EndEnumeration
Enumeration
#Off
#On
EndEnumeration
Global State.i
Global SampleCounter.l
Global ForcedDecayCounter.l=0
Global Groucho.d=0
Global InitializeSampleCounter.l=0
Global TheDuration.f=2000
Global ModAmountX1.f
Global ModAmountX2.f
Global ModAmountY1.f
Global ModAmountY2.f
;midi stuff
Global Chromatic$
Chromatic$="C C#D EbE F F#G AbA BbB C "
Global NoteVal.l
Global Freq.d
Global NoteCheck.i
Global foo.l
Global p.d
Global i.d
;ADSR stuff
Global Attack.d
Global Decay.d
Global Sustain.d
Global Release.d
Global AttackSamples.d
Global DecaySamples.d
Global SustainLevel.d
Global ReleaseSamples.d
Global ADSRLevel.d
Global UptoV.d ;the ADSR level we have reached when Release strikes
Global UptoP.l ;samplecount we have reached when the Release strikes
NoteCheck = 0
;filter stuff
Global cutoffo.d
Global t1o.d
Global t2o.d
Global reso.d
Global rawresolution.l
cutoffo = 1500
rawresolution = 50
Declare.f MoogFilter16(Uo.f)
Declare.f CubicBezier_ForDrawing(xval.f, aval.f, bval.f, cval.f, dval.f)
Declare.f CubicBezier(SampleNum.l, xval.f, aval.f, bval.f, cval.f, dval.f)
Declare.f slopeFromT(tee.f, Ai.f, Bi.f, Ci.f)
Declare.f xFromT(tee.f, Ai.f, Bi.f, Ci.f, Di.f)
Declare.f yFromT(tee.f, Ei.f, Fi.f, Gi.f, Hi.f)
Declare DrawTheBezier()
Declare.f CalculateTheWaveform(SamplePoint.l)
Declare ShowAboutWindow()
Procedure.i MIDI_GetInputDevices()
Protected mic.MIDIINCAPS, Num.i, i.i
Num = midiInGetNumDevs_()
For i = 0 To Num - 1
If midiInGetDevCaps_(i, @mic, SizeOf(MIDIINCAPS)) = 0
AddGadgetItem(#MIDIDevice, -1, PeekS(@mic\szPname))
EndIf
Next i
ProcedureReturn Num
EndProcedure
Procedure.s MIDI_Note(Note);returns note's name
ProcedureReturn Trim(Mid(Chromatic$, (Note % 12) * 2 + 1, 2)) + Str(Note / 12)
EndProcedure
Procedure MidiInCallback(hMidiIn.i, wMsg.i, dwInstance.l, dwParam1.l, dwParam2.l)
Protected Note.i, Velocity.i, Status.i
Static KeyCounter.i
Select wMsg ; process MIDI in events
Case #MIM_CLOSE
KeyCounter = 0
State = #Off
kata = "Off"
Case #MM_MIM_DATA
Status = dwParam1 & $FF
If Status = $90
Note = (dwParam1 >> 8) & $FF
Velocity = (dwParam1 >> 16) & $FF
If Velocity
Freq = Pow(2, (Note - 69) / 12) * 440
ModX1Freq = (ValF(Str(GetGadgetState(#GADGET_MODFREQCOARSEX1)) + "." + Str(GetGadgetState(#GADGET_MODFREQFINEX1))))*Freq
ModX2Freq = (ValF(Str(GetGadgetState(#GADGET_MODFREQCOARSEX2)) + "." + Str(GetGadgetState(#GADGET_MODFREQFINEX2))))*Freq
ModY1Freq = (ValF(Str(GetGadgetState(#GADGET_MODFREQCOARSEY1)) + "." + Str(GetGadgetState(#GADGET_MODFREQFINEY1))))*Freq
ModY2Freq = (ValF(Str(GetGadgetState(#GADGET_MODFREQCOARSEY2)) + "." + Str(GetGadgetState(#GADGET_MODFREQFINEY2))))*Freq
If KeyCounter = 0
State = #On
kata = "On"
EndIf
;If IsStatusBar(0) : StatusBarText(0, 1, MIDI_Note(Note) + " " + Str(Velocity), #PB_StatusBar_Center) : EndIf
KeyCounter + 1
Else
If KeyCounter : KeyCounter - 1 : EndIf
If KeyCounter = 0
State = #Off
kata = "Off"
InitializeTheBezier = 0
InitializeSampleCounter = 0
EndIf
EndIf
EndIf
If Status = $80
If KeyCounter : KeyCounter - 1 : EndIf
If KeyCounter = 0
State = #Off
kata = "Off"
InitializeTheBezier = 0
InitializeSampleCounter = 0
EndIf
EndIf
If IsStatusBar(0) : StatusBarText(0, 1, kata, #PB_StatusBar_Center) : EndIf
EndSelect
EndProcedure
;end midi stuff
Procedure Error(err)
MessageRequester("PortAudio", PeekS(Pa_GetErrorText(err)))
End
EndProcedure
Procedure.f CalculateTheWaveform(SamplePoint.l)
Protected N.l
Protected NumSamplePoints.l
Protected RawBezierVal.f
If Freq>0
NumSamplePoints = #SampleRate/Freq
Pee = Pee+1
If Pee = NumSamplePoints
Pee = 0
EndIf
RawBezierVal = (200 - (CubicBezier(SamplePoint, Pee/NumSamplePoints, ptA, ptB, ptC, ptD)))/200
If RawBezierVal = 0.5
RawBezierVal = 0
Else
;increasing the amplitude of the overall sound to give it an acceptable volume
RawBezierVal = ((RawBezierVal-0.5)*TheVolume/100)
EndIf
EndIf
ProcedureReturn RawBezierVal
EndProcedure
Procedure NewX1ModMethod(SampleVal.l)
;even though this is called NewX1Method, it is for the modulation of ALL points!
Protected MuX1
Protected MuY1
Protected MuX2
Protected MuY2
If SampleVal<=BezierEnvelopeSamples-1
MuX1 = VX1*CurveGadgetGetAttribute(No2, #CurveGadget_Y_Value_Scaled, SampleVal) / 100
MuY1 = VY1*CurveGadgetGetAttribute(No3, #CurveGadget_Y_Value_Scaled, SampleVal) / 100
MUX2 = VX2*CurveGadgetGetAttribute(No4, #CurveGadget_Y_Value_Scaled, SampleVal) / 100
MuY2 = VY2*CurveGadgetGetAttribute(No5, #CurveGadget_Y_Value_Scaled, SampleVal) / 100
Else
MuX1 = VX1*FinalCurveGadgetValX1
MuY1 = VY1*FinalCurveGadgetValY1
MuX2 = VX2*FinalCurveGadgetValX2
MuY2 = VY2*FinalCurveGadgetValY2
EndIf
If ModX1Freq>0
If ModAmountX1>0
NewX1Level = NewX1Level+ X1Sign*((MuX1*2)/(#SampleRate/ModX1Freq))
If NewX1Level>TheX1Level+MuX1
NewX1Level = TheX1Level+MuX1
X1Sign = -1
ElseIf NewX1Level<TheX1Level
NewX1Level = TheX1Level
X1Sign = 1
EndIf
Else
NewX1Level = NewX1Level+ X1Sign*((MuX1*2)/(#SampleRate/ModX1Freq))
If NewX1Level<TheX1Level-MuX1
NewX1Level = TheX1Level-MuX1
X1Sign = 1
ElseIf NewX1Level>TheX1Level
NewX1Level = TheX1Level
X1Sign = -1
EndIf
EndIf
EndIf
If ModY1Freq>0
If ModAmountY1>0
NewY1Level = NewY1Level+ Y1Sign*((MuY1*2)/(#SampleRate/ModY1Freq))
If NewY1Level>TheY1Level+MuY1
NewY1Level = TheY1Level+MuY1
Y1Sign = -1
ElseIf NewY1Level<TheY1Level
NewY1Level = TheY1Level
Y1Sign = 1
EndIf
Else
NewY1Level = NewY1Level+ Y1Sign*((MuY1*2)/(#SampleRate/ModY1Freq))
If NewY1Level<TheY1Level-MuY1
NewY1Level = TheY1Level-MuY1
Y1Sign = 1
ElseIf NewY1Level>TheY1Level
NewY1Level = TheY1Level
Y1Sign = -1
EndIf
EndIf
EndIf
If ModX2Freq>0
If ModAmountX2>0
NewX2Level = NewX2Level+ X2Sign*((MuX2*2)/(#SampleRate/ModX2Freq))
If NewX2Level>TheX2Level+MuX2
NewX2Level = TheX2Level+MuX2
X2Sign = -1
ElseIf NewX2Level<TheX2Level
NewX2Level = TheX2Level
X2Sign = 1
EndIf
Else
NewX2Level = NewX2Level+ X2Sign*((MuX2*2)/(#SampleRate/ModX2Freq))
If NewX2Level<TheX2Level-MuX2
NewX2Level = TheX2Level-MuX2
X2Sign = 1
ElseIf NewX2Level>TheX2Level
NewX2Level = TheX2Level
X2Sign = -1
EndIf
EndIf
EndIf
If ModY2Freq>0
If ModAmountY2>0
NewY2Level = NewY2Level+ Y2Sign*((MuY2*2)/(#SampleRate/ModY2Freq))
If NewY2Level>TheY2Level+MuY2
NewY2Level = TheY2Level+MuY2
Y2Sign = -1
ElseIf NewY2Level<TheY2Level
NewY2Level = TheY2Level
Y2Sign = 1
EndIf
Else
NewY2Level = NewY2Level+ Y2Sign*((MuY2*2)/(#SampleRate/ModY2Freq))
If NewY2Level<TheY2Level-MuY2
NewY2Level = TheY2Level-MuY2
Y2Sign = 1
ElseIf NewY2Level>TheY2Level
NewY2Level = TheY2Level
Y2Sign = -1
EndIf
EndIf
EndIf
X1HOOKA = NewX1Level/100
Y1HOOKA = NewY1Level/100
X2HOOKA = NewX2Level/100
Y2HOOKA = (NewY2Level+50)/100
EndProcedure
Procedure DrawTheBezier()
;this is for graphing the single cycle wave only
Protected N.i
Dim BezierDrawArr(399)
For N = 0 To 399
BezierDrawArr(N) = CubicBezier_ForDrawing(N/400, ptA, ptB, ptC, ptD)
Next N
StartDrawing(CanvasOutput(#GADGET_Canvas2))
Box(0, 0, 400, 200, $000000)
For N = 0 To 398
LineXY(N, BezierDrawArr(N), N+1, BezierDrawArr(N+1), $FFFFFF)
Next N
StopDrawing()
EndProcedure
Procedure.f CubicBezier_ForDrawing(xval.f, aval.f, bval.f, cval.f, dval.f)
;this is for graphing the single cycle wave only
Protected A.f = 0
Protected B.f = 0
Protected C.f = 0
Protected D.f = 0
Protected E.f = 0
Protected F.f = 0
Protected G.f = 0
Protected H.f = 0
Protected y0a.f
Protected x0a.f
Protected y1a.f
Protected x1a.f
Protected y2a.f
Protected x2a.f
Protected y3a.f
Protected x3a.f
Protected currentt.f
Protected nRefinementIterations.l
Protected i.l = 0
Protected currentx.f
Protected currentslope.f
Protected CB.f
y0a = 0.5
x0a = 0
y1a = bval
x1a = aval
y2a = dval
x2a = cval
y3a = 0.5
x3a = 1
A = x3a - 3*x2a + 3*x1a - x0a
B = 3*x2a - 6*x1a + 3*x0a
C = 3*x1a - 3*x0a
D = x0a
E = y3a - 3*y2a + 3*y1a - y0a
F = 3*y2a - 6*y1a + 3*y0a
G = 3*y1a - 3*y0a
H = y0a
currentt = xval
nRefinementIterations = 5
For i = 0 To nRefinementIterations-1
currentx = xFromT (currentt, A,B,C,D)
currentslope = slopeFromT (currentt, A,B,C)
currentt = currentt - ((currentx - xval)*(currentslope))
Next i
If currentt>1
currentt = 1
ElseIf currentt<0
currentt=0
EndIf
CB = yFromT(currentt, E,F,G,H)
cb = cb*200
If cb>200
cb = 200
EndIf
cb = 200-cb
ProcedureReturn CB
EndProcedure
Procedure.f CubicBezier(SampleNum.l, xval.f, aval.f, bval.f, cval.f, dval.f)
;this is the Bezier calculator used in the sound production
Protected A.f = 0
Protected B.f = 0
Protected C.f = 0
Protected D.f = 0
Protected E.f = 0
Protected F.f = 0
Protected G.f = 0
Protected H.f = 0
Protected y0a.f
Protected x0a.f
Protected y1a.f
Protected x1a.f
Protected y2a.f
Protected x2a.f
Protected y3a.f
Protected x3a.f
Protected currentt.f
Protected nRefinementIterations.l
Protected i.l = 0
Protected currentx.f
Protected currentslope.f
Protected CB.f
y0a = 0.5
x0a = 0
y1a = bval
x1a = aval
y2a = dval
x2a = cval
y3a = 0.5
x3a = 1
NewX1ModMethod(SampleNum)
x1a = X1HOOKA
y1a = Y1HOOKA
x2a = X2HOOKA
y2a = Y2HOOKA
A = x3a - 3*x2a + 3*x1a - x0a
B = 3*x2a - 6*x1a + 3*x0a
C = 3*x1a - 3*x0a
D = x0a
E = y3a - 3*y2a + 3*y1a - y0a
F = 3*y2a - 6*y1a + 3*y0a
G = 3*y1a - 3*y0a
H = y0a
currentt = xval
nRefinementIterations = 5
For i = 0 To nRefinementIterations-1
currentx = xFromT (currentt, A,B,C,D)
currentslope = slopeFromT (currentt, A,B,C)
currentt = currentt - ((currentx - xval)*(currentslope))
Next i
If currentt>1
currentt = 1
ElseIf currentt<0
currentt=0
EndIf
CB = yFromT(currentt, E,F,G,H)
cb = cb*200
If cb>200
cb = 200
EndIf
cb = 200-cb
ProcedureReturn CB
EndProcedure
Procedure.f slopeFromT(tee.f, Ai.f, Bi.f, Ci.f)
Protected dtdx.f
dtdx = 1/(3*Ai*tee*tee + 2*Bi*tee + Ci)
ProcedureReturn dtdx
EndProcedure
Procedure.f xFromT(tee.f, Ai.f, Bi.f, Ci.f, Di.f)
Protected ex.f
ex = Ai*(tee*tee*tee) + Bi*(tee*tee) + Ci*tee + Di
ProcedureReturn ex
EndProcedure
Procedure.f yFromT(tee.f, Ei.f, Fi.f, Gi.f, Hi.f)
Protected yi.f
yi = Ei*(tee*tee*tee) + Fi*(tee*tee) + Gi*tee + Hi
ProcedureReturn yi
EndProcedure
Global Stream
Procedure.f MoogFilter16(Uo.f)
;well, a low pass filter, anyway :-)
Static.d Po, Fo, Ko, ro, Xo, t1o, t2o
Static.d Y1o, y2o, y3o, y4o
Static.d oldxo, oldy1o, oldy2o, oldy3o
Static.i OldCutoffo
Static.l Oldrawresolution
If OldCutoffo <> cutoffo
Fo = 2 * cutoffo / #SampleRate
Po = Fo * (1.8 - 0.8 * Fo)
Ko = Po + Po - 1
t1o = (1 - Po) * 1.386249
t2o = 12 + t1o * t1o
OldCutoffo = cutoffo
EndIf
If Oldrawresolution <> rawresolution
ro = (rawresolution / 100) * (t2o + 6 * t1o) / (t2o - 6 * t1o)
Oldrawresolution = rawresolution
EndIf
Xo = Uo - ro * y4o
;Four cascaded onepole filters (bilinear transform)
Y1o = (Xo + oldxo) * Po - Ko * Y1o
y2o = (Y1o + oldy1o) * Po - Ko * y2o
y3o = (y2o + oldy2o) * Po - Ko * y3o
y4o = (y3o + oldy3o) * Po - Ko * y4o
;Clipper band limited sigmoid
y4o = y4o - (y4o * y4o * y4o) / 6
oldxo = Xo
oldy1o = Y1o
oldy2o = y2o
oldy3o = y3o
ProcedureReturn y4o
EndProcedure
Procedure LevelADSR(SampleTracker.l)
If SampleTracker>2147483640
SampleCounter = 0
UptoV = 0
EndIf
If State = #Off
If (ReleaseSamples+UptoP-SampleTracker)>=0
ADSRLevel = (UptoV*(ReleaseSamples+UptoP-SampleTracker))/ReleaseSamples
Else
ADSRLevel = 0
EndIf
Groucho = ADSRLevel
If IsStatusBar(0)
EndIf
EndIf
If State = #On
If InitializeTheBezier = 0
InitializeTheBezier = 1
ForcedDecayCounter = -1
Pee = -1
BezierEnvelopeSamples = (TheDuration/1000)*#SampleRate
CurveGadgetSetAttribute(No2, #CurveGadget_X_Maximum, BezierEnvelopeSamples-1)
CurveGadgetSetAttribute(No2, #CurveGadget_Y_Maximum, 100)
CurveGadgetSetAttribute(No3, #CurveGadget_X_Maximum, BezierEnvelopeSamples-1)
CurveGadgetSetAttribute(No3, #CurveGadget_Y_Maximum, 100)
CurveGadgetSetAttribute(No4, #CurveGadget_X_Maximum, BezierEnvelopeSamples-1)
CurveGadgetSetAttribute(No4, #CurveGadget_Y_Maximum, 100)
CurveGadgetSetAttribute(No5, #CurveGadget_X_Maximum, BezierEnvelopeSamples-1)
CurveGadgetSetAttribute(No5, #CurveGadget_Y_Maximum, 100)
ModAmountX1 = GetGadgetState(#GADGET_MODAMOUNTX1)
ModAmountX2 = GetGadgetState(#GADGET_MODAMOUNTX2)
ModAmountY1 = GetGadgetState(#GADGET_MODAMOUNTY1)
ModAmountY2 = GetGadgetState(#GADGET_MODAMOUNTY2)
TheX1Level = GetGadgetState(#GADGET_TrackA)
NewX1Level = TheX1Level
UX1 = ModAmountX1/100
If ModAmountX1>0
X1Sign = 1
VX1 = UX1*(99-TheX1Level)
Else
X1Sign = -1
VX1 = -1*UX1*(TheX1Level-1)
EndIf
FinalCurveGadgetValX1 = CurveGadgetGetAttribute(No2, #CurveGadget_Y_Value_Scaled, BezierEnvelopeSamples-1)/100
TheY1Level = GetGadgetState(#GADGET_TrackB)
NewY1Level = TheY1Level
UY1 = ModAmountY1/100
If ModAmountY1>0
Y1Sign = 1
VY1 = UY1*(49-TheY1Level)
Else
Y1Sign = -1
VY1 = -1*UY1*(TheY1Level-1)
EndIf
FinalCurveGadgetValY1 = CurveGadgetGetAttribute(No3, #CurveGadget_Y_Value_Scaled, BezierEnvelopeSamples-1)/100
TheX2Level = GetGadgetState(#GADGET_TrackC)
NewX2Level = TheX2Level
UX2 = ModAmountX2/100
If ModAmountX2>0
X2Sign = 1
VX2 = UX2*(99-TheX2Level)
Else
X2Sign = -1
VX2 = -1*UX2*(TheX2Level-1)
EndIf
FinalCurveGadgetValX2 = CurveGadgetGetAttribute(No4, #CurveGadget_Y_Value_Scaled, BezierEnvelopeSamples-1)/100
TheY2Level = GetGadgetState(#GADGET_TrackD)-50
NewY2Level = TheY2Level
UY2 = ModAmountY2/100
If ModAmountY2>0
Y2Sign = 1
VY2 = UY2*(49-TheY2Level)
Else
Y2Sign = -1
VY2 = -1*UY2*(TheY2Level-1)
EndIf
FinalCurveGadgetValY2 = CurveGadgetGetAttribute(No5, #CurveGadget_Y_Value_Scaled, BezierEnvelopeSamples-1) / 100
Attack = GetGadgetState(#Trackbar_Attack) / 1000
Decay = GetGadgetState(#Trackbar_Decay) / 1000
Sustain = GetGadgetState(#Trackbar_Sustain) / 100
Release = GetGadgetState(#Trackbar_Release) / 1000
AttackSamples = Attack*#SampleRate
DecaySamples = Decay*#SampleRate
SustainLevel = Sustain
ReleaseSamples = Release*#SampleRate
EndIf
ForcedDecayCounter = ForcedDecayCounter+1
If ForcedDecayCounter<=60
ADSRLevel = (1-Pow((ForcedDecayCounter/60),1))*Groucho
cutoffo = 250
Else
;got to set the following only the FIRST TIME ForcedDecayCounter increments above 60 samples:
If InitializeSampleCounter = 0
InitializeSampleCounter = 1
SampleCounter = 0
SampleTracker = 0
cutoffo = GetGadgetState(#Trackbar_FilterFreq)
EndIf
;let us calculate the actual ADSR level now shall we
If SampleTracker<=AttackSamples
ADSRLevel = SampleTracker/AttackSamples
ElseIf SampleTracker>AttackSamples And SampleTracker<=(AttackSamples+DecaySamples)
ADSRLevel = SustainLevel + (((AttackSamples+DecaySamples-SampleTracker)*(1-SustainLevel))/DecaySamples)
ElseIf SampleTracker>(AttackSamples+DecaySamples)
ADSRLevel = SustainLevel
EndIf
EndIf
UptoV = ADSRLevel
UptoP = SampleTracker
EndIf
EndProcedure
ProcedureC PaStreamCallback(*in, *output.Float, frameCount, *timeInfo.PaStreamCallbackTimeInfo, statusFlags, *userData)
While frameCount
LevelADSR(SampleCounter)
If SampleCounter<100
*output\f = MoogFilter16(CalculateTheWaveform(SampleCounter))*ADSRLevel*Pow((SampleCounter/100),5)
Else
*output\f = MoogFilter16(CalculateTheWaveform(SampleCounter))*ADSRLevel
EndIf
SampleCounter = SampleCounter+1
*output + 4
frameCount - 1
Wend
EndProcedure
Define err.i
err = Pa_Initialize()
If err <> #paNoError : Error(err) : EndIf
Define op.PaStreamParameters
op\channelCount = 1
op\device = Pa_GetDefaultOutputDevice()
op\sampleFormat = #paFloat32
op\suggestedLatency = 60/1000
;err = Pa_OpenStream(@Stream, #Null, @op, #SampleRate, #SampleRate / 100, #paNoFlag, @PaStreamCallback(), 0)
err = Pa_OpenStream(@Stream, #Null, @op, #SampleRate, #paframesperbufferunspecified, #paNoFlag, @PaStreamCallback(), 0)
If err <> #paNoError : Error(err) : EndIf
Define Indev.i
InDev = 0
Define hMi.i
hMi = 0
If OpenWindow(#WIN_MAIN, 0, 0, 535, 590, "Quad-Klanger", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
If CreateMenu(0, WindowID(#WIN_MAIN)) ; menu creation starts....
MenuTitle("?")
MenuItem(1, "About")
MenuItem(2, "Help")
EndIf
Frame3DGadget(#GADGET_FrameAmpEG, 400, 230, 120, 130, "Amplitude EG")
Frame3DGadget(#GADGET_FrameFilter, 400, 370, 120, 130, "Filter")
ComboBoxGadget(#MIDIDevice, 390, 520, 140, 20)
If MIDI_GetInputDevices()
SetGadgetState(#MIDIDevice, 0)
InDev = 0
Else
InDev = -1
EndIf
No2 = CurveGadget(#PB_Any, 220, 240, 160, 70, 8, $ffffff, $000000)
CurveGadgetSetAttribute(No2, #CurveGadget_PointRadius, 3)
CurveGadgetSetAttribute(No2, #CurveGadget_CatchRadius, 10)
CurveGadgetSetAttribute(No2, #CurveGadget_X_Maximum, 380)
CurveGadgetSetAttribute(No2, #CurveGadget_Y_Maximum, 10000)
No3 = CurveGadget(#PB_Any, 220, 320, 160, 70, 8, $ffffff, $000000)
CurveGadgetSetAttribute(No3, #CurveGadget_PointRadius, 3)
CurveGadgetSetAttribute(No3, #CurveGadget_CatchRadius, 10)
CurveGadgetSetAttribute(No3, #CurveGadget_X_Maximum, 380)
CurveGadgetSetAttribute(No3, #CurveGadget_Y_Maximum, 10000)
No4 = CurveGadget(#PB_Any, 220, 400, 160, 70, 8, $ffffff, $000000)
CurveGadgetSetAttribute(No4, #CurveGadget_PointRadius, 3)
CurveGadgetSetAttribute(No4, #CurveGadget_CatchRadius, 10)
CurveGadgetSetAttribute(No4, #CurveGadget_X_Maximum, 380)
CurveGadgetSetAttribute(No4, #CurveGadget_Y_Maximum, 10000)
No5 = CurveGadget(#PB_Any, 220, 480, 160, 70, 8, $ffffff, $000000)
CurveGadgetSetAttribute(No5, #CurveGadget_PointRadius, 3)
CurveGadgetSetAttribute(No5, #CurveGadget_CatchRadius, 10)
CurveGadgetSetAttribute(No5, #CurveGadget_X_Maximum, 380)
CurveGadgetSetAttribute(No5, #CurveGadget_Y_Maximum, 10000)
CanvasGadget(#GADGET_Canvas2, 10, 10, 400, 200, #PB_Canvas_ClipMouse)
StartDrawing(CanvasOutput(#GADGET_Canvas2))
Box(0, 0, 400, 200, $000000)
StopDrawing()
TrackBarGadget(#Trackbar_FilterFreq, 420, 400, 20, 80, 20, 5000, #PB_TrackBar_Vertical)
TrackBarGadget(#Trackbar_FilterReso, 460, 400, 20, 80, 0, 100, #PB_TrackBar_Vertical)
SetGadgetState(#Trackbar_FilterFreq, 1500)
SetGadgetState(#Trackbar_FilterReso, 50)
TrackBarGadget(#Trackbar_Attack, 420, 260, 20, 80, 1, 2000, #PB_TrackBar_Vertical)
TrackBarGadget(#Trackbar_Decay, 440, 260, 20, 80, 1, 5000, #PB_TrackBar_Vertical)
TrackBarGadget(#Trackbar_Sustain, 460, 260, 20, 80, 0, 100, #PB_TrackBar_Vertical)
SetGadgetState(#Trackbar_Sustain, 50)
TrackBarGadget(#Trackbar_Release, 480, 260, 20, 80, 1, 5000, #PB_TrackBar_Vertical)
SetGadgetState(#Trackbar_Attack, 1)
SetGadgetState(#Trackbar_Decay, 1000)
SetGadgetState(#Trackbar_Release, 1500)
TextGadget(#Text_Attack, 425, 247, 10, 20, "A")
TextGadget(#Text_Decay, 445, 247, 10, 20, "D")
TextGadget(#Text_Sustain, 465, 247, 10, 20, "S")
TextGadget(#Text_Release, 485, 247, 10, 20, "R")
TextGadget(#Text_FilterFreq, 425, 387, 10, 20, "F")
TextGadget(#Text_FilterReso, 455, 387, 20, 20, "Res")
TrackBarGadget(#GADGET_TrackVolume, 420, 140, 20, 80, 0, 1500, #PB_TrackBar_Vertical)
SetGadgetState(#GADGET_TrackVolume,400)
TheVolume = 400
TrackBarGadget(#GADGET_TrackDuration, 460, 140, 20, 80, 1000, 10000, #PB_TrackBar_Vertical)
SetGadgetState(#GADGET_TrackDuration,2000)
TheDuration = 2000
SpinGadget(#GADGET_ModDurationIncrement, 480, 170, 40, 25, 1, 2)
SetGadgetState (#GADGET_ModDurationIncrement, 2)
SetGadgetText(#GADGET_ModDurationIncrement, "x10") ; set initial value
TrackBarGadget(#GADGET_TrackA, 420, 30, 20, 80, 1, 99, #PB_TrackBar_Vertical)
SetGadgetState(#GADGET_TrackA, 50)
TrackBarGadget(#GADGET_TrackB, 440, 30, 20, 80, 0, 49, #PB_TrackBar_Vertical)
SetGadgetState(#GADGET_TrackB, 5)
TrackBarGadget(#GADGET_TrackC, 460, 30, 20, 80, 1, 99, #PB_TrackBar_Vertical)
SetGadgetState(#GADGET_TrackC, 75)
TrackBarGadget(#GADGET_TrackD, 480, 30, 20, 80, 50, 99, #PB_TrackBar_Vertical)
SetGadgetState(#GADGET_TrackD, 95)
SpinGadget(#GADGET_MODAMOUNTX1, 10, 260, 50, 25, -100, 100)
SetGadgetState (#GADGET_MODAMOUNTX1, 0) : SetGadgetText(#GADGET_MODAMOUNTX1, "0") ; set initial value
SpinGadget(#GADGET_MODFREQCOARSEX1, 80, 260, 50, 25, 0, 15)
SetGadgetState (#GADGET_MODFREQCOARSEX1, 0) : SetGadgetText(#GADGET_MODFREQCOARSEX1, "0") ; set initial value
SpinGadget(#GADGET_MODFREQFINEX1, 150, 260, 50, 25, 0, 99)
SetGadgetState (#GADGET_MODFREQFINEX1, 0) : SetGadgetText(#GADGET_MODFREQFINEX1, ".00") ; set initial value
SpinGadget(#GADGET_MODAMOUNTY1, 10, 340, 50, 25, -100, 100)
SetGadgetState (#GADGET_MODAMOUNTY1, 0) : SetGadgetText(#GADGET_MODAMOUNTY1, "0") ; set initial value
SpinGadget(#GADGET_MODFREQCOARSEY1, 80, 340, 50, 25, 0, 15)
SetGadgetState (#GADGET_MODFREQCOARSEY1, 0) : SetGadgetText(#GADGET_MODFREQCOARSEY1, "0") ; set initial value
SpinGadget(#GADGET_MODFREQFINEY1, 150, 340, 50, 25, 0, 99)
SetGadgetState (#GADGET_MODFREQFINEY1, 0) : SetGadgetText(#GADGET_MODFREQFINEY1, ".00") ; set initial value
SpinGadget(#GADGET_MODAMOUNTX2, 10, 420, 50, 25, -100, 100)
SetGadgetState (#GADGET_MODAMOUNTX2, 0) : SetGadgetText(#GADGET_MODAMOUNTX2, "0") ; set initial value
SpinGadget(#GADGET_MODFREQCOARSEX2, 80, 420, 50, 25, 0, 15)
SetGadgetState (#GADGET_MODFREQCOARSEX2, 0) : SetGadgetText(#GADGET_MODFREQCOARSEX2, "0") ; set initial value
SpinGadget(#GADGET_MODFREQFINEX2, 150, 420, 50, 25, 0, 99)
SetGadgetState (#GADGET_MODFREQFINEX2, 0) : SetGadgetText(#GADGET_MODFREQFINEX2, ".00") ; set initial value
SpinGadget(#GADGET_MODAMOUNTY2, 10, 500, 50, 25, -100, 100)
SetGadgetState (#GADGET_MODAMOUNTY2, 0) : SetGadgetText(#GADGET_MODAMOUNTY2, "0") ; set initial value
SpinGadget(#GADGET_MODFREQCOARSEY2, 80, 500, 50, 25, 0, 15)
SetGadgetState (#GADGET_MODFREQCOARSEY2, 0) : SetGadgetText(#GADGET_MODFREQCOARSEY2, "0") ; set initial value
SpinGadget(#GADGET_MODFREQFINEY2, 150, 500, 50, 25, 0, 99)
SetGadgetState (#GADGET_MODFREQFINEY2, 0) : SetGadgetText(#GADGET_MODFREQFINEY2, ".00") ; set initial value
TextGadget(#GADGET_TextX1, 420, 10, 20, 20, "X1")
TextGadget(#GADGET_TextY1, 440, 10, 20, 20, "Y1")
TextGadget(#GADGET_TextX2, 460, 10, 20, 20, "X2")
TextGadget(#GADGET_TextY2, 480, 10, 20, 20, "Y2")
TextGadget(#GADGET_TextMasterVolume, 420, 120, 30, 20, "Vol")
TextGadget(#GADGET_TextDuration, 450, 120, 50, 20, "Duration")
TextGadget(#GADGET_TextX1ModPercentage, 10, 240, 50, 20, "X1Mod%")
TextGadget(#GADGET_TextX1CoarseModFreq, 70, 240, 80, 20, "CoarseX1ModF")
TextGadget(#GADGET_TextX1FineModFreq, 150, 240, 70, 20, "FineX1ModF")
TextGadget(#GADGET_TextY1ModPercentage, 10, 320, 50, 20, "Y1Mod%")
TextGadget(#GADGET_TextY1CoarseModFreq, 70, 320, 80, 20, "CoarseY1ModF")
TextGadget(#GADGET_TextY1FineModFreq, 150, 320, 70, 20, "FineY1ModF")
TextGadget(#GADGET_TextX2ModPercentage, 10, 400, 50, 20, "X2Mod%")
TextGadget(#GADGET_TextX2CoarseModFreq, 70, 400, 80, 20, "CoarseX2ModF")
TextGadget(#GADGET_TextX2FineModFreq, 150, 400, 70, 20, "FineX2ModF")
TextGadget(#GADGET_TextY2ModPercentage, 10, 480, 50, 20, "Y2Mod%")
TextGadget(#GADGET_TextY2CoarseModFreq, 70, 480, 80, 20, "CoarseY2ModF")
TextGadget(#GADGET_TextY2FineModFreq, 150, 480, 70, 20, "FineY2ModF")
DrawTheBezier()
If CreateStatusBar(0, WindowID(#WIN_MAIN))
AddStatusBarField(100)
AddStatusBarField(100)
AddStatusBarField(30)
EndIf
AddKeyboardShortcut(#WIN_MAIN, #PB_Shortcut_Escape, 111)
;MIDI STUFF for NOTE ON/OFF DISPLAY
If midiInOpen_(@hMi, InDev, @MidiInCallback(), 0, #CALLBACK_FUNCTION) = #MMSYSERR_NOERROR
If midiInStart_(hMi) = #MMSYSERR_NOERROR
If IsStatusBar(0) : StatusBarText(0, 0, "MIDI IN", #PB_StatusBar_Center) : EndIf
Else
If IsStatusBar(0) : StatusBarText(0, 0, "No MIDI IN", #PB_StatusBar_Center) : EndIf
EndIf
Else
If IsStatusBar(0) : StatusBarText(0, 0, "No MIDI IN", #PB_StatusBar_Center) : EndIf
EndIf
;END MIDI STUFF
State = #Off
Pa_StartStream(Stream)
Define Quit.i
Quit = #False
Define Event.i
Repeat
Event = WaitWindowEvent()
Select Event
Case #PB_Event_Menu
Select EventMenu()
Case 1
MessageRequester("About", "Quad-Klanger v1.02 " + Chr(169) + " Jim Singh" + Chr(13) + "Bezier Sound Synthesizer")
Case 2
ShowAboutWindow()
EndSelect
If EventMenu() = 111 : Quit = #True : EndIf
Case #PB_Event_Gadget
Select EventGadget()
Case #MIDIDevice
midiInStop_(hMi)
midiInClose_(hMi)
InDev = GetGadgetState(#MIDIDevice)
midiInOpen_(@hMi, InDev, @MidiInCallback(), 0, #CALLBACK_FUNCTION)
If midiInStart_(hMi) = #MMSYSERR_NOERROR
If IsStatusBar(0) : StatusBarText(0, 0, "MIDI IN", #PB_StatusBar_Center) : EndIf
Else
If IsStatusBar(0) : StatusBarText(0, 0, "No MIDI IN", #PB_StatusBar_Center) : EndIf
EndIf
Case #Trackbar_FilterFreq
cutoffo = GetGadgetState(#Trackbar_FilterFreq)
Case #Trackbar_FilterReso
rawresolution = GetGadgetState(#Trackbar_FilterReso)
Case #Trackbar_Attack
Attack = GetGadgetState(#Trackbar_Attack) / 1000
Case #Trackbar_Decay
Decay = GetGadgetState(#Trackbar_Decay) / 1000
Case #Trackbar_Sustain
Sustain = GetGadgetState(#Trackbar_Sustain) / 100
Case #Trackbar_Release
Release = GetGadgetState(#Trackbar_Release) / 1000
Case #GADGET_TrackVolume
TheVolume = GetGadgetState(#GADGET_TrackVolume)
Case #GADGET_TrackDuration
TheDuration = GetGadgetState(#GADGET_TrackDuration)
Case #GADGET_TrackA
ptA = GetGadgetState(#GADGET_TrackA)/100
DrawTheBezier()
Case #GADGET_TrackB
ptB = GetGadgetState(#GADGET_TrackB)/100
DrawTheBezier()
Case #GADGET_TrackC
ptC = GetGadgetState(#GADGET_TrackC)/100
DrawTheBezier()
Case #GADGET_TrackD
ptD = GetGadgetState(#GADGET_TrackD)/100
DrawTheBezier()
Case #GADGET_MODAMOUNTX1
SetGadgetText(#GADGET_MODAMOUNTX1, Str(GetGadgetState(#GADGET_MODAMOUNTX1)))
Case #GADGET_MODFREQCOARSEX1
SetGadgetText(#GADGET_MODFREQCOARSEX1, Str(GetGadgetState(#GADGET_MODFREQCOARSEX1)))
Case #GADGET_MODFREQFINEX1
If GetGadgetState(#GADGET_MODFREQFINEX1) < 10
SetGadgetText(#GADGET_MODFREQFINEX1, "." + "0" + Str(GetGadgetState(#GADGET_MODFREQFINEX1)))
Else
SetGadgetText(#GADGET_MODFREQFINEX1, "." + Str(GetGadgetState(#GADGET_MODFREQFINEX1)))
EndIf
Case #GADGET_MODAMOUNTY1
SetGadgetText(#GADGET_MODAMOUNTY1, Str(GetGadgetState(#GADGET_MODAMOUNTY1)))
Case #GADGET_MODFREQCOARSEY1
SetGadgetText(#GADGET_MODFREQCOARSEY1, Str(GetGadgetState(#GADGET_MODFREQCOARSEY1)))
Case #GADGET_MODFREQFINEY1
If GetGadgetState(#GADGET_MODFREQFINEY1)<10
SetGadgetText(#GADGET_MODFREQFINEY1, "." + "0" + Str(GetGadgetState(#GADGET_MODFREQFINEY1)))
Else
SetGadgetText(#GADGET_MODFREQFINEY1, "." + Str(GetGadgetState(#GADGET_MODFREQFINEY1)))
EndIf
Case #GADGET_MODAMOUNTX2
SetGadgetText(#GADGET_MODAMOUNTX2, Str(GetGadgetState(#GADGET_MODAMOUNTX2)))
Case #GADGET_MODFREQCOARSEX2
SetGadgetText(#GADGET_MODFREQCOARSEX2, Str(GetGadgetState(#GADGET_MODFREQCOARSEX2)))
Case #GADGET_MODFREQFINEX2
If GetGadgetState(#GADGET_MODFREQFINEX2)<10
SetGadgetText(#GADGET_MODFREQFINEX2, "." + "0" + Str(GetGadgetState(#GADGET_MODFREQFINEX2)))
Else
SetGadgetText(#GADGET_MODFREQFINEX2, "." + Str(GetGadgetState(#GADGET_MODFREQFINEX2)))
EndIf
Case #GADGET_MODAMOUNTY2
SetGadgetText(#GADGET_MODAMOUNTY2, Str(GetGadgetState(#GADGET_MODAMOUNTY2)))
Case #GADGET_MODFREQCOARSEY2
SetGadgetText(#GADGET_MODFREQCOARSEY2, Str(GetGadgetState(#GADGET_MODFREQCOARSEY2)))
Case #GADGET_MODFREQFINEY2
If GetGadgetState(#GADGET_MODFREQFINEY2)<10
SetGadgetText(#GADGET_MODFREQFINEY2, "." + "0" + Str(GetGadgetState(#GADGET_MODFREQFINEY2)))
Else
SetGadgetText(#GADGET_MODFREQFINEY2, "." + Str(GetGadgetState(#GADGET_MODFREQFINEY2)))
EndIf
Case #GADGET_ModDurationIncrement
If GetGadgetState(#GADGET_ModDurationIncrement) = 1
SetGadgetText(#GADGET_ModDurationIncrement, "x1")
TrackBarGadget(#GADGET_TrackDuration, 460, 140, 20, 80, 100, 1000, #PB_TrackBar_Vertical)
SetGadgetState(#GADGET_TrackDuration,400)
TheDuration = 400
ElseIf GetGadgetState(#GADGET_ModDurationIncrement) = 2
SetGadgetText(#GADGET_ModDurationIncrement, "x10")
TrackBarGadget(#GADGET_TrackDuration, 460, 140, 20, 80, 1000, 10000, #PB_TrackBar_Vertical)
SetGadgetState(#GADGET_TrackDuration,2000)
TheDuration = 2000
EndIf
Case No2
If CurveGadgetEvent(No2) = 1
If CurveGadgetGetState(No2, CurvePoint())
EndIf
EndIf
Case No3
If CurveGadgetEvent(No3) = 1
If CurveGadgetGetState(No3, CurvePoint())
EndIf
EndIf
Case No4
If CurveGadgetEvent(No4) = 1
If CurveGadgetGetState(No4, CurvePoint())
EndIf
EndIf
Case No5
If CurveGadgetEvent(No5) = 1
If CurveGadgetGetState(No5, CurvePoint())
EndIf
EndIf
EndSelect
Case #PB_Event_CloseWindow
Quit = #True
EndSelect
Until Quit
EndIf
Procedure ShowAboutWindow()
Protected AboutWindow, myEditorGadget, CloseButton
Protected Text$
Protected Quit
AboutWindow = OpenWindow(#PB_Any, #PB_Ignore, #PB_Ignore, 620, 373, "Quad-Klanger Help", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
If AboutWindow
myEditorGadget = EditorGadget(#PB_Any, 5, 5, 610, 360, #PB_Editor_ReadOnly)
Text$ = Chr($d) + "The Quad-Klanger main window displays a single cycle of the basic waveform." + Chr($d)
Text$ + Chr($d) + "The X1, X2 sliders 'pull' the waveform along the X axis."
Text$ + Chr($d) + "The Y1, Y2 sliders pull' the waveform along the Y axis." + Chr($d)
Text$ + Chr($d) + "You can make each of the X1, X2, Y1, Y2 points 'wobble' as the Note plays by modulating each of them "
Text$ + Chr($d) + "independently, allowing a wide variety of harmonically rich sounds to be produced." + Chr($d)
Text$ + Chr($d) + "Let us take point X1 for example:" + Chr($d)
Text$ + Chr($d) + "The point will oscillate between where the X1 slider is currently set, to a level determined by X1Mod%."
Text$ + Chr($d) + "If X1Mod% = +80, the point will oscillate between where the X1 slider is currently set, to 80% of the way up"
Text$ + Chr($d) + "to the top of the slider. If X1Mod% = -30, the point will oscillate between where the X1 slider is currently set,"
Text$ + Chr($d) + "to 30% of the way to the bottom of the slider. The point will oscillate at a frequency determined by"
Text$ + Chr($d) + "CoarseX1ModF and FineX1ModF. If Coarse X1ModF = 2 and FineX1ModF = .25, the point will oscillate at"
Text$ + Chr($d) + "2.25 times the frequency of the Note being played on the keyboard. If Coarse X1ModF = 10 and"
Text$ + Chr($d) + "FineX1ModF = 0, the point will oscillate at 10 times the frequency of the Note being played on the keyboard."
Text$ + Chr($d) + "If Coarse X1ModF = 0 and FineX1ModF = .05, the point will oscillate at 0.05 times the frequency of the Note"
Text$ + Chr($d) + "being played on the keyboard. Integer multiples of the Note Frequency set by CoarseX1ModF result in harmonic"
Text$ + Chr($d) + "overtones being produced, whilst the fractional multiplier set by FineX1ModF results in inharmonic overtones." + Chr($d)
Text$ + Chr($d) + "You can make the point oscillation increase or decrease in amplitude over time as the Note plays, by moving "
Text$ + Chr($d) + "the 7 segments of the wobble amplitude curve for point X1 into your desired envelope shape, and the period "
Text$ + Chr($d) + "over which that envelope applies is determined by the Duration slider. The x1 setting for the Duration slider "
Text$ + Chr($d) + "constrains this period to a maximum of a couple of seconds, whilst the x10 setting allows the period to be "
Text$ + Chr($d) + "many seconds." + Chr($d)
Text$ + Chr($d) + "For example, if you draw the wobble amplitude curve as a straight line starting at lower left and ending at top "
Text$ + Chr($d) + "right of the envelope window, and you leave the Duration slider in its default position, then when you press a "
Text$ + Chr($d) + "Note, the point oscillation will occur between where the X1 slider is currently set, and a level which linearly "
Text$ + Chr($d) + "moves over a couple of seconds from X1 to the level determined by X1Mod%. This will change the harmonic "
Text$ + Chr($d) + "content of the sound as the Note plays, giving a kind of 'filter sweeping' effect." + Chr($d)
Text$ + Chr($d) + "The sound produced by this engine is then passed through a master low pass filter, the frequency of which is set "
Text$ + Chr($d) + "by the F slider and the Resonance by the Res slider, giving you even greater sound shaping versatility, and then "
Text$ + Chr($d) + "through the level ADSR, which you set with the Amplitude EG sliders." + Chr($d)
Text$ + Chr($d) + "The Vol slider adjusts the overall level of the sound." + Chr($d)
Text$ + Chr($d) + "The MIDI devices available on your system which Quad-Klanger can use to receive Midi note events from your "
Text$ + Chr($d) + "virtual or external hardware keyboard, are shown in the Midi devices drop-down. Select the device you wish to use, "
Text$ + Chr($d) + "and ensure your keyboard transmits through the same device."
SetGadgetText(myEditorGadget, Text$)
Repeat
Select WaitWindowEvent()
Case #PB_Event_Gadget : If EventGadget()=CloseButton : Break : EndIf
Case #PB_Event_CloseWindow : If EventWindow()=AboutWindow : Break : EndIf
EndSelect
ForEver
CloseWindow(AboutWindow)
EndIf
EndProcedure
Pa_AbortStream(Stream)
While Pa_IsStreamActive(Stream) : WaitWindowEvent(1) : Wend
Pa_Terminate()