REBOL
Docs Blog Get-it

REBOL Sound Ports

SDK Documentation

Contents

Overview
Supported Platforms
Quick Example
Important Event Handling Requirement!
Loading WAV Files
Using Sound Ports
Opening a Sound Port
Closing a Sound Port
Playing a Sound Sample
Waiting for a Sound Port
Clearing the Sound Port
Example: Play All Wave Files
Format of Sound Objects
Synthesizing Sounds
A Simple Tone
A Ping Sound
Bouncing Ping
Variations on a Ping
Handling Different Sound Formats
Types of Sound Interface Errors
No View/Pro or Command License Key
No Sound Device
Invalid Wave File Format
Did Not Initialize Event System
Catching Errors

Overview

This document describes how to use sound ports in REBOL/View/Pro and REBOL/Command/View. It includes information about:

Please send any comments, suggestions, and useful example code to us by using our feedback form.

Supported Platforms

The following REBOL platforms currently support sound:

Platform Version/Info
Windows XP, NT, 2000, 9X on x86 processors.
Linux libc6 on x86 processors with Linux Open Sound System. Linux kernel versions 2.2.x and higher have OSS support built in (assuming the kernel was compiled with such support enabled, and the correct driver is enabled). For some older kernels OSS is available separately, from http://www.opensound.com.
AmigaOS AHI and audio.device are supported. If AHI is installed then REBOL uses AHI (default Music unit), otherwise REBOL uses audio.device.

Sound support for Apple Macintosh OS X is also planned and will be provided when REBOL/View and related products are released for OS X.

Quick Example

Here is a quick example for the impatient that demonstrates how to use sound samples with REBOL:

print "Playing chimes..."

; Load the sound file data into memory:
ring: load %/c/winnt/media/chimes.wav

; Open access to the sound device:
sound-port: open sound://

; Play the sound:
insert sound-port ring

; Wait for the sound to finish:
wait sound-port

; Close the sound device:
close sound-port

Note that you must provide the PRINT function above, or this example will fail. See the next section for details.

Important Event Handling Requirement!

Before you can play a sound, you must initialize REBOL's internal event handler. There are a two ways to do so:

If you don't do either of the above, an error will occur when you try to play the sound with INSERT. The error will look like this:

** Access Error: Cannot open sound
** Near: insert sound-port ring

Here is an example that plays a sound without opening a window:

wait 0
ring: load %/c/winnt/media/chimes.wav
sound-port: open sound://
insert sound-port ring
wait sound-port
close sound-port

If you don't hear your sound and you get the error above, be sure to reread this section!

Also: we plan to make this requirement unnecessary in future releases of REBOL.

Loading WAV Files

WAV files (sound files in ".wav" format, customary in Windows) can be loaded as follows:

wav: load %ping.wav

The returned value is an object! that contains the sound sample and associated control information (sample rate, number of channels etc.). See below for more information on sound objects.

At this time only a subset of the full WAV standard is supported. The supported formats include 1-channel and 2-channel PCM samples, in 8 or 16 bits, signed or unsigned, any sample rate.

Compressed WAV formats (Mu-Law, A-Law, AD-PCM etc.) are not currently supported, but we plan to add them in future versions of REBOL.

Trying to load a WAV file in an unsupported format causes an error.

