Copyright © 2005-2015 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 DJ Studio for .NET gives the possibility to setup a certain number of callback delegates that inform about percentage of advancement of lengthy operations or about manual operations performed by the user on elements of the user interface like the waveform scroller.

 

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 or methods calls

Corresponding callback delegates

Method for initializing the delegates

 

 

 

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

LoadSound

LoadSoundFast

PlaySound

PauseSound

ResumeSound

StopSound

CloseSound

... and many others

CallbackForPlayersEvents

CallbackForPlayersEventsSet

The Bezier curve displayed on a volume curve designer control is modified by dragging one of the control points through the mouse interaction.

CallbackCurveDesignerPointsChange

CallbackCurveDesignerPointsChangeSet

A manual scroll happens on the waveform scroller.

CallbackWaveformScrollerManualScroll

CallbackWaveformScrollerManualScrollSet

A mouse event happens on the waveform scroller.

CallbackWaveformScrollerMouseNotif

CallbackWaveformScrollerMouseNotifSet

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 load and start playback of a sound file and how to monitor the playback status through the player's callback:

 

Visual Basic .NET

 

Imports AudioDjStudioApi

 

Namespace MyApplication

   Public Partial Class Form1

       Inherits Form

 

       Public Sub New()

           InitializeComponent()

       End Sub

 

      ' callback delegate

       Private addrCallbackForPlayersEvents As CallbackForPlayersEvents = New CallbackForPlayersEvents(PlayerCallback)

 

      ' callback that manages player's events

       Private Sub Sub PlayerCallback(ByVal nEvent As enumPlayerEvents, ByVal nPlayer As Int16, _

               ByVal nData1 As Int32, ByVal nData2 As Int32, ByVal fData3 As Single, _

               ByVal pBufferUnicode As IntPtr, ByVal nBufferLength As Int32)

           Select Case nEvent

               Case enumPlayerEvents.EV_SOUND_DONE

                   Console.WriteLine("Status: Sound playback completed")

               Case enumPlayerEvents.EV_SOUND_PAUSED

                   Console.WriteLine("Status: Sound playback paused")

               Case enumPlayerEvents.EV_SOUND_PLAYING

                   Console.WriteLine("Status: Sound playing...")

               Case enumPlayerEvents.EV_SOUND_STOPPED

                   Console.WriteLine("Status: Sound stopped")

               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

           audioDjStudio1.InitSoundSystem(1, 0, 0, 0, 0)

 

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

           audioDjStudio1.CallbackForPlayersEventsSet(addrCallbackForPlayersEvents)

 

           ' load a sound file and start playing

           audioDjStudio1.LoadSound (0, "c:\mysound.mp3")

           audioDjStudio1.PlaySound (0)

 

          ... do other stuffs

 

       End Sub

   End Class

End Namespace

 

 

Visual C#

 

using AudioDjStudioApi;

 

namespace MyApplication

{

   public partial class Form1 : Form

   {

       public Form1()

       {

           InitializeComponent();

       }

 

      // callback delegate

       CallbackForPlayersEvents addrCallbackForPlayersEvents;

 

      // callback that manages player's events

       void PlayerCallback(enumPlayerEvents nEvent, Int16 nPlayer, Int32 nData1, Int32 nData2, float fData3,

                           IntPtr pBufferUnicode, Int32 nBufferLength)

       {

           switch (nEvent)

           {

               case enumPlayerEvents.EV_SOUND_DONE:

                   Console.WriteLine("Status: Sound playback completed");

                   break;

               case enumPlayerEvents.EV_SOUND_PAUSED:

                   Console.WriteLine("Status: Sound paused");

                   break;

               case enumPlayerEvents.EV_SOUND_PLAYING:

                   Console.WriteLine("Status: Sound playing...");

                   break;

               case enumPlayerEvents.EV_SOUND_STOPPED:

                   Console.WriteLine("Status: Sound stopped");

                   break;

           }

          ... do something else

       }

 

