PBToolboxAI v5 ← Site

soundplayer — n_pbt_soundplayer #

← Component reference · Guide contents

Plays a sound or a piece of music — a file of the workstation or a URL — asynchronously (events) or synchronously (the call returns when the sound is over), and a pure beep without any file. Every format the WebView2 engine decodes: mp3, wav, ogg, opus, flac, aac, m4a, mp4, webm. No third-party DLL, no PlaySound limited to wav.

▶ See it live — Demo application, Sound player tile: the sounds, the code that plays them and this page, side by side.


At a glance #

Nonvisual objectn_pbt_soundplayer
Used forA chime before a message, an alert, a waiting music, an mp3 picked by the user, a stream from the Internet
PrincipleA hidden page carries a player; of_play returns at once and the events tell the rest, of_play_sync waits for the end of the sound
DependencyThe WebView2 runtime, already required by the library — nothing else

Quick start #

// In the window : the object
n_pbt_soundplayer inv_sound
inv_sound = create n_pbt_soundplayer

// Synchronous : the next line runs after the chime
inv_sound.of_play_sync(/*source*/ "C:\Windows\Media\chimes.wav")
MessageBox("Orders", "Order 4152 shipped.")

// Asynchronous : a music from the Internet, the window stays alive
inv_sound.ii_volume = 60
inv_sound.of_play(/*source*/ "https://example.org/music/lobby.mp3")

// When the window closes : the hidden player goes with the object
destroy inv_sound

The ue_* events are delivered on their own — the component drains them itself while the player is open and raises them on the component: nothing to wire, no receiver and no timer.

Where to write the events #

An object made by create n_pbt_soundplayer serves the synchronous sounds very well, and those started without waiting for news — it is what the quick start does —, but its events have no script: PowerBuilder cannot write ue_ended on an object created in a script. To react to the events, two ways:

Do not destroy the player from one of its own events: of_close is enough, and destroying the object does it for you.


File or URL, and why a long piece starts at once #


Properties #

PropertyTypeDefaultRole
ii_volumeinteger100Volume in percent, 0 to 100, handed over with the verb that starts a sound — and it is the volume of THAT sound: of_play, of_play_sync and of_crossfade set the main sound; of_play_over, of_beep and of_play_named play their own at it without touching the main sound (an alert at 100 over a background lowered to 20); a sound queued by of_enqueue keeps it for its turn. Changed while a sound plays, it waits for the next verb — only of_fade_to changes the sound playing
ib_loopbooleanfalseTrue to play the sound again and again until of_stop — a ring, a background music. Handed over with the next sound. of_play_sync ignores it: a synchronous sound plays once, a loop has no end to wait for. A looping sound never ends: the queue (of_enqueue) waits behind it until of_stop, of_next or of_stop_sound
il_timeout_mslong300000The longest a synchronous sound (of_play_sync, of_beep_sync, of_play_named_sync) may last; five minutes by default. Past it the call returns -4 and that sound is cut (ue_stopped follows) — that one only: a background, the overlays and the queue go on. 0 or less: 24 hours
ii_rateinteger100Speed in percent (25 to 400), handed over with the verb that starts a sound, for THAT sound: the main sound, or an overlay (of_play_over) without changing the main one
ii_paninteger0Balance of the MAIN sound, from -100 (left) to 100 (right), handed over with of_play, of_play_sync or of_crossfade, for FILES; a URL plays unbalanced
il_fade_mslong0Fade in milliseconds (10,000 at most): every start rises from silence, every of_stop or pause dies out, a resume rises again; ue_stopped still comes at once
ib_gapless_loopbooleanfalseWith ib_loop, a SHORT file of the workstation loops without a gap: decoded once, it joins seamlessly — a ring, a music bed. 4 MB and 60 seconds at most (past that, ue_failed with REASON_TOO_LONG); a URL loops the ordinary way. Handed over with the verb, like ib_loop
ib_mutedbooleanfalseEvery sound of the player silent at once — the main sound, the overlays, the beeps, the named sounds — ii_volume kept: false brings the sound back at the same level. Live
ib_queue_repeatbooleanfalseThe queue (of_enqueue) starts over after its last sound, again and again; of_next after the last goes to the first, of_previous before the first to the last. A queue whose sounds all fail stops anyway. Live
ipo_ownerpowerobjectnullThe visual object this player works for: the licence is checked on its class. Needed only in the demonstration application; a development or runtime key unlocks the player without it. Not unlocked, the player runs in demo mode — only the first 10 seconds of a sound are played

