The advanced SonorSound module guide

© 7th ARM Systems/VTi 1994-5

Written by Jason Tribbeck
HTML version by © Jason Tribbeck 10th June 1999

Aims of this guide

This document provides advanced technical information about the SWIs contained in SonorSound version 1.01 (21st February 1995). If the module is incorporated in any released application (PD, ShareWare, or commercial), then it must be acknowledged somewhere visible by the customers. A simple line in the !Run file is acceptable, or in the !Help, but must be on the lines of:

SonorSound module provided by 7th ARM Systems © 1995

Conventions

There are several conventions used throughout this guide:

SonorSound_Status (&48D40)

On entry:

-

On exit:

R0 is 0 if no sound is being produced
R1 is 0 if a buffer is waiting to be filled
R2 is sample playing speed
R3 is maximum buffer size
R4 is voice number
R5 is special number (usually task handle of WIMP application)

This call is used to find out about SonorSound's internal setup, and what needs to be played. For example,

if you wanted to find out if you could play, then:

        SWI     XSonorSound_Status
        TEQ     r0,#0              ; Is there sound being played?
        BNE     No_Can_Play
...

Or, if you wanted to find out if you needed to fill the buffer:

        SWI     XSonorSound_Status
        TEQ     r1,#0
        BEQ     FillBuffer

This call forms the major part of the buffering system employed by Sonor, which is done under NULL Wimp_Polls. The entire sequence of events is shown later.

SonorSound_SendBuffer (&48D41)

This is the same as in the manual. The special number in R2 is usually the task handle of your WIMP task. It is returned in R5 when calling SonorSound_Status. It is used so you can find out which task is currently playing the sample, and thus prevent clashes.

SonorSound_SetSpeed (&48D42)

On entry:

R0 is speed (1<<24 is 20.833KHz at 24MHz VIDC speed and 48µs DMA rate)

On exit:

Registers preserved.

If a VGA monitor is being used on a post 540 machine, then the VIDC rate is different, and thus you would have to find out what rate to play at. In addition, you also need to know the VIDC sound DMA rate. There are documented calls in the RISC OS 3 PRMs to do these things. Note that if you are using RISC OS 2, (but not RISC OS 2.01), you do not know the VIDC rate; assume it is 24MHz. Similarly, the RISC PC uses a 24MHz clock all the time, regardless of video clock speed. This may change with the advent of 16-bit sound sources.

The following will give you some of the values you require:

The formula used to calculate the playing speed is as follows:

R0 = (<required speed in Hz>/20833)*(<video speed in KHz>/24000)*(<dma rate>/48)*(1<<24)

SonorSound_Sample (&48D44)

On entry:

R0 is pointer to memory
R1 is length of sample (bytes)
R2 is period (2,000,000/frequency)
R3 is pointer to 4096-byte translation table (0 if linear values required)

On exit:

Registers preserved.

This call samples into memory, using an optional translation table. The maximum period is 65535, and the minimum is dependant on the sampler. Values below 10 must be avoided. A value 65535 corresponds to a frequency of around 30Hz.

The translation table is basically a 12-bit linear to µ-law logarithmic table. You can use the file ”Logs• inside the Sonor directory. This is a pre-calculated conversion table. The sound you will sample will be in logarithmic format, so you can play it out directly through the computer's speaker.

SonorSound_GetOffset (&48D45)

On entry:

-

On exit:

R0 is current position in the buffer.
R1 corrupted

This call returns the offset within the current buffer. The best way to calculate the offset within the sample is to have two variables, one pointing to the currently playing memory location, and another to the next playing memory location. An example of this is given later.

SonorSound_CancelBuffer (&48D46)

On entry:

-

On exit:

Registers preserved

This call stops sound production, and clears the buffers. It is different to SonorSound_Reset, in that it does not forget the special word, provided by R2 in SonorSound_SendBuffer.

SonorSound_SampleByte (&48D47)

On entry:

-

On exit:

R0 is 8-bit value left shifted two places.

