The Eastjesus Company Lost Tech Archive

Pulse Width Modulated Audio output on the original IBM PC

A method of producing AM radio quality analog audio using only the single bit toggle on the little speaker inside the original IBM PC and some clones of the era.
by Ivars Vilums

Here are three code snippets used to output analog audio using the speaker on the original IBM PC which was driven by a single bit of a flip-flop chip. Originally, I had written code to accomplish arbitrary audio out with a single bit output on a Z80 machine but later applied the technique and rewrote the code for the hardware on the IBM PC to create a pleasant chime sound used as part of the clock subsystem in "The Desk Organizer" from Conceptual Instruments. The original PC code was integrated into the UCSD Pascal code of the first incarnation of "The Desk Organizer" and later modified for the MASM assembler and integrated into the later MSDOS version of the same product.

This is the MASM version of the chime code for MSDOS (note the NOP's used to equalize the timing of the various paths which are critical to producing a glitch-free sound; clock cycles are in parentheses for the time critical sections):
    ;CHIMER
    ;-------------------------------
    ;9/10/1982:ijv
    ;transferred to MSDOS on 6/2/1984 by ijv

;=====================================================

DEVS        SEGMENT     'CODE'
PUBLIC        CHIME

        ;PROC        CHIME,2     ;(pitch,duration)

        ;Pulse Width generated Chime for IBM PC.

        ;constants

CENTER    EQU    32

TIMEHI    EQU    10
TIMELO    EQU    10

MODE    EQU    10110000B       ;command for 8253 timer


    ;ports

PORTB    EQU    61H           ;speaker control port
TIMCMD    EQU    43H           ;timer control port
TIMER    EQU    42H           ;timer2 counter port

    ;------ data section -------------------------

PITCH    DB    00H
DUR    DB    00H
DUMMY    DW    0000H

    ;----------------------------------------------

CHIMECODE    PROC        FAR
        ASSUME        CS:DEVS,DS:DEVS,ES:DEVS,SS:DEVS

    ;----------------------------------------------

CHIME    PROC    FAR

    CLI
    PUSH    BP
    MOV    BP,SP
    PUSH    DS
    MOV    CX,[BP+6]
    MOV    DUR,CL
    MOV    CX,[BP+8]
    MOV    PITCH,CL

    MOV    DL,31        ;set up max amplitude

    MOV    AL,MODE     ;turn timer off if free-running
    OUT    TIMCMD,AL
    OUT    TIMER,AL
    OUT    TIMER,AL

    IN    AL,PORTB    ;turn on speaker
    OR    AL,00000011B
    OUT    PORTB,AL

    ;======= start of time critical code =========

TONE:    MOV    BH,DUR        ;(14)

    ;do hi part of virtual square wave

WAVE:    MOV    BL,PITCH    ;(14)

PULSHI: MOV    AL,MODE     ;( 4)
    OUT    TIMCMD,AL    ;(10)
    ;                <-----speaker signal goes low here
    MOV    AL,CENTER    ;( 4)
    ADD    AL,DL        ;( 3)
    OUT    TIMER,AL    ;(10)
    XOR    AL,AL        ;( 3)
    OUT    TIMER,AL    ;(10)

    MOV    CX,TIMEHI    ;( 4)
HITIME: LOOP    HITIME        ;(17*TIMEHI+5)

    DEC    BL        ;( 3) virtual square wave width counter
    JNZ    TAIL1        ;(16/4)


    ;do lo part of virtual square wave

    MOV    BL,PITCH    ;(14)
PULSLO: MOV    AL,MODE     ;( 4)
    OUT    TIMCMD,AL    ;(10)
    ;                <-----speaker signal goes low here
    MOV    AL,CENTER    ;( 4)
    SUB    AL,DL        ;( 3)
    OUT    TIMER,AL    ;(10)
    XOR    AL,AL        ;( 3)
    OUT    TIMER,AL    ;(10)

    MOV    CX,TIMELO    ;( 4)
LOTIME: LOOP    LOTIME        ;(17*TIMELO+5)

    DEC    BL        ;( 3)
    JNZ    TAIL2        ;(16/4)

    DEC    BH        ;( 3) duration parameter
    JNZ    TAIL3        ;(16/4)

    SAR    DL,1        ;( 2) cut amplitude in half for next
    JNZ    TONE        ;(16/4)

    ;======= end of time critical code ===========

    IN    AL,PORTB    ;turn speaker off
    AND    AL,11111100B
    OUT    PORTB,AL

    POP    DS
    POP    BP
    STI
    RET    4

    ;------ time adjust sections -----------------

TAIL1:    NOP            ;( 3) add 41 clocks
    NOP            ;( 3)
    NOP            ;( 3)
    NOP            ;( 3)
    MOV    AX,DUMMY    ;(14)
    JMP    PULSHI        ;(15)

TAIL2:    NOP            ;( 3) add 41 clocks
    NOP            ;( 3)
    NOP            ;( 3)
    NOP            ;( 3)
    MOV    AX,DUMMY    ;(14)
    JMP    PULSLO        ;(15)

TAIL3:    MOV    AX,AX        ;( 2) add 20 clocks
    NOP            ;( 3)
    JMP    WAVE        ;(15)


CHIME        ENDP

CHIMECODE    ENDP

;=====================================================

DEVS        ENDS
        END


Next the more general arbitrary output driver code derived several years later:
    ;Pulse Width Modulated Digital Audio Device Driver for IBM PC.
    ;==============================================================
    ;      (c)1988 by Ivars Vilums. All Rights Reserved.
    ;--------------------------------------------------------------
    ;
    ;based on PWM.ASM Pascal procedure by ijv:8/85.
    ;based on Chime -  9/10/1982:ijv
    ;           as transferred to MSDOS on 6/2/1984 by ijv
    ;
    ;--------------------------------------------------------------
    ;
    ;Usage: from MSDOS prompt or batch file:
    ;
    ;        C>audio
    ;
    ;    where:
    ;        'audio' is this program
    ;
    ;    audio interrupt will be installed at 0F8H and
    ;    a copy of command.com executed.  To remove,
    ;    type 'exit' at the command prompt.
    ;
    ;    To install and run a program such that audio
    ;    automatically is removed at termination of program,
    ;    use:
    ;        C>audio /c [command]
    ;    where:
    ;        audio is this program
    ;        /c needed for proper functioning
    ;        [command] is any DOS command, program, or batch file.
    ;
    ;--------------------------------------------------------------
    ;
    ; Audio Interrupt
    ;----------------
    ;
    ; Int 0F8H
    ;   on entry: AH=0 - play buffer
    ;            DS:BX - address of buffer
    ;               DX - length in bytes of buffer
    ;               AL - PWM cycle length (1 to 6)
    ;
    ;--------------------------------------------------------------

;==============================================================================


        ;error codes used
        ;----------------
        ;codes returned to DOS on termination.
        ;can be accessed in batch file using ERRORLEVEL variable.
        ;MS 4 bits indicates which function failed,
        ;LS 4 bits indicates error returned by function.

COMSPECERROR    EQU    10H    ;can't find 'COMSPEC=' in ENVIRONMENT
ALLOCERROR    EQU    20H    ;error in allocating buffer
                ;  27H - arena trashed
                ;  28H - not enough memory
SIZEERROR    EQU    30H    ;error in reducing program size
                ;  37H - arena trashed
                ;  38H - not enough memory
                ;  39H - invalid block
EXECERROR    EQU    40H    ;error in EXEC function call
                ;  41H - invalid function
                ;  42H - file not found
                ;  48H - not enough memory
                ;  4AH - bad environment
                ;  4BH - bad format

    ;---------------------------------------------

        ;Interrupts
        ;----------

DOSINT        EQU    21H    ;MSDOS function interrupt vector
audioint    EQU    065H    ;audio interrupt vector.

        ;MSDOS function call definitions
        ;-------------------------------

PRINTSTR    EQU    9    ;function code to print string
SETVEC        EQU    25H    ;function code to set new ISR vector
GETVEC        EQU    35H    ;function code to get current ISR vector
GETMEM        EQU    48H    ;allocate memory function code
FREEMEM     EQU    49H    ;free allocated memory function code
MODMEM        EQU    4AH    ;modify allocated memory blocks function code
EXECFUNC    EQU    4BH    ;function code to run a program
TERMFUNC    EQU    4CH    ;terminate function code

    ;---------------------------------------------

    ;constants

MODE    EQU    10110000B       ;command for 8253 timer
PWIDTH    EQU    8

    ;ports

PORTB    EQU    61H           ;speaker control port
TIMCMD    EQU    43H           ;timer control port
TIMER    EQU    42H           ;timer2 counter port

    ;----------------------------------------------

        ;PSP info offsets
        ;----------------

ENVSEG        EQU    2CH    ;PSP offset for segment address of ENVIRONMENT
CMDLINE     EQU    80H    ;location of command line parms in PSP

    ;---------------------------------------------

        ;structure of PBLOCK for EXEC call
        ;---------------------------------

PRMREC        STRUC

ENVRECSEG    DW    ?    ;segment address of environment
PRMLINER    DW    ?    ;offset address of command line to be passed
PRMLINES    DW    ?    ;segment address of command line to be passed
DEFFCB1R    DW    ?    ;offset pointer to FCB1
DEFFCB1S    DW    ?    ;segment pointer to FCB1
DEFFCB2R    DW    ?    ;offset pointer to FCB2
DEFFCB2S    DW    ?    ;segment pointer to FCB2

PRMREC        ENDS

    ;---------------------------------------------

CSEG        SEGMENT PARA PUBLIC 'CODE'
        ASSUME    CS:CSEG,DS:CSEG
        ASSUME    ES:CSEG,SS:CSEG

        ORG    100H        ;this is a .COM file
STARTUP:    JMP    INIT

    ;---------------------------------------------

        ;Data Area
        ;---------

NOTICE        DB    '(c)1988 by Ivars Vilums. All Rights Reserved.'

INTSTACK    DW    128 DUP(?)    ;local stack
STACKTOP    DW    0
STACKSAVER    DW    ?        ;place to save our stack ptrs.
STACKSAVES    DW    ?

OLDVECR     DW    ?        ;place to save interrupt vector we use.
OLDVECS     DW    ?

PBLOCK        DB    14 DUP(?)    ;parameter block for EXEC.
REFSTRING    DB    'COMSPEC='      ;constant used to find system shell

Signon        DB    'Audio driver installed.$'
Signoff     DB    'Audio driver removed.$'

    ;---------------------------------------------

INIT        PROC    NEAR

        ;use internal stack

        CLI
        MOV    AX,OFFSET STACKTOP
        MOV    SP,AX
        STI

        ;free memory beyond this program

        MOV    BX,OFFSET ENDLOC+15    ;length of this program
        MOV    CL,4            ;convert to paragraphs
        SHR    BX,CL            ;by div 16
        MOV    AH,MODMEM
        INT    DOSINT
        JNC    XB1
        OR    AX,SIZEERROR
        JMP    ERREXIT

        ;save old vector

XB1:        PUSH    ES
        MOV    AL,audioint
        MOV    AH,GETVEC
        INT    DOSINT
        MOV    OLDVECS,ES
        MOV    OLDVECR,BX
        POP    ES

        ;set vector to our routine

        push    ds
        mov    ah,SETVEC
        mov    al,audioint
        mov    dx,offset newint
        int    DOSINT
        pop    ds

        ;save machine state

        PUSH    DS            ;save regs and stack
        PUSH    ES
        CLI
        MOV    CS:STACKSAVER,SP
        MOV    CS:STACKSAVES,SS
        STI

        ;set up parameter block for Exec call

        MOV    PBLOCK.ENVRECSEG,00H    ;use default environment
        MOV    PBLOCK.PRMLINES,CS    ;use parms on command line
        MOV    PBLOCK.PRMLINER,CMDLINE
        MOV    PBLOCK.DEFFCB1S,CS    ;point to FCB's in our PSP
        MOV    PBLOCK.DEFFCB1R,5CH
        MOV    PBLOCK.DEFFCB2S,CS
        MOV    PBLOCK.DEFFCB2R,6CH

        ;get name of system shell from ENVIRONMENT

        MOV    BX,ENVSEG        ;find 'COMSPEC='
        MOV    DS,[BX]
        MOV    SI,0

EX0:        CMP    BYTE PTR [SI],0     ;end of ENV is 00H
        JNE    EX1
        OR    AX,COMSPECERROR     ;can't find COMSPEC
        JMP    ERREXIT         ;stack can be blown

EX1:        MOV    DI,OFFSET REFSTRING    ;see if this rec is the one
        MOV    CX,8
        CLD
        REPE    CMPSB
        JE    EX3

EX2:        INC    SI            ;advance to end of this rec
        CMP    BYTE PTR [SI],0     ;recs end with 00H
        JNE    EX2
        INC    SI            ;pt to 1st byte of next rec
        JMP    EX0

EX3:        MOV    DX,SI            ;set DS:DX to point to
                        ;drv:[path]\filename.ext of
                        ;current system shell
        MOV    BX,CS
        MOV    ES,BX            ;set ES:BX to pt to PBLOCK
        MOV    BX,OFFSET PBLOCK


        ;do signon message

        PUSH    DS
        PUSH    DX

        PUSH    CS
        POP    DS
        MOV    DX,OFFSET SIGNON
        MOV    AH,PRINTSTR
        INT    DOSINT
        POP    DX
        POP    DS

        ;execute next program

        MOV    AH,EXECFUNC        ;do the EXEC
        MOV    AL,0
        INT    DOSINT
        JNC    XB3
        OR    AX,EXECERROR
        JMP    ERREXIT

        ;restore machine state

XB3:        CLI
        MOV    SS,CS:STACKSAVES    ;get back our internal stack
        MOV    SP,CS:STACKSAVER
        STI

        POP    ES            ;restore segment regs.
        POP    DS

        ;restore old interrupt vector

        PUSH    DS            ;save for later
        MOV    DX,OLDVECR
        MOV    DS,OLDVECS
        MOV    AL,audioint
        MOV    AH,SETVEC
        INT    DOSINT
        POP    DS

        ;end this program

        MOV    AL,0        ;return code for no errors

        ;do signoff message

        PUSH    DS
        PUSH    DX

        PUSH    CS
        POP    DS
        MOV    DX,OFFSET SIGNOFF
        MOV    AH,PRINTSTR
        INT    DOSINT
        POP    DX
        POP    DS

ENDPROG:    MOV    AH,TERMFUNC
        INT    DOSINT

ERREXIT:    JMP    ENDPROG     ;AL contains return error code

INIT        ENDP

    ;--------------------------------------------------------------

ISR        PROC    FAR

    ; Interrupt service routine
    ;--------------------------


PWM    PROC    NEAR

newint:

    STI
    PUSH    ES
    PUSH    DS
    PUSH    SI
    PUSH    DI
    PUSH    DX
    PUSH    CX
    PUSH    BX
    PUSH    AX

    CLI
    MOV    AH,AL        ;save cycle rate for later
    MOV    AL,MODE     ;turn timer off if free-running
    OUT    TIMCMD,AL
    OUT    TIMER,AL
    OUT    TIMER,AL

    IN    AL,PORTB    ;turn on speaker
    OR    AL,00000011B
    OUT    PORTB,AL

    ;======= start of time critical code =========


PULSE:

PULSE1: MOV    AL,MODE     ;( 4)
    OUT    TIMCMD,AL    ;(10)
                    ;<-----speaker signal goes low here
    MOV    AL,[BX]     ;(10)    ;get sample into AL
    AND    AL,0FH        ;( 4)    ;get low order nibble
    AND    AL,0FH        ;( 4)    ;time adjustment
    OUT    TIMER,AL    ;(10)    ;set signal transition time
    XOR    AL,AL        ;( 3)
    OUT    TIMER,AL    ;(10)

    XOR    CH,CH
    MOV    CL,AH
ADLP:
    ;NOP
    ;NOP
    LOOP    ADLP

    INC    DX        ;( 2)
    DEC    DX        ;( 2)
    JNZ    PULSE2        ;(16/4)

    ;----------------------------------------------

PULSE8: MOV    AL,MODE     ;( 4)
    OUT    TIMCMD,AL    ;(10)
                    ;<-----speaker signal goes low here
    MOV    AL,[BX]     ;(10)    ;get sample into AL
    SHR    AL,1        ;( 2)    ;get high order nibble
    SHR    AL,1        ;( 2)
    SHR    AL,1        ;( 2)
    SHR    AL,1        ;( 2)
    OUT    TIMER,AL    ;(10)    ;set signal transition time
    XOR    AL,AL        ;( 3)
    OUT    TIMER,AL    ;(10)

    XOR    CH,CH
    MOV    CL,AH
ADLP8:
    ;NOP
    ;NOP
    LOOP    ADLP8

    ;increment pointer and check for end of buffer

    INC    BX        ;( 2)
    DEC    DX        ;( 2)
    JNZ    PULSE        ;(16/4)
    JMP    DONE

    ;----------------------------------------------

PULSE7: MOV    AL,MODE     ;( 4)
    OUT    TIMCMD,AL    ;(10)
                    ;<-----speaker signal goes low here
    MOV    AL,[BX]     ;(10)    ;get sample into AL
    SHR    AL,1        ;( 2)    ;get high order nibble
    SHR    AL,1        ;( 2)
    SHR    AL,1        ;( 2)
    SHR    AL,1        ;( 2)
    OUT    TIMER,AL    ;(10)    ;set signal transition time
    XOR    AL,AL        ;( 3)
    OUT    TIMER,AL    ;(10)

    XOR    CH,CH
    MOV    CL,AH
ADLP7:
    ;NOP
    ;NOP
    LOOP    ADLP7

    INC    DX        ;( 2)
    DEC    DX        ;( 2)
    JNZ    PULSE8        ;(16/4)

    ;----------------------------------------------

PULSE6: MOV    AL,MODE     ;( 4)
    OUT    TIMCMD,AL    ;(10)
                    ;<-----speaker signal goes low here
    MOV    AL,[BX]     ;(10)    ;get sample into AL
    SHR    AL,1        ;( 2)    ;get high order nibble
    SHR    AL,1        ;( 2)
    SHR    AL,1        ;( 2)
    SHR    AL,1        ;( 2)
    OUT    TIMER,AL    ;(10)    ;set signal transition time
    XOR    AL,AL        ;( 3)
    OUT    TIMER,AL    ;(10)

    XOR    CH,CH
    MOV    CL,AH
ADLP6:
    ;NOP
    ;NOP
    LOOP    ADLP6

    INC    DX        ;( 2)
    DEC    DX        ;( 2)
    JNZ    PULSE7        ;(16/4)

    ;----------------------------------------------

PULSE2: MOV    AL,MODE     ;( 4)
    OUT    TIMCMD,AL    ;(10)
                    ;<-----speaker signal goes low here
    MOV    AL,[BX]     ;(10)    ;get sample into AL
    AND    AL,0FH        ;( 4)    ;get low order nibble
    AND    AL,0FH        ;( 4)    ;time adjustment
    OUT    TIMER,AL    ;(10)    ;set signal transition time
    XOR    AL,AL        ;( 3)
    OUT    TIMER,AL    ;(10)

    XOR    CH,CH
    MOV    CL,AH
ADLP2:
    ;NOP
    ;NOP
    LOOP    ADLP2

    INC    DX        ;( 2)
    DEC    DX        ;( 2)
    JNZ    PULSE3        ;(16/4)

    ;----------------------------------------------

PULSE5: MOV    AL,MODE     ;( 4)
    OUT    TIMCMD,AL    ;(10)
                    ;<-----speaker signal goes low here
    MOV    AL,[BX]     ;(10)    ;get sample into AL
    SHR    AL,1        ;( 2)    ;get high order nibble
    SHR    AL,1        ;( 2)
    SHR    AL,1        ;( 2)
    SHR    AL,1        ;( 2)
    OUT    TIMER,AL    ;(10)    ;set signal transition time
    XOR    AL,AL        ;( 3)
    OUT    TIMER,AL    ;(10)

    XOR    CH,CH
    MOV    CL,AH
ADLP5:
    ;NOP
    ;NOP
    LOOP    ADLP5

    INC    DX        ;( 2)
    DEC    DX        ;( 2)
    JNZ    PULSE6        ;(16/4)

    ;----------------------------------------------

PULSE3: MOV    AL,MODE     ;( 4)
    OUT    TIMCMD,AL    ;(10)
                    ;<-----speaker signal goes low here
    MOV    AL,[BX]     ;(10)    ;get sample into AL
    AND    AL,0FH        ;( 4)    ;get low order nibble
    AND    AL,0FH        ;( 4)    ;time adjustment
    OUT    TIMER,AL    ;(10)    ;set signal transition time
    XOR    AL,AL        ;( 3)
    OUT    TIMER,AL    ;(10)

    XOR    CH,CH
    MOV    CL,AH
ADLP3:
    ;NOP
    ;NOP
    LOOP    ADLP3

    INC    DX        ;( 2)
    DEC    DX        ;( 2)
    JNZ    PULSE4        ;(16/4)

    ;----------------------------------------------

PULSE4: MOV    AL,MODE     ;( 4)
    OUT    TIMCMD,AL    ;(10)
                    ;<-----speaker signal goes low here
    MOV    AL,[BX]     ;(10)    ;get sample into AL
    AND    AL,0FH        ;( 4)    ;get low order nibble
    AND    AL,0FH        ;( 4)    ;time adjustment
    OUT    TIMER,AL    ;(10)    ;set signal transition time
    XOR    AL,AL        ;( 3)
    OUT    TIMER,AL    ;(10)

    XOR    CH,CH
    MOV    CL,AH
ADLP4:
    ;NOP
    ;NOP
    LOOP    ADLP4

    INC    DX        ;( 2)
    DEC    DX        ;( 2)
    JNZ    PULSE5        ;(16/4)

    ;======= end of time critical code ===========

DONE:    IN    AL,PORTB    ;turn speaker off
    AND    AL,11111100B
    OUT    PORTB,AL

    STI
    POP    AX
    POP    BX
    POP    CX
    POP    DX
    POP    DI
    POP    SI
    POP    DS
    POP    ES

    IRET

PULSET: NOP            ;( 3)
    NOP            ;extra 3 for test
    JMP    PULSE1        ;(15)


PWM    ENDP

ISR        ENDP

;-------------------------------------------------------

ENDLOC        DB    ?

CSEG        ENDS
        END    STARTUP




And finally an example of using it from an IBM Pascal application:
{$Include:'IBMIntrp.Int'}
{$Include:'Devices.Int'}
{$Include:'Video.Int'}
{$Include:'Grafics.Int'}

Program Play(Input,Output);
Uses IBMIntrp,Devices,Video,Grafics;

Type Bufftype=Array[0..32000] of Byte;
Var Buffer:Bufftype;
    BuffStart,BuffEnd:Integer;

Procedure PWM(Vars Start_Address:Byte; Count,Rate:WORD);
Const AudioInt=#65;
Var Regs:RegList;
    BufPtr:ADS of Byte;
Begin
  Regs.AX:=Rate;
  BufPtr:=ads Start_Address;
  Regs.DS:=BufPtr.S;
  Regs.BX:=BufPtr.R;
  Regs.DX:=Count;
  Intrp(AudioInt,Regs,Regs);
End;

PROCEDURE ReadBuff;
Var Spot:Integer;
    Divisor,Offset:Integer;
    X1,X2,Y1,Y2:Byte;
    FName:LString(80);
    DFile:File of Byte;
BEGIN
  Write('Name of file to read:');
  Readln(FName);
  Write('Offset:');
  Readln(Offset);
  Write('Divisor:');
  Readln(Divisor);
  Assign(DFile,FName);
  Reset(DFile);
  Spot:=0;
  While (Spot<=8000) and Not(EOF(DFile)) do
    Begin
      If Not(EOF(DFile)) then Read(DFile,X1);
      X2:=X1;
      Y1:=X1;
      If Not(EOF(DFile)) then Read(DFile,X2);
      X1:=Wrd(Ord((X1+X2) div 2) div Divisor);
      If Not(EOF(DFile)) then Read(DFile,Y1);
      Y2:=Y1;
      If Not(EOF(DFile)) then Read(DFile,Y2);
      Y1:=Wrd(Ord((Y1+Y2) div 2) div Divisor);
      Buffer[Spot]:=(Y1*16)+X1;
      Spot:=Spot+1;
    End;
  Close(DFile);
  BuffStart:=0;
  BuffEnd:=8000;
END;

PROCEDURE GndBuff;
Var Spot:Integer;
    FillVal:Byte;
BEGIN
  Write('Value to fill with:');
  Readln(FillVal);
  For Spot:=0 to 32000 do Buffer[Spot]:=(FillVal*16)+FillVal;
  BuffEnd:=32000;
END;

PROCEDURE Distribs;
Var Tally:Array[0..255] of Integer;
    Min,Max,Spot,DC,Scale:Integer;
BEGIN
  SetGrafMode(Graf320col);
  DrawLine(31,0,289,0,1);
  DrawLine(31,199,289,199,1);
  DrawLine(31,0,31,199,1);
  DrawLine(289,0,289,199,1);
  For Spot:=0 to 255 do Tally[Spot]:=0;
  Min:=Ord(Buffer[BuffStart]);
  Max:=Min;
  DC:=Min;
  For Spot:=BuffStart to BuffEnd do
    Begin
      Tally[Ord(Buffer[Spot])]:=Tally[Ord(Buffer[Spot])]+1;
      If Ord(Buffer[Spot])>Max then Max:=Ord(Buffer[Spot]);
      If Ord(Buffer[Spot])<Min then Min:=Ord(Buffer[Spot]);
    End;
  Scale:=0;
  For Spot:=0 to 255 do If Tally[Spot]>Scale then Scale:=Tally[Spot];
  Scale:=Scale div 197;
  For Spot:=0 to 254 do
    DrawLine(Spot+32,Tally[Spot] div Scale,Spot+32,Tally[Spot+1] div Scale,1);
  Eval(OneKey(True));
  SetGrafMode(text80col);
END;

PROCEDURE SaveBuff;
Var BFile:File of Byte;
    BName:LString(80);
    Spot:Integer;
BEGIN
  Write('Save as:');
  Readln(BName);
  Assign(BFile,BName);
  ReWrite(BFile);
  For Spot:=BuffStart to BuffEnd do Write(BFile,Buffer[Spot]);
  Close(BFile);
END;

PROCEDURE LoadBuff;
Var BFile:File of Byte;
    BName:LString(80);
BEGIN
  Write('Name of file:');
  Readln(BName);
  Assign(BFile,BName);
  Reset(BFile);
  While Not(EOF(BFile)) and (BuffEnd<32000) do
    Begin
      BuffEnd:=BuffEnd+1;
      Read(BFile,Buffer[BuffEnd]);
    End;
  Close(BFile);
END;

PROCEDURE PlayBuff;
Var R:Word;
BEGIN
  Write('Rate:');
  Readln(R);
  While R<256 do
    Begin
      PWM(Buffer[BuffStart],Wrd(BuffEnd-BuffStart),R);
      Write('Rate:');
      Readln(R);
    End;
END;

PROCEDURE Help;
BEGIN
Writeln('-> play  read load save  set fill  report  help quit  <-');
END;

PROCEDURE Init;
BEGIN
  BuffStart:=0;
  BuffEnd:=32000;
END;

PROCEDURE SetEnds;
BEGIN
  Write('Start:');
  Readln(BuffStart);
  Write('  End:');
  Readln(BuffEnd);
END;

PROCEDURE Commands;
Var Done:Boolean;
    Cmd:LString(80);
BEGIN
  Done:=False;
  Repeat
    Write('>');
    Readln(Cmd);
    If Cmd='play' then PlayBuff;
    If Cmd='load' then LoadBuff;
    If Cmd='save' then SaveBuff;
    If Cmd='read' then ReadBuff;
    If Cmd='fill' then GndBuff;
    If Cmd='quit' then Done:=True;
    If Cmd='report' then Distribs;
    If Cmd='set' then SetEnds;
    If Cmd='help' then Help;
  Until Done;
END;

Begin
  Help;
  Commands;
End.



Copyright 2014 by The Eastjesus Company. All rights reserved.

Go to the home page of
The Eastjesus Company Home Page