Methods #

MethodRole
of_get_last_error ( ) → stringWhy the last call failed: missing file, undecodable format, http:// address, silent URL, no audio output, number of a sound no longer under way. Filled by ue_failed too, and after every -5. After an of_play_sync that returned 0: "stopped" when the sound was cut rather than played to its end
of_open ( ) → longCreates the player. Optional — every method does it — but calling it when the window opens pays the cost once. Returns the handle (> 0), or -2 when the player could not be created (of_get_last_error() says why)
of_is_open ( ) → booleanTrue once the player exists
of_play (string as_source {, long al_start_ms}) → longPlays without waiting: a file of the workstation or an https URL, the end comes through ue_ended. al_start_ms starts the sound that far in (in demo mode, never past the first 10 seconds). The main sound playing is replaced, without an event; a beep or a named sound goes on. Returns 0 once started, -5 when the source is refused at once — empty, an http:// address, a malformed file:// address, a format refused by its name: of_get_last_error() says which, the sound playing goes on and no ue_failed follows —, -2 when the player could not be created, -4 when the page did not answer
of_play_sync (string as_source) → longPlays and waits for the end: the call returns when the sound is over (or stopped). The window keeps painting, but past 250 ms every PBToolboxAI component of the application shows the busy cursor and ignores the mouse until the call returns: keep it for short sounds. Plays once, whatever ib_loop says. Returns 0 once the sound is over (of_get_last_error() = "stopped" when it was cut), -2 when the player could not be created, -4 when it could not play, when il_timeout_ms elapsed (that sound alone is cut) or when another synchronous sound is already waiting
of_beep (long al_hz, long al_ms) → longA pure tone without a file: al_hz (20 to 20,000) for al_ms milliseconds (10 to 5,000), at ii_volume; a value beyond a bound is brought back to it. A channel of its own: of_play does not cut it, of_stop does. Returns at once; ue_ended follows. Returns 0 once started, -5 when al_hz or al_ms is 0 or less (an uninitialised variable does not sound a default beep), -2 when the player could not be created, -4 when the page did not answer
of_beep_sync (long al_hz, long al_ms) → longThe same tone, and the call returns once it is over: two in a row make a two-note signal. Returns 0, -5 when al_hz or al_ms is 0 or less, -2 when the player could not be created, or -4 without an audio output, when il_timeout_ms elapsed (the beep alone is cut) or when another synchronous sound is already waiting (of_get_last_error())
of_stop ( ) → longStops everything the player plays — the sound, a beep, a named sound, the overlays, a crossfade — and empties the queue; one ue_stopped follows, at once. With il_fade_ms, the main sound dies out over that time instead of stopping dead Returns 0 once done.
of_pause ( ) → longSuspends the sound where it is (over il_fade_ms); of_resume picks it up. Returns 0 once done, -5 when there is nothing to suspend — no main sound, or already paused; a beep or an overlay is not suspended —, -2 when the player could not be created, -4 when the page did not answer
of_resume ( ) → longPicks the sound up where of_pause left it. Returns 0 once done, -5 when no sound is paused, -2 when the player could not be created, -4 when the page did not answer
of_seek (long al_ms) → longMoves to a position of the sound, in milliseconds from its start. In demo mode, never past the first 10 seconds. To START from a position, of_play takes one: the player keeps no stopped sound to move in. Returns 0 once done, -5 when no sound is playing (nothing to move), -2 when the player could not be created, -4 when the page did not answer
of_is_playing ( ) → booleanTrue while a sound, a beep, a named sound or an overlay is under way — a paused sound counts, it has not finished
of_is_paused ( ) → booleanTrue while the sound is suspended by of_pause
of_duration ( ) → longThe length of the sound in milliseconds, 0 while unknown; ue_started carries it too
of_position ( ) → longWhere the sound is, in milliseconds — poll it from a timer to move a progress bar of yours
of_source ( ) → stringWhat of_play received last, verbatim
of_play_over (string as_source) → longPlays a sound ON TOP of what plays, without cutting it, at ii_volume and ii_rate — its own: the background keeps its own (a background lowered by of_fade_to stays low under an alert at 100); of_stop_sound stops it alone, of_stop with the rest. Returns the number of the sound (> 0) — its events carry it, two overlays of the same file are told apart by it —, -5 when the source is refused at once, -2 when the player could not be created, -4 when the page did not answer
of_enqueue (string as_source) → longQUEUES a sound: played at once when the player is idle, after the current sound (or beep, or named sound) otherwise, never cut. The queued sound keeps ii_volume, ii_rate and ib_loop as they are at the call, for its turn; a looping sound never ends, the queue waits behind it. A sound that cannot play raises ue_failed at its turn and the queue goes on. The queue keeps what it played: of_next, of_previous. Returns the number of the sound (> 0), -2 when the player could not be created, -4 when the page did not answer
of_clear_queue ( ) → longForgets the queue — the sounds waiting and those it already played —, without cutting the one playing; ue_queue_done still follows its end. Returns 0 once done, -2 when the player could not be created, -4 when the page did not answer
of_queue_count ( ) → longReturns the number of sounds still waiting
of_preload (string as_source) → longLoads a sound without playing it: the first of_play of that source starts at once. A file that turns out unreadable is reported by that of_play, through ue_failed. Returns 0 once done, -5 when the source is refused at once (of_get_last_error() says why), -2 when the player could not be created, -4 when the page did not answer
of_fade_to (integer ai_volume, long al_ms) → longBrings the volume to ai_volume (0 to 100) in al_ms ms on the sound playing; the target becomes ii_volume, and a sound played over it meanwhile does not interrupt the fade. Returns 0 once done, -2 when the player could not be created
of_play_named (string as_name) → longA SYNTHESISED sound, no file, at ii_volume: SOUND_SUCCESS, SOUND_ERROR, SOUND_WARNING, SOUND_INFO, SOUND_NOTIFY. A channel of its own: of_play does not cut it, of_stop does. Returns 0 once started, -2 when the player could not be created; an unknown name comes through ue_failed (REASON_UNKNOWN)
of_play_named_sync (string as_name) → longThe same sound, and the call returns at its end. Returns 0, -2 when the player could not be created, -4 on an unknown name, without an audio output or when another synchronous sound is already waiting (of_get_last_error())
of_has_output ( ) → booleanTrue when the workstation has an audio output at all: to ask BEFORE an alert. The sound always goes to the default output of Windows
of_equalizer (boolean ab_on {, integer ai_gains[]}) → long5-band equalizer (60 / 230 / 910 / 3600 / 14000 Hz, of_eq_frequencies gives the centers), gain in dB from -24 to 24 (a value outside is brought back to the nearest bound); the overload sets all gains (a band the array does not reach goes back to 0), of_equalizer_band sets one live. LOCAL files. Returns 0 once done, -2 when the player could not be created
of_equalizer_band (integer ai_band, integer ai_gain) → longSets the gain of ONE band (ai_band 1 to 5, ai_gain in dB, -24 to 24) and applies it at once to the sound playing — a slider of yours moved live; the other bands keep their gain. Returns 0, -5 when the band is out of range, -2 when the player could not be created
of_eq_frequencies (ref long al_freqs[]) → longFills al_freqs with the center frequency of each band, in Hz and in order (60, 230, 910, 3600, 14000) — what a band label of yours shows. Returns how many bands there are, 5
of_meter (boolean ab_on {, integer ai_bars}) → longTurns a VU meter / analyser on: then poll of_level (0 to 100) and of_spectrum (ai_bars bars) from a timer to draw your own visualiser. LOCAL files. Returns 0 once done, -5 when ai_bars is out of 1 to 64 (nothing is changed), -2 when the player could not be created
of_level ( ) → longReturns the loudness right now, 0 to 100, read live — a VU meter needle; 0 when the meter (of_meter) is off or nothing plays. Poll it from a timer
of_spectrum (ref long al_bars[]) → longFills al_bars with the spectrum, read live: as many values as of_meter asked for, 0 to 100 each, low frequencies first — the bars of an analyser of yours. Returns how many bars there are, 0 with the meter off
of_crossfade (string as_source, long al_ms) → longCrossfade: the current sound dies out over al_ms (10,000 at most) while the new source rises — a change of track with no gap; 0 switches at once. The new sound takes ii_volume, ii_rate, ii_pan and ib_loop, and an of_fade_to during the rise takes it over. of_stop during the fade stops both, and an of_play_sync waiting for the first sound returns; the sound replaced raises nothing. Returns 0 once started, -5 when the source is refused at once — the sound playing then goes on, untouched —, -2 when the player could not be created, -4 when the page did not answer
of_play_range (string as_source, long al_from_ms, long al_to_ms) → longPlays a SEGMENT of a file, from al_from_ms to al_to_ms — a bank of short sounds in one file, each played by its bounds. Replaces the main sound like of_play, with its settings; with ib_loop the segment plays again from its start. ue_ended at its end. Returns 0 once started, -5 when al_from_ms is negative, al_to_ms not after it, or the source refused at once, -2 when the player could not be created, -4 when the page did not answer
of_stop_sound (long al_sound) → longStops ONE sound by its number (of_play_over and of_enqueue return it, the events carry it): an overlay — the others go on —, the main sound (over il_fade_ms, the queue moving on to the next), a beep, a named sound; one ue_stopped follows. A sound still WAITING is taken out of the queue, without an event. Returns 0 once done, -5 when no sound has that number now, -2 when the player could not be created, -4 when the page did not answer
of_next ( ) → longPlays the NEXT sound of the queue now — the sound playing is replaced, without an event; with ib_queue_repeat, after the last comes the first. ue_source_changed says where the queue is. Returns 0 once done, -5 when there is no next sound, -2 when the player could not be created, -4 when the page did not answer
of_previous ( ) → longPlays the sound BEFORE the one playing in the queue — the queue keeps what it played; the queue idle, the last one played again. With ib_queue_repeat, before the first comes the last. Returns 0 once done, -5 when there is none, -2 when the player could not be created, -4 when the page did not answer
of_queue_index ( ) → longReturns the place in the queue (1 = the first sound queued) of the sound playing, 0 when that sound does not come from the queue or nothing plays
of_is_buffering ( ) → booleanTrue while the sound runs but waits for its data — the time ue_buffering announced: an Internet stream that stalls, a jump far into a file read from a URL. A file of the workstation never waits
of_process_events ( )Drains the event queue and raises them on the object. The component's own pump calls it for you while the player is open — you never call it
of_close ( ) → longReleases the player, stopping the sound first, at once (no fade): a sound that was playing ends on its ue_stopped, raised before the call returns; done for you when the object is destroyed. Do not destroy the player from one of its events: of_close is enough Returns 0 once done.
of_reset ( ) → longStops the sound and puts every setting back to its default Returns 0 once done.

Events #

EventWhen
ue_started (string as_source, long al_duration_ms, long al_sound)The sound really starts — a URL, once enough data has arrived. al_duration_ms is the length when the file says it, 0 otherwise; al_sound is the number of the sound
ue_ended (string as_source, boolean ab_truncated, long al_sound)The sound is over, of itself. ab_truncated is true when the demo limit cut it, never with a licence. A looping sound never ends: it stops. A main sound REPLACED by another (of_play, of_crossfade, of_next…) raises nothing: your script replaced it
ue_failed (string as_source, string as_message, string as_reason, long al_sound)The sound could not play — a source refused at once by a method is its -5, not this event. as_reason is a word to test, a REASON_* constant: REASON_INSECURE (http:// address), REASON_FORMAT (format refused by its name), REASON_UNSUPPORTED (missing file, malformed file:// address), REASON_NETWORK, REASON_DECODE, REASON_NO_SOURCE, REASON_UNKNOWN (no such named sound), REASON_UNAVAILABLE (no audio output), REASON_TOO_LONG (a gapless loop of a file too long for it), REASON_FAILED (anything else, a script error of the page included); as_message is the sentence to show, in the display language; al_sound the number of the sound. A queued sound that fails does not stop the queue
ue_stopped ( )Raised once when of_stop (or of_close, of_stop_sound, or the end of il_timeout_ms for the sound of that call) cut what was playing — not when a sound ends of itself, that is ue_ended
ue_paused ( ) / ue_resumed ( )The sound is suspended, then picks up again
ue_progress (long al_position_ms, long al_duration_ms)Once a second of real time while playing, whatever ii_rate: position and length
ue_queue_done ( )The last sound of the queue (of_enqueue) has ended — never with ib_queue_repeat, the queue starts over
ue_buffering (boolean ab_on)ab_on true: the sound runs but waits for its data — an Internet stream that stalls, a jump far into a file read from a URL; false: it runs again (or is paused, stopped, replaced, in error). A wait shorter than a quarter of a second says nothing
ue_source_changed (long al_index, string as_source)The queue moves to one of its sounds — of itself, by of_next, of_previous or ib_queue_repeat: al_index is its place in the queue (1 = the first sound queued), as_source what was queued

Several sounds, fades, queue #

// A background in a loop, faded in ; an alert over it, the background lowered meanwhile
inv_sound.il_fade_ms = 1500
inv_sound.ib_loop = true
inv_sound.of_play(/*source*/ "C:\Windows\Media\Ring05.wav")
inv_sound.of_fade_to(/*volume*/ 20, /*ms*/ 500)

// The alert has its own volume : the background stays at 20
inv_sound.ii_volume = 100
inv_sound.of_play_over(/*source*/ "C:\Windows\Media\Alarm01.wav")

// Announcements in a row : each one waits for the previous
inv_sound.of_enqueue(/*source*/ "order-4152.mp3")
inv_sound.of_enqueue(/*source*/ "order-4153.mp3")

// A confirmation with no file at all
inv_sound.of_play_named(/*name*/ n_pbt_soundplayer.SOUND_SUCCESS)
// Local variables
long ll_first

// Two alerts of the same file : each one has its own number
ll_first = inv_sound.of_play_over(/*source*/ "C:\Windows\Media\Alarm01.wav")
inv_sound.of_play_over(/*source*/ "C:\Windows\Media\Alarm01.wav")

// Later : the first one stopped alone, the other goes on
inv_sound.of_stop_sound(/*sound*/ ll_first)

// A queue that starts over after its last sound, and the next sound right now
inv_sound.ib_queue_repeat = true
inv_sound.of_next()

// A ring that loops without a gap, then everything silent for a moment
inv_sound.ib_loop = true
inv_sound.ib_gapless_loop = true
inv_sound.of_play(/*source*/ "C:\Windows\Media\Ring05.wav")
inv_sound.ib_muted = true

Examples #

A two-note signal, without any file #

// Each call waits for its tone : the second starts when the first is over
inv_sound.of_beep_sync(/*hz*/ 660, /*ms*/ 180)
inv_sound.of_beep_sync(/*hz*/ 990, /*ms*/ 220)

An mp3 picked by the user, with a progress bar #

// Local variables
string ls_path, ls_file
constant unsignedlong OFN_EXPLORER = 2
constant unsignedlong OFN_NOCHANGEDIR = 64

// Let the user pick a music file - in the Explorer style, without moving the current folder of the application - then play it
if GetFileOpenName("Music", ls_path, ls_file, "mp3", "Music (*.mp3;*.wav;*.flac),*.mp3;*.wav;*.flac", /*initdir*/ "", OFN_EXPLORER + OFN_NOCHANGEDIR) = 1 then
	inv_sound.of_play(/*source*/ ls_path)          // returns at once ; ue_started brings the length
	Timer(0.5)                         // in the window's timer event :
end if

// event timer of the window
if inv_sound.of_is_playing() then
	hpb_progress.Position = inv_sound.of_position() * 100 / Max(inv_sound.of_duration(), 1)
end if

A ring in a loop until the user acts #

// A ring at 40 %, in a loop until it is stopped
inv_sound.ii_volume = 40
inv_sound.ib_loop = true
inv_sound.of_play(/*source*/ "C:\Windows\Media\Ring01.wav")

// ... later, in the button that acknowledges the call :
inv_sound.of_stop()

Good practice #

← Component reference · Guide contents