This call is used by Sonor to build the internal tables for the Oscilloscope In and FFT In windows. There may be problems with slow samplers, if this is called too quickly. In general use, no problems should occur.

SonorSound_ResetDevice (&48D48)

This is an historical call that must not be called.

SonorSound_FIRFilter (&48D49)

On entry:

R0 is pointer to sample
R1 is pointer to end of sample+1
R2 is pointer to linear to logarithmic table (4096 bytes long)
R3 is pointer to logarithmic to linear table (256 words long)
R4 is pointer to fixed-point FIR elements (21 words long, 16-bit fixed point notation)
R5 is hourglass percentage calculation

On exit:

Registers preserved

This call performs FIR filtering on the sample, between the desired positions. FIR means Finite Impulse Response, which is a standard, well defined technique for filtering as used by DSP device (DSP = Digital Signal Processor). It is a lot faster than performing FFT, filtering, and then inverse FFT sections, and is much more predictable.

The linear to logarithmic table is the same as used in SonorSound_Sample, while the logarithmic to linear table is found inside Sonor as ”AntiLogs•.

A fixed-point notation means that the floating-point values are multiplied by 65536, and then converted into integers, so that 0.5 would be represented by 32768.

The parameters for high-pass and low pass filters are as follows:

WordL.P.H.P.
WordL.P.H.P.
00.0000-0.0227
10.0354-0.0194
20.0000-0.0075
3-0.04550.0601
40.0000-0.0232
50.0637-0.0477
60.0000-0.0077
7-0.10610.1214
80.0000-0.0234
90.3183-0.3026
100.50000.4922
110.3183-0.3026
120.0000-0.0234
13-0.10610.1214
140.0000-0.0077
150.0637-0.0477
160.0000-0.0232
17-0.04550.0601
180.0000-0.0075
190.0354-0.0194
200.0000-0.0227

If you want to do your own filters, then you would have to refer to a text book. There are numerous books on FIR filters (and also DSP solutions) available.

The hourglass percentage value is basically the number of bytes to convert before the hourglass percentage goes up by one. For example, if the sample size was 65536 bytes, then this value would be 655.

SonorSound_DCT (&48D4A)

On entry:

R0 is pointer to samples (converted to words)
R1 is pointer to frequency data (filled in by SWI)

On exit:

Registers preserved

DCT (Discreet Cosine Transfer) transforms are similar to FFTs in that they take linear data, and converts it to frequency data. The input data must be words, so the following code fragment would suffice:

DEFPROCdct(A%,B%) : REM A% is pointer to source, B% is pointer to result
LOCALT%
FORT%=0TO63
  temp%!(T%<<2)=antilog%?(A%?T%) : REM antilog% is address of conversion table
NEXT
SYS"SonorSound_DCT",temp%,B%
ENDPROC

The resulting table has the frequency results from 0 Hz at +0, to f/2 Hz at +63 (words).

This SWI is used by the FFT displays in Sonor.

SonorSound_Information (&48D4B)

On entry:

R0 is device slot number (0-31, -1 means current)

On exit:

R0 is pointer to device's name (in RMA, 0 means no device loaded in that slot)
R1 is flag (0 means no new devices since the last call to SonorSound_Information)
R2 is device's special word
R3 is device's maximum sample rate

This call allows you to find out which samplers are currently available, or the current device's maximum sample rate. You can use this information to build menus (as in Sonor), or to warn the user if the sample rate exceeds that specified for the sampler (also as in Sonor).

The device's special word is a 4-byte identifier, unique to each device driver. Current driver names are:

MSMP&504D534DVTi MIDI Sampler driver
PSmp&706D5350VTi Printer Port sampler (A400... and A5000... types)
PPS2&32535050VTi Printer Port sampler (fix for certain A400-type machines)
FAST&54534146Fast VTi Printer Port Sampler
PS12&32315350ARM Designs 12-bit Printer Port sampler (not currently available)
TB12&32314254ARM Designs Turbo 12-bit Printer Port sampler (not currently available)
GTi5&35695447VTi Printer Port Sampler A5000 Turbo

