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 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 ... and many others |
||
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. |
||
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. |
||
The waveform analyzer has been resized horizontally, usually after a call to the WaveformAnalyzer.Move method. |
||
A custom vertical line on the waveform analyzer has been moved through the mouse or through the WaveformAnalyzer.GraphicItemHorzPositionSet method. |
||
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.. |
||
The playback position of the sound under editing reaches a custom vertical line inside the waveform analyzer. |
||
The playback position of the sound under editing reaches the beginning of a custom horizontal line inside the waveform analyzer. |
||
The playback position of the sound under editing leaves the end of a custom horizontal line inside the waveform analyzer. |
||
The playback position of the sound under editing reaches the beginning of a custom wave range inside the waveform analyzer. |
||
The playback position of the sound under editing leaves the end of a custom wave range inside the waveform analyzer. |
||
A graphic item is clicked with the mouse on the waveform analyzer |
||
A graphic item is double-clicked with the mouse on the waveform analyzer |
||
A mouse event happens on the waveform analyzer. |
||
The waveform analyzer has completed its graphic rendering and it's now possible adding custom graphics directly from the container application's code. |
||
A manual scroll happens on the waveform scroller. |
||
A mouse event happens on the waveform scroller. |
||
During playback there is a change on the VU-Meter peak values. |
||
During playback the status of the player changes |
||
A playback session is completed |
||
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 |
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 ... and many others |
||
Notifications from CoreAudio devices |
||
A new spectral analysis is requested |
||
A decompression of a ZIP file's entry is performed |
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.