Copyright © 2006-2019 MultiMedia Soft

How to synchronize the container application through callback delegates

Previous pageReturn to chapter overviewNext page

As an alternative to standard events supported by Visual Studio when dealing with Winforms-based applications, in order to allow managing feedback coming from the component's code to the container application code, Audio Sound Recorder for .NET gives the possibility to setup a certain number of callback delegates that inform about percentage of advancement of a lengthy operation or about manual operations performed by the user on elements of the user interface like the waveform analyzer.

 

Some of these callback delegates are invoked from the application's main thread while others are invoked from secondary threads. Let's see the difference between these two categories of callback delegates:

 

Callback delegates invoked from the application's main thread

Callback delegates invoked from secondary threads

 

 

Callback delegates invoked from the application's main thread

 

A list of situations that notify the container application through delegates can be found on the table below:

 

Situations

Corresponding callback delegates

Method for initializing the delegates

 

 

 

A method involving the usage of a recorder has been invoked, for example

StartFromAsioDevice

StartFromDirectSoundDevice

StartFromWasapiCaptureDevice

StartFromWasapiLoopbackDevice

Pause

Resume

Stop

... and many others

CallbackForRecorderEvents

CallbackForRecorderEventsSet

The range of sound displayed on the waveform analyzer is modified after a zooming operation or after a mouse scrolling operation or after a call to the WaveformAnalyzer.SetDisplayRange method.

CallbackWaveformAnalyzerRange

CallbackWaveformAnalyzerRangeSet

A portion of the waveform on the waveform analyzer has been selected/deselected through a mouse operation or through a call to the WaveformAnalyzer.SetSelection method.

CallbackWaveformAnalyzerSelection

CallbackWaveformAnalyzerSelectionSet

The waveform analyzer has been resized horizontally, usually after a call to the WaveformAnalyzer.Move method.

CallbackWaveformAnalyzerWidth

CallbackWaveformAnalyzerWidthSet

A custom vertical line on the waveform analyzer has been moved through the mouse or through the WaveformAnalyzer.GraphicItemHorzPositionSet method.

CallbackWaveformAnalyzerLineMoved

CallbackWaveformAnalyzerLineMovedSet

A custom horizontal line on the waveform analyzer has been moved through the mouse or through the WaveformAnalyzer.GraphicItemHorzPositionSet method or through the WaveformAnalyzer.GraphicItemVertPositionSet method..

CallbackWaveformAnalyzerHorzLineMoved

CallbackWaveformAnalyzerHorzLineMovedSet

The playback position of the sound under editing reaches a custom vertical line inside the waveform analyzer.

CallbackWaveformAnalyzerLineReached

CallbackWaveformAnalyzerLineReachedSet

The playback position of the sound under editing reaches the beginning of a custom horizontal line inside the waveform analyzer.

CallbackWaveformAnalyzerHorzLineReached

CallbackWaveformAnalyzerHorzLineReachedSet

The playback position of the sound under editing leaves the end of a custom horizontal line inside the waveform analyzer.

CallbackWaveformAnalyzerHorzLineLeaved

CallbackWaveformAnalyzerHorzLineLeavedSet

The playback position of the sound under editing reaches the beginning of a custom wave range inside the waveform analyzer.

WaveAnalyzerWaveRangeReached

WaveAnalyzerWaveRangeReachedSet

The playback position of the sound under editing leaves the end of a custom wave range inside the waveform analyzer.

WaveAnalyzerWaveRangeLeaved

WaveAnalyzerWaveRangeLeavedSet

A graphic item is clicked with the mouse on the waveform analyzer

CallbackWaveformAnalyzerGraphItemClick

CallbackWaveformAnalyzerGraphItemClickSet

A graphic item is double-clicked with the mouse on the waveform analyzer

CallbackWaveformAnalyzerGraphItemDblClick

CallbackWaveformAnalyzerGraphItemDblClickSet

