Stereo Sound Generator & Monitor Scope Rev2.7 23 June 2013

Share your advanced PureBasic knowledge/code with the community.
Simo_na
Enthusiast
Enthusiast
Posts: 177
Joined: Sun Mar 03, 2013 9:01 am

Re: Rev2.4 May 16 2013 - Stereo Sound Generator & Monitor S

Post by Simo_na »

:)

I get the same problem....
---------------------
if i can give a tip .. .replace the wave play function with pink noise generator (many people needs good pink noise generator...)
RichardL
Enthusiast
Enthusiast
Posts: 532
Joined: Sat Sep 11, 2004 11:54 am
Location: UK

Re: Rev2.4 May 16 2013 - Stereo Sound Generator & Monitor S

Post by RichardL »

Friday 17th May 2013

Hi Folks...
At the risk of shooting myself in the foot I believe I have overcome the problem kindly reported by Infratec, BP and Simo_na. This was essentially a timing problem made even worse when running in Debug mode. I have separated the 'scope refresh into a separate thread and made some changes so the cursors remain active and can be adjusted even when the signals sources are dormant. See Rev 2.4a in the first posting.

I hope to find time to further improve the single shot triggering but it will not be until later next week... to many other things to do including attending a wedding and testing lots of beer afterwards.

@Simo_na
I'm not sure how to produce 1/F weighted noise without resorting to an FFT/IFFT approach. Do you know a method... I'm sure there is one lurking out there? I can still remember how mind blowing Bresenham's algorithms were when I first met them; plotting a circle without a Sin(), Cos() or Sqrt() in sight, so nothing would surprise me!

Have a good weekend...
Richard L
Joris
Addict
Addict
Posts: 890
Joined: Fri Oct 16, 2009 10:12 am
Location: BE

Re: Rev2.4 May 16 2013 - Stereo Sound Generator & Monitor S

Post by Joris »

RichardL wrote:.. to many other things to do including attending a wedding and testing lots of beer afterwards.
Even if we can not join the wedding, enjoy it. :)
Last edited by Joris on Sun May 19, 2013 12:04 pm, edited 1 time in total.
Yeah I know, but keep in mind ... Leonardo da Vinci was also an autodidact.
Simo_na
Enthusiast
Enthusiast
Posts: 177
Joined: Sun Mar 03, 2013 9:01 am

Re: Rev2.4 May 16 2013 - Stereo Sound Generator & Monitor S

Post by Simo_na »

RichardL wrote:
@Simo_na
I'm not sure how to produce 1/F weighted noise without resorting to an FFT/IFFT approach. Do you know a method... I'm sure there is one lurking out there? I can still remember how mind blowing Bresenham's algorithms were when I first met them; plotting a circle without a Sin(), Cos() or Sqrt() in sight, so nothing would surprise me!

Have a good weekend...
Richard L

ahhhh.... i know only this ... http://musicdsp.org/showone.php?id=220 :oops:



........................ and testing lots of beer afterwards :mrgreen: :mrgreen:

EDIT
http://home.earthlink.net/~ltrammell/tech/newpink.htm
Simo_na
Enthusiast
Enthusiast
Posts: 177
Joined: Sun Mar 03, 2013 9:01 am

Re: Rev2.4 May 16 2013 - Stereo Sound Generator & Monitor S

Post by Simo_na »

; --- FOR ANOTHER DAY (Maybe!)
; Better X scaling management
; Switch sampling rate 44100, 96000,22k?
; Voltage calibration

Sampling rate = 44.100 - 48.000 - 88.200 - 96.000 - 192.000
Consider any soundcard have different chipset and different chipset have different converter and :) different converter have different Voltage or the 0 dBfs is not correspondent @ 0 dBu
for example :
the chipset of Fireface 800 (RME) @ 0 dBfs (max value for the digital world) corresponds to +13 dBu , +13 dBu corresponds @ +10,782 dBv or 9.786 Vpp or 3.460 Vrms.
Any soundcard have his rules, not all are equals ( almost never)

As you know these are the reference values :

The 0 dBu is : 774.6 mVrms / 1.095 Vp / 2.191 Vpp / +17.782 dBrG (@ 100 mVrms) / -0.695 dBm (@ 600 Ohm) / 1.427 mW (@8 Ohm)
The 0 dBv is : 1 Vrms / 1.414 Vp / 2.828 Vpp / +2.218 dBu
The 0 dBm is : 839.1 mVrms / 1.187 Vp / 2.373 Vpp / -1.523 dBV / +0.695 dBu
For the MICS considers the middle value @ : 19.95 mVrms / 28.22 mVp / 56.43 mVpp / -34 dBV / -14 dBrG (@100) / -32.477 (@600) / 0.947 (@8)
The max dBu in analog devices (almost all) is capable to : 7.2 Vrms / 10.18 Vp / 20.36 Vpp / +17.147 dBV / +19.365 dBu / +18.670 dBm (@600).
Simo_na
Enthusiast
Enthusiast
Posts: 177
Joined: Sun Mar 03, 2013 9:01 am

Re: Rev2.4 May 16 2013 - Stereo Sound Generator & Monitor S

Post by Simo_na »

look this for 30 secs

Code: Select all

; ****************************************************************

; An investigation into using 'Waveform Audio' for Audio output,
; specifically to produce a stereo tone generator. Then I added
; a 'scope display and an input channel and it became a bit like
; Topsy and just grow'd!

; Status: Works and meets my needs.

; (C) Richard Leman 2013
; Free to copy, use or ignore entirely at your own risk.

; Rev 2.5a - 17th May 2013
; Added : The V1, V2 and Trigger thresholds now track dragging of the trace datum lines.
; Added : Optional invert option on both scope traces.
; Added : A = A + B. Invert B and you see the difference between two traces.
; Added : Trigger sense polarity + / -
; Added : Mouse wheel trace expansion / compression. Added X annotation.

; Sorted: Freeze mode buffer roll over
; Sorted: Display refresh timing issue, overcome by making display a Thread().
;         and moving single shot logic out of the display thread.

; --- FOR ANOTHER DAY (Maybe!)
; Better X scaling management
; Switch sampling rate 44100, 96000,22k?
; Voltage calibration


; ****************************************************************

;{- Useful references
; http://msdn.microsoft.com/en-gb/library/windows/desktop/dd743833(v=vs.85).aspx
; http://www.ex-designz.net/apidetail.asp?api_id=531
; http://stilzchen.kfunigraz.ac.at/skripten/comput07/oszi_sound/Mod_sound.bas
;}
;{- Procedure declarations
Declare StartSoundOutput()
Declare StopSoundOutput()
Declare StartSoundInput()
Declare StopSoundInput()
Declare CalcWave(*SBuf,nSamples)
Declare ScopeRefresh(*z)
Declare ShowCursorReport()
Declare WinCallback(hwnd, uMsg, wParam, lParam)
Declare SetGadgetMouseXY(Win,Gadget,Mx,My,flags=%111)
Declare BuildScopeBackdrop(Bn)
;}
;{- Globals and constants
Enumeration  ; Windows, Menus, Gadgets, Hot keys
  ; WINDOWS
  #Win_SNDWin   = 100
 
  ;GADGETS
  #Gad_Backdrop   = 1000
  #Gad_SwitchOut
  #Gad_SwitchIn
  #Gad_TraceASwitch
  #Gad_TraceBSwitch
  #Gad_InvertA
  #Gad_InvertB
  #Gad_AddAB
  #Gad_Scope   
  #Gad_WavL   
  #Gad_WavR   
  #Gad_SwitchL
  #Gad_SwitchR
  #Gad_FreqL   
  #Gad_FreqR 
  #Gad_VolumeL 
  #Gad_VolumeR 
  #Gad_Lock     
  #Gad_LockOfs 
  #Gad_SwitchDF
  #Gad_SwitchDP
  #Gad_PlayWAV
  #Gad_CursTimeSwitch
  #Gad_CursVoltSwitch
  #Gad_CursResults
  #Gad_VoltRef
  #Gad_SS_Offset
  #Gad_SSArm
  #Gad_TriggerSelect
  #Gad_TrigSense
  #Gad_Spoof
 
  ; MENU ITEMS
  #Men_OutDevice1 = 2000
  #Men_InDevice1  = 2010
 
  ; Hot keys
  #Key_CntrlC     = 3000
  #Key_CntrlS
  #Key_CntrlP
EndEnumeration
Enumeration  ; Things that might be dragged on the 'scope display
  #DragNone
  #DragChanAY
  #DragChanBY
  #DragTime1
  #DragTime2
  #DragVolts1
  #DragVolts2
  #DragTrig
EndEnumeration
Enumeration  ; Trigger options
  #TraceA
  #TraceB
  #SingleShotA
  #SingleShotB
  #FreeRun
EndEnumeration

Global Dragging = #DragNone
Global DragX, DragY

Global SampleClock    = 44100 ; Sampling/Replay frequency in 'samples per second'
Global BlockSize      = 4096  ; Number of samples in capture/play block
Global BytesPerSample = 2     ; Number of bytes needed for each sample
Global Channels       = 2     ; Number of channels, 1 for mono, 2 for stereo.
Global DevOut         = 1
Global DevIn          = 1
Global hWaveOut
Global hWaveIn
Global nBuf           = 16
Global LockLR, LockOffset
Global ScopeImage
Global La.f, Ra.f , DoDP, DoDF
Global hWAV

;----------------------------------------------------------------------------------------------------------------------
global lk.f
;----------------------------------------------------------------------------------------------------------------------

WAVPath$ = "C:\Temp\"

#PIx2 = 2*#PI
#Twiddle = 7  ; How close a click needs to be to a dragable line

; ========================================================
Global PlayFormat.WAVEFORMATEX
Global RecFormat.WAVEFORMATEX
Global MyOutDevs.WAVEOUTCAPS
Global MyInDevs.WAVEINCAPS
Global Dim outHdr.WAVEHDR(nBuf)
Global Dim inHdr.WAVEHDR(nBuf)
Global *InBufMem, *InBufMemEnd  ; Start and end of Input buffer
Global FreeRun = #False         ; Triggered display

Structure Draggable
  Time1X.i
  Time2X.i
  Volts1Y.i
  Volts2Y.i
  Trig.i
  TrigLineTimer.i
 
  Volts1Yb4.i
  Volts2Yb4.i
  TrigYb4.i
EndStructure

Global Drag.Draggable

With Drag
  \Time1X    = 20
  \Time2X    = 40
  \Volts1Y   = 50
  \Volts2Y   = 100
  \Trig      = 50
  \TrigLineTimer = ElapsedMilliseconds() + 5000
EndWith

Structure ScopeDisplay
  width.i
  height.i
  TimeCursorSwitch.i
  VoltsCursorSwitch.i
  PixPermSec.f
  PixPerVolt.f
  Mag.i
  VoltRefIndex.i
  VoltRefDisc$
  ShowInOut.i
  TraceASwitch.i
  TraceBSwitch.i
  Copy.i
  TriggerMode.i
  TriggerModeDisc$
  TrigLevel.w
  TrigSense.i
 
  YPosA.i
  YPosB.i
  SenseA.i
  SenseB.i
  APlusB.i
 
  SSBuffer.i        ; Address of circular store used for storing SS data
  SSBufSize.i       ; Size of SS buffer!
  SSBufferEnd.i     ; Address of end of SS data buffer
  SSMode.i          ; #True when user has selected single shot on ChanA or B.
  SSArm.i           ; #True when user has primed the single shot mode
  SSFired.i         ; #True after the single shot event has occured. Input now blocked.
  SSOffset.i        ; Offset from start of block after Single Shot trigger fired.
  SSPosSlider.i     ; Slider position relative to mid-position... scaled.
 
  DisplayP.i
  DisplayStart.i
  DisplayEnd.i
  DisplayTrig.i
 
EndStructure
Global Scope.ScopeDisplay

With Scope
  \width        = 512
  \height       = 256
  \TimeCursorSwitch.i  = #False
  \VoltsCursorSwitch.i = #False
  \PixPerVolt   = 50     ; This is arbitrary
  \VoltRefIndex = 1
  \Mag          = 16     ; Actual value = \mag/16. \mag 1 => 256
  \VoltRefDisc$ = "ChanA (Red)|ChanB (Green)|Bottom|Centre|"
  \ShowInOut    = 1                ; 0=Input   1=Output
  \TrigLevel    = 0
  \TriggerMode  = #TraceA
  \TriggerModeDisc$ = "TraceA|TraceB|Single shot A|Single shot B|Free-run|"
  \TraceASwitch = #True
  \TraceBSwitch = #True
  \SSMode       = #False
  \SSArm        = #False
  \SenseA       = -1     ; True/Invert -1 / +1
  \SenseB       = -1
  \APlusB       = 0
  \TrigSense    = 1       ; Pos/Neg  +1 / -1
EndWith

Structure Waves
  WaveForm.i
  Frequency.i
  Volume.f
  Switch.i
  DatumImage.i
EndStructure
Global ChanA.Waves
Global ChanB.Waves

; Initial signal generator frequencies etc
With ChanA                ; 300 Hz Sinewave on ChanA, On
  \Frequency = 500
  \Volume    = 0.3
  \Switch    = #True
  \WaveForm  = 0
EndWith
With ChanB
  \Frequency = 500      ; 2401 Hz Sinewave on ChanB, On
  \Volume    = 0.3
  \Switch    = #True
  \WaveForm  = 0
EndWith

LockLR     = #False       ; Option to lock ChanB frequency to ChanA
DoDF       = #True        ; When A&B frequencies are BOTH controlled by the ChanA channel there is an
DoDP       = #False       ; option for having a phase or frequency offset.

Global WinW = 760, WinH = 546
;}
;{- Build Control panel
BuildScopeBackdrop(1)     ; Display size Image() with grid

; Create two time cursors
Global CursorTime1 = CreateImage(#PB_Any,1,Scope\height)
StartDrawing(ImageOutput(CursorTime1))
For n = 0 To Scope\height Step 10
  For m = n To n+5
    Plot(0,m,#White)
  Next
Next
StopDrawing()
Global CursorTime2 = CopyImage(CursorTime1,#PB_Any)

; Create two volts cursors
Global CursorV1 = CreateImage(#PB_Any,Scope\width,1)
StartDrawing(ImageOutput(CursorV1))
  For n = 0 To Scope\width-6 Step 10
    For m = n To n+5 
      Plot(m,0,#White)
    Next
  Next
StopDrawing()
Global CursorV2 = CopyImage(CursorV1,#PB_Any)

; Dotted baselines for the two channels
ChanA\DatumImage  = CreateImage(#PB_Any,Scope\width,1)
StartDrawing(ImageOutput(ChanA\DatumImage))
  For n = 0 To Scope\width-6 Step 10
    For m = n To n+5 
      Plot(m,0,#Red)
    Next
  Next
StopDrawing()

ChanB\DatumImage = CreateImage(#PB_Any,Scope\width,1)
StartDrawing(ImageOutput(ChanB\DatumImage))
  For n = 0 To Scope\width-6 Step 10
    For m = n To n+5 
      Plot(m,0,#Green)
    Next
  Next
StopDrawing()

; Dotted line for trigger level
Global CursorTrig = CreateImage(#PB_Any,Scope\width,1)
StartDrawing(ImageOutput(CursorTrig))
  For n = 0 To Scope\width-6 Step 10
    For m = n To n+5 
      Plot(m,0,#Yellow)
    Next
  Next
StopDrawing()
 
; ====== Main Window, buttons etc. =======
OpenWindow(#Win_SNDWin,0,0,WinW,WinH,"Audio Generator and Monitor - R2.4a  (C)2013 Richard Leman (G8CDD) ",#PB_Window_SystemMenu |#PB_Window_ScreenCentered)

; Main display area
CanvasGadget(#Gad_Scope,10,10,Scope\width,Scope\height,#PB_Canvas_ClipMouse)
ScrollBarGadget(#Gad_SS_Offset,10,py+Scope\height+15,Scope\width,15,0,8191,1)

;{ Trigger controls
px = 530 : py = 2
Frame3DGadget(#PB_Any,px,py,220,135," Trigger ")

px + 10 : py + 20
TextGadget(#PB_Any,px,py,100,20,"Trig source", #PB_Text_Center)
ComboBoxGadget(#Gad_TriggerSelect,px,py+20,100,20)
CheckBoxGadget(#Gad_SSArm,        px,py+45,100,20,"Arm S/Shot")

px + 100
TextGadget(#PB_Any,px,py,100,20,"Sense", #PB_Text_Center)
ButtonGadget(#Gad_TrigSense,px+42,py+20,18,18,"+")

py + 70
; TextGadget(#PB_Any,px,py,200,20,"-    Trig Level    +", #PB_Text_Center)
; ScrollBarGadget(#Gad_TrigLevel,px,py+20,200,15,0,8192,4)

; Trigger mode options
For n = 1 To CountString(Scope\TriggerModeDisc$,"|")
  AddGadgetItem(#Gad_TriggerSelect,-1,StringField(Scope\TriggerModeDisc$,n,"|"))
Next
SetGadgetState(#Gad_TriggerSelect,#TraceA)
;}
;{ Trace management
px = 530 : py = 142
Frame3DGadget(#PB_Any,px,py,220,150," Trace Management ")

px + 10 : py + 20
TextGadget(#PB_Any,px,py,100,20,"Signal Source")
OptionGadget(#Gad_SwitchOut,px,py+20,80,18, "Audio Gen")
OptionGadget(#Gad_SwitchIn, px+85,py+20,80,18, "External")
py + 45
TextGadget(#PB_Any,px,py,100,20,"Trace switches")
CheckBoxGadget(#Gad_TraceASwitch,px,py+20,80,18,"Trace 'A'")
CheckBoxGadget(#Gad_TraceBSwitch,px+85,py+20,80,18,"Trace 'B'")
py + 40
CheckBoxGadget(#Gad_InvertA,px,py,   80,18,"Invert 'A'")
CheckBoxGadget(#Gad_InvertB,px+85,py,80,18,"Invert 'B'")
CheckBoxGadget(#Gad_AddAB,  px,py+20,80,18,"A=A+B")
;}
;{ Audio Generator controls
px = 05 : py = 375
Frame3DGadget(#PB_Any,px,py,615,145," Audio Generators ")

px + 5 : py +18
TextGadget(#PB_Any,px, py,50,18,"Channel", #PB_Text_Center)
TextGadget(#PB_Any,px, py+20,50,20,"'A'",#PB_Text_Center)
TextGadget(#PB_Any,px, py+45,50,20,"'B'",#PB_Text_Center)

px + 55
TextGadget(#PB_Any,px, py,50,18,"On/Off", #PB_Text_Center)
CheckBoxGadget(#Gad_SwitchL,px+22,py+20, 15, 15, "",#PB_CheckBox_Right)
CheckBoxGadget(#Gad_SwitchR,px+22,py+45, 15, 15, "",#PB_CheckBox_Right)

px + 55
TextGadget(#PB_Any,px, py,80,18,"Waveform", #PB_Text_Center)
ComboBoxGadget(#Gad_WavL,px,py+20,80,20)
ComboBoxGadget(#Gad_WavR,px,py+45,80,20)

px + 85
TextGadget(#PB_Any,px,py,200,20,"Frequency",#PB_Text_Center)
ScrollBarGadget(#Gad_FreqL,px,py+22,200,15,100,10000,1)
ScrollBarGadget(#Gad_FreqR,px,py+47,200,15,100,10000,1)

px + 205
TextGadget(#PB_Any,px,py,200,20,"Amplitude",#PB_Text_Center)
ScrollBarGadget(#Gad_VolumeL,px,py+22,200,15,0,100,1)
ScrollBarGadget(#Gad_VolumeR,px,py+47,200,15,0,100,1)

px = 10 : py + 70
TextGadget(#PB_Any,px,py,70,20,"Lock 'B' to 'A'",#PB_Text_Center)
CheckBoxGadget(#Gad_Lock, px+27,  py+20, 15,15,"" )

px + 65
TextGadget(#PB_Any        ,px,   py,   100,20, "Lock Offset",#PB_Text_Center)
StringGadget(#Gad_LockOfs, px+20,py+20,60, 20, "0", #PB_String_Numeric)

px + 105
OptionGadget(#Gad_SwitchDF,px,py+10, 110,20, "Freq  (Hz)")
OptionGadget(#Gad_SwitchDP,px,py+30, 110,20, "Phase (Deg)")

px+115
ButtonGadget(#Gad_PlayWAV, px,py+20, 200,20, "Play WAV")

; Outgoing waveform choices
WaveType$ = "Sine|Square|Sawtooth|Noise|WAV File|"
For n = 1 To CountString(WaveType$,"|")
  AddGadgetItem(#Gad_WavL,-1,StringField(WaveType$,n,"|"))
  AddGadgetItem(#Gad_WavR,-1,StringField(WaveType$,n,"|"))
Next
;}
;{ Measurement cursors
px = 05 : py = 290
Frame3DGadget(#PB_Any,px,py,455,75," Measurement Cursors ")

px + 5 : py +18
TextGadget(#PB_Any,px, py,50,18,"Time", #PB_Text_Center)
CheckBoxGadget(#Gad_CursTimeSwitch,px+17 ,py+20, 15, 15, "")

px + 55
TextGadget(#PB_Any,px, py,50,18,"Volts", #PB_Text_Center)
CheckBoxGadget(#Gad_CursVoltSwitch,px+17,py+20, 15, 15, "")

px + 55
TextGadget(#PB_Any,px, py-8,230,18,"Measured Values", #PB_Text_Center)
EditorGadget(#Gad_CursResults,px, py+10, 230, 38, #PB_Editor_ReadOnly)
LoadFont(1,"Courier New",8) : SetGadgetFont(#Gad_CursResults,FontID(1))

px + 240
TextGadget(#PB_Any,px, py,90,18,"Volt Ref", #PB_Text_Center)
SpinGadget(#Gad_VoltRef,px,py+20,90,20,1,CountString(Scope\VoltRefDisc$,"|"),#PB_Spin_ReadOnly)

; Truly horrible...
TextGadget(#Gad_Spoof,1,1,1,1,"") : HideGadget(#Gad_Spoof,#True)
;}
;{ Hot keys to save display to clip/file/printer
AddKeyboardShortcut(#Win_SNDWin,#PB_Shortcut_Control | #PB_Shortcut_C,#Key_CntrlC)
AddKeyboardShortcut(#Win_SNDWin,#PB_Shortcut_Control | #PB_Shortcut_S,#Key_CntrlS)
AddKeyboardShortcut(#Win_SNDWin,#PB_Shortcut_Control | #PB_Shortcut_P,#Key_CntrlP)
;}
;{ Set controls to match initial conditions etc.
SetGadgetState(#Gad_FreqL,ChanA\Frequency)  : SetGadgetState(#Gad_VolumeL,100*ChanA\Volume) : SetGadgetState(#Gad_SwitchL,ChanA\Switch)
SetGadgetState(#Gad_FreqR,ChanB\Frequency)  : SetGadgetState(#Gad_VolumeR,100*ChanB\Volume) : SetGadgetState(#Gad_SwitchR,ChanB\Switch)
SetGadgetState(#Gad_WavL,ChanA\WaveForm)    : SetGadgetState(#Gad_WavR,ChanB\WaveForm)

SetGadgetState(#Gad_Lock,LockLR)
SetGadgetState(#Gad_SwitchDF,DoDF) : SetGadgetState(#Gad_SwitchDP,DoDP)
DisableGadget(#Gad_PlayWAV,#True)
; SetGadgetState(#Gad_TrigLevel,4096)

; Build a menu for some config items
CreateMenu(1,WindowID(#Win_SNDWin))

; Output device selection
OpenSubMenu("Sound Output Devices")
NumOutDevs = waveOutGetNumDevs_()
If NumOutDevs
  For n = 0 To NumOutDevs - 1
    If waveOutGetDevCaps_(n,@MyOutDevs,SizeOf(WAVEOUTCAPS)) = 0
      MenuItem(n + #Men_OutDevice1,PeekS(@MyOutDevs\szPname))
    EndIf
  Next
EndIf
CloseSubMenu()

; Input device selection
OpenSubMenu("Sound Input Devices")
NumInDevs = waveInGetNumDevs_()
If NumInDevs
  For n = 0 To NumInDevs - 1
    If waveInGetDevCaps_(n,@MyInDevs,SizeOf(WAVEINCAPS)) = 0
      MenuItem(n + #Men_InDevice1,PeekS(@MyInDevs\szPname))
    EndIf
  Next
EndIf
CloseSubMenu()

; Set initial description for voltage cursor reference
Scope\VoltRefIndex = 1
SetGadgetText(#Gad_VoltRef,StringField(Scope\VoltRefDisc$,Scope\VoltRefIndex,"|"))
DisableGadget( #Gad_VoltRef,#True)

; Misc...
SetGadgetState(#Gad_SwitchOut,#True)                                 ; Initially the 'scope show the SigGen output
SetGadgetAttribute(#Gad_Scope,#PB_Canvas_Cursor,#PB_Cursor_Cross)    ; Cursor to be used when mouse over the Canvas
SetGadgetState(#Gad_SS_Offset,GetGadgetAttribute(#Gad_SS_Offset,#PB_ScrollBar_Maximum)>>1)
SetGadgetState(#Gad_TraceASwitch,#True)
SetGadgetState(#Gad_TraceBSwitch,#True)
;}
;}
;{- Preparation and startup
SetMenuItemState(1,#Men_OutDevice1,#True)
SetMenuItemState(1,#Men_InDevice1,#True)
DisableGadget(#Gad_SSArm,#True)
StartSoundOutput()
SetWindowCallback(@WinCallback())             ; Handles Scrollbars and Sound Output callback
StartSoundInput()
UseJPEGImageEncoder()
Drag\TrigLineTimer = ElapsedMilliseconds() + 5000
CreateThread(@ScopeRefresh(),1)
;}
;{- Dispatch
Finish = #False
Repeat



  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow              ;{/ Close windowS
      Finish = #True
      ;}
    Case #PB_Event_Gadget                   ;{/ BUTTONS AND GADGETS
      Select EventGadget()
        Case #Gad_SwitchL                   ;{ Switch channel 'A' On/Off
          ChanA\Switch  = GetGadgetState(#Gad_SwitchL)
          ;}
        Case #Gad_SwitchR                   ;{ Switch channel 'B' On/Off
          ChanB\Switch = GetGadgetState(#Gad_SwitchR)
          ;}
        Case #Gad_TriggerSelect             ;{ Select Trigger mode
          Scope\TriggerMode = GetGadgetState(#Gad_TriggerSelect)
         
          If Scope\TriggerMode = #SingleShotA Or Scope\TriggerMode = #SingleShotB ; If a single shot mode has been selected,
            DisableGadget(#Gad_SSArm,#False) ; enable the 'Arm' button,
            SetGadgetState(#Gad_SSArm,#False); clear it, (waiting for user to set)
            Scope\SSArm = #False             ; clear the 'Arm' flag 
            Scope\SSFired = #False           ; the trigger is not 'Fired'.
            Scope\SSOffset = 0               ; Tidy, for debug!
          Else
            SetGadgetState(#Gad_SSArm,#False); Clear the 'Arm' button and
            DisableGadget(#Gad_SSArm,#True)  ; disable it.
            Scope\SSFired = #False           
          EndIf
          SetActiveGadget(#Gad_Spoof)        ; Remove focus???
          ;}
        Case #Gad_TrigSense                 ;{
          Scope\TrigSense * -1
          SetGadgetText(#Gad_TrigSense,Mid("- +",Scope\TrigSense + 2,1))
          ;}
        Case #Gad_SSArm                     ;{ Single shot Arm
          Scope\SSArm = GetGadgetState(#Gad_SSArm)
          If Scope\SSArm
            Scope\SSFired = #False
            SetGadgetState(#Gad_SS_Offset,GetGadgetAttribute(#Gad_SS_Offset,#PB_ScrollBar_Maximum)>>1) ; Put scoll bar central
            StartDrawing(CanvasOutput(#Gad_Scope))   ; Clear display by overdrawing with backdrop grid
              DrawImage(ImageID(ScopeImage),0,0)
            StopDrawing()
          Else
            Scope\SSFired = #False
          EndIf
          ;}
        Case #Gad_Lock                      ;{ Lock ChanB to ChanA channel
          LockLR = GetGadgetState(#Gad_Lock)
          DisableGadget(#Gad_FreqR,LockLR) ; Control user access to 'B' channel slider
          If LockLR = #False
            ChanB\Frequency = GetGadgetState(#Gad_FreqR)
          Else
            La.f = Ra.f
          EndIf
          ;}         
        Case #Gad_SwitchDF, #Gad_SwitchDP   ;{ Choose phase or frequency offset
          DoDF = GetGadgetState(#Gad_SwitchDF)    ; A flip-flop pair
          DoDP = DoFR ! 1
          ;}
        Case #Gad_TraceASwitch              ;{ Display switch Trace A
          Scope\TraceASwitch = GetGadgetState(#Gad_TraceASwitch)
          ;}
        Case #Gad_TraceBSwitch              ;{ Display switch Trace B
          Scope\TraceBSwitch = GetGadgetState(#Gad_TraceBSwitch)
          ;}
        Case #Gad_InvertA                   ;{ Invert Trace A / B
          Scope\SenseA = 2 * GetGadgetState(#Gad_InvertA) - 1
        Case #Gad_InvertB         
          Scope\SenseB = 2 * GetGadgetState(#Gad_InvertB) - 1
          ;}
        Case #Gad_AddAB                      ;{ Add traces (A=A+B)
          Scope\APlusB = GetGadgetState(#Gad_AddAB)
          ;}
        Case #Gad_WavL                      ;{ ChanA waveform selection
          ChanA\WaveForm = GetGadgetState(#Gad_WavL) ; Get selected waveform designator
         
          ; TEMPORARY: Sound file (must be WAV, 16 bit, mono, 44100, non-compressed
          If ChanA\WaveForm = 4 ; Choose file to be played...
            WAVFile$ = OpenFileRequester("Load a WAV File",WAVPath$,"WAV Files|*.WAV",0)
            If WAVFile$
              WAVPath$ = GetPathPart(WAVFile$)
              DisableGadget(#Gad_PlayWAV,#False)
              SetGadgetText(#Gad_PlayWAV,"Play<"+GetFilePart(WAVFile$)+">")
            EndIf
          EndIf
          SetActiveGadget(#Gad_Spoof) ; Remove focus???
          ;}
        Case #Gad_WavR                      ;{ ChanB waveform selection
          ChanB\WaveForm = GetGadgetState(#Gad_WavR)
          SetActiveGadget(#Gad_Spoof) ; Remove focus???
          ;}
        Case #Gad_PlayWAV                   ;{ Kludge to start playing selected 16bit Mono 44.1K sample rate WAV file
          ; No header checks, no nuffin'...
          If IsFile(hWAV) : CloseFile(hWAV) : hWAV = 0 : EndIf ; Close a prior file.
          If WAVFile$                                          ; If filename defined,
            hWAV  = OpenFile(#PB_Any,WAVFile$)                 ; open the new file,
            FileSeek(hWAV,36)                                  ; Seek to <<normal>> start of data.
          EndIf
          ;}
        Case #Gad_CursTimeSwitch            ;{ Switch time cursors On / Off 
          Scope\TimeCursorSwitch = GetGadgetState(#Gad_CursTimeSwitch)
          ShowCursorReport()
          ;}
        Case #Gad_CursVoltSwitch            ;{ Switch volts cursors On/Off
          Scope\VoltsCursorSwitch = GetGadgetState(#Gad_CursVoltSwitch)
          ShowCursorReport()
          DisableGadget( #Gad_VoltRef,Scope\VoltsCursorSwitch ! 1)
          ;}
        Case #Gad_VoltRef                   ;{ Choose the zero datum for voltage cursors
          Scope\VoltRefIndex = GetGadgetState(#Gad_VoltRef)
          SetGadgetText(#Gad_VoltRef,StringField(Scope\VoltRefDisc$,Scope\VoltRefIndex,"|"))
          SetActiveGadget(#Gad_Spoof) ; Remove focus???
          ShowCursorReport()
          ;}
        Case #Gad_SwitchOut, #Gad_SwitchIn  ;{ Select display to show sig-gen or input
          Scope\ShowInOut = GetGadgetState(#Gad_SwitchOut)
          Scope\SSArm    = #False
          Scope\SSFired  = #False
          Scope\SSOffset = 0
          ;}
        Case #Gad_Scope                     ;{ Display Canvas Events
          Select EventType()
              ; Left mouse down over the display will be to select a feature for dragging.
              ; Some elasticity is provided so the user does not need to click directly
              ; on the line, if it is near enough the cursor jumps to the precise
              ; position of the line... which does NOT move until it is dragged.
             
            Case #PB_EventType_LeftButtonDown ;{ START dragging...
              DragX = GetGadgetAttribute(#Gad_Scope, #PB_Canvas_MouseX)
              DragY = GetGadgetAttribute(#Gad_Scope, #PB_Canvas_MouseY)
             
              ; Trigger line has priority
              If (Abs(DragY - Drag\Trig) < #Twiddle)                         ; Trigger threshold
                SetGadgetMouseXY(#Win_SNDWin,#Gad_Scope,DragX,Drag\Trig)
                Dragging = #DragTrig
              EndIf
             
              If Scope\VoltsCursorSwitch
                If (Not Dragging) And (Abs(DragY - Drag\Volts1Y) < #Twiddle) ; V1 cursor...
                  SetGadgetMouseXY(#Win_SNDWin,#Gad_Scope,DragX,Drag\Volts1Y)
                  Dragging = #DragVolts1
                EndIf
               
                If (Not Dragging) And (Abs(DragY - Drag\Volts2Y) < #Twiddle) ; V2 cursor...
                  SetGadgetMouseXY(#Win_SNDWin,#Gad_Scope,DragX,Drag\Volts2Y)
                  Dragging = #DragVolts2
                EndIf
              EndIf             
             
              If Scope\TraceASwitch                         
                If (Not Dragging) And Abs(DragY - Scope\YPosA) < #Twiddle    ; If close to baseline for ChanA waveform...
                  SetGadgetMouseXY(#Win_SNDWin,#Gad_Scope,DragX,Scope\YPosA) ; snap mouse to the exact position...
                  Dragging = #DragChanAY                                    ; set flag specifying the cursor to be dragged.
                  Drag\Volts1Yb4 = Drag\Volts1Y - Scope\YPosA                ; Keep offset of Y datum from volts cursors so
                  Drag\Volts2Yb4 = Drag\Volts2Y - Scope\YPosA                ; they can be kept in sync...
                  Drag\TrigYb4   = Drag\Trig    - Scope\YPosA                ; and offset of the trigger line.
                EndIf
              EndIf
             
              If Scope\TraceBSwitch                         
                If (Not Dragging) And (Abs(DragY - Scope\YPosB) < #Twiddle)  ; ChanB channel...
                  SetGadgetMouseXY(#Win_SNDWin,#Gad_Scope,DragX,Scope\YPosB)
                  Dragging = #DragChanBY
                  Drag\Volts1Yb4 = Drag\Volts1Y - Scope\YPosB             
                  Drag\Volts2Yb4 = Drag\Volts2Y - Scope\YPosB
                  Drag\TrigYb4   = Drag\Trig    - Scope\YPosB
                EndIf
              EndIf
             
             
              If Scope\TimeCursorSwitch
                If (Not Dragging) And (Abs(DragX - Drag\Time1X) < #Twiddle) ; T1 cursor....
                  SetGadgetMouseXY(#Win_SNDWin,#Gad_Scope,Drag\Time1X,DragY)
                  Dragging = #DragTime1 
                EndIf
               
                If (Not Dragging) And (Abs(DragX - Drag\Time2X) < #Twiddle) ; T2 Cursor...
                  SetGadgetMouseXY(#Win_SNDWin,#Gad_Scope,Drag\Time2X,DragY)
                  Dragging = #DragTime2 
                EndIf
              EndIf
             
              If Dragging : beep_(5000,10) : EndIf
             
              ;}
            Case #PB_EventType_LeftButtonUp   ;{  FINISH dragging
              Dragging = #DragNone
              ;}
          EndSelect
          ;}
      EndSelect
      ;}
    Case #PB_Event_Menu                     ;{/ MENU ITEMS AND HOT-KEYS
      T = EventMenu()
      Select T
         
          ; MENU ITEMS
          ; ==========
          ; Input device selection
        Case #Men_InDevice1 To #Men_InDevice1 + 9
          For n = #Men_InDevice1 To #Men_InDevice1 + 9        ; Clear all 'ticks'
            SetMenuItemState(1,n,#False)
          Next
          SetMenuItemState(1,T,#True)                         ; Set 'tick' to match selection
          DevIn = T - #Men_InDevice1 + 1                      ; Calc new number number
          StopSoundInput()                                    ; Close current input device
          StartSoundInput()                                   ; Open New input device
         
          ; Output device selection
        Case #Men_OutDevice1 To #Men_OutDevice1 + 9
          For n = #Men_OutDevice1 To #Men_OutDevice1 + 9     
            SetMenuItemState(1,n,#False)
          Next
          SetMenuItemState(1,T,#True)
          DevOut = T - #Men_OutDevice1 + 1 
          StopSoundOutput()       
          StartSoundOutput()   
         
          ; HOT KEYS
          ; ========
        Case #Key_CntrlC
          beep_(5000,10)
          ClearClipboard()
          SetClipboardImage(Scope\Copy)
         
        Case #Key_CntrlP
          ; Make a copy of the screen image and add som annotation
          Copy2 = CreateImage(#PB_Any,ImageWidth(Scope\Copy), 2*ImageHeight(Scope\Copy))
          LoadFont(1,"Courier New",12)
          StartDrawing(ImageOutput(Copy2))
            DrawingFont(FontID(1))
            Box(0,0,ImageWidth(Copy2),ImageHeight(Copy2),#White) ; Make the backdrop white
            FrontColor(#Black)
            DrawImage(ImageID(Scope\Copy),0,25)                  ; Draw the 'scope display,                           
            DrawingMode(#PB_2DDrawing_Transparent)
            k$ = "AGMO Rev 2.3"
            dx = (ImageWidth(Copy2)-TextWidth(k$))/2
            DrawText(dx,2,k$)
            py = ImageHeight(Scope\Copy) + 35
            DrawText(20,py,"Fred")                               ; and some info...
          StopDrawing()
          FreeFont(1)
         
          ; Choose printer
          If PrintRequester()
            If StartPrinting("G8CDD")
              If StartDrawing(PrinterOutput())
                  DrawingMode(#PB_2DDrawing_Transparent)
                  DrawImage(ImageID(Copy2),0,330,PrinterPageWidth(),PrinterPageWidth())
                StopDrawing()
              EndIf
              StopPrinting()
            EndIf
          EndIf
          FreeImage(Copy2)
         
        Case #Key_CntrlS ; Save the 'scope image as a BMP or JPG file.
          beep_(5000,10)
          Copy2 = CopyImage(Scope\Copy,#PB_Any) ; Copy the image
         
          ; Path to working directory
          If Not Len(SavePathPart$)
            SavePathPart$="C:\Temp\"
          EndIf
         
          ; Ask user to provide the file name, ensure it has the proper extension and save the image
          File$ = SaveFileRequester("Provide the filename for the image save (BMP/JPG)",SavePathPart$,"Bitmap File (*.bmp)|*.bmp|JPEG File (*.jpg)|*.jpg",0)
          If File$
            k$=UCase(Right(File$,4))
            If k$<>".BMP" And k$<>".JPG"
              File$ + StringField(".BMP|.JPG",SelectedFilePattern()+1,"|")
            EndIf
            SavePathPart$ = GetPathPart(File$)
            SaveImage(Copy2,File$,#PB_ImagePlugin_BMP)
          EndIf
         
          FreeImage(Copy2) ; Get rid of the copy
      EndSelect
      ;}
  EndSelect
 
  Select EventType()
      ; Frequency / Phase offset StringGadget()
    Case #PB_EventType_Change                                 ; If gadget contents changed,
      If EventGadget() = #Gad_LockOfs                         ; and offsets are enables,
        LockOffset = Val(GetGadgetText(#Gad_LockOfs))         ; get the current offset value.
      EndIf
  EndSelect
Until Finish
;}
;{- PackUpFallOut
StopSoundOutput()
StopSoundInput()
;}
End

Procedure WinCallback(hwnd, uMsg, wParam, lParam)            ;- Window callback to service scrollbar and sound output message
  Static T
  Static *PriorCopyPointer


  Select uMsg
    Case #WM_HSCROLL            ;{ Two frequency, two amplitude controls and trigger adjustments
      Select lParam
        Case GadgetID(#Gad_FreqL)    : ChanA\Frequency = GetGadgetState(#Gad_FreqL)                  ; Frequency value directly from control
        Case GadgetID(#Gad_FreqR)    : ChanB\Frequency = GetGadgetState(#Gad_FreqR)
        ;Case GadgetID(#Gad_VolumeL)  : ChanA\Volume    = GetGadgetState(#Gad_VolumeL) / 100          ; Scale volume over range 0 to 1. FLOAT
        ;Case GadgetID(#Gad_VolumeR)  : ChanB\Volume    = GetGadgetState(#Gad_VolumeR) / 100
        Case GadgetID(#Gad_SS_Offset): Scope\SSPosSlider = ((GetGadgetState(#Gad_SS_Offset)-4095)*4) ; Position the display origin.
      EndSelect
      ;}
    Case #MM_WOM_DONE           ;{ Sound output, a play buffer has been returned.
      


*hWaveO.WAVEHDR = lParam                        ; lParam has the address of WAVEHDR
      *P    = *hWaveO\lpData                          ; Where to write NEW data
      CalcWave(*P,BlockSize>>2)                       ; ?/4 one WORD each ChanA and ChanB
      *hWaveO\dwBytesRecorded = BlockSize             ; Number of bytes written into buffer
      waveOutWrite_(hWaveOut,lParam, SizeOf(WAVEHDR)) ; Send to sound device => jack socket => cable =>
     
      If Scope\ShowInOut
        Scope\DisplayP     = *P
        Scope\DisplayStart = *P
        Scope\DisplayEnd   = *P+BlockSize-1
        ;Scope\DisplayTrig  = #True
      EndIf
      ;}
    Case #MM_WIM_DATA           ;{ Sound input.
      *hWaveIn.WAVEHDR = lParam                       
      *P = *hWaveIn\lpData
      waveInAddBuffer_(wParam, lParam, SizeOf(WAVEHDR))
     
      ; Trace Freeze management
      If Scope\ShowInOut = 0                         ; If showing the INPUT...
       
        If Scope\SSFired = #True                     ; and single shot mode has fired (Set in ScopeRefresh())...
          ; Update 'frozen' display. (Done repeatidly so cursors etc move)
          T =  Scope\SSOffset +  Scope\SSPosSlider
          If T < 0 : T = Scope\SSBufSize + T : EndIf ; +T?  Yes, T is -ve
          Scope\DisplayP     = Scope\SSBuffer + T
          Scope\DisplayStart = Scope\SSBuffer
          Scope\DisplayEnd   = Scope\SSBufferEnd
        Else 
          ; Not 'fired', so we continue to store incoming data in the circular buffer
          *Q = Scope\SSBuffer + (*P - *InBufMem)     ; Calculate position to save data and...
          CopyMemory(*P,*Q, BlockSize)               ; copy input buffer to circular store,
          Scope\DisplayP     = *P
          Scope\DisplayStart = *P
          Scope\DisplayEnd   = *P + BlockSize
         
          ; Keep storing data AFTER a trigger for half of the memory size
          ; (Scope\DisplayTrig is only set by single shot trigger event.)
          If Scope\DisplayTrig                       ; Triggered and not counted down half the buffers...
            Debug Scope\DisplayTrig
            Scope\DisplayTrig - 1                    ; decrement counter.
            If Scope\DisplayTrig < 1                 ; When all done,
              Scope\SSFired    = #True               ; set flag (which stops data capture),                   
              Scope\SSArm      = #False              ; disarm the trigger
              SetGadgetState(#Gad_SSArm,#False)      ; and set the 'Arm' button to match.
            EndIf
          EndIf
        EndIf
      EndIf
      ;}
    Case #WM_MOUSEWHEEL         ;{ MOUSE wheel, while mouse is over display
      MinDec = 1
      With Scope
        If \TriggerMode = #TraceA Or \TriggerMode = #TraceB Or \TriggerMode = #FreeRun : MinDec = 16 : EndIf
      EndWith
      Mx.w = WindowMouseX(#Win_SNDWin)
      My.w = WindowMouseY(#Win_SNDWin)
      If Mx>GadgetX(#Gad_Scope) And Mx<GadgetX(#Gad_Scope)+GadgetWidth(#Gad_Scope)
        With Scope
          If My>GadgetY(#Gad_Scope) And My<GadgetY(#Gad_Scope)+GadgetHeight(#Gad_Scope)
            If wParam>0 ; Increase
              \Mag << 1
              If \Mag > 256
                \Mag = 256
              Else
                beep_(5000,10)
                ShowCursorReport()
              EndIf
            Else        ; Decrease
              \Mag >> 1
              If \Mag < MinDec
                \Mag = MinDec
              Else
                beep_(5000,10)
                ShowCursorReport()
              EndIf
            EndIf
          EndIf
        EndWith
      EndIf
      ;}
  EndSelect
  ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure
Procedure ScopeRefresh(*z)  ;- Redraw the oscilloscope display
  EnableExplicit
  Protected LastVa.w, LastVb.w, STrig,Va.w, Vb.w,Ya.w,Yb.w,X,Y,n,vt.w, T,Sx.f, dx.f,*Bl
  Protected OldX.f, OldYa, OldYb
  Static CrsRep$,k$
  Protected Counter
  Protected *DisplayP, *DisplayStart, *DisplayEnd
 
  Delay(500) ; Wait for first time data to be ready
 
  Repeat



   
    ; Get date to be displayed
    *DisplayP     = Scope\DisplayP      ; Display to start from this address
    *DisplayStart = Scope\DisplayStart  ; Beginning of buffer, needed if we wrap round.
    *DisplayEnd   = Scope\DisplayEnd    ; End of buffer
   
    ; Derive Trigger level from the Trigger line and the current Trace datum.
    Select Scope\TriggerMode
      Case #TraceA,#SingleShotA : vt = (Scope\YPosA - Drag\Trig)<<8
      Case #TraceB,#SingleShotB : vt = (Scope\YPosB - Drag\Trig)<<8
    EndSelect
    STrig = 0                                               ; Default, do NOT sweep this time...
 
  ;{ Adjust co-ords of any feature being dragged..
  If Dragging
    Y = WindowMouseY(#Win_SNDWin) - GadgetY(#Gad_Scope)
    X = WindowMouseX(#Win_SNDWin) - GadgetX(#Gad_Scope)
    Select Dragging
      Case #DragChanAY
        Scope\YPosA   = Y                                 ; Red trace base line
        If Scope\TriggerMode = #TraceA
          Drag\Volts1Y = Y + Drag\Volts1Yb4
          Drag\Volts2Y = Y + Drag\Volts2Yb4
          Drag\Trig    = Y + Drag\TrigYb4
        EndIf
       
      Case #DragChanBY
        Scope\YPosB   = Y                                 ; ... Green
        If Scope\TriggerMode = #TraceB
          Drag\Volts1Y = Y + Drag\Volts1Yb4
          Drag\Volts2Y = Y + Drag\Volts2Yb4
          Drag\Trig    = Y + Drag\TrigYb4
        EndIf
       
      Case #DragTime1  : Drag\Time1X = X : STrig = #True  ; Time cursor T1
      Case #DragTime2  : Drag\Time2X = X : STrig = #True  ; ... T2
      Case #DragVolts1 : Drag\Volts1Y= Y : STrig = #True  ; Volts cursor V1
      Case #DragVolts2 : Drag\Volts2Y= Y : STrig = #True  ; ... V2
      Case #DragTrig   : Drag\Trig   = Y                  ; Trigger level
        STrig = #True
        Drag\TrigLineTimer = ElapsedMilliseconds() + 5000 ; Start Trig reference line timer
    EndSelect
    ShowCursorReport()                                    ; Update the report text
  EndIf
  ;}
 
  ; Free a previous Image() and make new copy of 'scope backdrop
  If Scope\Copy :  FreeImage(Scope\Copy) : Scope\Copy = 0 : EndIf
  Scope\Copy = CopyImage(ScopeImage,#PB_Any)     
 
  StartDrawing(ImageOutput(Scope\Copy))          ; Draw on the copy
    ;{ Draw cursor lines and annotation
    ; Show the two channel frequencies
    DrawText(8,8,Str(ChanA\Frequency)+" Hz",#Red)
    DrawText(450,8,Str(ChanB\Frequency)+" Hz",#Green)
   
    ; X Scale
    If Scope\Mag = 16
      k$ = "X : 1 mSec/div"
    ElseIf Scope\Mag > 16
      k$ = "X : 1/"+Str(Scope\Mag >> 4) + " mSec/div"
    Else
      k$ = "X : "+Str(16/Scope\Mag)  + " mSec/div"
    EndIf
    DrawText(8,Scope\height - 18,k$)
   
    ; Draw the base lines (Zero volts) for each trace... dotted lines
    If Scope\TraceASwitch : DrawImage(ImageID(ChanA\DatumImage),0,Scope\YPosA): EndIf
    If Scope\TraceBSwitch : DrawImage(ImageID(ChanB\DatumImage),0,Scope\YPosB): EndIf
   
    ; Draw the time and voltage cursors... dotted lines
    If  Scope\TimeCursorSwitch
      DrawImage(ImageID(CursorTime1),Drag\Time1X-1,0) : DrawText(Drag\Time1X+2,240,"T1")
      DrawImage(ImageID(CursorTime2),Drag\Time2X-1,0) : DrawText(Drag\Time2X+2,240,"T2")
    EndIf
   
    If Scope\VoltsCursorSwitch
      DrawImage(ImageID(CursorV1),0,Drag\Volts1Y)     : DrawText(490,Drag\Volts1Y-18,"V1")
      DrawImage(ImageID(CursorV2),0,Drag\Volts2Y)     : DrawText(490,Drag\Volts2Y-18,"V2")
    EndIf
   
    ; Trigger level cursor / reminder blob
    If ElapsedMilliseconds() < Drag\TrigLineTimer
      DrawImage(ImageID(CursorTrig),0,Drag\Trig)      : DrawText(480,Drag\Trig - 18,"Trig",#Yellow)
    Else
      Circle(0,Drag\Trig,2,#Yellow)
    EndIf
    ;}
    ;{ SS trigger marker.... Blue line
    *Bl = 0
    If Scope\SSFired
      *Bl = Scope\SSOffset + Scope\SSBuffer - 4
    EndIf
    ;}
   
    LastVa = $7FFF * Scope\TrigSense    ; Prime the 'scope trigger
    LastVb = $7FFF * Scope\TrigSense       
   
    T = ElapsedMilliseconds() + 20
    Sx = 0
    Repeat
     
      ; Display pointer rolled over...
      If *DisplayP >= *DisplayEnd
        *DisplayP = *DisplayStart
      EndIf
     
      ; Draw blue line
      If *DisplayP = *Bl
        LineXY(Sx,0,Sx,Scope\height,#Blue)
      EndIf
     
      ; Get the ChanA and ChanB signals
      Va = PeekW(*DisplayP) : *DisplayP + 2             
      Vb = PeekW(*DisplayP) : *DisplayP + 2
     
      ; TRIGGER: Detect signal crossing the trigger level....
      Select Scope\TriggerMode
        Case #FreeRun                                      ;{ Not waiting for trigger...
          STrig = #True                                    ;}
        Case #TraceA                                       ;{ Channel 'A' trigger
          If Scope\TrigSense = 1
            If (LastVa <= vt) And (Va > vt)                ; Moved +ve though trigger level...
              STrig = #True                                ; Set trigger flag...
            EndIf                   
          Else
            If (LastVa >= vt) And (Va < vt)                ; Moved -ve though trigger level...
              STrig = #True                                ; Set trigger flag...
            EndIf             
          EndIf
          LastVa = Va                                      ;} Keep current 'A' value
        Case #TraceB                                       ;{ Channel 'B' trigger
          If Scope\TrigSense = 1
            If (LastVb <= vt) And (Vb > vt)
              STrig = #True               
            EndIf                               
          Else
            If (LastVb >= vt) And (Vb < vt)
              STrig = #True
            EndIf 
          EndIf
          LastVb = Vb                                      ;}               
        Case #SingleShotA                                  ;{ Waiting for 'A' storage trigger event...
          If Scope\SSArm And (Scope\SSFired = #False)     
            If (LastVa <= vt) And (Va > vt)               
              If Not Scope\DisplayTrig                     ; If run-on buffer counter NOT primed...
                Scope\SSOffset = *DisplayP - *InBufMem - 4 ; keep OFFSET of trigger event (from start of buffer memory),
                Scope\DisplayTrig = (nBuf>>1)-1            ; set counter of buffers to run-on on before stopping.
              EndIf
            EndIf
            LastVa = Va
          Else
            STrig = #True
          EndIf
          ;}
        Case #SingleShotB                                  ;{...'B'
          If Scope\SSArm And (Scope\SSFired = #False) 
            If (LastVb <= vt) And (Vb > vt)
              If Not Scope\DisplayTrig                   
                Scope\SSOffset = *DisplayP - *InBufMem - 4
                Scope\DisplayTrig = (nBuf>>1)-1         
              EndIf
            EndIf
            LastVb = Vb
          Else
            STrig = #True
          EndIf
          ;}
      EndSelect
     
      ;{ Plot display points following trigger event
      If STrig
        If (Sx < Scope\width)
          Ya = (Int(Va)>>8)*Scope\SenseA               ; ChanA waveform
          Yb = (Int(Vb)>>8)*Scope\SenseB               ; ChanB
          If Scope\APlusB                              ; If A=A+B...
            Ya + Yb           
          EndIf
          Yb + Scope\YPosB                             ; Offset the ChanA waveform
          Ya + Scope\YPosA
          If Sx > 0                                    ; After the first point...
            If Scope\TraceASwitch                      ; If showing Channel A...
              LineXY(OldX,OldYa,Sx,Ya,#Red)            ; join to previous point
            EndIf
            If Scope\TraceBSwitch                      ; If showing channel B...
              LineXY(OldX,OldYb,Sx,Yb,#Green)
            EndIf
          EndIf
         
          ; Keep values to become the previous, next time
          OldX  = Sx
          OldYa = Ya
          OldYb = Yb
         
          ; Increment X position
          dx = (Scope\Mag / 16)
          Sx + dx
        EndIf
      EndIf
       ;}
     
    Until (Sx > 511) Or ElapsedMilliseconds() > T
   
  StopDrawing()
 
  ; Update the display
  StartDrawing(CanvasOutput(#Gad_Scope))   
    DrawImage(ImageID(Scope\Copy),0,0)
  StopDrawing()
 
  Delay(5) ; Prevent resource hogging
 
ForEver

DisableExplicit
EndProcedure
Procedure StartSoundOutput()
  Protected T,i, *P
  Static *OutBufMem
 
  With PlayFormat
    \wFormatTag      = #WAVE_FORMAT_PCM
    \nChannels       = 2
    \wBitsPerSample  = 16
    \nSamplesPerSec  = SampleClock
    \nBlockAlign     = (\nChannels * \wBitsPerSample)/8 ; = 4
    \nAvgBytesPerSec = \nSamplesPerSec * \nBlockAlign   ; = 176400
  EndWith
 
  If *OutBufMem : FreeMemory(*OutBufMem) : EndIf                       ; Free a prior assignement
  *OutBufMem = AllocateMemory(BlockSize * nBuf)                        ; Reserve memory for all the buffers
 
  T =  waveOutOpen_(@hWaveOut, #WAVE_MAPPER+DevOut, @PlayFormat, WindowID(#Win_SNDWin), #True, #CALLBACK_WINDOW | #WAVE_FORMAT_DIRECT)
  If T = #MMSYSERR_NOERROR
   
    ; NOTE:  'n' contiguous buffers are more convenient to debug with.
    *P = *OutBufMem                                                    ; Pointer to start of memory
    For i = 0 To nBuf-1                                                ; For each buffer...
      outHdr(i)\lpData         = *P                                    ; start of buffer
      outHdr(i)\dwBufferLength = BlockSize                             ; size of buffer
      outHdr(i)\dwFlags        = 0
      outHdr(i)\dwLoops        = 0
      T | waveOutPrepareHeader_(hWaveOut, outHdr(i), SizeOf(WAVEHDR)) 
      *P + BlockSize
    Next
   
    For i = 0 To nBuf-1
      PostMessage_(WindowID(#Win_SNDWin),#MM_WOM_DONE,0,outHdr(i))
    Next
   
  EndIf
 
  ProcedureReturn T
 
EndProcedure
Procedure StopSoundOutput()
  WaveOutReset_(hWaveOut)
  For i = 0 To nBuf - 1
    waveOutUnprepareHeader_(hWaveOut, outHdr(i), SizeOf(WAVEHDR))
  Next
  waveOutClose_(hWaveOut)
EndProcedure
Procedure StartSoundInput()
  Protected T, i, *P
 
  ; Could use PlayFormat... but they could be changed separately.
  With RecFormat
    \wFormatTag      = #WAVE_FORMAT_PCM
    \nChannels       = 2
    \wBitsPerSample  = 16
    \nSamplesPerSec  = SampleClock                     ; = 44100
    \nBlockAlign     = (\nChannels * \wBitsPerSample)/8 ; = 4
    \nAvgBytesPerSec = \nSamplesPerSec * \nBlockAlign   ; = 176400
  EndWith
 
  ; Memory for incoming data blocks
  If *InBufMem : FreeMemory(*InBufMem) : EndIf
  *InBufMem = AllocateMemory(BlockSize * nBuf)        ; Reserve memory for ALL the input buffers
  *InBufMemEnd = *InBufMem + (BlockSize * nBuf) - 1
 
  ; Buffer for freeze store
  With Scope
    If \SSBuffer : FreeMemory(\SSBuffer) : EndIf
    \SSBufSize = BlockSize * nBuf
    \SSBuffer = AllocateMemory(\SSBufSize)
    \SSBufferEnd = \SSBuffer + \SSBufSize - 1
  EndWith
 
  T =  waveInOpen_(@hWaveIn, #WAVE_MAPPER+DevIn, @RecFormat,  WindowID(#Win_SNDWin), #Null, #CALLBACK_WINDOW | #WAVE_FORMAT_DIRECT)
  If T = #MMSYSERR_NOERROR
    *P = *InBufMem
    For i = 0 To nBuf-1
      inHdr(i)\lpData         = *P
      inHdr(i)\dwBufferLength = BlockSize
      T | waveInPrepareHeader_(hWaveIn, inHdr(i), SizeOf(WAVEHDR)) ; Note: inHdr(i) returns pointer to the i'th structure... not contents of first element
      T | waveInAddBuffer_(hWaveIn, inHdr(i), SizeOf(WAVEHDR))
      *P + BlockSize
    Next
   
    If waveInStart_(hWaveIn) = #MMSYSERR_NOERROR
      ;SetTimer_(WindowID(#Win_SNDWin), 42, 5, 0); Why?
    EndIf
   
  EndIf
  ProcedureReturn T
EndProcedure
Procedure StopSoundInput()
  WaveInReset_(hWaveIn)
  For i = 0 To nBuf - 1
    waveInUnprepareHeader_(hWaveIn, inHdr(i), SizeOf(WAVEHDR))
  Next
  waveInClose_(hWaveIn)
EndProcedure
Procedure ShowCursorReport()
 
  Protected CrsRep$ = "" ,f.f
 
  If  Scope\TimeCursorSwitch
   
    f =  Scope\PixPermSec * (Scope\Mag/16)
   
    CrsRep$ + "T1=" + RSet(StrF(Drag\Time1X / f, 2),5," ") + " "
    CrsRep$ + "T2=" + RSet(StrF(Drag\Time2X / f, 2),5," ") + " "
    CrsRep$ + "dT=" + RSet(StrF((Drag\Time2X-Drag\Time1X) / f, 2),5," ") + " mSec" + Chr(10)
  EndIf
 
  If Scope\VoltsCursorSwitch
   
    ; Select the reference location for the voltage cursors
    Select Scope\VoltRefIndex      ; 1 => 4  = "ChanA|ChanB|Bottom|Centre|"
      Case 1 : T = Scope\YPosA
      Case 2 : T = Scope\YPosB
      Case 3 : T = Scope\height
      Case 4 : T = Scope\height /2
    EndSelect
   
    ; Volts cursors...
    CrsRep$ + "V1=" + RSet(StrF((T - Drag\Volts1Y) / Scope\PixPerVolt,2),5," ") + " "
    CrsRep$ + "V2=" + RSet(StrF((T - Drag\Volts2Y) / Scope\PixPerVolt,2),5," ") + " "
    CrsRep$ + "dV=" + RSet(StrF((Drag\Volts2Y-Drag\Volts1Y) / Scope\PixPerVolt,2),5," ")
   
  EndIf
  SetGadgetText(#Gad_CursResults,CrsRep$)
 
EndProcedure
Procedure CalcWave(*SBuf,nSamples)                           ;- Calculate the waveform for ChanA / ChanB channels
  ; This routine  generates ChanA and ChanB waveforms.
  ; Both channels are phase continuous between multiple calls.
  ; ChanA and ChanB samples are interleaved and each sample is a WORD value. (L.w, R.w, L.w, R.w, L.w, R.w.... etc
  ; The receiving buffer MUST have a length that is a MULTIPLE of 4
 
  Static Angle.f,Vl.f,Vr.f,Kl.f,Kr.f
  Protected *P, sample
  ;{ Calculate the frequency scaling factors -
  Kl = ChanA\Frequency / ((SampleClock)/#PIx2)
  If LockLR                                  ; If channels are locked together, there are two lock modes...
    If DoDF                                  ; (1) With a user specified frequency offset...
      ChanB\Frequency = ChanA\Frequency + LockOffset
    Else                                     ; (2) With a phase offest...
      ChanB\Frequency = ChanA\Frequency
      Ra = La + ((LockOffset/360) * #PIx2)
    EndIf
   
  EndIf
  Kr = ChanB\Frequency /((SampleClock)/#PIx2)
  ;}
  ;{ Generate waveform data
  *P = *SBuf

  For sample = 0 To nSamples-1
   
    ; Derive ChanA channel waveform points.
    If ChanA\Switch                                           ; If ChanA channel is switched ON...
      Select ChanA\WaveForm
        Case 0 
;----------------------------------------------------------------------------------------------------------------------
 Vl = Sin(La+(sin(lk))) * (32767) * ChanA\Volume
if lk > 32767
lk=0
endif
;----------------------------------------------------------------------------------------------------------------------



          ; SineWave
        Case 1 : Vl = 32767 * ChanA\Volume                    ; Square
          If La > #PI : Vl = -Vl : EndIf
        Case 2 : Vl = 32767 * ChanA\Volume * (La-#PI)/#PI     ; Sawtooth
        Case 3 : Vl = (Random(65535)-32768) *  ChanA\Volume   ; Noise
        Case 4                                               ; WAV file ~ Temporary
          If hWAV And Not Eof(hWAV)
            Vl = ReadWord(hWAV) *  ChanA\Volume
          Else
            If hWAV : CloseFile(hWAV) : hWAV = 0 : EndIf
            Vl = 0
          EndIf
      EndSelect
      La + Kl                                                ; Calculate angle for next time
      If La > #PIx2 : La - #PIx2 : EndIf                     ; limit to 2*PI radians
    Else                                                     ; Not ON so point is zero
      Vl = 0
    EndIf
    PokeW(*P,Vl)                                             ; Put point in buffer
    *P + BytesPerSample                                      ; move buffer pointer to next ChanB sample
   
    ; Derive ChanB channel waveform point
    If ChanB\Switch
      Select ChanB\WaveForm
;----------------------------------------------------------------------------------------------------------------------
        Case 0 : Vr = Sin(Ra+(-cos(lk))) * 32767 * ChanB\Volume     

;----------------------------------------------------------------------------------------------------------------------    
        Case 1 : Vr = 32767 * ChanB\Volume
          If Ra > #PI : Vr = -Vr : EndIf                     
        Case 2 : Vr = 32767 * ChanB\Volume * (Ra-#PI)/#PI   
        Case 3 : Vr = (Random(65535)-32768) *  ChanB\Volume 
        Case 4 : ; Nothing! 
      EndSelect
      Ra + Kr
      If Ra > #PIx2 : Ra - #PIx2 : EndIf
    Else
      Vr = 0
    EndIf
    PokeW(*P,Vr)
    *P + BytesPerSample

;----------------------------------------------------------------------------------------------------------------------
   lk +0.006
;----------------------------------------------------------------------------------------------------------------------
  Next
  ;}
 




EndProcedure
Procedure SetGadgetMouseXY(Win,Gadget,Mx,My,flags=%111)      ;- Set mouse position over specified gadget
  Static OldWin = -1
  Static XFix
  Static YFix
 
  ; Flags : Bitwise indicators of the presence of a BORDER, TITLE and MENUBAR
  ; All default to TRUE.  (%BMT)
 
  ; Get the corrections needed to take account of borders, titles and menus
  If Win <> OldWin
    YFix = 0
    If flags & %001 : YFix + GetSystemMetrics_(#SM_CYCAPTION)    : EndIf
    If flags & %010 : YFix + GetSystemMetrics_(#SM_CYMENU)       : EndIf
    If flags & %100 : YFix + GetSystemMetrics_(#SM_CYFIXEDFRAME) : EndIf
   
    XFix = 0
    If flags & %100 : XFix + GetSystemMetrics_(#SM_CXFIXEDFRAME) : EndIf
   
    OldWin = Win
  EndIf
 
  Mx + WindowX(Win)+GadgetX(Gadget) + XFix
  My + WindowY(Win)+GadgetY(Gadget) + YFix     
  SetCursorPos_(Mx,My)
 
EndProcedure
Procedure BuildScopeBackdrop(Bn)                             ;- Create the backdrop for the 'scope with 1mSec grid lines

  If IsImage(ScopeImage) : FreeImage(ScopeImage) : EndIf
 
ScopeImage = CreateImage(#PB_Any,Scope\width,Scope\height)
Scope\PixPermSec =  Scope\width /(1000*BlockSize/(SampleClock*8)) ; Pixels per mSec

StartDrawing(ImageOutput(ScopeImage))
  FrontColor(RGB($70,$28,$A))
  f.f = Scope\PixPermSec
  X.f = 0
  While X <Scope\width
    LineXY(X,0,X,Scope\height-1)
    X +  Scope\PixPermSec
  Wend
 
  Y.f = 0  ; Horizontal lines are purely cosmetic at present!
  T = Scope\height/2
  While Y < T
    LineXY(0,128-Y,Scope\width,Scope\height/2-Y)
    LineXY(0,128+Y,Scope\width,T+Y)
    Y +  Scope\PixPermSec
  Wend
  Scope\YPosB = Int(T + f.f)
  Scope\YPosA = Int(T - f.f)
StopDrawing()

EndProcedure 
RichardL
Enthusiast
Enthusiast
Posts: 532
Joined: Sat Sep 11, 2004 11:54 am
Location: UK

Re: Rev2.4 May 16 2013 - Stereo Sound Generator & Monitor S

Post by RichardL »

Stereo Sound Generator & Monitor Scope Rev 2.5
Friday 24th May 2013

The wedding went well and the follow up beer quality checks were all OK (is that something to do with my brother being a brewer? :lol: ).
I have been very busy this week and had only a little time to carry this low priority :( project forward.

- The whole display area can now be dragged after a waveform has been captured.
- The mouse wheel expands and contracts the horizontal axis... even more so when showing a captured waveform.
- Used in combination these two features make it possible to examine the leading edge of a transient event and then the overall envelope, plus what happened before and after.
- Trigger event line changed from blue to cyan.

Next week... maybe...
There is a problem that needs fixing with the trigger detection/display. Sometimes the trigger event line shows in the wrong place.

@Simo_na
Very pretty... why not modify the waveform generator to accept a flag so the 'A' channel can have FM, AM and PM using channel 'B' as the modulation source?

New version (Rev 2.5) will appear in the first posting in a short while.

Have a good weekend,
RichardL
infratec
Always Here
Always Here
Posts: 7592
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Stereo Sound Generator & Monitor Scope Rev2.5 24 May 201

Post by infratec »

Hi Richard,

a new bug report from me (V2.5):

1. Start the program inside of the IDE
2. Switch 'Trig Source' to 'Single shot A'
3. Toggle a few times the 'Am S/shot' checkbox

This results in
StopDrawing() has to be called before StartDrawing()
Bernd
RichardL
Enthusiast
Enthusiast
Posts: 532
Joined: Sat Sep 11, 2004 11:54 am
Location: UK

Re: Stereo Sound Generator & Monitor Scope Rev2.5 24 May 201

Post by RichardL »

Hi Infratec,
I tried this several times and almost wore out my mouse key finger on the 'Am S/shot' checkbox!
I checked with PB4.6 and 5.11, still no problem. :?
Are you running with ThreadSafe enabled?
Richard
infratec
Always Here
Always Here
Posts: 7592
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Stereo Sound Generator & Monitor Scope Rev2.5 24 May 201

Post by infratec »

Hi Richard,

sorry for your time :!:

Please insert this in your code:

Code: Select all

CompilerIf Not #PB_Compiler_Thread
   MessageRequester("WARNING!", "Set compiler options: 'Create threadsafe executable'")
   End
CompilerEndIf
I simply forgot to remember that you use threads.

Bernd
Simo_na
Enthusiast
Enthusiast
Posts: 177
Joined: Sun Mar 03, 2013 9:01 am

Re: Rev2.4 May 16 2013 - Stereo Sound Generator & Monitor S

Post by Simo_na »

RichardL wrote: @Simo_na
Very pretty... why not modify the waveform generator to accept a flag so the 'A' channel can have FM, AM and PM using channel 'B' as the modulation source?

RichardL
like 'side chain' ?

Have a good weekend ;)
RichardL
Enthusiast
Enthusiast
Posts: 532
Joined: Sat Sep 11, 2004 11:54 am
Location: UK

Re: Stereo Sound Generator & Monitor Scope Rev2.8 08 July 20

Post by RichardL »

Good morning folks,

An update... [see the first post for Rev 2.8 ]
I have changed the signal generator section to use the improved KnobGadget(), also to be found in Tricks'n'Tips.
The frequency settings now use the multi-turn option to provide a smoother change than a slider or single turn knob.
Download the KnobGadgets library and save it as "KnobGadgetDev06.pbi" in the same directory as the SSG&MS source.
At the same time I have added a pair of StringGadget() to allow direct entry of the two channel frequencies.
I've also made a few minor changes and corrections.

Hope you like it.
RichardL
Simo_na
Enthusiast
Enthusiast
Posts: 177
Joined: Sun Mar 03, 2013 9:01 am

Re: Stereo Sound Generator & Monitor Scope Rev2.7 23 June 20

Post by Simo_na »

now the code doesn't work, not with the last release of PB :oops:
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5494
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Stereo Sound Generator & Monitor Scope Rev2.7 23 June 20

Post by Kwai chang caine »

What a madness work !!! :shock:
Works very good, thanks for sharing 8)
ImageThe happiness is a road...
Not a destination
SeregaZ
Enthusiast
Enthusiast
Posts: 628
Joined: Fri Feb 20, 2009 9:24 am
Location: Almaty (Kazakhstan. not Borat, but Triple G)
Contact:

Re: Stereo Sound Generator & Monitor Scope Rev2.7 23 June 20

Post by SeregaZ »

this topic is a live? can you make a little help with making PB version of emu of SN76489?

i am not very well know english, and less i am understand tehnical english. by digging some source i open some America for my self for making sound. it is a big step for me, but i cant understand how to move far. i have some .as resourses and try to convert, but hear only some scratch... and i use bass.dll. but i can see you made sound without bass.

i try to plug code into your generators, but sounds a little another. and how to correct plug noise channel? for magic set one for 0 channel and second for 1 channel :)

noise - 3 channel, but original have frequency for noise, you have always same noise :( or it just old version of generator.

i copy .as sourse and my scratching trying convert it. can you help making correct sound, as swf file play in browser? and how to mix all 4 channel?

https://www.dropbox.com/s/jua4xhhs09mi0 ... s.zip?dl=1



a lot of unanderstandible explanations here: http://www.smspower.org/Development/SN76489


i cant finish all project :(((
Post Reply