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

Share your advanced PureBasic knowledge/code with the community.
RichardL
Enthusiast
Enthusiast
Posts: 532
Joined: Sat Sep 11, 2004 11:54 am
Location: UK

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

Post by RichardL »

Hi Folks,
My previous attempt to create a reliable tone generator foundered on my ill advised attempt to coerce the underlying code behind PlaySound() to cooperate with my code. I found this did not work reliably on all the computers I have access to for testing. A nice try, but no cigar!

http://www.purebasic.fr/english/viewtop ... 12&t=54421

Because at the moment I need the tone generator more than having it work using DirectX I wrote the following replacement using the rather older set of Waveform Audio sound functions.

http://msdn.microsoft.com/en-gb/library ... s.85).aspx

Rev2.7 - Now uses a KnobGadget().... see the 'Useful references' section.
Rev2.8 - Improved frequency entry with multi-turn knob or direct entry via keyboard
All the best
RichardL

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.6 - 25th May 2013
; Improved : Single shot trigger .... STILL NOT PERFECT

; Rev 2.7 - (1238)
; Uses KnobGadget.pbi ... see 'Useful References' for source.

; Rev 2.8 - (1277)
; Added StringGadget()s for direct entry of frequencies. (For Slim)
; Freq and Offset StringGadgets() now read after Return is pressed

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

CompilerIf Not #PB_Compiler_Thread
  MessageRequester("WARNING!", "Set compiler options: 'Create threadsafe executable'")
  End
CompilerEndIf

XIncludeFile "KnobGadgetDev06.pbi"