SonorSound_SelectDevice (&48D4C)

On entry:

R0 is sampler slot (0-31)

On exit:

Registers preserved

This call allows you to select which sampler to use. The slot must exist.

SonorSound_LoadDevice (&48D4D)

On entry:

R0 is pointer to filename, or ”- <filename>•.

On exit:

Registers preserved.

This call allows you to load in a new device driver into memory. If the filename is preceded by a ”- • (space must be present), then the device will not be loaded if there already one loaded with the same sampler special word. If it is missing, the driver will be loaded.

SonorSound_RegisterDevice (&48D4E)

On entry:

R0 is pointer to device

On exit:

Registers preserved

This call allows a device driver to be loaded from memory. It must reside in RMA. (The manual is incorrect).

SonorSound_FindDevice (&48D4F)

On entry:

R0 is sampler special word

On exit:

R0 is driver slot number, or -1 if device driver not installed

This call is used by Sonor to find out if the default driver is installed.

The following SWIs were introduced in version 1.01

SonorSound_TCJSample (&48D50)

On entry:

R0 is pointer to memory to hold sample
R1 is length of memory
R2 is period (2,000,000/frequency)
R3 is +/- value for auto-trigger
R4 is +/- value for auto-finish
R5 is auto-trigger count
R6 is auto-finish count

On exit :
R1 is number of samples left in memory

This call is similar to SonorSound_Sample, but allows automatic start/stopping of samples. It also performs a guaranteed 2:1 compression using delta compression, which is calculated on the fly. It was written for the Talking Canvas Junior application, and is not used by Sonor.

The auto-trigger values depend on the noisy-ness of the environment. A value of 16 means that if the input should deviate by 12.5% ([16*100]/128), then the sample will start, unless [R5] samples have been taken, in which case, the sample will start anyway.

The auto-finish value (R4) is used to determine when the sample ends. The usual values are from 3 to 7, with the min/max values of 0 to 8. Basically, if there are [R6] samples during which the input deviates by less than [R4], then the sample will finish.

By using both of these values, it is possible to create perfect samples which start at the start, and end at the end.

These values (and the value returned by R1) can determine where the sample cut-off point is, and how big the actual sample is:

int record( char *data,int max_size,int trigger,int stop,int period,\
            int trig_samps,int stop_samps)
{
  kernel_swi_regs regs;

  /* Must have called SonorSound_SelectDevice */
  regs.r[0]=(int)data;
  regs.r[1]=max_size;
  regs.r[2]=period;
  regs.r[3]=trigger;
  regs.r[4]=stop;
  regs.r[5]=trig_samps;
  regs.r[6]=stop_samps;
  _kernel_swi(XSonorSound_TCJSample,&regs,&regs);

  if(regs.r[1]>0)                   /* Were there samples to go?       */
  {
    regs.r[1]+=regs.r[6];           /* Add the finish length           */
    if(regs.r[1]>=max_size)
      return 0;                     /* No sample was actually taken    */
    return (max_size-regs.r[1]);    /* Well, this must be the size     */
  }
  return max_size;                  /* The sample continued to the end */
}

SonorSound_TCJSendBuffer (&48D51)

On entry:

R0 is pointer to buffer
R1 is length of buffer (bytes - must be <= <max. buffer size>/2)
R2 is task number
R3 is current offset

On exit :
R3 is new offset

This call is similar to SonorSound_SendBuffer, but plays the compressed samples produced by SonorSound_TCJSample and SonorSound_TCJCompress.

The current offset is a special value that must be preserved between calls to SonorSound_TCJSendBuffer. The first value (when playing from the start of a sample) is &80. Then, the values will change at whim. Note that you cannot play from the middle of a compressed sample (except with care).

SonorSound_TCJDecompress (&48D52)

On entry:

R0 is pointer to source
R1 is length (in bytes)
R2 is pointer to destination
R3 is current offset

On exit:

