26

Systems like the ZX Spectrum have simple audio hardware with just one channel. But apparently it was possible to do polyphony on these systems. How was that achieved?

The amazing thing with these systems is that the audio hardware seems to only be designed for playing a simple single tone, but people were able to transcend these limitations.

1
  • I just stumbled upon an in-depth video on this very topic. Apparently the guy has a series on it but this video is specifically on the Speccy: youtube.com/… Jan 23, 2022 at 8:17

3 Answers 3

20

I know of two ways of achieving polyphony in system with 1-bit audio systems:

  • The simple way: time-multiplex two or more frequencies. For example, if you want the system to produce a mix of two tones, which are pure square waves in origin like these:

Two square waves of different frequency

you can simply render a period of first tone, followed by a period of second tone, and again, a period of first tone...

Combined waveform

This method limits your tones to be just pure frequencies, and if a very low frequency tone is to be mixed with a very high frequency one, the result is not very good. Nevertheless, this method works fine for simple tunes which may have some simple chords (such as the kind of tunes you can achieve using the PLAY command in a Spectrum 128K)

  • The PWM way: this uses a modulation technique called PWM, which allows a 1-bit output driver to generate any complex sound, by varying the duty cycle of a very high fixed frequency tone. The frequency of the PWM signal is then the sampling period of a digital signal, and the number of different values the duty cycle can have is the resolution of that digital signal. For example, a PWM signal with a frequency of 10kHz and with 16 possible levels for duty cycle can encode a 4-bit digital signal with a sampling frequency of 10kHz. There is an implementation of PWM that allows for fast operation, called sigma-delta modulation. I wrote some PoC programs for the Spectrum to illustrate this method. There is a thread in WOS discussing the matter, with some programs you can actually load and hear. One of them I provide it with full source code.

It works as follows: first, I take a raw PCM sound and use sigma-delta modulation to convert it into a stream of bits, each of them being the actual output to put in the speaker

/*
Offline conversion of 8-bit sample files (RAW format, no header) to
1-bit sigma-delta encoded stream files.

(C)2012 Miguel Angel Rodriguez Jodar (mcleod_ideafix)
Dept. Architecture and Computer Technology. University of Seville, Spain
Licensed under the terms of GPL.

Change input and output files to suit your needs.
TO-DO: process input file in chunks, rather than completely load it in memory
*/

#define INPUTFILE "yeaaaah.raw"
#define OUTPUTFILE "yeahh4bit_sigma_delta.bin"

#include <stdio.h>
#include <stdlib.h>

void main (void)
{
    FILE *f;
    int leido;
    int i,sample,integrador,salida,filesize;
    unsigned char *buffer;    /* remove "unsigned" if using signed samples */
    unsigned char *buffout;

    f=fopen (INPUTFILE, "rb");
    fseek (f, 0, SEEK_END);
    filesize = ftell (f);
    fseek (f, 0, SEEK_SET);
    buffer=malloc(filesize);
    buffout=malloc(filesize/4);
    filesize = fread (buffer, 1, filesize, f);
    fclose(f);

    integrador = 0;
    salida = 0;
    memset (buffout, 0, filesize/4);

    for (i=0;i<filesize;i++)
    {
        sample = buffer[i]-128;  /* remove -128 if using signed samples */
        integrador += (sample-salida);
        if (integrador>0)
        {
            salida=127;
            buffout[i/4] = buffout[i/4] | (1<<(7-(i*2)%8));
        }
        else
        {
            salida=-128;
        }

        /* repeat once (2x oversampling) */
        integrador += (sample-salida);
        if (integrador>0)
        {
            salida=127;
            buffout[i/4] = buffout[i/4] | (1<<(7-(i*2+1)%8));
        }
        else
        {
            salida=-128;
        }
    }

    f=fopen (OUTPUTFILE, "wb");
    fwrite (buffout, 1, filesize/4, f);
    fclose(f);

    free(buffer);
    free(buffout);
}

Then, the stream of bits are simply played by a machine code routine.

; Simple player for 1-bit samples. See file 8tosigmadelta.c for sigma-delta
; offline conversion of 8-bit sample files to 1-bit sample files.
; (C)2012 Miguel Angel Rodriguez Jodar. (mcleod_ideafix)
; Dept. of Architecture and Computing Technology. University of Seville, Spain
; Licensed under the terms of GPL.

; Assembled using PASMO.

                org 49152
BORDERCOLOUR    equ 7

DemoPlaySample  proc
                ld hl,Sample
                ld de,LenSample
                call PlaySample
                ret
                endp

PlaySample      proc
                di
AnotherSample:  ld a,(hl)      ;Load 8 samples
                ld b,8
