;
;   SAMPLASM.ASM
;   ------------
;
; One voice sampler playback system using fractional addition       16/3/89
;
;   with optional patch to drive PC speaker using PWM
;
;   For use with SAMPLER.PAS  V2
;
;
;
;
;                   (C) Copyright 1989 by Rowan McKenzie
;
;                  You may copy these files  or use the source  code  only
;        for non-profit purposes. Please contact me if you wish to use any
;        part of the package for commercial purposes.
;
;
;                       Rowan McKenzie 
;                       35 Moore Ave,
;                       Croydon 3136
;                       Vic Australia
;
;
;
;   This code provides the assembler section of SAMPLER.PAS
;
;     This version uses the system timer for sample timing, and is therefore
;   clock speed independent, although the CPU must be able to keep up with
;   the interrupt rate (20kHz!).
;
;
;
; Notes:
; ------
;
;  To assemble the D to A converter version, set pwm equ false
;
;  To assemble the PWM version, set pwm equ true
;
;  The D/A version uses the system timer (at 20kHz) to synchronise output
;     samples (and to make the code clock speed independent)
;
;  The PWM version uses the system timer in the same way, but triggers the
;     speaker timer channel as a monostable whose pulse length is proportional
;     to the sample value. Note that time constants are all scaled assuming
;     20kHz sample rate. If another sample rate is used, these will need to be
;     recalculated. Also, faster sampling rates mean lower effective resolution
;     from the one shot because it is clocked at a fixed frequency. The current
;     setup gives the equivalent of a 6 bit d/a converter (I know that's not
;     very good, but it's better than most PC speakers can justify!)
;
;
;
;  Variables defined in Pascal source:
;  -----------------------------------
;
;  release is a boolean variable indicating whether key releases should
;          terminate sound playback
;
;  loop is a boolean variable indicating that the loop section of the sample
;          should be played continuously after playback is complete
;
;  daout is an integer variable containing the d/a converter port
;          (for D/A version)
;
;  increment is an integer variable containing a fractional increment constant which
;          determines the pitch of the note to be played
;
;  bufloop is an integer variable containing the offset of the start of the
;          loop section
;
;  bufend is an integer variable containing the offset of the end of the sample
;          (for when the sample code is implemented)
;
;  bufstart is an integer variable containing the offset of the start of the
;          sample
;
;  kbdmode is a boolean variable indicating whether keyboard service routine
;          should pass control to bios (false) or handle the key itself (true)
;
;  kbdflag is a byte variable to indicate a key has been pressed during replay
;
;  keyval is a byte variable which will contain the scan code of a key pressed
;          during replay
;
;  tconstant is a byte variable which sets the system timer rate (20kHz)
;
;  timer is a boolean variable which indicates the timer should be used
;          to determine how long notes should last
;
;  tinterval is an integer variable indicating how long a note should be
;          played for (no. interrupts mod modulus)
;
;  modulus is a byte variable which determines after how many interrupts the
;          tinterval counter should be decremented
;
;  song is a boolean variable indicating whether a song is being played
;
;  trigger is an integer variable describing the minimum input level required
;          to begin sampling
;

        page    255,132
title sampler asm module

; ===== Equates =====

true    equ     1
false   equ     0

pwm     equ     false                   ;pwm version if true else d/a version

tdelay  equ     2                       ;delay constant between samples while
                                        ; waiting for trigger
lshiftr equ     170                     ;scan code for left shift release
rshiftr equ     182
tccont  equ     43h                     ;control reg of 8253 timer chip
tccount equ     42h                     ;counter reg 2 (speaker)

speaker equ     97                      ;speaker port for PWM version

data     segment word public
;
; data
;

        extrn   tconstant:word,keyval:word,kbdflag:word,bufstart:word
        extrn   bufend:word,bufloop:word,increment:word,daout:word,loop:word
        extrn   release:word,timer:word,tinterval:word,modulus:word,song:word
        extrn   trigger:word,kbdmode:word,quickexit:word,buffer:word
        extrn   bufferw:word,bufflen:word

sysold  dd      ?                       ;temp storage for old system int vector
sptemp  dw      ?                       ;storage for sp for quick exit
duration dw     ?                       ;duration counter
modul   db      ?                       ;local modulus counter

data    ends

code    segment byte public
        assume  cs:code,ds:data

        public  restore,sample,initial,replay,replayt,scalewave,echo

kbdold  dd      ?                       ;temp storage for old kbd int vector
dstemp  dw      ?                       ;holds ds incase it's needed


;************************************************************
;
;  Restore  - restore int vectors and exits
;
;************************************************************

restore proc near
        cli                                     ;restore old kbd interrupt
        push    ds
        mov     dx,word ptr cs:[kbdold]
        mov     ds,word ptr cs:[kbdold+2]
        mov     ax,2509h
        int     21h                             ;restore old kbd vector
        pop     ds

        if	pwm
        mov     al,0b6h
        out     tccont,al               ;change timer to square wave gen mode

        in      al,speaker
        and	al,0fch
        out     speaker,al              ;speaker one shot disable
        endif

        sti
        ret
restore endp

;***********************************************************************
;
;  Initialise - save int vector contents, replace with my version
;
;***********************************************************************

initial proc near
        cli
        mov     cs:[dstemp],ds
        mov     ax,3509h                        ;get old kbd int
        int     21h
        mov     word ptr cs:[kbdold],bx            ;save it
        mov     word ptr cs:[kbdold+2],es
        mov     dx,offset kbdint
        push    ds
        mov     ax,cs
        mov     ds,ax
        mov     ax,2509h
        int     21h                             ;replace with mine
        pop     ds
        mov     byte ptr [kbdflag],0            ;no key activity

        if	pwm
        mov     al,0b6h
        out     tccont,al               ;change timer to square wave gen mode
        mov     al,byte ptr[tconstant]
	shr	al,1
        out     tccount,al              ;produce mean equivalent dc voltage
	xor	al,al
        out     tccount,al
        in      al,speaker
        or      al,3
        out     speaker,al              ;speaker one shot enable
	else
        mov     dx,[daout]
        mov	al,127
	out	dx,al
        endif

        sti
        ret
initial endp


;******************************************************************************
;
;  Sample data from A/D port
;
;******************************************************************************

sample  proc near
        cli

        in      al,21h
        or      al,98h                      ;disable comms, printer interrupts
        out     21h,al

        mov     ax,3508h
        int     21h                             ;get system timer vector
        mov     word ptr [sysold],bx
        mov     word ptr [sysold+2],es          ;and save it

        mov     dx,offset sysint
        push    ds
        mov     ax,cs
        mov     ds,ax
        mov     ax,2508h
        int     21h                             ;replace with new one
        pop     ds

        mov     ah,byte ptr [trigger]
        mov     dx,[daout]
trig:   in      al,dx                           ;wait for trigger level
        mov     cx,tdelay
tdel:   loop    tdel                            ;delay before next sample taken
        cmp     al,ah
        jc      trig

        mov     al,36h
        out     43h,al
        mov     al,byte ptr [tconstant]
        out     40h,al                          ;set to ~20kHz system timer!
        mov     al,0
        out     40h,al
        sti

        mov     bx,[bufstart]
        mov     di,[bufend]

        mov     es,[buffer+2]

sync:   hlt                                     ;wait for system interrupt
        in      al,dx
        mov     es:[bx],al                         ;store sample
        inc     bx
        cmp     bx,di
        jbe     sync                            ;until buffer full

        call    vrestore

        cli
        in      al,21h
        and     al,67h
        out     21h,al
        sti

        ret
sample  endp


;******************************************************************************
;
;  Scale waveform for PWM replay
;
;******************************************************************************

scalewave proc near

        push    ds

        mov     es,[bufferw+2]       ;copy from buffer to bufferw with scale
        mov     si,[bufferw]
        mov     cx,bufflen
        inc     cx
        mov     di,[buffer]
        mov     ds,[buffer+2]

sloop:  mov     al,[di]              ;div 4 +1
        shr     al,1
        shr     al,1
        inc     al
        mov     es:[si],al
        inc     si
        inc     di
        loop    sloop

        pop     ds
        ret
scalewave endp


;*************************************************************************
;
;  Echo - echos A/D to D/A for monitoring
;
;*************************************************************************

echo    proc near
        cli

        push    bp
        in      al,21h
        or      al,98h                      ;disable comms, printer interrupts
        out     21h,al

        mov     [sptemp],sp                     ;remember sp incase kbd abort
        mov     al,1
        mov     byte ptr [quickexit],al
        mov     ax,3508h
        int     21h                             ;get system timer vector
        mov     word ptr [sysold],bx
        mov     word ptr [sysold+2],es          ;and save it

        mov     dx,offset sysint
        push    ds
        mov     ax,cs
        mov     ds,ax
        mov     ax,2508h
        int     21h                             ;replace with new one
        pop     ds

        if      pwm
        mov     al,0b0h
        out     tccont,al                       ;change timer to one shot mode
        mov     al,255
        out     tccount,al
        mov     al,0
        out     tccount,al
        mov     al,90h
        out     tccont,al
        endif

        mov     al,36h
        out     43h,al
        mov     al,byte ptr [tconstant]
        out     40h,al                          ;set to ~20kHz system timer!
        mov     al,0
        out     40h,al
        sti

        if      pwm
        in      al,speaker
        or      al,3
        out     speaker,al                      ;speaker one shot enable
        else
        mov     dx,[daout]
        endif


eloop:
        if      pwm
        mov     dx,[daout]
        endif

        in      al,dx

        if      pwm
        shr     al,1
        shr     al,1
        inc     al
        mov     dx,tccount
        out     dx,al                   ;activate one shot
        out     dx,al                   ;seems to need this else distortion
        else
        out     dx,al
        endif

        hlt                             ;wait for system interrupt

        jmp     eloop                   ;exit via exitr

echo    endp


;*************************************************************************
;
;  Replay  - plays stored sound under interrupt control using fractional
;            addition, handles loop function, looks for keypresses
;
;*************************************************************************

replay  proc near
        cli

        in      al,21h
        or      al,98h                      ;disable comms, printer interrupts
        out     21h,al

        push    bp
        mov     [sptemp],sp                     ;remember sp incase kbd abort
        mov     al,1
        mov     byte ptr [quickexit],al
        mov     ax,3508h
        int     21h                             ;get system timer vector
        mov     word ptr [sysold],bx
        mov     word ptr [sysold+2],es          ;and save it

        mov     dx,offset sysint
        push    ds
        mov     ax,cs
        mov     ds,ax
        mov     ax,2508h
        int     21h                             ;replace with new one
        pop     ds

        if      pwm
        mov     al,0b0h
        out     tccont,al                       ;change timer to one shot mode
        mov     al,255
        out     tccount,al
        mov     al,0
        out     tccount,al
        mov     al,90h
        out     tccont,al
        endif

        mov     al,36h
        out     43h,al
        mov     al,byte ptr [tconstant]
        out     40h,al                          ;set to ~20kHz system timer!
        mov     al,0
        out     40h,al
        sti

        if      pwm
        in      al,speaker
        or      al,3
        out     speaker,al                      ;speaker one shot enable
        endif

loopa:  mov     bx,[tinterval]
        mov     si,bx                           ;local note duration counter
        mov     bl,byte ptr [modulus]
        mov     [modul],bl                      ;local modulus counter

        if      pwm
        mov     es,[bufferw+2]
        else
        mov     es,[buffer+2]
        endif

        mov     bx,[bufstart]
        mov     bp,[increment]
        mov     ah,byte ptr [loop]

        ife     pwm
        mov     dx,[daout]
        else
        mov     dx,tccount
        endif

repeat: mov     di,[bufend]
        mov     ch,bl
        mov     cl,0

loops:  add     cx,bp                   ; fractional add (ch.cl is result)
        mov     bl,ch                   ; lsnib of buffer pointer
        adc     bh,0
waiti:  mov     al,es:[bx]                 ; get sample

        hlt                             ;wait for system interrupt

        if      pwm
        out     dx,al                   ;activate one shot
        out     dx,al                   ;seems to need this else distortion
        else
        out     dx,al
        endif

testlop: cmp     bx,di
        jb       loops
        test    ah,ah                   ;loop mode?
        jz      exitr
        mov     bx,[bufloop]
        jmp     repeat                  ;yes, restart from loop position

exitr:  mov     al,0
        mov     byte ptr [quickexit],al
        call    vrestore

        cli
        in      al,21h                  ;enable serial/parallel ints
        and     al,67h
        out     21h,al
        sti

        pop     bp
        ret
replay  endp


;*************************************************************************
;
;  Replayt - plays stored sound under interrupt control using fractional
;            addition, handles loop function, looks for keypresses. this
;            version uses timer control
;
;*************************************************************************

replayt proc near
        cli

        in      al,21h
        or      al,98h                      ;disable comms, printer interrupts
        out     21h,al

        push    bp
        mov     [sptemp],sp                     ;remember sp incase kbd abort
        mov     al,1
        mov     byte ptr [quickexit],al
        mov     ax,3508h
        int     21h                             ;get system timer vector
        mov     word ptr [sysold],bx
        mov     word ptr [sysold+2],es          ;and save it

        mov     dx,offset sysint
        push    ds
        mov     ax,cs
        mov     ds,ax
        mov     ax,2508h
        int     21h                             ;replace with new one
        pop     ds

        if      pwm
        mov     al,0b0h
        out     tccont,al                       ;change timer to one shot mode
        mov     al,255
        out     tccount,al
        mov     al,0
        out     tccount,al
        mov     al,90h
        out     tccont,al
        endif

        mov     al,36h
        out     43h,al
        mov     al,byte ptr [tconstant]
        out     40h,al                          ;set to ~20kHz system timer!
        mov     al,0
        out     40h,al
        sti

        if      pwm
        in      al,speaker
        or      al,3
        out     speaker,al                      ;speaker one shot enable
        endif

loopat: mov     si,[tinterval]                  ;local note duration counter
        mov     bl,byte ptr [modulus]
        mov     [modul],bl                      ;local modulus counter

        if      pwm
        mov     es,[bufferw+2]
        else
        mov     es,[buffer+2]
        endif

        mov     bx,[bufstart]
        mov     bp,[increment]
        mov     ah,[modul]

        ife     pwm
        mov     dx,[daout]
        else
        mov     dx,tccount
        endif

repeatt: mov     di,[bufend]
        mov     ch,bl
        mov     cl,0

loopst: add     cx,bp                   ; fractional add (ch.cl is result)
        mov     bl,ch                   ; lsnib of buffer pointer
        adc     bh,0
waitit: mov     al,es:[bx]              ; get sample

        hlt                             ;wait for system interrupt

        if      pwm
        out     dx,al                   ;activate one shot
        out     dx,al                   ;seems to need this else distortion
        else
        out     dx,al
        endif

        dec     ah                      ;time to dec tinterval?
        jz      ntestlop
        cmp     bx,di
        jb      loopst
        test    byte ptr [loop],0ffh    ;loop mode?
        jz      waith
        mov     bx,[bufloop]
        jmp     repeatt                 ;yes, restart from loop position

ntestlop: dec     si                    ;see if time up
        mov     al,byte ptr [modulus]   ;reset modulus counter
        mov     ah,al
        js      timeupt                 ; yep, go and check if song mode
        cmp     bx,di                   ;2nd copy of testlop avoids jump
        jb      loopst
        test    byte ptr [loop],0ffh    ;loop mode?
        jz      waith
        mov     bx,[bufloop]
        jmp     repeatt                 ;yes, restart from loop position

timeupt: test    byte ptr [song],0ffh    ;song mode?
        jnz     exitrt                  ;yes, immediate exit
        jmp     loopat                  ;no, loop back to start of sound

waith:  test    si,0ffffh               ;wait for full duration
        jns     waitit                  ;wait for keyboard or timer
exitrt: jmp     exitr

replayt endp


;
; local procedures
;


;
; Speaker interrupt service routine
;
;  Does nothing except synchronise output operations
;

sysint  proc far
        push    ax
        mov     al,20h
        out     20h,al                          ;EOI to int controller
        pop     ax

        iret
        sysint  endp

;
; Comm1,2 interrupt service routine
;
;  Prevents mouse activity from slowing cpu during sampling/replay
;

comint  proc far
        push    ax
        mov     al,20h
        out     20h,al                          ;EOI to int controller
        pop     ax

        iret
        comint  endp

;
; Custom keyboard interrupt handler - handles keypresses, key releases (if
;             desired), modifies kbd flags. Note if TP4 kbd or screen
;             procedure was in progress, DS may have been changed, so restore
;             from dstemp.
;

kbdint  proc  far                               ;custom keyboard interrupt handler
        push    ds
        mov     ds,cs:[dstemp]
        test    byte ptr [kbdmode],0ffh         ;handle key here?
        jnz     kserv
        pop     ds
        jmp     cs:kbdold                       ;no, give to bios
kserv:  push    ax
        in      al,60h                          ;get key code
        cmp     al,lshiftr                      ;always note shift releases
        jz      notrel
        cmp     al,rshiftr
        jz      notrel
        test    byte ptr [release],0ffh         ;release sensitive?
        jnz     isrel
        test    al,al                           ;no, ignore if key released
        js      pressd
        jmp     notrel
isrel:                                          ;release sensitive
        cmp     al,byte ptr [keyval]            ;ignore if same key as last time
        jz      pressd                          ; ie ignore autorepeat
        test    al,al
        jns     notrel                          ;if keydown, go record it
        mov     ah,al
        xor     ah,byte ptr [keyval]
        cmp     ah,129                          ;record release of last key but
        jns     pressd                          ; not release of older key
notrel: mov     byte ptr [keyval],al            ;record keypressed
        mov     byte ptr [kbdflag],al
pressd: mov     al,0ffh
        in      al,61h
        or      al,80h
        out     61h,al
        and     al,7fh
        out     61h,al
        mov     al,20h                          ;EOI
        out     20h,al
        pop     ax
        test    byte ptr [quickexit],0ffh
        jz      noquick
        test    byte ptr [kbdflag],0ffh
        jz      noquick
        mov     es,[sptemp]
        pop     ds
        push    es
        pop     sp
        jmp     exitr

noquick: pop     ds
        iret
        kbdint  endp


vrestore proc  near                      ;restore hardware, system timer vector
        cli
        mov     al,36h
        out     43h,al
        xor     al,al
        out     40h,al                          ;restore system timer frequency
        out     40h,al

        mov     al,0b6h
        out     tccont,al               ;change timer to square wave gen mode
        mov     al,byte ptr[tconstant]
	shr	al,1
        out     tccount,al              ;produce mean equivalent dc voltage
	xor	al,al
        out     tccount,al

        push    ds
        lds     dx,sysold
        mov     ax,2508h                        ;restore old system timer vec
        int     21h
        pop     ds
        sti
        ret
        vrestore endp

code    ends
        end