A mouse event happens on the waveform analyzer.

CallbackWaveformAnalyzerMouseNotif

CallbackWaveformAnalyzerMouseNotifSet

The waveform analyzer has completed its graphic rendering and it's now possible adding custom graphics directly from the container application's code.

CallbackWaveformAnalyzerPaintDone

CallbackWaveformAnalyzerPaintDoneSet

A manual scroll happens on the waveform scroller.

CallbackWaveformScrollerManualScroll

CallbackWaveformScrollerManualScrollSet

A mouse event happens on the waveform scroller.

CallbackWaveformScrollerMouseNotif

CallbackWaveformScrollerMouseNotifSet

During playback there is a change on the VU-Meter peak values.

CallbackVuMeterValueChange

CallbackVuMeterValueChangeSet

During playback the status of the player changes

CallbackSoundPlaybackStatusChanged

CallbackSoundPlaybackStatusChangedSet

A playback session is completed

CallbackSoundPlaybackDone

CallbackSoundPlaybackDoneSet

On WIndows XP, the system default multimedia input/output device (sound card) is changed through the Windows Control Panel's multimedia settings or USB device is plugged or unplugged

CallBackDeviceChange

CallBackDeviceChangeSet

 

Let's see a couple of code snippets that clarify how a callback delegate can be instantiated and managed. The snippets below show how to start a recording session from the system default recording device and display the sound duration and size on corresponding labels.

 

Visual Basic .NET

 

Imports AudioSoundRecorderApi

 

Namespace MyApplication

   Public Partial Class Form1

       Inherits Form

 

       Public Sub New()

           InitializeComponent()

       End Sub

 

      ' callback delegate

       Private addrRecorderCallback As CallbackForRecorderEvents = New CallbackForRecorderEvents(AddressOf RecorderCallback)

 

      ' callback that manages recorde's events

       Private Sub RecorderCallback(ByVal nEvent As enumRecorderEvents, ByVal nData1 As Int32, ByVal nData2 As Int32, _

                            ByVal nDataHigh3 As Int32, ByVal nDataLow3 As Int32, _

                            ByVal nDataHigh4 As Int32, ByVal nDataLow4 As Int32)

           Select Case nEvent

               Case enumRecorderEvents.EV_REC_START

                   Console.WriteLine ("Recording started")

               Case enumRecorderEvents.EV_REC_STOP

                   Console.WriteLine ("Recording stopped")

               Case enumRecorderEvents.EV_REC_SIZE

                   Dim nDataSize As Int64 = (Convert.ToInt64 (nDataHigh4) << 32) Or Convert.ToInt64 (nDataLow4)

                   labelSize.Text = "Recording size in bytes: " & nDataSize.ToString()

               Case enumRecorderEvents.EV_REC_DURATION

                   labelDuration.Text = "Recording duration: " & m_audioRecorderAPI.FromMsToFormattedTime (nData1, False, True, ":", ".", 3)

               Case Else

                   Return

           End Select          

 

          ... do something else

       End Sub

 

       Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load

          ' initialize the component

           audioSoundRecorder1.InitSoundSystem(1, 0, 0, 0, 0)

           audioSoundRecorder1.UseThreadsInSyncMode(True)

 

          ' predispose the callback that will notify about recorder's events

           audioSoundRecorder1.CallbackForRecorderEventsSet(addrRecorderCallback)

 

          ' use custom resampling format 44100 Hz Stereo and record in MP3 format

           audioSoundRecorder1.EncodeFormats.ResampleMode = enumResampleModes.RESAMPLE_MODE_CUSTOM_FORMAT

           audioSoundRecorder1.EncodeFormats.ResampleCustomFrequency = 44100

           audioSoundRecorder1.EncodeFormats.ResampleCustomChannels = 2

           audioSoundRecorder1.EncodeFormats.ForRecording == enumEncodingFormats.ENCODING_FORMAT_MP3

 

          ' start a recording session from the system default recording device

           audioSoundRecorder1.StartFromDirectSoundDevice (0, 0, "c:\myrecording.mp3")

 

          ... do other stuffs

 

       End Sub

   End Class