;{- Useful references 
; http://www.purebasic.fr/english/viewtopic.php?f=12&t=54793               ; KnobGadgets are here!
; 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 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_UserFA
  #Gad_UserFB
  
  ; KnobGadgets... Group together
  #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
  #Key_Return
EndEnumeration
Enumeration  ; Things that might be dragged on the 'scope display
  #DragNone
  #DragChanAY
  #DragChanBY
  #DragTime1
  #DragTime2
  #DragVolts1
  #DragVolts2
  #DragTrig
  #DragScreen
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

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
  
  ScreenDragX.i
  OldDragX.i
  ScreenDragTotal.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 = 0
  \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
  ModMode.i
  ModDisc$
EndStructure
Global ChanA.Waves
Global ChanB.Waves

Enumeration
  #ModOff
  #ModAM 
  #ModFM 
EndEnumeration

; Initial signal generator frequencies etc
With ChanA                ; 300 Hz Sinewave on ChanA, On
  \Frequency = 300
  \Volume    = 0.3
  \Switch    = #True
  \WaveForm  = 0
  \ModMode   = #ModFM 
  \ModDisc$  = "CW|AM|FM|"
EndWith
With ChanB
  \Frequency = 2401      ; 2401 Hz Sinewave on ChanB, On
  \Volume    = 0.1
  \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 = 580
;}
;{- 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 = CreateImage(#PB_Any,Scope\width,1)
StartDrawing(ImageOutput(CursorV2))
  For n = 5 To Scope\width-6 Step 10
    For m = n To n+5 
      Plot(m,0,#White)
    Next
  Next
StopDrawing()

; 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.8  (C)2013 Richard Leman (G8CDD) ",#PB_Window_SystemMenu |#PB_Window_ScreenCentered)

; 'Scope 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,WinW-10,185," 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 + 45
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
SetKnobCaptionColour(#Black)

SetKnobDotColour(RGB(128,00,00))
SetKnobMultiTurn(10)
KnobGadget(#Gad_FreqL,   px,    py-10,  150,   "", 100,10000,#MULTITURN1 | #JAZZ_top)
TextGadget(#PB_Any,      px,    py+143,44,20,"Freq A",#PB_Text_Right)
StringGadget(#Gad_UserFA,px+52, py+141,46,20,"",#PB_String_Numeric)

KnobGadget(#Gad_VolumeL, px+320,py-5,100,"Level - A",0,100,#POTKNOB  | # JAZZ_top )

SetKnobDotColour(RGB(00,128,00))
KnobGadget(#Gad_FreqR,   px+155, py-10,150,"",100,10000,#MULTITURN1 | #JAZZ_top)
TextGadget(#PB_Any,      px+155, py+143,44,20,"Freq B",#PB_Text_Right)
StringGadget(#Gad_UserFB,px+212, py+141,46,20,"",#PB_String_Numeric)

KnobGadget(#Gad_VolumeR, px+425,py-5,100,"Level - B",0,100,#POTKNOB | # JAZZ_top )

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

px + 80
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)
OptionGadget(#Gad_SwitchDF,px+20,py+45, 90,20, "Freq  (Hz)")
OptionGadget(#Gad_SwitchDP,px+20,py+70, 85,20, "Phase (Deg)")

px+450
ButtonGadget(#Gad_PlayWAV, px,py+70, 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)
AddKeyboardShortcut(#Win_SNDWin,#PB_Shortcut_Return,                  #Key_Return)
;}
;{ Set controls to match initial conditions etc.
SetKnobState(#Gad_FreqL,ChanA\Frequency)    : SetGadgetText(#Gad_UserFA,Str(ChanA\Frequency))
SetKnobState(#Gad_VolumeL,100*ChanA\Volume)
SetGadgetState(#Gad_SwitchL,ChanA\Switch)
SetGadgetState(#Gad_WavL,ChanA\WaveForm)

SetKnobState(#Gad_FreqR,ChanB\Frequency): SetGadgetText(#Gad_UserFB,Str(ChanB\Frequency))
SetKnobState(#Gad_VolumeR,100*ChanB\Volume)
SetGadgetState(#Gad_SwitchR,ChanB\Switch)
SetGadgetState(#Gad_WavR,ChanB\WaveForm)

SetGadgetState(#Gad_Lock,LockLR)
SetGadgetState(#Gad_SwitchDF,DoDF)
SetGadgetState(#Gad_SwitchDP,DoDP)

DisableGadget(#Gad_PlayWAV,#True)
;}
;{ 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 = 0
SetGadgetText(#Gad_VoltRef,StringField(Scope\VoltRefDisc$,1+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)
          Scope\SSFired = #False
          If Scope\SSArm
            FillMemory(*InBufMem,BlockSize * nBuf)
            FillMemory(Scope\SSBuffer,Scope\SSBufSize) 
            SetGadgetState(#Gad_SS_Offset,GetGadgetAttribute(#Gad_SS_Offset,#PB_ScrollBar_Maximum)>>1) ; Put scroll bar central
            StartDrawing(CanvasOutput(#Gad_Scope))   ; Clear display by overdrawing with backdrop grid
              DrawImage(ImageID(ScopeImage),0,0)
            StopDrawing()
          EndIf
          ;}
        Case #Gad_Lock                      ;{ Lock ChanB to ChanA channel
          LockLR = GetGadgetState(#Gad_Lock)
          DisableGadget(#Gad_FreqR,LockLR)  ; Prevent user access to 'B' channel freq
          DisableGadget(#Gad_UserFB,LockLR)
          If LockLR = #False
            ChanB\Frequency = GetKnobState(#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
          T = GetGadgetState(#Gad_VoltRef)  ; 1...n
          Scope\VoltRefIndex = T - 1
          SetGadgetText(#Gad_VoltRef,StringField(Scope\VoltRefDisc$,T,"|"))
          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 (Not Dragging) And Scope\SSFired                           ; Drag Screen...
                Dragging = #DragScreen
                Drag\ScreenDragX = DragX
                Drag\OldDragX    = DragX
              EndIf
               
              If Dragging : beep_(5000,10) : EndIf
              
              ;}
            Case #PB_EventType_LeftButtonUp   ;{  FINISH dragging
              Dragging = #DragNone
              ;}
          EndSelect
          ;}
        Case #Gad_FreqL To #Gad_VolumeR     ;{ Frequency and Amplitude KnobGadgets()
           Select EventType()
           Case #PB_EventType_LeftButtonDown 
             KnobService(EventGadget())
              
            Case  #PB_EventType_MouseWheel          
              T = GetGadgetAttribute(EventGadget(),#PB_Canvas_WheelDelta)  ; Get mouse delta
              SetKnobState(EventGadget(),GetKnobState(EventGadget())+(T))  ; Apply it to Knob
              
          EndSelect  
          
          ;}
      EndSelect
      ;} 
    Case #PB_Event_Menu                     ;{/ MENU ITEMS AND HOT-KEYS
      T = EventMenu()
      Select T
          ; MENU ITEMS
        Case #Men_InDevice1 To #Men_InDevice1 + 9   ;{ Input device selection
          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
          ;}
        Case #Men_OutDevice1 To #Men_OutDevice1 + 9 ;{ Output device selection
          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 ;{ Copy current display to the clipboard
          beep_(5000,10)
          ClearClipboard()
          SetClipboardImage(Scope\Copy)
          ;}
        Case #Key_CntrlP ;{ Make a copy of the screen image and add some annotation
          
          Copy2 = CreateImage(#PB_Any,ImageWidth(Scope\Copy), 2*ImageHeight(Scope\Copy))
          LoadFont(1,"Courier New",12)
          StartDrawing(ImageOutput(Copy2))
            DrawingMode(#PB_2DDrawing_Default)
            DrawingFont(FontID(1))
            Box(0,0,ImageWidth(Copy2),ImageHeight(Copy2),#White) ; Make the backdrop white
            FrontColor(#Black)
            MessageRequester("test",Str(IsImage(Scope\Copy)))
            DrawImage(ImageID(Scope\Copy),0,25)                  ; Draw the 'scope display 
            DrawingMode(#PB_2DDrawing_Transparent)
            k$ = "AGMO Rev 2.3"                                  ; Draw the title
            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)
          SetClipboardImage(Copy2)
          
          ; 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 path+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
          ;}
        Case #Key_Return ;{ 'Return' key pressed over some StringGadgets() forces focus...
          ;                 to a spoof gadget. The string gadget is then read out, see
          ;                 #PB_EventType_LostFocus EventType().
          T = GetActiveGadget()
          If T=#Gad_LockOfs   Or T=#Gad_UserFA Or T=#Gad_UserFB
            SetActiveGadget(#Gad_Spoof) 
          EndIf
          
      EndSelect
      ;}
  EndSelect
  
  Select EventType()
      
    Case #PB_EventType_LostFocus                                ; If gadget contents were changed...
      Select EventGadget()
          
        Case #Gad_LockOfs                         
          LockOffset = Val(GetGadgetText(#Gad_LockOfs))         
          
        Case #Gad_UserFA
          T = Val(GetGadgetText(#Gad_UserFA))
          If T>= 100 And T<=10000
            ChanA\Frequency = T
            SetKnobState(#Gad_FreqL, T)
          EndIf
          
        Case #Gad_UserFB
          T = Val(GetGadgetText(#Gad_UserFB))
          If T>= 100 And T<=10000
            ChanB\Frequency = T
            SetKnobState(#Gad_FreqR, T)  
          EndIf
          
      EndSelect
  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 #PBM_KNOB
      Select lParam
        Case GadgetID(#Gad_FreqL)    : ChanA\Frequency = wParam  : SetGadgetText(#Gad_UserFA,Str(wParam)) ; Frequency value directly from control
        Case GadgetID(#Gad_FreqR)    : ChanB\Frequency = wParam  : SetGadgetText(#Gad_UserFB,Str(wParam))
        Case GadgetID(#Gad_VolumeL)  : ChanA\Volume    = wParam / 100       ; Scale volume over range 0 to 1. FLOAT
        Case GadgetID(#Gad_VolumeR)  : ChanB\Volume    = wParam / 100

      EndSelect
    Case #WM_HSCROLL            ;{ Two frequency, two amplitude controls and trigger adjustments
      Select lParam
        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

      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 - 1
          
          ; 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                       ; If triggered but not counted down half the buffers... 
            Scope\DisplayTrig - 1                    ; decrement buffer 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.
              Drag\ScreenDragTotal = 0
            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)                                   ;- THREAD: 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$,Qt
  Protected Counter
  Protected *DisplayP, *DisplayStart, *DisplayEnd
  
  Delay(500) ; Wait for first time data to be ready
  
  Repeat
    
    ; Location of data to be displayed
    *DisplayP     = Scope\DisplayP      ; Start from this address
    *DisplayStart = Scope\DisplayStart  ; Beginning of buffer, needed if we wrap round.
    *DisplayEnd   = Scope\DisplayEnd    ; End of buffer, ditto.
    
    ; 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...
    
    ;{ DRAGGING: 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\VoltRefIndex = #TraceA
            Drag\Volts1Y = Y + Drag\Volts1Yb4
            Drag\Volts2Y = Y + Drag\Volts2Yb4
          EndIf
          If Scope\TriggerMode = #TraceA Or Scope\TriggerMode = #SingleShotA
            Drag\Trig    = Y + Drag\TrigYb4
          EndIf
          
        Case #DragChanBY
          Scope\YPosB   = Y                                 ; ... Green 
          If Scope\VoltRefIndex = #TraceB
            Drag\Volts1Y = Y + Drag\Volts1Yb4
            Drag\Volts2Y = Y + Drag\Volts2Yb4
          EndIf
          If Scope\TriggerMode = #TraceB Or Scope\TriggerMode = #SingleShotB
            Drag\Trig    = Y + Drag\TrigYb4
          EndIf
          
        Case #DragTime1  : Drag\Time1X = X  ; Time cursor T1 RJL
        Case #DragTime2  : Drag\Time2X = X  ; ... T2
        Case #DragVolts1 : Drag\Volts1Y= Y  ; Volts cursor V1
        Case #DragVolts2 : Drag\Volts2Y= Y  ; ... V2
        Case #DragTrig   : Drag\Trig   = Y  ; Trigger level
          Drag\TrigLineTimer = ElapsedMilliseconds() + 5000 ; Start Trig reference line timer
          
        Case #DragScreen                                    ; Screen Drag... unfinished.  RJL
          If X <> Drag\OldDragX And Scope\SSFired 
            Drag\ScreenDragTotal + (X - Drag\OldDragX)*4    ; *4? Two WORD values
          EndIf
          Drag\OldDragX = X
          
      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  Blue line trigger marker....
      *Bl = 0
      If Scope\SSFired
        *Bl = Scope\SSOffset + Scope\SSBuffer - 4
      EndIf
      ;}
      
      LastVa = $7FFF * Scope\TrigSense    ; Prime the 'scope trigger 
      LastVb = $7FFF * Scope\TrigSense        
      
      *DisplayP - Drag\ScreenDragTotal
      
      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,#Cyan)
          *Bl = Scope\SSBufferEnd +$10000
        EndIf
        
        ; Get the ChanA and ChanB signals
        Va = PeekW(*DisplayP) : *DisplayP + 2              
        Vb = PeekW(*DisplayP) : *DisplayP + 2
        
        ; TRIGGER: Detect signal crossing a 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)                ; Triggered!
                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            ; prime 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) * 32767 * ChanA\Volume 
        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) * 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
    
  Next
  ;}
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
Last edited by RichardL on Mon Jul 08, 2013 10:53 am, edited 11 times in total.
RichardL
Enthusiast
Enthusiast
Posts: 532
Joined: Sat Sep 11, 2004 11:54 am
Location: UK

Re: Another Stereo Tone generator using 'Waveform Audio'

Post by RichardL »

Tuesday 30th April 2013
I wondered just how accurate the frequency generated by the tone generator was. In essence the precision will be determined by the clock oscillator in the sound card, which should be good; but I did not realise just how good...

The following measurements were made on the left channel output set to provide a square wave.
The Demanded frequency was set using the scroll bar gadgets to be the values in the left column.

Measuring instrument Tti TF930 frequency meter. (9 digit, 48Hr warm up.)
Frequency meter sampling time set to 10 seconds.
The Measured frequency was from the frequency meter, shown in the right column.

Code: Select all

Demanded Hz       Measured Hz
100                    100.005
1000                  1000.055
5000                  5000.271
10000                10000.54
At least I can reasonably claim to have got the arithmetic right and the crystal on my sound card is not far out!
There is every reason to suspect yours will be equally good.
Little John
Addict
Addict
Posts: 4779
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: Another Stereo Tone generator using 'Waveform Audio'

Post by Little John »

Works fine here, thanks for sharing
(BenQ Joybook R55, Soundcard: Conexant High Definition Audio,
Windows XP x86, PB 5.11).

Square on the left channel and at the same time sawtooth on the right channel blows my ears off. :lol:
Joris
Addict
Addict
Posts: 890
Joined: Fri Oct 16, 2009 10:12 am
Location: BE

Re: Another Stereo Tone generator using 'Waveform Audio'

Post by Joris »

Works fine here too.
If it isn't hard to do, to add the devices names (got shown 6 here, yet not all hardware is on) I can probably check the frequency measurements too, if you wont that ?
(I'm still to busy to do this myself now. But, I have made some API code for this once in GB32, it just needs to be converted...)

At least thanks for such nice code.
Yeah I know, but keep in mind ... Leonardo da Vinci was also an autodidact.
RichardL
Enthusiast
Enthusiast
Posts: 532
Joined: Sat Sep 11, 2004 11:54 am
Location: UK

Re: Another Stereo Tone generator using 'Waveform Audio'

Post by RichardL »

Hi Joris,
I tried to get the descriptions, see the following code, but I received rather useless descriptions.
Try it and let me know what you get...

I got much better results with DirectSoundCaptureEnumerate() but I'm not sur ethat the order of listing is the same... the result could be a wrong order of the menu items.

Richard

Code: Select all

; Exercise to learn about getting device names etc...
; Status : Early days...

; http://msdn.microsoft.com/en-gb/library/windows/desktop/dd743833(v=vs.85).aspx

MyInDevs.WAVEINCAPS
MyOutDevs.WAVEOUTCAPS
format.WAVEFORMATEX

With format 
  \wFormatTag      = #WAVE_FORMAT_PCM
  \nChannels       = 2
  \nSamplesPerSec  = 44100
  \wBitsPerSample  = 16
  \nBlockAlign     = (\nChannels * \wBitsPerSample)/8
  \nAvgBytesPerSec = \nSamplesPerSec * \nBlockAlign
EndWith

hWaveIn  = 0
hWaveOut = 0

InDevs = waveInGetNumDevs_()

If InDevs
  For n = 0 To InDevs-1
    Debug n
    T =  waveInOpen_(hWaveIn, #WAVE_MAPPER+n,@format,0,0,#WAVE_FORMAT_QUERY)
    If T = 0
      If waveInGetDevCaps_(hWaveIn,@MyInDevs,SizeOf(WAVEINCAPS)) = 0
        Debug "Channels : "  + Str(MyInDevs\wChannels)
        Debug "Name     : "  + PeekS(@MyInDevs\szPname)
        Debug "Formats  : $" + Hex(MyInDevs\dwFormats)
      EndIf
      waveInClose_(hWaveIn)
    EndIf
  Next
EndIf

Debug "==================="

OutDevs = waveOutGetNumDevs_()

If OutDevs
  For n = 0 To OutDevs-1
    Debug n
    T =  waveOutOpen_(@hWaveOut, #WAVE_MAPPER+n,@format,0,0,#WAVE_FORMAT_QUERY)
    If T = 0
      If waveOutGetDevCaps_(hWaveOut,@MyOutDevs,SizeOf(WAVEOUTCAPS)) = 0
        Debug "Channels : "  + Str(MyOutDevs\wChannels)
        Debug "Name     : "  + PeekS(@MyOutDevs\szPname)
        Debug "Formats  : $" + Hex(MyOutDevs\dwFormats)
      EndIf
      waveOutClose_(hWaveOut)
    EndIf
    Debug ""
  Next
EndIf
Joris
Addict
Addict
Posts: 890
Joined: Fri Oct 16, 2009 10:12 am
Location: BE

Re: Another Stereo Tone generator using 'Waveform Audio'

Post by Joris »

This gives a better result here. Look at hWaveIn that I changed into n as the counter.
waveInOpen_(hWaveIn... doesn't have to be used to get the names, but most likely only when selecting the in- or output.

Code: Select all

; Exercise to learn about getting device names etc...
; Status : Early days...

; http://msdn.microsoft.com/en-gb/library/windows/desktop/dd743833(v=vs.85).aspx

MyInDevs.WAVEINCAPS
MyOutDevs.WAVEOUTCAPS
format.WAVEFORMATEX

With format 
  \wFormatTag      = #WAVE_FORMAT_PCM
  \nChannels       = 2
  \nSamplesPerSec  = 44100
  \wBitsPerSample  = 16
  \nBlockAlign     = (\nChannels * \wBitsPerSample)/8
  \nAvgBytesPerSec = \nSamplesPerSec * \nBlockAlign
EndWith
msg.s{250}
hWaveIn  = 0
hWaveOut = 0

InDevs = waveInGetNumDevs_()

If InDevs
  For n = 0 To InDevs-1
    Debug n
   ; T =  waveInOpen_(hWaveIn, #WAVE_MAPPER+n,@format,0,0,#WAVE_FORMAT_QUERY)
   ; If T = 0
   wi.l=waveInGetDevCaps_(n,@MyInDevs,SizeOf(WAVEINCAPS)) 
   If wi=#MMSYSERR_NOERROR
        Debug "Channels : "  + Str(MyInDevs\wChannels)
        Debug "Name     : "  + PeekS(@MyInDevs\szPname)
        Debug "Formats  : $" + Hex(MyInDevs\dwFormats)
        Debug "Support  : $" + Hex(MyOutDevs\dwSupport)
      Else
        waveInGetErrorText_(wi,msg,Len(msg))
        MessageRequester("Information", msg, #PB_MessageRequester_Ok)
      EndIf
    ;  waveInClose_(hWaveIn)
    ;EndIf
  Next
EndIf

Debug "==================="

OutDevs = waveOutGetNumDevs_()

If OutDevs
  For n = 0 To OutDevs-1
    Debug n
   ; T =  waveOutOpen_(hWaveOut, #WAVE_MAPPER+n,@format,0,0,#WAVE_FORMAT_QUERY)
   ; If T = 0
   wo.l=waveOutGetDevCaps_(n,@MyOutDevs,SizeOf(WAVEOUTCAPS))
      If wo=#MMSYSERR_NOERROR
        Debug "Channels : "  + Str(MyOutDevs\wChannels)
        Debug "Name     : "  + PeekS(@MyOutDevs\szPname)
        Debug "Formats  : $" + Hex(MyOutDevs\dwFormats)
        Debug "Support  : $" + Hex(MyOutDevs\dwSupport)
     Else
        waveOutGetErrorText_(wo,msg,Len(msg))
        MessageRequester("Information", msg, #PB_MessageRequester_Ok)
      EndIf
   ;   waveOutClose_(hWaveOut)
   ; EndIf
    Debug ""
  Next
EndIf
Last edited by Joris on Sun May 12, 2013 9:21 am, edited 1 time in total.
Yeah I know, but keep in mind ... Leonardo da Vinci was also an autodidact.
RichardL
Enthusiast
Enthusiast
Posts: 532
Joined: Sat Sep 11, 2004 11:54 am
Location: UK

Enhanced Stereo Tone generator using 'Waveform Audio'

Post by RichardL »

Thursday 2nd May 2013

I have added some new features (see the updated code posted in the first posting):

- Device descriptions to device selection menu (Thanks for the hint Joris) **
- Voltage cursors with display of absolute and delta values
- Time cursors with display of absolute and delta values
- The baselines for both traces can be dragged

Take note: If it is a dotted line you can drag it!

** I have not tested all the output device types yet, I'm waiting for some jack plug adaptors to arrive so I can plug into the audio socket on the back of my HDMI screen and into the phones socket on the USB 'sound card'.

If I get time I will probably add code to keep the voltage cursors in a fixed relationship with the trace datum when selected; this way you could move the datum line and the voltage cursors keep the same relative positions.

RichardL
User avatar
VB6_to_PBx
Enthusiast
Enthusiast
Posts: 627
Joined: Mon May 09, 2011 9:36 am

Re: ENHANCED - Stereo Tone generator using 'Waveform Audio'

Post by VB6_to_PBx »

Waveform Audio plays .WAV file at a slow speed

almost seems like half-speed ??

how can i play .WAV file at normal speed ??
 
PureBasic .... making tiny electrons do what you want !

"With every mistake we must surely be learning" - George Harrison
RichardL
Enthusiast
Enthusiast
Posts: 532
Joined: Sat Sep 11, 2004 11:54 am
Location: UK

Re: ENHANCED - Stereo Tone generator using 'Waveform Audio'

Post by RichardL »

@VB6_to_PBx:

I only intended to play a mono file (See the comments in the section where the file is selected.)

The speed being exactly half of what you expect is right, what you are hearing is one left sample followed by one right sample.

In the CalcWave() procedure you could read a WORD into the right channel in the same manner as the left and it should then work with a stereo file OK. Where is says 'Case 4 : Nothing' copy the matching code from the other channel and change references to 'Vl' to 'Vr' and 'left\Volume' to 'right\Volume'.

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

Re: ENHANCED - Stereo Tone generator using 'Waveform Audio'

Post by RichardL »

Friday 3rd May 2013
The project now more than meets my present and projected needs, so goes on-ice except for any minor tweaks or bug fixes. It is time to clean out the interim versions and other junk!

I have added the following for version 2.2:

- The ability to show a sound INPUT on the 'scope display.
- An input device selector on the menu bar.
- A switch to select viewing the signal generator output or the sound card's inputs. (Which would have been done better with a pair of OptionGadgets())

The listing in the first posting will be updated shortly.

All the best and please let me know if it works for you,
RichardL
User avatar
BasicallyPure
Enthusiast
Enthusiast
Posts: 539
Joined: Thu Mar 24, 2011 12:40 am
Location: Iowa, USA

Re: Ver2.2 April 3 - Stereo Sound Generator and Monitor Sco

Post by BasicallyPure »

Hi RichardL,

It works for me.

Board: ASUSTeK Computer INC. M5A78L-M LX PLUS Rev X.0x
processor... AMD AthlonII x4 640 3.00 GHz.
RAM... 8GB
System Type... 64 bit Operating system.
OS... Windows 7 Home Premium SP 1.
Audio... onboard Realtek High Definition Audio.
Audio driver by... Realtek Semiconductor Corp.
Driver date... 12/13/2011
Driver version... 6.0.1.6526

If you want to decrease the CPU load a bit you can make the following changes in the ScopeRefresh() procedure.
You don't need the temporary image.

Code: Select all

  ;Copy = CopyImage(ScopeImage,#PB_Any)     ; <-- don't need this
  ;StartDrawing(ImageOutput(Copy))          ; <-- change this
  StartDrawing(CanvasOutput(#Gad_Scope))    ; <-- to this
  DrawImage(ImageID(ScopeImage),0,0)      ; <-- add this line
you can then remove all of this at the end of the procedure.

Code: Select all

  ; Transfer the new display to the display gadget
  ;StartDrawing(CanvasOutput(#Gad_Scope))   
    ;DrawImage(ImageID(Copy),0,0)
  ;StopDrawing()
  ;FreeImage(Copy)                            ; Dispense with temporary image
BP
BasicallyPure
Until you know everything you know nothing, all you have is what you believe.
RichardL
Enthusiast
Enthusiast
Posts: 532
Joined: Sat Sep 11, 2004 11:54 am
Location: UK

Re: Ver2.2 April 3 - Stereo Sound Generator and Monitor Sco

Post by RichardL »

Hi BP,
Thanks for testing and the comments.

One reason for keeping an interim Image is that I can use Cntrl+C/P/S to copy the image to the clipboard/Printer/file, which I do in a different version of the same program. This requires putting the FreeImage() at the top of the loop so it remains hanging around for other code to use. I use the same three CNTRL+x commands in many instrumentation programs I have written over the years and my clients expected them to be there. Old habbits stay around even after I retired (almost :wink: ) I double buffer into Image(Copy2) so the original can be free'd up while relatively slow printers/disk drives get round to it.

RichardL
luciano
Enthusiast
Enthusiast
Posts: 151
Joined: Wed Mar 09, 2011 8:25 pm

Re: Ver2.2 April 3 - Stereo Sound Generator and Monitor Sco

Post by luciano »

Richard,
I find there is a sort of fading on the scope right side.
It is due to the fact that some parts of the waveform are not plotted; if you lower the frequency, you will see that one whole period is missing sometimes.
Plotting starts when Strig is true so values in input buffer are terminated before reaching the end of the oscilloscope.
I suggest to increase the input buffer
line 96:

Code: Select all

Global BlockSize      = 4096  ; Number of samples in capture/play block
lines 543 and 544:

Code: Select all

      CalcWave(*P,BlockSize/4)                             ; ?/4 one WORD each ChanA and ChanB
      *hWaveO\dwBytesRecorded = blocksize                  ; Number of bytes written into buffer

line 697:

Code: Select all

For n = 0 To BlockSize>>2
Thanks for the very fine application, I attach 2 screen grabs, before and after

Image

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

Re: Ver2.2 April 3 - Stereo Sound Generator and Monitor Sco

Post by RichardL »

@luciano
Thank you for your suggestions and I'm glad you like it :D
In addition to the changes you suggest it would be good to change the grid to keep the lines at 1mSec spacing, like this:

Code: Select all

Scope\PixPermSec =  Scope\width /(1000*BlockSize/(CAPTURECLOCK*8)) ; Pixels per mSec
@All
I have added a few minor additions to save the screen images. I will put these up later next week provided the weather deteriorates and so I have some time free (= I do not need to cut the grass or hack out the weeds) :wink: .

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

Re: Ver2.2 April 3 - Stereo Sound Generator and Monitor Sco

Post by Simo_na »

Thanks for sharing... :)


might be possible to add crosshair cursors like these ?

Image

see this youtube video : http://www.youtube.com/watch?v=uNNNJ29xeFE


PS
The green waveform once that is varied with the slider is no longer triggered...
Post Reply