How to synchronize the container application with the control |
|
Some method available inside Active Sound Recorder 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:
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 BOOL_TRUE.
When using the synchronous mode, loading a sound file and immediately performing a waveform analysis may work like this (Visual Basic 6 code):
' set the synch mode activeSoundRecorder1.UseThreadsInSyncMode BOOL_TRUE
' load a sound file and check the result Dim nReturn as enumErrorCodes nReturn = activeSoundRecorder1.StartFromFile ("", "c:\myfile.mp3") If nReturn <> enumErrorCodes.ERR_NOERROR Then MsgBox "An error occurred" Exit Sub End If
' perform waveform analysis activeSoundRecorder1.WaveformAnalyzer.AnalyzeFullSound
' display the waveform on the analyzer activeSoundRecorder1.WaveformAnalyzer.SetDisplayRange 0, -1
|
In this case the waveform analysis would not start until the StartFromFile method doesn't return from its execution so, when calling the WaveformAnalyzer.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 WaveformAnalyzer.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 Stop method.
As a final note, it's very important to remember that a call to a method of a certain ActiveX control should be never performed from within a management function of an event generated by the same ActiveX control: this is usually cause of errors and dead-lock situations and it's a practice that should be always avoided.
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
Dim nReturn as enumErrorCodes nReturn = activeSoundRecorder1.StartFromFile "", "c:\myfile.mp3" activeSoundRecorder1.WaveformAnalyzer.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 StartFromFile 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 find on the table below:
RecordedSound.RequestDeleteRange RecordedSound.RequestExportToFile RecordedSound.RequestInsertSilence RecordedSound.RequestReduceToRange |
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, for example StartFromFile, StartFromFileEncrypted, StartFromMemory, 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:
• | RecordingStarted : 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. |
• | RecordingPerc : this event is generated during the loading session; the container application could catch it in order to modify the mentioned progress bar value. |
• | RecordingStopped : 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 ActiveX control should be never performed from within a management function of an event generated by the same ActiveX control: 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 RecordingStopped 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 methods available inside the RecordedSound COM object having the "Apply" suffix, for example RecordedSound.RequestDeleteRange, RecordedSound.RequestInsertSilence, RecordedSound.RequestReduceToRange, 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 string on the user interface informing.that the editing session in running. |
• | SoundEditDone : this event is generated immediately before closing the secondary thread. |
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 ActiveX control should be never performed from within a management function of an event generated by the same ActiveX control: 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.
The procedure below allows using a single one-shot timer that could manage some of the situations described above:
• | At design-time, from the VB6 toolbox, insert a Timer object inside the form containing the Active Sound Recorder 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 StartFromFile method, catch the RecordingStopped event and add the code below:
Private Sub ActiveSoundRecorder1_RecordingStopped(ByVal bResult As ACTIVESOUNDRECORDERLib.enumBoolean) ' check result If bResult = BOOL_TRUE then ' force analysis of the loaded sound g_strNextOperation = "AnalyzeSound" TimerSync.Enabled = True Else MsgBox "Sound failed to load with the following error code: " & ActiveSoundRecorder1.LastError End If End Sub
|
Now let's catch the timer's tick and perform requested operation
Private Sub TimerSync_Timer() ' disable the timer to avoid unwanted recursions TimerSync.Enabled = False
' start requested operation If g_strNextOperation = "AnalyzeSound" Then ActiveSoundRecorder1.WaveformAnalyzer.AnalyzeFullSound End If End Sub
|
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:
ActiveSoundRecorder1_WaveAnalysisDone(ByVal nTotalPeaksDetected As Long, ByVal fPeakDurationInMs As Single, etc.) ' force bitmap creation g_strNextOperation = "CreateWaveformBitmap" TimerSync.Enabled = True End Sub
|
At this point we can modify the handler of the TimerSync timer in order to manage the bitmap creation feature:
Private Sub TimerSync_Timer() ' disable the timer to avoid unwanted recursions TimerSync.Enabled = False
' start requested operation If g_strNextOperation = "AnalyzeSound" Then ActiveSoundRecorder1.WaveformAnalyzer.AnalyzeFullSound ElseIf g_strNextOperation = "CreateWaveformBitmap" Then ActiveSoundRecorder1.WaveformAnalyzer.SnapshotViewSaveToFile 0, -1, etc. End If End Sub
|
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.