Copyright © 2008-2018 MultiMedia Soft

How to synchronize the container application with the API

Previous pageReturn to chapter overviewNext page

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.

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

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

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

CallbackVuMeterValueChange

CallbackVuMeterValueChangeSet

The editor of a VST effect is resized

CallbackVstEditorResized

CallbackVstEditorResizedSet

A parameter of a VST effect has been modified through the editor of the VST

CallbackVstParamChanged

CallbackVstParamChangedSet

 

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

 

 

 

AppendAutomationExecute

CallbackPercentage and

CallbackAppendAutomationFilePerc

CallbackPercentageSet and

CallbackAppendAutomationFilePercSet

ExportAndSplitStereoChannelsToFile

ExportToFile

JoinFilesFromDisk

LoadSound

LoadSoundEncrypted

LoadSoundFromClipboard

LoadSoundFromEditingSession

LoadSoundFromMemory

LoadSoundFromRawFile

LoadSoundFromRawMemory

LoadSoundFromRecordingSession

MixAutomationExecute

RequestUploadFileToFTP

RequestUploadSessionToFTP

SilencePositionsDetect

SoundComposer.SessionComposeItems

WaveformAnalyzer.AnalyzeFullSound

CallbackPercentage

CallbackPercentageSet

DeleteRange

InsertSilence

ReduceToRange

RemoveSilence

TrimSilence

Effects.CustomDspApply

Effects.DcOffsetRemovalApply

Effects.DeClickFilterApply

Effects.DeNoiseFilterApply

Effects.DirectXApply

Effects.EqualizerApply

Effects.FilterApply

Effects.NormalizationSimpleApply

Effects.NormalizationAdvancedApply

Effects.PitchApply

Effects.PlaybackRateApply

Effects.ReverseApply

Effects.TempoApply

Effects.VolumeAutomationApply

Effects.VolumeFlatApply

Effects.VolumeSlidingApply

Effects.VstApply

CallbackEditPerc

CallbackEditPercSet

ConvertAutomationExecute

ConvertFile

ConvertFileRaw

CallbackConvertFilePerc

CallbackConvertFileDone

CallbackConvertFilePercSet

CallbackConvertFileDoneSet

PlaySound

PlaySoundRange

PauseSound

ResumeSound

CallbackSoundPlaybackStatusChanged,

CallbackSoundPlaybackDone and CallbackVuMeterValueChange

CallbackSoundPlaybackDoneSet, CallbackSoundPlaybackStatusChangedSet and CallbackVuMeterValueChangeSet

A new spectral analysis is requested

CallbackWaveAnalyzerSpectralViewStart

CallbackWaveAnalyzerSpectralViewDone

CallbackWaveAnalyzerSpectralViewStartSet

CallbackWaveAnalyzerSpectralViewDoneSet

 

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.