Copyright © 2008-2023 MultiMedia Soft

How to synchronize the container application through events

Previous pageReturn to chapter overviewNext page

Some method available inside Audio Sound Editor 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 control performs lengthy operations inside secondary threads and you have the choice to decide if the threading model will work in asynchronous mode or in synchronous mode. Let's see the difference between these two modes:

 

Synchronous mode
Asynchronous mode

 

 

Synchronous mode

 

For historical reasons and in order to avoid breaking compatibility with past versions of the component, this is NOT the default mode so, in order to use the synchronous mode, you will have to enable it using the UseThreadsInSyncMode method with its bUseThreadsInSyncMode parameter set to true.

 

When using the synchronous mode, loading a sound file and immediately performing a waveform analysis may work like this (Visual C# code):

 

 

// set the synch mode

audioSoundEditor1.UseThreadsInSyncMode (true);

 

// load a sound file and check the result

enumErrorCodes nReturn = audioSoundEditor1.LoadSound (@"c:\myfile.mp3");

if (nReturn != enumErrorCodes.ERR_NOERROR)

{

   MessageBox.Show ("An error occurred");

   return;

}

 

// perform waveform analysis

audioSoundEditor1.DisplayWaveformAnalyzer.AnalyzeFullSound ();

 

// display the waveform on the analyzer

audioSoundEditor1.DisplayWaveformAnalyzer.SetDisplayRange (0, -1);

 

 

In this case the waveform analysis would not start until the LoadSound method doesn't return from its execution so, when calling the DisplayWaveformAnalyzer.AnalyzeFullSound method, you would be sure that the loaded sound is totally in memory and can be analyzed without problems; at the same time, the displaying of the waveform through the WaveformAnalyzer.SetDisplayRange method will not be executed until the waveform analysis, started through the DisplayWaveformAnalyzer.AnalyzeFullSound method, is not completed.

 

It's important to note that in this situation the container application's user interface will not be blocked and that the container application will continue receiving events generated by the component itself or by other elements of the user interface: this allows for example to update the value of a progress bar control during the loading of the sound file or during the waveform analysis and to cancel the sound loading by clicking a button whose event handler will call the LoadSoundCancel method.

 

As a final note, it's very important to remember that a call to a method of a certain .NET component should be never performed from within a management function of an event generated by the same .NET component: this is usually cause of errors and dead-lock situations and it's a practice that should be always avoided.

 

 

 

Asynchronous mode

 

This is the default mode adopted by the component since its first release: the main side effect of this multithreaded approach is the fact that, when the method call returns, the requested operation could still be running in background so requested data wouldn't be already available and any method call trying to access data would fail by returning an error code; this means that, for example, the code below will not work in most cases:

 

THE CODE BELOW WILL NOT WORK

 

enumErrorCodes nReturn = audioSoundEditor1.LoadSound (@"c:\myfile.mp3");

audioSoundEditor1.DisplayWaveformAnalyzer.AnalyzeFullSound ();

 

 

The reason why, in most cases, code above may not work is quite simple: when the WaveformAnalyzer.AnalyzeFullSound method is called, the secondary thread started by LoadSound method is still running in background so the waveform analysis doesn't have full sound data available; as a further note, the nReturn value only reports us if the launch of the secondary thread was performed without errors but will not tell us if the loading of the sound file was completed successfully; for this reason it's very important that the container application synchronizes itself with events fired by the control.

 

A list of methods that will start a secondary thread can be found on the table below:

 

AppendAutomationExecute

ConvertAutomationExecute

ConvertFile

ConvertFileRaw

DeleteRange

ExportAndSplitStereoChannelsToFile

ExportToFile

InsertSilence

JoinFilesFromDisk

LoadSound

LoadSoundEncrypted

LoadSoundFromClipboard

LoadSoundFromEditingSession

LoadSoundFromMemory

LoadSoundFromRawFile

LoadSoundFromRawMemory

LoadSoundFromRecordingSession

MixAutomationExecute

ReduceToRange

RemoveSilence

RequestUploadFileToFTP

RequestUploadSessionToFTP

SilencePositionsDetect

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

WaveformAnalyzer.AnalyzeFullSound

 

 

 

Let's make a couple of practical examples for having a better understanding of the issue:

 

Loading a sound file

 

The container application requests the loading of a sound file through a certain number of methods whose name begins with "LoadSound" for example LoadSound, LoadSoundEncrypted, LoadSoundFromMemory, etc: the call to one of these methods will start a secondary thread and will immediately return to the caller, meaning that the loading session will be still working in background when the method call will be completed;

 

The container application needs to synchronize its code through events generated by the component so loading advancement could be monitored through following events:

 

SoundLoadingStarted : this event is generated immediately before starting the secondary thread; the container application could catch it in order to display a hidden progress bar that could be used to notify the user about the loading session advancement.
SoundLoadingPerc : this event is generated during the loading session; the container application could catch it in order to modify the mentioned progress bar value.
SoundLoadingDone : this event is generated immediately before closing the secondary thread; the container application could catch it in order to know if the file was loaded successfully and to hide the progress bar.

 

After receiving the last event, the container application could request further operations with the loaded sound, for example it could start an editing session or to export the loaded sound in a different sound format.

 

It's very important to remember that a call to a method of a certain .NET component should be never performed from within a management function of an event generated by the same .NET component: this is usually cause of errors and dead-lock situations and it's a practice that should be always avoided; the best approach in cases like this would be using the SoundLoadingDone event to trigger a one-shot timer and to call methods for editing or exporting the sound file from within the management function of the timer's event.

 

 

Editing a sound file

 

The container application requests the editing of a sound file through a certain number of methods, for example through the ReduceToRange or DeleteRange methods or through methods available inside the EffectsMan class having the "Apply" suffix, for example Effects.CustomDspApply, Effects.DirectXApply, Effects.TempoApply, etc.: the call to one of these methods will start a secondary thread and will immediately return to the caller, meaning that the editing session will be still working in background when the method call will be completed; the container application will be notified about editing advancement through the following events:

 

SoundEditStarted : this event is generated immediately before starting the secondary thread; the container application could catch it in order to display a hidden progress bar that could be used to notify the user about the editing session advancement.
SoundEditPerc : this event is generated during the editing session; the container application could catch it in order to modify the mentioned progress bar value.
SoundEditDone : this event is generated immediately before closing the secondary thread; the container application could catch it in order to know if the file was edited successfully and to hide the progress bar.

 

After receiving the last event, the container application could request further operations with the edited sound, for example it could start an exporting session of the loaded sound in a different sound format.

 

 

Performing waveform's analysis of the loaded sound file

 

The container application requests the calculation of the sound's waveform through a call to the WaveformAnalyzer.AnalyzeFullSound method; when this method returns, the waveform is still being calculated in background and the container application will be notified about calculation advancement through the following events:

 

WaveAnalysisStart : this event is generated immediately before starting the secondary calculation thread; the container application could catch it in order to display a hidden progress bar that could be used to notify the user about the analysis advancement.
WaveAnalysisPerc : this event is generated during the calculation; the container application could catch it in order to modify the mentioned progress bar value
WaveAnalysisDone : this event is generated immediately before closing the secondary thread; the container application could catch it in order to hide the progress bar and, eventually, to display a message to the user

 

After receiving the last event, the container application could request further operations with the calculated waveform, for example it could request to obtain the bitmap representation of the waveform through a call to the WaveformAnalyzer.SnapshotViewSaveToFile method.

 

Also in this case it's very important to remember that a call to a method of a certain .NET component should be never performed from within a management function of an event generated by the same .NET component: this is usually cause of errors and dead-lock situations and it's a practice that should be always avoided; the best approach in cases like this would be using the WaveAnalysisDone event to trigger a one-shot timer and to call the WaveformAnalyzer.SnapshotViewSaveToFile method from within the management function of the timer's event.

 

 

As a latest suggestion, keep management functions of events generated by the component as fast/short as possible and never use them in order to display messages or dialog boxes which require user interaction.

 

 

How to create and use one-shot timers in C#

 

The procedure below allows using a single one-shot timer that could manage some of the situations described above:

 

At design-time, from the Visual Studio toolbox, insert a Timer object inside the form containing the Audio Sound Editor component: inside this sample it will be named TimerSync
At design-time, set the Enabled property of the timer object to False
At design-time, set the Interval property of the timer object to 50
Inside your code add a global variable of type "string" that you could use for knowing the next operation that needs to be performed immediately after receiving the timer's tick: inside this sample it will be named g_strNextOperation

 

Supposing that we have requested a loading session of a sound file through the LoadSound method, catch the SoundLoadingDone event and add the code below:

 

 

private void audioSoundEditor1_SoundLoadingDone(object sender, AudioSoundEditor.SoundLoadingDoneEventArgs e)

{  

   if (e.bResult == true)

  {

      // force analysis of the loaded sound

      g_strNextOperation = "AnalyzeSound";

      TimerSync.Enabled = true;

  }

   else

      MessageBox.Show ("Sound failed to load with the following error code: " + audioSoundEditor1.LastError.ToString ());

}

 

 

Now let's catch the timer's tick and perform requested operation

 

 

private void TimerSync_Tick(object sender, System.EventArgs e)

{

   // reset the timer to avoid unwanted recursions

  TimerSync.Enabled = false;

   

  // start requested operation

  if (g_strNextOperation == "AnalyzeSound")

      audioSoundEditor1.DisplayWaveformAnalyzer.AnalyzeFullSound ();

}

 

 

The code above starts the needed waveform analysis so, in order to know when this will be completed, we need to catch the WaveAnalysisDone event like this:

 

 

private void audioSoundEditor1_WaveAnalysisDone(object sender, AudioSoundEditor.WaveAnalysisDoneEventArgs e)

{

   // force bitmap creation

  g_strNextOperation = "CreateWaveformBitmap";

  TimerSync.Enabled = true;

}

 

 

At this point we can modify the handler of the TimerSync timer in order to manage the bitmap creation feature:

 

 

private void TimerSync_Tick(object sender, System.EventArgs e)

{

   // reset the timer to avoid unwanted recursions

  TimerSync.Enabled = false;

   

  // start requested operation

  switch (g_strNextOperation)

  {

  case "AnalyzeSound":

      audioSoundEditor1.DisplayWaveformAnalyzer.AnalyzeFullSound ();

      break;

  case "CreateWaveformBitmap":

      audioSoundEditor1.DisplayWaveformAnalyzer.SnapshotViewSaveToFile (0, -1, etc.);

      break;

  }

}

 

 

As you may understand, you could expand the timer's handler above for all operations executed inside secondary threads by adding new values for the g_strNextOperation variable.