End Namespace

 

 

Visual C#

 

using AudioSoundRecorderApi;

 

namespace MyApplication

{

   public partial class Form1 : Form

   {

       public Form1()

       {

           InitializeComponent();

       }

 

      // callback delegate

       CallbackForRecorderEvents addrRecorderCallback;

 

      // callback that manages recorder's events

       void RecorderCallback(enumRecorderEvents nEvent, Int32 nData1, Int32 nData2, Int32 nDataHigh3, Int32 nDataLow3, Int32 nDataHigh4, Int32 nDataLow4)

       {

           switch (nEvent)

           {

           case enumRecorderEvents.EV_REC_START:

               Console.WriteLine ("Recording started");

               break;

           case enumRecorderEvents.EV_REC_STOP:

               Console.WriteLine ("Recording stopped");

               break;

           case enumRecorderEvents.EV_REC_SIZE:

               Int64 nDataSize = (Convert.ToInt64 (nDataHigh4) << 32) | Convert.ToInt64 (nDataLow4);

               labelSize.Text = "Recording size in bytes: " + nDataSize.ToString();

               break;

           case enumRecorderEvents.EV_REC_DURATION:

               labelDuration.Text = "Recording duration: " + m_audioRecorderAPI.FromMsToFormattedTime (nData1, false, true, ":", ".", 3);

               break;

           }

       }

 

       private void Form1_Load(object sender, EventArgs e)

       {

          // initialize the component

           audioSoundRecorder1.InitRecordingSystem();

           audioSoundRecorder1.UseThreadsInSyncMode(true);

 

          // predispose the callback that will notify about recorder's events

           addrRecorderCallback = new CallbackForRecorderEvents(RecorderCallback);

           audioSoundRecorder1.CallbackForRecorderEventsSet(addrRecorderCallback);

         

          // use custom resampling format 44100 Hz Stereo and record in MP3 format

           audioSoundRecorder1.EncodeFormats.ResampleMode = enumResampleModes.RESAMPLE_MODE_CUSTOM_FORMAT;

           audioSoundRecorder1.EncodeFormats.ResampleCustomFrequency = 44100;

           audioSoundRecorder1.EncodeFormats.ResampleCustomChannels = 2;

           audioSoundRecorder1.EncodeFormats.ForRecording == enumEncodingFormats.ENCODING_FORMAT_MP3;

 

          // start a recording session from the system default recording device

           audioSoundRecorder1.StartFromDirectSoundDevice (0, 0, @"c:\myrecording.mp3");

 

          ... do other stuffs

       }

      ...

   }

  ...

}

 

 

IMPORTANT NOTE ABOUT DELEGATES AND THEIR SCOPE: When an instance of a callback delegate is passed to one of the API functions, the delegate object is not reference counted. This means that the .NET framework would not know that it might still being used by the API so the Garbage Collector might remove the delegate instance if the variable holding the delegate is not declared as global. As a general rule, make sure to always keep your delegate instance in a variable which lives as long as the API needs it by using a global variable or member.

 

 

 

Callback delegates invoked from secondary threads

 

Some method available inside Audio Sound Editor API for .NET performs lengthy operations which, on a single-threaded environment, could block the user interface of the container application also for several seconds: just think about the time requested to load a sound file whose duration is more than 30 minutes or to perform the waveform analysis for a song longer than 5 minutes and you will perfectly understand that this kind of tasks couldn't be completed in less than one second.

 

In order to avoid this kind of blocks, the API performs lengthy operations inside secondary threads executed in synchronous mode: this means that the call to a method starting a new thread will not return control to the container application till the moment in which the secondary thread has not completed its task and has been closed.

 