LoopSample:     ld c,a         ;Backup to C
                and 80h        ;Isolate high bit
                sra a
                sra a
                sra a          ;Shift it to SPK bit
                or BORDERCOLOUR   ;Apply desired border colour
                out (254),a     ;Output to spk
                ld a,c         ;Restore from C
                rla            ;Next sample is now in high bit
                nop            ;
                nop            ; Delay
                nop            ;
                nop            ;
                djnz LoopSample  ;Go to process next sample
                inc hl
                dec de
                ld a,d
                or e
                jp nz,AnotherSample    ;Go to process next 8 samples
                ei
                ret
                endp

Sample:         equ $
                incbin "yeahh4bit_sigma_delta.bin"
LenSample       equ $-Sample

                end DemoPlaySample

I made some other PoC for several ways to achieve some kind of digital sound playing in a Spectrum. namely:

  • Using two bits (MIC and SPK) which gives me a (very rude and non linear) 2-bit DAC
  • Using the AY chip to modulate a fixed signal with the volume control
  • Using PWM
  • Using an actual 8-bit DAC (SpecDrum)

Results can be loaded and executed, from here: http://www.atc.us.es/~rodriguj/prelude_spectrum.zip

The original 4 channel MOD file is included. My demos use three channels (you may not notice that until the end, in where a chord is played)

The high pitch you hear in the PWM version is caused by the PWM frequency to be so low that it enters the audible spectrum. "Serious" PWM applications use a very high pitch PWM signal.

13

Just wanted to add a historical note (as this is retro computing). All Manchester computers from the earliest days generated sound by attaching a speaker to a single bit of a register (usually the accumulator).

This sound was used to monitor the activity in the machine; because it if crashed or halted unexpectedly the sound would cease and if it was stuck in an infinite loop a characteristic whine would be produced which would alert the operators. From the Manchester Mark I through to MU5 the machine halls were alive with the sounds of the computer.

Most users became accustomed to the patterns of sound produced and could tell one kind of software from another by the sound patterns produced, the sound of a compile was different from the sound of a sort, for example. It was also clear very early on that although a single bit flip produced the sound that sophisticated sound patterns of a polyphonic nature could be perceived from it.

It did not take long for the many musicians working with the computers to turn this to musical use, and from that time all Manchester computers could sing. The main technique used was time multiplexing as previously described by @mcleod_ideafix.

A very early recording exists from 1951 of this, and the origins of early sound from Manchester Computers can be read here.


BCT's Historical Sock Puppet Account

2
  • On the Atari 2600, if one hooks an oscilloscope up to one of the lower address-bus pins and synchronizes the scope with the frame rate (or even uses "power-line sync" mode which is often close to the frame rate) it will often be possible to tell something of what's going on by watching the patterns on the scope. One won't be able to judge objects' horizontal positions, but one will often be able to observe objects moving around vertically.
    – supercat
    Jul 1, 2016 at 22:58
  • 1
    Are there also recordings of the normal sound (that is, the sound the machines made during normal computations, rather than when explicitly programmed for music)?
    – celtschk
    Jul 3, 2016 at 7:48
12

And these systems had not just a single audio channel, but a single-bit audio channel. There are no doubt many techniques, but a simple approach is to rapidly interleave the output of two or more channels. The low-pass filtering of the speaker will serve to effectively mix the channels together. Not the greatest fidelity but better than nothing.

As an example, consider the following pseudo-code to output a square wave:

c0 = 0; f0 = 128;
loop:
   c0 = c0 + f0;
   if (c0 >= 128) audio_out = 1; else audio_out = 0;
   c0 = c0 % 256;
   goto loop;

With f0 = 128 as shown the output frequency will be the maximum possible which is 1 / time-to-execute. Let's say that works out to 50,0000 Hz. We can output notes of different frequency by choosing different values of f0. If f0 = 1 then we get a 50000/128 = 390 Hz tone.

To output two square waves at once simply add the code for another square wave:

loop:
   c0 = c0 + f0;
   if (c0 >= 128) audio_out = 1; else audio_out = 0;
   c0 = c0 % 256;
   goto waste;
waste:
   c1 = c1 + f1;
   if (c1 >= 128) audio_out = 1; else audio_out = 0;
   c1 = c1 % 256;
   goto loop;

The time to execute the loop has doubled so all our frequencies have dropped in half with the maximum now 25,000 Hz. The higher level music player will have to adjust f0 and f1 values accordingly. Also notice the goto waste; statement I added. That guarantees that the loop time exactly doubles and that the spacing between audio outputs is consistent. It can be surprising how much these little things can matter when it comes to audio.

Music players usually have c0 and c1 be fixed-point numbers that loop over waveform buffers to allow for output other than simple square waves.

1
  • I've written a number of music programs using that approach. Many of them get really warbly on machines with caches.
    – supercat
    Oct 30, 2016 at 14:54

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .