How to synchronize the container application through callback delegates |
|
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 ... and many others |
||
The Bezier curve displayed on a volume curve designer control is modified by dragging one of the control points through the mouse interaction. |
||
A manual scroll happens on the waveform scroller. |
||
A mouse event happens on the waveform scroller. |
||
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 |
||
The position of TracksBoard's play head line is changed |
||
The range displayed on the TracksBoard is changed after a zooming operation or after a scrolling operation or after a call to the TracksBoard.DisplayRangeSet method |
||
The TracksBoard's window has been resized horizontally, usually after a call to the TracksBoard.Move method |
||
A mouse event happened on the TracksBoard |
||
An item on the TracksBoard has been moved through the mouse or through code |
||
A sound items, or a volume point within the sound item, has been selected on the TracksBoard |
||
A volume operation is performed on a specific item on the TracksBoard |
||
A mouse button is clicked over a sound item available on the TracksBoard |
||
A mouse button is double clicked over a sound item available on the TracksBoard |
||
The TracksBoard has completed its graphic rendering |
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 LoadEncryptedSoundSync LoadEncryptedSoundFromMemorySync PlaySound on an Internet stream |
||
Operations related to CD drives |
||
Notifications from CoreAudio devices |
||
Notifications from MIDI sub-system |
CallbackForMidiKeyboardEventsSet |
|
Notifications from the Downloader object |
||
Notifications from casting sessions |
||
A decompression of a ZIP file's entry is performed |
||
A music recognition session is performed |
||
Notifications from the Youtube object |
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 ()); } ... } ... }
|