During execution of the secondary thread, percentage of advancement of the thread's task is reported to the container application through a set of callback delegates. A list of methods that will start a secondary thread, with the corresponding callback delegate, can be found on the table below:

 

Situations

Corresponding callback delegates

Method for initializing the delegates

 

 

 

A method involving the usage of a recorder has been invoked, for example

StartFromFile

StartFromMemory

... and many others

CallbackForRecorderEvents

CallbackForRecorderEventsSet

Notifications from CoreAudio devices

CallbackForCoreAudioEvents

CallbackForCoreAudioEventsSet

A new spectral analysis is requested

CallbackWaveAnalyzerSpectralViewStart

CallbackWaveAnalyzerSpectralViewDone

CallbackWaveAnalyzerSpectralViewStartSet

CallbackWaveAnalyzerSpectralViewDoneSet

A decompression of a ZIP file's entry is performed

CallbackZipOperationPerc

CallbackZipOperationPercSet

 

Let's see a couple of code snippets that clarify how a callback delegate invoked from a secondary thread can be instantiated and managed.

The snippets below show how to start a recording session from a sound file stored locally and immediately proceed with the sound's waveform analysis: during the loading and the waveform analysis the recording percentage is displayed on a label and on progress bar.

 

Visual Basic .NET

 

Imports AudioSoundRecorderApi

 

Namespace MyApplication

   Public Partial Class Form1

       Inherits Form

 

       Public Sub New()

           InitializeComponent()

       End Sub

 

      ' delegate for managing callbacks invoked from secondary threads

       Private Delegate Sub RecorderCallbackDelegate(ByVal nEvent As enumRecorderEvents, _

                               ByVal nData1 As Int32, ByVal nData2 As Int32, _

                               ByVal nDataHigh3 As Int32, ByVal nDataLow3 As Int32, _

                               ByVal nDataHigh4 As Int32, ByVal nDataLow4 As Int32)

 

      ' callback delegate

       Private addrRecorderCallback As CallbackForRecorderEvents = New CallbackForRecorderEvents(AddressOf RecorderCallback)

 

      ' callback that manages recorde's events

       Private Sub RecorderCallback(ByVal nEvent As enumRecorderEvents, ByVal nData1 As Int32, ByVal nData2 As Int32, _

                            ByVal nDataHigh3 As Int32, ByVal nDataLow3 As Int32, _

                            ByVal nDataHigh4 As Int32, ByVal nDataLow4 As Int32)

 

          ' check if we are being invoked from a secondary thread

           If Me.InvokeRequired Then

              ' this callback is being called from a secondary thread so, in order to access controls on the form, we must

               ' call Invoke using this same function as a delegate

               Me.Invoke(New RecorderCallbackDelegate(AddressOf RecorderCallback), nEvent, nData1, nData2, _

                                                      nDataHigh3, nDataLow3, nDataHigh4, nDataLow4)

               Return

           End If

 

           Select Case nEvent

               Case enumRecorderEvents.EV_REC_PERC:

                   strStatus = "Status: Loading sound file... " & nData1.ToString() & "%"

               Case enumRecorderEvents.EV_REC_WAVE_ANALYSIS_PERC:

                   strStatus = "Status: Analyzing waveform... " & nData1.ToString() & "%"

               Case Else

                   Return

           End Select

 

          ' safely update elements of the user interface

           LabelStatus.Text = strStatus

           progressBar1.Value = nData1

       End Sub

 

       Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load

          ' initialize the component

           audioSoundRecorder1.InitSoundSystem(1, 0, 0, 0, 0)

           audioSoundRecorder1.UseThreadsInSyncMode(True)

 

          ' predispose the callback that will notify about recorder's events

           audioSoundRecorder1.CallbackForRecorderEventsSet(addrRecorderCallback)

 

           progressBar1.Visible = False

 

          ' load the new song

           audioSoundRecorder1.StartFromFile("", "c:\mysound.mp3")

 

           LabelStatus.Text = "Status: Idle"

           progressBar1.Visible = False

 

          ' request full waveform analysis using default resolution

           progressBar1.Visible = True

           audioSoundRecorder1.DisplayWaveformAnalyzer.AnalyzeFullSound()

 

           LabelStatus.Text = "Status: Idle"

           progressBar1.Visible = False

 

          ' display the full waveform

           audioSoundRecorder1.DisplayWaveformAnalyzer.SetDisplayRange(0, -1)

 

       End Sub

   End Class

