Hi Jim,
I will put in multiple posts the all sound work.
I worked very hard on it. QSoundPlayer and QAudioOutput are ok for a game or another application but are very buggy at intensive use.
The main version is v1.0. After this, I will add the version v0.9 and tell only the differences.
Let's start...
VERSION 1.0
Main commands: Sound, SoundPlay, SoundPlayer, SoundLoad, SoundLoadRaw
Handle playing sounds: SoundPause, SoundStop, SoundPlayerOFF, SoundWait, SoundSeek
Info: SoundLength, SoundPosition, SoundState, SoundSampleRate, SoundID
Play options: SoundVolume, SoundFade, SoundLoop
Options to generate sounds: SoundHarmonics, SoundEnvelope, SoundWaveform
Sound (statement)
SOUND statement is a powerful and, in the same time, a versatile command.
It plays a sound from the computer's speakers and waits for it to finish.
The syntax is:
Frequency is expressed in Hz and duration is expressed in milliseconds (1000 in a second).
An array or list containing frequency and duration may also be passed. This eliminates any clicking between sounds when more than one is being output sequentially.
One dimensional arrays and lists must have an even number of values (frequency, duration...).
For polyphonic sounds data should be passed using two dimensional array or list of lists. Each row is a list of sequentially sounds, but all rows are played simultaneously. In case of polyphonic sounds, arrays and lists can have any number of rows but each row must have an even number of columns.
SOUND statement it also accept to play an audio file specified in file_name string . It support for several types of audio files like mp3 or wav. Other file formats may or may not play depending on the audio codecs installed on the computer. The specified file (file_name) can be a local file in which case it contains the path and file name (relative or absolute path), or a network file in which case it contains the URL, or a loaded sound. For loaded sounds check SOUNDLOAD statement.
Example playing a file
Using list of lists for polyphonic sounds give the opportunity to have a number of different sounds on every row / voice. Below is an example for adding a short additional voice.
Example 1 of monophony vs. polyphony
For a great fidelity of sound, SOUND statement accepts frequencies in float format (duration too), but it also accepts notes in human notation:
- Neo-Latin: Do, Re, Mi, Fa, Sol, La, Si
- English: C, D, E, F, G, A, B
- German: C, D, E, F, G, A, H
- Byzantine: Ni, Pa, Vu, Ga, Di, Ke, Zo
Format is the standard notation: Note[octave=4][accidentals]
Octave is 4, by default. So, SOUND "A", 1000 will play the A/La note (440Hz). This is the same as SOUND "A4", 1000.
For more informations about musical notation check: https://en.wikipedia.org/wiki/Musical_note
Accidentals can be:
b
- (flat) lowers it by the a semitone#
- (sharp) raises a note by a semitoneRare:
bb
- (double-flat) lowering it by two semitones##
- (double-sharp / non standard notation) raising the frequency by two semitonesSoundPlay (statement)
SOUNDPLAY - play a sound asynchronously.
SOUNDPLAY has almost the same syntax as SOUND statement, but instead of playing the sound and wait to finish it, it plays it asynchronously. Because it returns immediately without waiting for the sound to finish playing, it is a perfect solution for most applications that request sounds without freezing application during the sound playing.
To handle the sound just played, user can use SOUNDID function to get the last sound ID number which can then be stored in a variable so that user can then pause it or stop it with the appropriate functions. This means that if user has multiple instances of the same sound playing at any time he can target just one instance of that sound to deal with using the sound functions.
The syntax is:
At stop, it will release any system resources used and free the memory. Any further attempt to handle this sound (pause/play...) by using it’s ID will have no effect. This is the perfect solution for most games that need sound FX.
SOUNDPLAY statement can be also used to start or to resume playing the specified sound by ID. If the ID is not specified, the last sound ID created is used. If the specified sound is no longer available, nothing is happening.
SoundPlayer (function)
SOUNDPLAYER has the same syntax as SOUNDPLAY function but it is acting just like a media player. So, instead of playing the sound immediately, it is stopped by default. The sound can be played later anytime and how many times it is needed. The sound is not deleted at stop or when SOUNDPLAYER is reaching the end of the current sound, so it is the user job to manually delete it when no longer needs it by using SOUNDPLAYEROFF statement. After turning player off, any attempt to handle this sound (pause/play...) by using it’s ID will have no effect.
This function returns the sound ID (integer) for further manipulation (play, stop, pause, wait...).
The syntax is:
The returned ID is an integer and it is unique during the runtime of program.
SoundLoad (function)
SOUNDLOAD has the same syntax as SoundPlayer statement, but instead of playing the sound, it loads it into memory for further use. This is a function and returns the ID of loaded sound (string).
The syntax is:
Example
Why would I use SOUNDLOAD?
If a user use to play a sound over a network or to play it from a file, there are moments that program freezes for a very short/annoying period of times and play it with a noticed delay.
Loading sound in advance into memory guarantee fast access and no delay even for generated sounds using [frequency, duration] data.
Example
Playing loaded sound is faster, but the sound duration is in fact the same in both cases. So, why the first method takes a bit longer? The difference is that generating a sound from an array consumes a bit of CPU. In the second case the sound is already created. The difference in time is the time that CPU needs to create the sound from array. This can be visible to the user as a short freeze of program.
Loading sounds has many advantages.
• It is faster
• It is safer in case that sound file is hosted over the network
• Loaded sounds can store sounds generated at that time with different settings (harmonics, envelope, waveform)
• Loaded sounds can remember some playing options as SOUNDVOLUME, SOUNDFADE, SOUNDLOOP every time that resource is played
Example: set initial playing options directly to loaded sound instead to set those settings each time we play the same sound
To unload a sound see UNLOAD statement.
SoundLoadRaw (function)
SOUNDLOADRAW create a sound from raw data (a list of pressure values in range from -1.0 to 1.0). This gives the user endless possibilities to create any desired sound.
This is a function and returns the ID of loaded sound (string) which can be used as any other loaded sound.
The syntax is:
Examples (source: http://js.do/blog/sound-waves-with-javascript/)
To unload a sound see UNLOAD statement.
Handle playing sounds: SoundPause, SoundStop, SoundPlayerOFF, SoundWait, SoundSeek, SoundSystem
SoundPause (statement)
Pause playing the specified sound by ID.
The syntax is:
The sound_id is optional. If the ID is not specified, the last sound ID created is used.
If the specified sound is no longer available, nothing is happening.
SoundStop (statement)
Stop playing the specified sound by ID.
If it is a sound played by SOUNDPLAY function, the sound will be deleted immediately. In case of a SOUNDPLAYER, the sound is stopped and it reset the play position to the beginning.
The syntax is:
The sound_id is optional. If the ID is not specified, the last sound ID created is used.
If the specified sound is no longer available, nothing is happening.
SoundPlayerOFF (statement)
Turn off specified SOUNDPLAYER and delete it from memory. If ID points to a regular sound, it will be stopped just like SOUNDSTOP statement.
The syntax is:
The sound_id is optional. If the ID is not specified, the last sound ID created is used.
If the specified sound is no longer available, nothing is happening.
SoundWait (statement)
Wait for the playing sound specified by ID to complete.
The syntax is:
The sound_id is optional. If the ID is not specified, the last sound ID created is used.
If the specified sound is no longer available, nothing is happening.
SoundSeek (statement)
Seek to the specific playback position. Position to seek is expressed in decimal seconds.
The syntax is:
The sound_id is optional. If the ID is not specified, the last sound ID created is used.
If the specified sound is no longer available, nothing is happening.
SoundSystem (statement)
SOUNDSYSTEM is the great way to control all the sounds in the same time.
The syntax is:
SOUNDSYSTEM accept only those global commands:
0 - stop all playing sounds
1 - play/resume all sounds paused with previous soundsystem(2) command.
2 - pause all playing sounds
3 - stop and delete all sounds and players
4 - play/resume all paused sounds even sounds manually paused
Info: SoundLength, SoundPosition, SoundState, SoundSampleRate, SoundID
SoundLength (function)
SOUNDLENGTH - Returns the length in decimal seconds of the specified sound.
The syntax is:
The sound_id is optional. If the ID is not specified, the last sound ID created is used.
SoundPosition (function)
SOUNDPOSITION - Returns the position in decimal seconds of the specified sound.
The syntax is:
The sound_id is optional. If the ID is not specified, the last sound ID created is used.
SoundState (function)
SOUNDSTATE - Returns the playback status of the specified sound.
The syntax is:
The sound_id is optional. If the ID is not specified, the last sound ID created is used. If the specified sound is deleted, then the returned state will be 0-Stopped.
State values are:
0 Stopped
1 Playing
2 Paused
SoundSampleRate (function)
SOUNDSAMPLERATE - Returns the sample rate of the sound system. The default is 44100 but this can be changed from preferences menu.
The syntax is:
Sample Rate is the number of samples of a sound that are taken per second to represent the event digitally. The more samples taken per second, the more accurate the digital representation of the sound can be. For example, the current sample rate for CD-quality audio is 44,100 samples per second. This sample rate can accurately reproduce the audio frequencies up to 20,500 hertz, covering the full range of human hearing.
SoundID (function)
SOUNDID - return ID of the last sound created with SOUND, SOUNDPLAY or SOUNDPLAYER commands.
The syntax is:
Last edit: Florin Oprea 2017-03-19
Play options: SoundVolume, SoundFade, SoundLoop
SoundVolume (statement)
SOUNDVOLUME - adjust volume for specified sound.
The syntax is:
Levels must be numeric values from 0.0 (silence) to 10.0 (full volume). Values outside this range will be clamped. By default the volume is 10.0.
Because each sound has individual volume, this give the possibility to play in the same time multiple sounds at different volume or to fade/mute certain sounds without affecting the others sounds.
The sound_id is optional. If the ID is not specified, the last sound ID created is used.
If the specified sound is no longer available, nothing is happening.
IMPORTANT: SOUNDVOLUME can be also used to set the initial volume level for a loaded sound by using it´s ID. Any future play of this resource will be made by using this default setting.
Example: set initial playing options directly to loaded sound instead to set those settings each time we play the same sound
SoundFade (statement)
SOUNDFADE - gradual increase or decrease in the volume level of specified sound.
The syntax is:
Desired volume level must be numeric values from 0.0 (silence) to 10.0 (full volume). Values outside this range will be clamped.
Duration is the duration of the fade effect in seconds.
Delay is the delay in seconds before the effect begins.
The sound_id is optional. If the ID is not specified, the last sound ID created is used.
If the specified sound is no longer available, nothing is happening.
A SOUNDFADE effect can be canceled either by another SOUNDFADE command or by SOUNDVOLUME command.
IMPORTANT: SOUNDFADE can be also used to set this effect for a loaded sound by using it´s ID. Any future play of this resource will be made by using this default setting.
Example: set initial playing options directly to loaded sound instead to set those settings each time we play the same sound
SoundLoop (statement)
SOUNDLOOP - set the sound to loop a certain number of times. This statement doesn’t actually PLAY the sound loop, just sets it up for looping.
The syntax is:
By default, every sound is played once. This is equivalent to set the loop number to 1. Setting the loop number to 5 for example it will make the sound to play five times on the row.
Setting the loop number to 0 it will make the sound playing on a continuous loop, until it is stopped by user.
SOUNDLOOP can be applied to a sound that is currently played, or to a stopped SOUNDPLAYER. To do this the desired sound ID can be specified. If the ID is not specified, the last sound ID created is used.
If the specified sound is no longer available, nothing is happening.
Example
If SOUNDLOOP is applied to a sound that is currently played, then the specified number of loops will include the current loop also.
If SOUNDLOOP is applied to a SOUNDPLAYER it will remember the specified number of loops at every play.
Example - any fire FX is played 3 times
IMPORTANT: SOUNDLOOP can be also used to set the number of loops for a loaded sound by using it´s ID. Any future play of this resource will be made by using this default setting.
Example: set initial playing options directly to loaded sound instead to set those settings each time we play the same sound
Options to generate sounds: SoundHarmonics, SoundEnvelope, SoundWaveform
SoundHarmonics (statement)
SOUNDHARMONICS - add or remove harmonics.
A harmonic is a wave with a frequency that is a positive integer multiple of the frequency of the original wave, known as the fundamental frequency. The original wave is also called 1st harmonic, the following harmonics are known as higher harmonics.
For example, if the fundamental frequency is 200 Hz, the frequencies of the first three higher harmonics are 400 Hz (2nd harmonic), 600 Hz (3rd harmonic) and 800 Hz (4th harmonic).
In reality, almost every sound that you hear (unless it is created by some electronic laboratory instrument) produces these harmonics. They are, in fact, responsible for creating the different sounds that we hear. Harmonics contribute to what is called the “timbre” or tone color of an instrument making possible to distinguish between a violin, a trumpet and a flute. This is also true about the human voice–every voice is different, because every voice has a different harmonics pattern. While harmonics always sound as geometric multiples of a fundamental tone, different harmonics will be more amplified in different instruments and in different people.
The syntax is:
SOUNDHARMONICS without any argument will delete all harmonics that were set before and will restore the fundamental frequency’s amplitude (1st harmonic) to maximum value which is 1.0. This is the default state.
User can add a harmonic by specify the harmonic number and the amplitude of that harmonic. The amplitude range is from 0.0 to 1.0.
Example - clarinet sound
To delete a harmonic, just set the amplitude level to 0. It is possible to delete even the 1st harmonic, which is the fundamental frequency.
To add multiple harmonics at once, user can pass an array or a list of desired values like {harmonic, amplitude…}
Example - clarinet sound
Video link: https://youtu.be/YsZKvLnf7wU?list=PL0CF041F562C5BE5E
SoundEnvelope (statement)
SOUNDENVELOPE - change the amplitude envelope for future sounds that will be generated.
The syntax is:
ADSR Envelope elements:
• Attack (ms) - How quickly the sound reaches full amplitude (1.0) after the sound is activated
• Decay (ms) - How quickly the sound drops to the sustain level after the initial peak (the next level in the ADSR chain).
• Sustain (amplitude 0.0 - 1.0) - The "constant" volume that the sound takes after decay until the note is released. Note that this parameter specifies a volume level rather than a time period. Values outside the range of 0.0 to 1.0 will be clamped.
• Release (ms) - The time it takes for the note to fall from the sustain level to zero (silence) when released. If sustain level is set to 0.0, then the release time (ms) represents the time it takes for the note to fall from the maximum level of amplitude (1.0) to zero (silence).
Great resources to learn about it:
https://youtu.be/A6pp6OMU5r8
https://youtu.be/9niampRkFW0
Example - Jingle Bells - bells envelope
Example - Here Comes The Bride - pads envelope
Example - bonus effect
SoundWaveform (statement)
SOUNDWAVEFORM - change the waveform for future sounds that will be generated.
The syntax is:
To choose one of the preselected waveforms use it´s appropriate number: 0 - sinus, 1 - square, 2 - triangle, 3 - sawtooth, 4 - pulse. The default value is 0 (sine).
Example - sound waveform (build in types)
User has also the possibility to create and use its own waveform by using a one-dimensional array or a list of elements. Data can be a raw list of data, or a list of logical coordinates. The bool argument determines whether data are logical coordinates (true) or raw data (false). By default this is false which mean that data is a raw data.
Raw data
Each element is a pressure value of the waveform and can be in range -1.0 to 1.0. Values outside this range will be clamped.
Creating a custom waveform by using raw data is easy: just pass those values to SOUNDWAVEFORM statement using an array or a list of elements. The statement will use those values to create a real waveform. For example, SOUNDWAVEFORM {0, -1, -0.5, 1, 0.75} will create this waveform:
As you can see, the waveform is split in equal parts based on the number of data elements. Each segment starts from the pressure value stored in one data element from the list and go to the value from the following data element. The statement will connect all those points into strait lines and generate the waveform. But because sounds are composed by multiple repeated waveforms, the last value from list will be connected to the first value of the next waveform, so user do not need to close the waveform, it will be done automatically (see last red line).
Perfect sounds have a perfect sinusoidal wave. So using few pressure data this will generate sharp angles and the result can sound different that we expect.
Try to play with example from above to understand this fact.
List of logical coordinates
If bool is true, then the data is interpreted as a list of logical coordinates: {pressure, distance, pressure, distance…}. Each pressure value of the waveform can be in range -1.0 to 1.0. Values outside this range will be clamped. Each distance values shows how distanced are the two pressure values that separates them. The sum of all distances values give length of one complete cycle. Thus distances between pressure values are relatives because the waveform will be enlarged or shrunk to fit the sound frequency, but distances remain proportional to each other because those are calculated as a percentage of the wavelength.
So, to recreate the square waveform we can any value for the distance x as long the two values are the same
SOUNDWAVEFORM {1, x, 1, 0, -1, x, -1}, true
- whatever value it would have x it will still represent 50% of the wavelength.To recreate the standard triangle wave form we can use:
SOUNDWAVEFORM {0, 10, 1, 20, -1, 10, 0}, true
. The waveform starts from 0 and rises to 1 for a distance of 10 (25% of the wavelength); then starts to decrease to -1 for a distance of 20 (50% of the wavelength) which is twice as first; then rise back to 0 for a distance of 10 (50% of the wavelength).The sawtooth waveform can be recreate as:
SOUNDWAVEFORM {0, 5, 1, 0, -1, 10, 0}, true
. The waveform starts from 0 and rises to 1 for a distance of 5 (one-third of the wavelength); then sudden decrease to -1 (distance 0); then rise back to 0 for a distance of 10 (two thirds of the wavelength) which is twice as first.VERSION v1.0 vs. v0.9
In version 0.9 SOUNDPLAY is a function.
Because it is a function it returns the sound ID so we do not need SOUNDID anymore from v1.0.
But most of sound effects do not need later manipulaltion, so forcing user to assign every time a variable with sound ID is a bad habbit. I play a LOT with those statements/functions and I don't like SOUNDPLAY as a function.
In the same time because we need to play SOUNDPLAYER and to resume paused sounds played with SOUNDPLAY/SOUNDPLAYER this command (SOUNDPLAY) is a statement too, which is confusing. I did't find a better name...
In rest the versions are the same.
I like 100% version 1.0.
...and I hate v0.9...
General discussion
1) Most of the statements/commands use time values in ms format. Only SOUNDSEEK, SOUNDLENGTH, SOUNDPOSITION and SOUNDFADE use time in seconds.
Do you think it is ok or all statements/commands should use ms? Or it is ok like this?
2) BasicMediaPlayer.h/cpp can be dropped. All old functions/statements are properly emulated by new system.
3) SOUNDLENGTH and SOUNDPOSITION should have aliases as SOUNDLEN and SOUNDPOS?
4) I upgrade BasicDownloader:
- now follow redirects
- show error in case of 3xx, 4xx, 5xx errors
- emulate browser to acces more resources on web
5) I add a DROP function to STACK to quick drop a number of elements from stack. This is usefull to clear the stack when an array/list in stack is not needed anymore because of an error and we want to continue program using ONERROR or TRY/CATCH.
I also do several changes to clear the stack or to push expected result in case of errors to properly continue program in case of ONERROR or TRY/CATCH.
I will continue to improve the code (mixer), speed, stability, but I will not change syntax. Take in mind that Qt have several bugs in audio part. Some troubles about volume will be ok in 5.8 version.
I struggled a lot. QMediaPlayer and QAudioOutput have a lot of strange bugs/behaviours that shows only in certain extreme situations. I managed to avoid most of them.
The code is stable and the architecture is clear. Also the syntax is consistent and brings endless possibilities to user with few but powerful commands.
I liked your last idea, so I introduced SOUNDPLAYER, which is a great solution.
I put a lot of examples too...
Compile, play with it, enjoy it... tell me what you think...
Respectfully,
Florin Oprea