R0 is pointer to end of source
R2 is pointer to end of destination
R3 is new offset

This call decompresses the sample (or sample fragment) into memory. The current offset is the same principle as SonorSound_TCJSendSample. The decompressed sample is 8-bit linear unsigned sample format, not µ-law logarithmic.

In order to decompress a whole sample in chunks, the following code would suffice:

DEFPROCdecompress(ptr%,len%,file%)
LOCAL C%,L%
REM temp% is a 256-byte DIMmed area of data
C%=&80
REPEAT
  IF len%>128 L%=128 ELSE L%=len%
  SYS"SonorSound_TCJDecompress",ptr%,L%,temp%,C% TO ptr%,,,C%
  SYS"OS_GBPB",1,file%,temp%,L%*2 : REM Length *2
  len%-=L%
UNTIL len%=0
ENDPROC

SonorSound_TCJCompress(&48D52)

On entry:

R0 is pointer to source
R1 is length (in bytes, should be a multiple of 2)
R2 is pointer to destination
R3 is current offset

On exit:

R0 is pointer to end of source
R2 is pointer to end of destination
R3 is new offset

This call performs the opposite of SonorSound_TCJDecompress, in that it will take an 8-bit linear unsigned sample, and compress it using the 2:1 delta compression.

The following code will compress the data:

int compress(char *ptr,int length,FILE *f)
{
  int len;
  char temp[256];
  _kernel_swi_regs regs;

  regs.r[0]=(int)ptr;
  regs.r[3]=0x80;

  while(length) {
    if(length>256)
      len=256;
    else
      len=length;

    regs.r[1]=len;
    regs.r[2]=(int)temp;

    _kernel_swi(SonorSound_TCJCompress,&regs,&regs);

    if(fwrite(temp,1,len>>1,f)!=(len>>1)) 
      return 0;                          /* Couldn't save data */

    length-=len;
  }
  return 1;                              /* Saved data okay    */
}

WIMP Polling and the SonorSound module

In order to allow Sonor to use the application's memory to hold samples, rather than the RMA, sprite or system area, the SonorSound module uses a buffering system, which allows it to hold two 32K buffers in memory. Every so often, Sonor will interrogate the module, to find out if one of the buffers is empty; if it is, then Sonor will fill it. The sequence of events for playing a note (under WIMP control) is as follows:

SYS "SonorSound_Status" TO ,,,,voice%
SYS "Sound_AttachVoice",1,voice%
SYS "SonorSound_CancelBuffer"
SYS "SonorSound_SetSpeed",speed%
SYS "Sound_Control",1,&fff1,0,255

memory%=sample%
length_2_play%=sample_length%
buffer1%=sample%
buffer2%=sample%

REPEAT
  SYS "Wimp_Poll",0,block% TO reason%,block%
  IF reason%=0 THEN

    REM First stage - find out if anything is playing%

    SYS "SonorSound_Status" TO playing%,buffer_empty%,,size%
    IF length_2_play% THEN
      IF buffer_empty%=0 THEN
        IF length_2_play%>size% bufflen%=size% ELSE bufflen%=length_2_play%

        SYS "SonorSound_SendBuffer",memory%,bufflen%,task_handle%

        buffer1%=buffer2% : REM Change the current buffer to the last one entered
        buffer2%=memory% : REM Set the next buffer to be played
        memory%+=bufflen% : REM Go to the next section of memory
        length_2_play%-=bufflen% : REM And subtract the length to play
      ENDIF
    ENDIF

    REM Second stage - find out the current offset
    IF playing% THEN
      SYS "SonorSound_GetOffset" TO offset%
      currently_playing%=buffer1%+offset% : REM Calculate the current offset into the sample
    ENDIF
  ENDIF
  REM Rest of WIMP poll loop
UNTIL finished%
END

Finally

Sonor is © 7th ARM Systems for VTi 1994-5
SonorSound is © 7th ARM Systems for VTi 1994-5
This document is © ARM Designs 1995 and © Jason Tribbeck 1999

All trademarks acknowledged