End Namespace

 

 

Visual C#

 

using AudioSoundRecorderApi;

 

namespace MyApplication

{

   public partial class Form1 : Form

   {

       public Form1()

       {

           InitializeComponent();

       }

 

      // delegate for managing callbacks invoked from secondary threads

       delegate void RecorderCallbackDelegate(enumRecorderEvents nEvent, Int32 nData1, Int32 nData2,

                                               Int32 nDataHigh3, Int32 nDataLow3,

                                               Int32 nDataHigh4, Int32 nDataLow4);      

 

      // callback delegate

       CallbackForRecorderEvents addrRecorderCallback;

 

      // callback that manages recorder's events

       void RecorderCallback(enumRecorderEvents nEvent, Int32 nData1, Int32 nData2,

                             Int32 nDataHigh3, Int32 nDataLow3, Int32 nDataHigh4, Int32 nDataLow4)

       {

          // check if we are being invoked from a secondary thread

           if (this.InvokeRequired)

           {

               // this callback is being called from a secondary thread so, in order to access controls on the form, we must

               // call Invoke using this same function as a delegate

               this.Invoke(new RecorderCallbackDelegate(RecorderCallback), nEvent, nData1, nData2, nDataHigh3, nDataLow3, nDataHigh4, nDataLow4);

               return;

           }

           switch (nEvent)

           {

           case enumRecorderEvents.EV_REC_PERC:

               strStatus = "Status: Loading sound file... " + nData1.ToString() + "%";

               break;

           case enumRecorderEvents.EV_REC_WAVE_ANALYSIS_PERC:

               strStatus = "Status: Analyzing waveform... " + nData1.ToString() + "%";

               break;

           }

 

          // safely update elements of the user interface

           LabelStatus.Text = strStatus;

           progressBar1.Value = nData1;

       }

 

       private void Form1_Load(object sender, EventArgs e)

       {

          // initialize the component

           audioSoundRecorder1.InitRecordingSystem();

           audioSoundRecorder1.UseThreadsInSyncMode(true);

 

          // predispose the callback that will notify about recorder's events

           addrRecorderCallback = new CallbackForRecorderEvents(RecorderCallback);

           audioSoundRecorder1.CallbackForRecorderEventsSet(addrRecorderCallback);

         

           progressBar1.Visible = true;

 

           // load the new song

           audioSoundRecorder1.StartFromFile("", @"c:\mysound.mp3");

 

           LabelStatus.Text = "Status: Idle";

           progressBar1.Visible = false;

 

           // request full waveform analysis using default resolution

           progressBar1.Visible = true;

           audioSoundRecorder1.DisplayWaveformAnalyzer.AnalyzeFullSound();

 

           LabelStatus.Text = "Status: Idle";

           progressBar1.Visible = false;

 

          // display the full waveform

           audioSoundRecorder1.DisplayWaveformAnalyzer.SetDisplayRange(0, -1);

       }

      ...

   }

  ...

}

 

 

 

IMPORTANT NOTE ABOUT DELEGATES AND THEIR SCOPE: When an instance of a callback delegate is passed to one of the API functions, the delegate object is not reference counted. This means that the .NET framework would not know that it might still being used by the API so the Garbage Collector might remove the delegate instance if the variable holding the delegate is not declared as global. As a general rule, make sure to always keep your delegate instance in a variable which lives as long as the API needs it by using a global variable or member.