       private void Form1_Load(object sender, EventArgs e)

       {

          // initialize the component

           audioDjStudio1.InitSoundSystem(1, 0, 0, 0, 0);

 

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

           addrCallbackForPlayersEvents = new CallbackForPlayersEvents(PlayerCallback);

           audioDjStudio1.CallbackForPlayersEventsSet(addrCallbackForPlayersEvents);

         

          // load a sound file and start playing

           audioDjStudio1.LoadSound (0, @"c:\mysound.mp3");

           audioDjStudio1.PlaySound (0);

 

          ... 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 DJ Studio 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, advancement of the thread's task is reported to the container application through a set of callback delegates. A list of methods and situations that will start a secondary thread, with the corresponding callback delegate, can be found on the table below:

 

Situations or methods calls

Corresponding callback delegates

Method for initializing the delegates

 

 

 

Most of methods invoked on a player, for example

AmazonStoreCdInfoCreate

BeatsDetectRequest

LoadSoundSync

LoadSoundFromMemorySync

LoadEncryptedSoundSync LoadEncryptedSoundFromMemorySync

LoadInternetStream

LoadTrackFromCd

PlaySound on an Internet stream

PlayListAddItemEx

PlayListLoadSync

RequestSoundBPM

SilenceDetectionOnFile

SilenceDetectionOnPlayerRequest

SoundLyricsRequest

VideoPlayer.LoadSync

Waveform.AnalyzeFullSound

CallbackForPlayersEvents

CallbackForPlayersEventsSet

Operations related to CD drives

CallbackForCdDrivesEvents

CallbackForCdDrivesEventsSet

Notifications from CoreAudio devices

CallbackForCoreAudioEvents

CallbackForCoreAudioEventsSet

Notifications from MIDI sub-system

CallbackForMidiKeyboardEvents

CallbackForMidiMarkerEvents

CallbackForMidiStreamEvents

CallbackForMidiKeyboardEventsSet

CallbackForMidiMarkerEventsSet

CallbackForMidiStreamEventsSet

Notifications from the Downloader

CallbackForDownloaderEvents

CallbackForDownloaderEventsSet

Notifications from casting sessions

CallbackForCastingEvents

CallbackForCastingEventsSet

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 manage the percentage advancement of a waveform analysis of the loaded sound: in this case the API is invoked from a container form, so we must keep count of the fact that controls instanced on a form are not thread-safe so, in order to access them, we should add some further coding. The snippets below show how to manage the percentage advancement of a sound waveform analysis operation performed from inside a container form (Form1) having controls instanced on its user interface, for example a label and a progress bar, that need to be updated to inform the user about the current advancement of the waveform analysis:

 

Visual Basic .NET

 

Imports AudioDjStudioApi

 

Namespace MyApplication

   Public Partial Class Form1

       Inherits Form

 

       Public Sub New()

           InitializeComponent()

       End Sub

 

      ' callback delegate

       Private Delegate Sub CallbackForPlayersEventsDelegate(ByVal nEvent As enumPlayerEvents, ByVal nPlayer As Int16, _

               ByVal nData1 As Int32, ByVal nData2 As Int32, ByVal fData3 As Single, _

               ByVal pBufferUnicode As IntPtr, ByVal nBufferLength As Int32)

       Private addrCallbackForPlayersEvents As CallbackForPlayersEvents = New CallbackForPlayersEvents(PlayerCallback)

 

      ' callback that manages player's events

       Private Sub Sub PlayerCallback(ByVal nEvent As enumPlayerEvents, ByVal nPlayer As Int16, _

               ByVal nData1 As Int32, ByVal nData2 As Int32, ByVal fData3 As Single, _

               ByVal pBufferUnicode As IntPtr, ByVal nBufferLength 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 CallbackForPlayersEventsDelegate(AddressOf PlayerCallback), nEvent, nPlayer, _

                                                           nData1, nData2, fData3, pBufferUnicode, nBufferLength)

               Return

           End If

           Select Case nEvent

               Case enumPlayerEvents.EV_WAVE_ANALYSIS_START

                   labelProgress.Visible = True

                   progressBar1.Visible = True

                   labelProgress.Text = "Waveform analysis progress"

                   progressBar1.Value = 0

 

               Case enumPlayerEvents.EV_WAVE_ANALYSIS_PERC

                   progressBar1.Value = nData1

 

               Case enumPlayerEvents.EV_WAVE_ANALYSIS_DONE

                   progressBar1.Visible = False

                   labelProgress.Visible = False

               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

           audioDjStudio1.InitSoundSystem(1, 0, 0, 0, 0)

 

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

           audioDjStudio1.CallbackForPlayersEventsSet(addrCallbackForPlayersEvents)

 

           ' load a sound file

           Dim nResult as enumErrorCodes = m_audioAPI.LoadSound (0, "c:\mysound.mp3")

           If nResult = enumErrorCodes.NOERROR Then

              ' request full waveform analysis

               audioDjStudio1.DisplayWaveform.AnalyzeFullSound (0, enumWaveformResolutions.WAVEFORM_RES_MAXIMUM)

           Else

               MessageBox.Show ("Cannot load file due to error" & nResult)

           End If

 

       End Sub

   End Class

End Namespace

 

 

Visual C#

 

using AudioDjStudioApi;

 

namespace MyApplication

{

   public partial class Form1 : Form

   {

       public Form1()

       {

           InitializeComponent();

       }

 

      // callback delegate

       delegate void CallbackForPlayersEventsDelegate(enumPlayerEvents nEvent, Int16 nPlayer,

                       Int32 nData1, Int32 nData2, float fData3, IntPtr pBufferUnicode, Int32 nBufferLength);

       CallbackForPlayersEvents addrCallbackForPlayersEvents;

 

      // callback that manages player's events

       void PlayerCallback(enumPlayerEvents nEvent, Int16 nPlayer, Int32 nData1, Int32 nData2, float fData3,

                           IntPtr pBufferUnicode, Int32 nBufferLength)

       {

          // 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 CallbackForPlayersEventsDelegate(PlayerCallback), nEvent, nPlayer, nData1, nData2, fData3, pBufferUnicode, nBufferLength);

               return;

           }

 

           switch (nEvent)

           {

           case enumPlayerEvents.EV_WAVE_ANALYSIS_START:

               labelProgress.Visible = true;

               progressBar1.Visible = true;

               labelProgress.Text = "Waveform analysis progress";

               progressBar1.Value = 0;

               break;

 

           case enumPlayerEvents.EV_WAVE_ANALYSIS_PERC:

               progressBar1.Value = nData1;

               break;

 

           case enumPlayerEvents.EV_WAVE_ANALYSIS_DONE:

               progressBar1.Visible = false;

               labelProgress.Visible = false;

               break;

           }

          ... do something else

       }

 

       private void Form1_Load(object sender, EventArgs e)

       {

          // initialize the component

           audioDjStudio1.InitSoundSystem(1, 0, 0, 0, 0);

 

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

           addrCallbackForPlayersEvents = new CallbackForPlayersEvents(PlayerCallback);

           audioDjStudio1.CallbackForPlayersEventsSet(addrCallbackForPlayersEvents);

         

          // load a sound file

           enumErrorCodes nResult = audioDjStudio1.LoadSound (0, @"c:\mysound.mp3");

           if (nResult == enumErrorCodes.NOERROR)

              // request full waveform analysis

               audioDjStudio1.DisplayWaveform.AnalyzeFullSound (0, enumWaveformResolutions.WAVEFORM_RES_MAXIMUM);

           else

               MessageBox.Show ("Cannot load sound file due to error " + nResult.ToString ());

       }

      ...

   }

  ...

}