How to synchronize the container application with the API |
|
In order to allow managing feedback coming from the API code to the container application code, Audio Sound Editor API 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 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 |
|
|
|
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. |
||
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. |
||
During playback there is a change on the VU-Meter peak values. |
||
The editor of a VST effect is resized |
||
A parameter of a VST effect has been modified through the editor of the VST |
||
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 manage changes performed on the waveform analyzer instanced inside a container form (Form1): the two callback delegates notify the container form of the change of the waveform's selection and the change of the displayed waveform's range:
Visual Basic .NET |
Imports AudioSoundEditorApi
Namespace VolumeAutomation Public Partial Class Form1 Inherits Form
Public Sub New() InitializeComponent() End Sub
' callback delegates Private addrWaveformSelectionChange As CallbackWaveformAnalyzerSelection Private addrWaveformRangeChange As CallbackWaveformAnalyzerRange
' callback that manages changes of selected portion of the waveform Private Sub WaveformSelectionChangeCallback(ByVal bSelectionAvailable As Boolean, ByVal nBeginPosInMs As Int32, ByVal nEndPosInMs As Int32) ... do something End Sub
' callback that manages changes of visible range of the waveform Private Sub WaveformRangeChangeCallback(ByVal nBeginPosInMs As Int32, ByVal nEndPosInMs As Int32) ... do something End Sub
Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load ' initialize the component audioSoundEditor1.InitEditor() audioSoundEditor1.UseThreadsInSyncMode(True)
' predispose the callback that will notify about selection changes on the waveform analyzer addrWaveformSelectionChange = New CallbackWaveformAnalyzerSelection(AddressOf WaveformSelectionChangeCallback) audioSoundEditor1.CallbackWaveformAnalyzerSelectionSet(addrWaveformSelectionChange)
' predispose the callback that will notify about changes of the displayed sound range on the waveform analyzer addrWaveformRangeChange = New CallbackWaveformAnalyzerRange(AddressOf WaveformRangeChangeCallback) audioSoundEditor1.CallbackWaveformAnalyzerRangeSet(addrWaveformRangeChange)
' create the waveform analyzer (always call this function on the end of the form's Load fucntion) audioSoundEditor1.DisplayWaveformAnalyzer.Create(Me.Handle, Picture1.Left, Picture1.Top, Picture1.Width, Picture1.Height, Me.BackColor)
... do other stuffs
End Sub End Class End Namespace
|
Visual C# |
using AudioSoundEditorApi;
namespace MyApplication { public partial class Form1 : Form { public Form1() { InitializeComponent(); }
// callback delegates CallbackWaveformAnalyzerSelection addrWaveformSelectionChange; CallbackWaveformAnalyzerRange addrWaveformRangeChange;
// callback that manages changes of selected portion of the waveform void WaveformSelectionChangeCallback(bool bSelectionAvailable, Int32 nBeginPosInMs, Int32 nEndPosInMs) { ... do something }
// callback that manages changes of visible range of the waveform void WaveformRangeChangeCallback(Int32 nBeginPosInMs, Int32 nEndPosInMs) { ... do something }
private void Form1_Load(object sender, EventArgs e) { // initialize the API audioSoundEditor1.InitEditor(); audioSoundEditor1.UseThreadsInSyncMode(true);
// predispose the callback that will notify about selection changes on the waveform analyzer addrWaveformSelectionChange = new CallbackWaveformAnalyzerSelection(WaveformSelectionChangeCallback); audioSoundEditor1.CallbackWaveformAnalyzerSelectionSet(addrWaveformSelectionChange);
// predispose the callback that will notify about changes of the displayed sound range on the waveform analyzer addrWaveformRangeChange = new CallbackWaveformAnalyzerRange(WaveformRangeChangeCallback); audioSoundEditor1.CallbackWaveformAnalyzerRangeSet(addrWaveformRangeChange);
// create the waveform analyzer audioSoundEditor1.DisplayWaveformAnalyzer.Create(this.Handle, Picture1.Left, Picture1.Top, Picture1.Width, Picture1.Height, this.BackColor);
... 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:
Methods or situations |
Corresponding callback delegates |
Method for initializing the delegates |
|
|
|
ExportAndSplitStereoChannelsToFile |
||
Effects.NormalizationSimpleApply |
||
CallbackSoundPlaybackDoneSet, CallbackSoundPlaybackStatusChangedSet and CallbackVuMeterValueChangeSet |
||
A new spectral analysis is requested |
||
Notifications from the Downloader 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 sound loading operation and a sound level normalization performed by a console application not having its own user interface:
Visual Basic .NET |
Imports AudioSoundEditorApi
Namespace NormalizeSoundLevel Friend Class Program
' callback that manages notifications of sound loading advancement Private Shared Sub PercentageCallback(ByVal nOperation As enumOperationsWithPercentage, ByVal nPercentage As Int16) Select Case nOperation Case enumOperationsWithPercentage.OPERATION_SOUND_LOADING Console.Write(Constants.vbCr & "Loading ") Case Else Return End Select Console.Write("percentage " & nPercentage.ToString() & "%") End Sub
' callback that manages notifications of sound editing advancement Private Shared Sub PercentageEditCallback(ByVal nPercentage As Int16, ByVal nCommand As enumSoundEditCommands) Console.Write(Constants.vbCr & "Editing percentage " & nPercentage.ToString() & "%") End Sub
' callback delegates Private Shared addrCallbackPercentage As CallbackPercentage = New CallbackPercentage(AddressOf PercentageCallback) Private Shared addrCallbackEditPercentage As CallbackEditPerc = New CallbackEditPerc(AddressOf PercentageEditCallback)
Shared Sub Main(ByVal args As String()) ' initialize the component audioSoundEditor1.InitEditor() audioSoundEditor1.UseThreadsInSyncMode(True)
' predispose the callbacks that will notify about advancement of lengthy operations audioSoundEditor1.CallbackPercentageSet(addrCallbackPercentage) audioSoundEditor1.CallbackEditPercSet(addrCallbackEditPercentage)
' load the sound file Dim nResult As enumErrorCodes = audioSoundEditor1.LoadSound("c:\myfile.mp3") If nResult = enumErrorCodes.ERR_NOERROR Then ' apply normalization nResult = audioSoundEditor1.Effects.NormalizationToTargetApply(0, -1, 98, 100, 100) If nResult = enumErrorCodes.ERR_NOERROR Then ... do something with the normalized song End If End If End Sub End Class End Namespace
|
Visual C# |
using AudioSoundEditorApi;
namespace NormalizeSoundLevel { class Program { // callback that manages notifications of sound loading advancement static void PercentageCallback(enumOperationsWithPercentage nOperation, Int16 nPercentage) { switch (nOperation) { case enumOperationsWithPercentage.OPERATION_SOUND_LOADING: Console.Write("\rLoading "); break; default: return; } Console.Write("percentage " + nPercentage.ToString() + "%"); }
// callback that manages notifications of sound editing advancement static void PercentageEditCallback(Int16 nPercentage, enumSoundEditCommands nCommand) { Console.Write("\rEditing percentage " + nPercentage.ToString() + "%"); }
// callback delegates static CallbackPercentage addrCallbackPercentage = new CallbackPercentage(PercentageCallback); static CallbackEditPerc addrCallbackEditPercentage = new CallbackEditPerc(PercentageEditCallback);
static void Main(string[] args) { // initialize the component audioSoundEditor1.InitEditor(); audioSoundEditor1.UseThreadsInSyncMode(true);
// predispose the callbacks that will notify about advancement of lengthy operations audioSoundEditor1.CallbackPercentageSet(addrCallbackPercentage); audioSoundEditor1.CallbackEditPercSet(addrCallbackEditPercentage);
// load the sound file enumErrorCodes nResult = audioSoundEditor1.LoadSound(@"c:\myfile.mp3"); if (nResult == enumErrorCodes.ERR_NOERROR) { // apply normalization nResult = audioSoundEditor1.Effects.NormalizationToTargetApply(0, -1, 98, 100, 100); if (nResult == enumErrorCodes.ERR_NOERROR) { ... do something with the normalized song } } } } }
|
Accessing the console to display a message is thread-safe so we have seen that we can output the percentage of advancement without problems also if the callback delegate is invoked from a secondary thread; in case the API should be used from a container form, we should 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 loading operation and 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:
Visual Basic .NET |
Imports AudioSoundEditorApi
Namespace VolumeAutomation Public Partial Class Form1 Inherits Form
Public Sub New() InitializeComponent() End Sub
' instance of the API Private audioAPI As AudioSoundEditorApi.AudioSoundEditorApi = New AudioSoundEditorApi.AudioSoundEditorApi()
' delegate for managing callbacks invoked from secondary threads Private Delegate Sub PercentageCallbackDelegate(ByVal nOperation As enumOperationsWithPercentage, ByVal nPercentage As Int16)
' callback delegate Private addrCallbackPercentage As CallbackPercentage
' callback that manages changes of selected portion of the waveform Private Sub PercentageCallback(ByVal nOperation As enumOperationsWithPercentage, ByVal nPercentage As Int16) ' 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 PercentageCallbackDelegate(AddressOf PercentageCallback), nOperation, nPercentage) Return End If
Dim strStatus As String Select Case nOperation Case enumOperationsWithPercentage.OPERATION_SOUND_LOADING strStatus = "Status: Loading sound file... " & nPercentage.ToString() & "%" Case enumOperationsWithPercentage.OPERATION_WAVE_ANALYSIS strStatus = "Status: Analyzing waveform... " & nPercentage.ToString() & "%" Case Else Return End Select ' safely update elements of the user interface LabelStatus.Text = strStatus progressBar1.Value = nPercentage End Sub
Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load ' initialize the API audioAPI.InitEditor()
' predispose the callback that will notify about advancement of lengthy operations addrCallbackPercentage = New CallbackPercentage(AddressOf PercentageCallback) audioAPI.CallbackPercentageSet(addrCallbackPercentage)
' load a sound file Dim nResult As enumErrorCodes = audioAPI.LoadSound ("c:\myfile.mp3") If nResult = enumErrorCodes.ERR_NOERROR Then ' perform sound analysis audioAPI.DisplayWaveformAnalyzer.AnalyzeFullSound () End If
... do other stuffs End Sub End Class End Namespace
|
Visual C# |
using AudioSoundEditorApi;
namespace MyApplication { public partial class Form1 : Form { public Form1() { InitializeComponent(); }
// instance of the API AudioSoundEditorApi.AudioSoundEditorApi audioAPI = new AudioSoundEditorApi.AudioSoundEditorApi();
// delegate for managing callbacks invoked from secondary threads delegate void PercentageCallbackDelegate (enumOperationsWithPercentage nOperation, Int16 nPercentage);
// callback delegate CallbackPercentage addrCallbackPercentage;
// callback that manages percentage advancements void PercentageCallback(enumOperationsWithPercentage nOperation, Int16 nPercentage) { // check if we are being invoked from a secondary thread if (this.InvokeRequired) { // this callback is being invoked 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 PercentageCallbackDelegate(PercentageCallback), nOperation, nPercentage); return; }
string strStatus; switch (nOperation) { case enumOperationsWithPercentage.OPERATION_SOUND_LOADING: strStatus = "Status: Loading sound file... " + nPercentage.ToString() + "%"; break; case enumOperationsWithPercentage.OPERATION_WAVE_ANALYSIS: strStatus = "Status: Analyzing waveform... " + nPercentage.ToString() + "%"; break; default: return; } // safely update elements of the user interface LabelStatus.Text = strStatus; progressBar1.Value = nPercentage; }
private void Form1_Load(object sender, EventArgs e) { // initialize the API audioAPI.InitEditor();
// predispose the callback that will notify about advancement of lengthy operations addrCallbackPercentage = new CallbackPercentage(PercentageCallback); audioAPI.CallbackPercentageSet(addrCallbackPercentage);
' load a sound file enumErrorCodes nResult = audioAPI.LoadSound (@"c:\myfile.mp3"); if (nResult == enumErrorCodes.ERR_NOERROR) { ' perform sound analysis audioAPI.DisplayWaveformAnalyzer.AnalyzeFullSound (); }
... 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.