In order to load such a file into REBOL it has to be converted to regular PCM WAV format first. There are several utility programs available for different platforms that allow conversion of sound files, e.g. "sox" for Unix and Windows (http://sourceforge.net/projects/sox/). "sox" can also be used to convert files in other formats (Sun ".au" format, Amiga IFF-8SVX format etc.) to WAV format.

Using Sound Ports

Opening a Sound Port

To open a sound port:

sound-port: open sound://

No other options are necessary. The sound port automatically adjusts its parameters to sound samples (rate, number of channels etc.) when samples are played. Only one sound port can be open at any time.

Be sure to read the note above about initializing the event system.

Closing a Sound Port

To close a previously opened sound port:

close sound-port

If a sound sample is currently being played then REBOL tries to abort it as quickly as possible (varies by platform).

Playing a Sound Sample

To play a sound through a previously opened sound port:

insert sound-port wav

Inserting a sound object! into a sound port causes the sound sample to be played. If the sound port is inactive at the time of the INSERT call then the call returns immediately, and the sound starts playing in the background. If the sound port is already playing a sound then the INSERT call blocks until the current sound has finished playing. After that INSERT returns and the new sound starts playing in the background.

For example, the code:

insert sound-port sound1
insert sound-port sound2
insert sound-port sound3

will play each sample in turn after the prior sample has completed (as long as they use the same sound parameters, see note below). REBOL will block (automatically wait) while each sample is playing. If you want to play multiple sounds, but do not want to block, you must use WAIT as described below.

When you INSERT multiple sounds, they must all use the identical sound parameters (such as their sample rate). For example, you cannot intermix samples of different rates with the INSERT command. However, you can use the CLEAR command to reset the sound port, see examples below.

Waiting for a Sound Port

To wait for a sound to finish:

wait sound-port

The WAIT call returns after the current sound has finished playing. You can also wait for other ports and a timer while a sound is playing. For example:

result: wait reduce [3 sound-port net-port]

will wait for the sound port, the network port, or timeout in 3 seconds. The port that completed the wait will be returned. If the timeout occurred, a NONE value will be returned.

result: wait reduce [3 sound-port net-port]
switch reduce [
    sound-port [play next sound]
    net-port [do network stuff]
    none [print "timeout"]
] result

Note that REDUCE is used to convert REBOL variables to their values before the SWITCH is done (otherwise you will be switching on WORDS not their VALUES).

Clearing the Sound Port

The CLEAR function can be used to stop a sound sample that is playing and reset the sound port.

To stop a sound that is playing:

clear sound-port

CLEAR attempts to abort the currently playing sound and return immediately.

The CLEAR function is also used to reset the sound port if you want to play sounds that use different parameters, such as sample rates. See the example below.

Not all platforms allow sounds to be aborted at all times.

Example: Play All Wave Files

The following example will play all valid wave files (.wav) found in the current directory. If the wave file format is not supported by REBOL (for instance, it may be in one of many different compressed formats) a short error message will be displayed, and the program will go to the next file in the directory.

print "Play all files..." ; (also inits the event system)

sound-port: open sound://

foreach file load %. [
    ; Is the file a wav file?
    if all [
        suffix: find/tail file ".wav"
        tail? suffix
    ][
        prin ["Playing:" file "..."]
        if error? err: try [
            wav: load file
            insert sound-port wav
            wait sound-port
            clear sound-port
            print ""
            true
        ][
            probe disarm err
            print " (invalid format)"
        ]
    ]
]

close sound-port

Note that the sound port must be cleared after each sound in order to properly reset the sound device to prepare it for sound that use different parameters, such as different sample rates.

Be sure to read the note above about initializing the event system.

Format of Sound Objects

Sound objects currently contain the following fields:

type: 'sound                  ; identifies the object! as a sound sample
rate: integer!                ; sample rate, e.g. 44100
channels: integer             ; number of channels: 1 or 2
bits: integer!                ; number of bits per sample: 8 or 16
volume: integer! or decimal!  ; volume, between 0 and 1.
data: binary!                 ; the sound sample itself

REBOL has a template object called "sound", defined in the global context, that can be used to create new sound objects:

wav: make sound [
    rate: 22050
    channels: 1
    bits: 8
    volume: 0.8
    data: #{80808080ffff0000ffff000080...}
]

The "data" binary contains the raw, uncompressed sound sample in unsigned, big-endian PCM format, 8 or 16 bits per sample. This means that the "quiet point" is at #{80} for 8 bits and #{8000} for 16 bits. The maximum value is #{ff} for 8 bits and #{ffff} for 16 bits, the minimum value is #{00} for 8 bits and #{0000} for 16 bits. For sound samples with two channels both channels are interleaved, i.e. samples for each channel alternate within the binary.

DO NOT create sound objects completely from scratch by using make object! because the structure of a sound object is subject to change in future versions of REBOL. Always create new sound objects by cloning the global "sound" object, as shown above.

Synthesizing Sounds

You can also synthesize your own sound wave files in REBOL for creating unique sounds. This can be handy in those cases where you want to play a special sound in your REBOL application to get the user's attention, but you don't want to take extra disk space (or network transfer time) for the sound sample file.

Be sure to read the note above about initializing the event system.

A Simple Tone

The example below will generate a simple sine wave tone and play it at half volume and a rate of 44100 samples per second.

print "Play a simple tone..." ; (init event system too)

; Generate a sine wave tone (1 cycle of the wave):
tone: #{}
for phase 1 360 6 [
    val: 128 + to-integer 127 * sine phase
    append tone to-char val
]

; Set up the sound sample parameters:
sample: make sound [
    rate: 44100
    channels: 1
    bits: 8
    volume: 0.5
    data: #{}
]

; Repeat the sine wave for 1000 cycles:
loop 1000 [append sample/data tone]

; Play the tone now:
sound-port: open sound://
insert sound-port sample
wait sound-port
close sound-port

This example works by filling a binary string with values that range from 0 to 255 in a sine wave pattern, then using that binary string as the waveform to play. The sine wave is repated 1000 times.

A Ping Sound

This example alters the above tone to give it an simple envelope of decreasing amplitude (volume). The result is a nice ping sound. The ping is then repeated 10 times:

print "Play a ping sound..." ; (init event system too)

; Generate a ping waveform as a binary value:
ping: #{}
for amplitude 200 1 -1 [
    for phase 1 360 16 [
        val: 128 + to-integer 127 * sine phase
        val: amplitude * val / 200
        append ping to-char val
    ]
]

; Set up the sound sample parameters:
sample: make sound [
    rate: 44100 / 2
    channels: 1
    bits: 8
    volume: 0.5
    data: #{}
]

; Make the ping sound 10 times:
loop 10 [append sample/data ping]

; Play the sound now:
sound-port: open sound://
insert sound-port sample
wait sound-port
close sound-port

Of course, you can also do this with less memory by repeating the INSERT 10 times rather than creating a sample that has the same data repeated 10 times.

Bouncing Ping

Here's what happens when you drop a ping on the ground:

print "Generating sound..."
bounce: #{}
repeat modulate 20 [
    delta: negate log-2 modulate
    for amplitude 400 1 delta [
        for phase 1 360 16 [
            val: 128 + to-integer 127 * sine phase
            val: amplitude * val / 400
            append bounce to-char val
        ]
    ]
]

; Set up the sound sample parameters:
sample: make sound [
    rate: 44100 / 2
    channels: 1
    bits: 8
    volume: 0.5
    data: bounce
]

; Play the sound now:
sound-port: open sound://
insert sound-port sample
wait sound-port
close sound-port

Variations on a Ping

This example creates three different ping sounds, then plays them first in a sequence then in combination.

print "Generating sounds..."

; Generate a ping waveform as a binary value:
make-ping: func [len pitch /local ping] [
    ping: copy #{}
    for amplitude len 1 -1 [
        for phase 1 360 pitch [
            val: 128 + to-integer 127 * sine phase
            val: amplitude * val / len
            append ping to-char val
        ]
    ]
    ping
]

ping1: make-ping 200 20
ping2: make-ping 400 30
ping3: make-ping 600 40

; Set up the sound sample parameters:
sample: make sound [
    rate: 44100 / 2
    channels: 1
    bits: 8
    volume: 0.5
    data: #{}
]

print "Arrange pings in a sequence:"
loop 4 [
    append sample/data ping1
    append sample/data ping2
    append sample/data ping3
    append sample/data ping3
]

sound-port: open sound://
insert sound-port sample

print "Merge pings to play at same time:"
clear sample/data
loop 20 [append sample/data ping1]

; Average the three ping sounds together:
data: sample/data
forall data [
    d1: first data
    d2: first ping2
    d3: first ping3
    change data to-char d1 + d2 + d3 / 3
    ping2: next ping2
    if tail? ping2 [ping2: head ping2]
    ping3: next ping3
    if tail? ping3 [ping3: head ping3]
]

; Queue next sound:
insert sound-port sample

wait sound-port
close sound-port

Handling Different Sound Formats

REBOL supports a large variety of sound formats (8 or 16 bits, one or two channels, different sample rates). Not all sound hardware supports all of these formats, i.e. in some situations a sound may have to be converted to a different format before it can be played.

Depending on the operating system and sound driver this conversion is sometimes done transparently, by the operating system, but at other times it may have to be done by REBOL. REBOL makes the following conversion attempts in order to play a sound:

In most cases conversion results in a playable sound, but there are exceptions, e.g. if your sound hardware only supports sounds with 8000 samples per second, the operating system does not support sound conversion, and you are trying to play a sound recorded at 22050 samples per second. In that case trying to INSERT the sound into a sound port causes an error.

If REBOL successfully converts a sound in order to play it then this conversion will be visible in the sound object inserted into the port, e.g. assume you are trying to play a 16-bit sound on hardware that only supports 8 bits, and the operating system provides no conversion:

>> snd: load %mysound.wav
>> snd/bits
== 16
>> sound-port: open sound://
>> insert sound-port snd
== 10000
>> snd/bits
== 8

The sound was converted from 16 bits to 8 bits as the result of inserting it into the sound port. Making the conversion permanent in the sound object is intentional. It has the advantage that if the same sound is played later, it does not have to be converted again.

Types of Sound Interface Errors

No View/Pro or Command License Key

If you try to play a sound without a REBOL license file, you will receive this error message:

** Script Error: Feature not available in this REBOL
** Near: sound-port: open sound://

Note: newer versions of REBOL will not require the license.key file in order to play sound.

No Sound Device

If your system does not have a sound device, or if REBOL cannot interface properly to your sound device, you will see the following error when you attempt to play a sound:

** Access Error: Cannot open sound
** Near: insert sound-port ring

You can catch this error in the same way you catch other errors. See the error catch section below.

Invalid Wave File Format

If REBOL does not recognize the format of the wave file, it will throw an error message such as:

** Access Error: Bad image data
** Near: load %zipidee.wav

This happens if the wave file is a format that REBOL cannot load, such as one of many wave compressed formats. You will need to convert the file to an uncompressed format (see above).

You can trap these errors in loading wave files with code such as this:

if error? try [
    wav: load %soundfile.wav
][
    print "Bad wave format"
]

Did Not Initialize Event System

If you did not initialize the event system by opening the console window (with something like a PRINT) or by calling WAIT (with zero as the argument), or by calling DO-EVENTS, you will see this error:

** Access Error: Cannot open sound
** Near: insert sound-port ring

Catching Errors

You can catch any or all of the above errors with the TRY function. Here is a simple example:

if error? err: try [
    wav: load file
    insert sound-port wav
    wait sound-port
    clear sound-port
    true
][
    print ["ERROR:" mold disarm err]
    ask "Press return to continue"
]

This example will catch errors when loading the wav file (e.g. an unsupported wave format) or when playing the sound (e.g. no sound device). It will display information about the error.

About | Contact | PrivacyREBOL Technologies 2024