Original source code written in January 2019 by Antoine Vignau to allow owners of Heuristics' SpeechLab 20A use FP BASIC (aka Applesoft) with the card. Big thanks to Jeremy Apple for his time.

The SpeechLab 20A card is one, if not the first, speech recognition interface card. It allows you to record up to 32 words. Using PRINT and INPUT statements, it was easy to comunicate with the card. Note that 32 words is a firmware limitation, they could have provided 64 or even more words in the firmware but that was 1978 and memory was expensive.

Cold boot the diskette and type RUN SPEECH PLOT

The source code is included. It is written in Merlin and has enough comments for you to understand how to program it.

*
* SpeechLab 20A RAM code
* (c) 1978, Heuristics
* (c) 2019, Brutal Deluxe Software
* Visit http://www.brutaldeluxe.fr/
*
* Thank you, Jeremy Apple
*

         org   $6000
         mx    %11
         lst   off

*---------------------------------------------------------
* Version
*
* v1.0 - 20190120 - Antoine Vignau
*       After a couple of tries, it is now working
*       Thank you for your help, Jeremy Apple
*       It would be cool to have a new version of the card!
*

*--- Equates

numWORDS        =       32      ; number of words one can record (variable)
wordSIZE        =       64      ; length of a spoken word (fixed)

*--- Zero page equates

CSWL    =       $36             ; for output
CSWH    =       CSWL+1
KSWL    =       CSWH+1          ; for input
KSWH    =       KSWL+1

A5H     =       $45             ; ACC
XREG    =       A5H+1           ; $46
YREG    =       XREG+1          ; $47
STATUS  =       YREG+1          ; $48

zpF0    =       $fc             ; Zero page addresses I use
zpF1    =       zpF0+1          ; $FC..$FF
zpPTR   =       zpF1+1

*--- Firmware/ROM equates

BELL1   =       $FBDD           ; Beep
COUT1   =       $FDF0           ; Output a char
RESTORE =       $FF3F           ; Restore A/X/Y/P
SAVE    =       $FF4A           ; Save A/X/Y/P/S
IORTS   =       $FF58           ; The official RTS

*---------------------------------------------------------
* How to use
*
* Installation of the driver
*  10 PRINT CHR$(4);"BLOAD SPEECHLAB20A RAM DRIVER"
*  20 DP = 24576 : REM $6000 FOR PR#
*  30 DI = DP + 3 : REM $6003 FOR IN#
*  40 POKE DP + 8, 5 : REM SLOT OF THE CARD
*  REM NO NEED FOR LOMEM ANYMORE
*
* Init the card
*  10 CALL DP : PRINT : CALL DP
*  ==> simulates PR#slot : PRINT : PR#0
*
* Get the buffer address
*  10 BU = PEEK(DP + 7) * 256 + PEEK(DP + 6)
*
* Record a word
*  10 CALL DP : PRINT "WORD" : CALL DP
*  ==> simulates PR#slot : PRINT "WORD" : PR#0
*
* Get a word
*  10 CALL DI : INPUT V$ : CALL DI
*  ==> simulates IN#slot : INPUT V$ : IN#0
*  ==> If V$ is an empty string, no word was recognized
* 

*---------------------------------------------------------
* Room for improvement
*
* Make buffers movable
*  The buffers follow the code. One could add pointers
*  to put them in other areas of memory
*
* Change the number of words
*  That one is easy, change numWORDS to the value you want
*
* PR# and IN#
*  One could keep the doPR and doIN to simulate the PR#
*  and IN# BASIC calls. PR#0 and IN#0 could be kept
*  That would save one page of code
*

*---------------------------------------------------------
* Entry point

        jmp     doPR            ; CALL $6000
        jmp     doIN            ; CALL $6003
        da      ptrBUFFER       ; PEEK($6007)*256 + PEEK($6006)
L12F2   ds      1               ; POKE($6008),slot
                                ; later transformed to slot*16

*---------------------------------------------------------
* The card runs with the INT ROM on
* If the "modern" ROMs are called,
* then calls to SAVE/RESTORE will be useless

LC809                   ; our new entry point
        lda     L12F2   ; is slot already 
        cmp     #7+1    ; set to slot*16?
        bcs     LC817   ; yes
        asl
        asl
        asl
        asl
        sta     L12F2   ; no, do slot*16
        
LC817    JSR   SAVE     ; save all parms

         LDA   CSWH     ; output is already set to our card?
         CMP   #>LC900  ; L12F1
         BEQ   LC878    ; yes
        
         LDA   #LC84B
         STA   KSWH
         LDA   #IORTS
         STA   CSWH

         LDY   #$00     ; string index is 0
         STY   L12F4
         JSR   LC956    ; DO MAGIC
         CMP   #numWORDS
         BPL   LC875
         TAX            ; index
         LDA   L12F6,X  ; pointer
         STA   zpPTR
         LDA   L1317,X
         STA   zpPTR+1
         JSR   RESTORE  ; restore

LC84B    JSR   SAVE     ; save
         LDY   L12F4    ; get index
         LDA   (zpPTR),Y        ; get char
         INC   L12F4    ; y++
         CMP   #$8D     ; end of string?
         BNE   LC864    ; no

LC85A    LDA   #LC817
         STA   KSWH
         LDA   #$8D     ; exit with a RET
LC864    PHA            ; save
         JSR   RESTORE  ; restore
         PLA            ; pull A
         RTS            ; return

LC86A    LDA   #COUT1
         STA   CSWH
         rts            ; exit to RANGE ERR (modified)

LC875    JMP   LC85A

*--- Card is already init'ed

LC878    LDA   A5H      ; get a char
         CMP   #$8D     ; 8D means reset list of words
         BEQ   LC8D8

         LDA   #LC89F
         STA   CSWH

         LDY   #$00     ; Y as a counter
         STY   L12F4
         LDX   L12F3    ; word index
         CPX   #numWORDS
         BPL   LC86A    ; we're full!
         LDA   L12F6,X  ; get its pointer
         STA   zpPTR
         LDA   L1317,X
         STA   zpPTR+1
         JSR   RESTORE  ; restore registers

LC89F    JSR   SAVE     ; save registers
         LDA   A5H      ; get A
         LDY   L12F4    ; get Y
         STA   (zpPTR),Y        ; save char
         INC   L12F4    ; y++
         CMP   #$8D
         BNE   LC8D4

         LDA   L12F3    ; we're done, get word index
         JSR   LC909    ; DO MAGIC
         INC   L12F3    ; next index
         LDX   L12F3    ; get it again
         LDA   zpPTR    ; ptr to char
         CLC
         ADC   L12F4    ; +index in string
         STA   L12F6,X  ; save pointer low
         LDA   zpPTR+1
         ADC   #$00
         STA   L1317,X  ; save pointer high

         LDA   #LC817
         STA   CSWH
LC8D4    jmp   RESTORE  ; restore and exit (JMP)

LC8D8    LDA   #$00     ; word index
         STA   L12F3
         LDA   #L1339
         STA   L1317
         LDA   #$00     ; init table of words
         LDX   #numWORDS-1
LC8EB    STA   L0820,X
         DEX
         BPL   LC8EB
         jmp   RESTORE  ; restore all and return (JMP)

*---------------------------------------------------------
*--- Buffers and friends

L081F   ds      1               ; a value
L12F3   ds      1               ; index in list of words, see L12F6
L12F4   ds      1               ; Y as an index
L12F5   ds      1               ; A as an index
        
        ds      \
        
*---------------------------------------------------------
* The $Cs00 page

LC900    JSR   LC809

LC909    STA   L081F
         TAX
         LDA   #$01
         STA   L0820,X

LC912    JSR   LC973
         BEQ   LC96D
         JSR   LCB2D
         LDA   #L0AF1
         STA   L0808+2
         LDA   #$00
         LSR   L081F
         BCC   LC92D
         LDA   #wordSIZE
LC92D    LSR   L081F
         BCC   LC934
         ORA   #$80
LC934    CLC
         ADC   L0808+1
         STA   L0808+1
         LDA   L0808+2
         ADC   L081F
         STA   L0808+2
         LDA   #L0AB1
         STA   L0805+2
         LDA   #wordSIZE-1
         JSR   L0804
         LDA   #$00
         RTS

LC956    JSR   LC973
         CMP   #$00
         BEQ   LC967
         JSR   LCB2D
         JSR   LCA94
         LDA   L0840
         RTS

LC967    JSR   BELL1
         JMP   LC956

LC96D    JSR   BELL1
         JMP   LC912

*--- CALLED BY $Cs00

LC973    LDA   #$00
         STA   L0840+$1
         STA   L0840+$2
LC97B    LDA   #L0859
         STA   L080F+2
         JSR   LC9E9
         BEQ   LC97B
         LDA   #$08
         STA   L0840+$7
LC98F    JSR   LC9E9
         BEQ   LC973
         DEC   L0840+$7
         BNE   LC98F
         LDA   #$0A
         STA   L0840+$8
LC99E    JSR   LC9E9
         BEQ   LC9B9
         LDA   #$0A
         STA   L0840+$8
LC9A8    LDA   L080F+2  ; end of buffer?
         CMP   #>L0AB1
         BNE   LC99E
         LDA   L080F+1
         CMP   #L0859
         ROR
         ROR   L0840+$15
         ROR
         ROR   L0840+$15
         LDA   L0840+$15
         RTS

*---------------------------------------------------------
                
LC9E9    LDY   #$00
         LDX   L12F2
         STY   L0840+$9
         LDA   $C080,X
         EOR   #$FF
         AND   #$01
         BEQ   LCA0B
         STA   L0840+$9
         JMP   LCA09

         ds    \

*---------------------------------------------------------

LCA09    LDA   #$08
LCA0B    JSR   L080F
         LDA   $C080,X
         EOR   #$FF
         AND   #$10
         BEQ   LCA1A
         STA   L0840+$9
LCA1A    LDA   #$08
         JSR   L080F
         JSR   LCA26
         LDA   L0840+$9
         RTS

LCA26    LDA   #$C8             ; counter
         STA   L0840+$A
         LDA   #$00
         STA   L0840+$4
         STA   L0840+$5
         LDA   L0840+$1
         STA   L0840+$B
         LDA   L0840+$2
         STA   L0840+$C
LCA3F    LDA   $C080,X
         AND   #$08
         CMP   L0840+$B
         BEQ   LCA52
         STA   L0840+$B
         INC   L0840+$4
LCA4F    JMP   LCA5B

LCA52    LDA   L0840+$4
         JMP   LCA58
LCA58    JMP   LCA4F

LCA5B    LDA   $C080,X
         AND   #$80
         CMP   L0840+$C
         BEQ   LCA6E
         STA   L0840+$C
         INC   L0840+$5
LCA6B    JMP   LCA77

LCA6E    LDA   L0840+$5
         JMP   LCA74
LCA74    JMP   LCA6B

LCA77    DEC   L0840+$A
         BNE   LCA3F
         LDA   L0840+$4
         CMP   #$20
         BCC   LCA86
         STA   L0840+$9
LCA86    JSR   L080F
         LDA   L0840+$5
         CMP   #$50
         BCC   LCA90
LCA90    JSR   L080F
         RTS

LCA94    LDA   #L0AF1
         SBC   #$00
         STA   L0800+2
         LDA   #$F4
         STA   L0840+$D
         LDA   #$01
         STA   L0840+$E
         LDA   #$20
         STA   L0840
         LDY   #$FF
LCAB4    INY
         CPY   #numWORDS
         BMI   LCABA
         RTS

LCABA    LDA   #wordSIZE        ; next
         CLC
         ADC   L0800+1
         STA   L0800+1
         LDA   #$00
         ADC   L0800+2
         STA   L0800+2
         LDA   L0820,Y
         BEQ   LCAB4
         LDA   #$00
         STA   L0840+$F
         STA   L0840+$10
         LDX   #wordSIZE-1
LCADA    JSR   L0800
         SEC
         SBC   L0AB1,X
         BCS   LCAE7
         EOR   #$FF
         ADC   #$01
LCAE7    CLC
         ADC   L0840+$10
         STA   L0840+$10
         LDA   #$00
         ADC   L0840+$F
         STA   L0840+$F
         CMP   L0840+$D
         JMP   LCB09
         
         ds    \
         
*---------------------------------------------------------

LCB09    BCC   LCB15
         BNE   LCAB4
         LDA   L0840+$10
         CMP   L0840+$E
         BCS   LCAB4
LCB15    DEX
         BMI   LCB1B
         JMP   LCADA

LCB1B    LDA   L0840+$F
         STA   L0840+$D
         LDA   L0840+$10
         STA   L0840+$E
         STY   L0840
         JMP   LCAB4

*--- CALLED BY $Cs00

LCB2D    LDA   #L0AB1
         STA   L080F+2
         LDA   L0840+$15
         STA   L0840+$13
         LDA   #$00
         STA   L0840+$11
         LDA   #$10
         STA   L0840+$12
         JSR   LCBA3
         STA   zpF0
         STA   zpF1
         LDA   L0840+$13
         ASL
         ASL
         STA   L0840+$16
         BEQ   LCB5B
         SEC
         SBC   #$04
LCB5B    CLC
         ADC   #L0859
         ADC   #$00
         STA   L081B+2
         LDA   #$10
         STA   L12F5
LCB6D    LDY   #$00
LCB6F    JSR   L081B
         JSR   L080F
         INY
         CPY   #$04
         BNE   LCB6F
         LDA   zpF0
         CLC
         ADC   zpF1
         STA   zpF0
         SEC
         SBC   #$10
         BMI   LCB8C
         STA   zpF0
         LDA   #$04
         BPL   LCB8E
LCB8C    LDA   #$00
LCB8E    CLC
         ADC   L0840+$16
         ADC   L081B+1
         STA   L081B+1
         BCC   LCB9D
         INC   L081B+2
LCB9D    DEC   L12F5
         BNE   LCB6D
         RTS

LCBA3    CLC
         LDX   #$F7
         LDA   L0840+$11
LCBA9    ROL   L0840+$13
         INX
         BMI   LCBB2
         JMP   LCBC7

LCBB2    ROL
         BCC   LCBBB
         SBC   L0840+$12
         SEC
         BCS   LCBA9
LCBBB    SEC
         SBC   L0840+$12
         BCS   LCBA9
         ADC   L0840+$12
         CLC
         BCC   LCBA9
LCBC7    STA   L0840+$11
         RTS

*---------------------------------------------------------

L0800    LDA   |$0000,X   ; CODE AT $0800
         RTS

L0804    TAX
L0805    LDA   |$0000,X
L0808    STA   |$0000,X
         DEX
         BPL   L0805
         RTS

L080F    STA   |$0000
         INC   L080F+1
         BNE   L081A
         INC   L080F+2
L081A    RTS

L081B    LDA   |$0000,Y
         RTS

        ds      \

*---------------------------------------------------------
* doPR
* We simulate PR#slot and here

doPR    lda     #0
        bne     doPRR   ; restore

        ldx     CSWL    ; put our hook routine
        stx     oldCSWL
        ldy     CSWH
        sty     oldCSWH
        ldx     #LC900
        tya             ; A is not 0

doPRE   stx     CSWL    ; save values
        sty     CSWH
        sta     doPR+1  ; flip/flop
        rts

doPRR   ldx     oldCSWL ; restore old values
        ldy     oldCSWH
        lda     #0
        beq     doPRE

oldCSWL ds      1
oldCSWH ds      1

*---------------------------------------------------------
* doIN
* We simulate IN#slot and here

doIN    lda     #0
        bne     doINR   ; restore

        ldx     KSWL    ; put our hook routine
        stx     oldKSWL
        ldy     KSWH
        sty     oldKSWH
        ldx     #LC900
        tya             ; A is not 0
        
doINE   stx     KSWL    ; save values
        sty     KSWH
        sta     doIN+1  ; flip/flop
        rts

doINR   ldx     oldKSWL ; restore old values
        ldy     oldKSWH
        lda     #0
        beq     doINE

oldKSWL ds      1
oldKSWH ds      1

*---------------------------------------------------------
*--- Buffers and friends

ptrBUFFER                               ; address of the buffer
L0840   ds      $19                     ; variables
L0859   ds      600                     ; read buffer
                                        ; the buffer is $258 (600d) bytes long
L0AB1   ds      wordSIZE                ; a buffer ($40 bytes)
L0AF1   ds      numWORDS*wordSIZE       ; a buffer ($800 bytes)

L0820   ds      numWORDS                ; a table of numWORDS bytes (0: no word, 1: a word)
L12F6   ds      numWORDS+1              ; table of pointers low ($21 bytes)
L1317   ds      numWORDS+1              ; table of pointers high ($21 bytes)
L1339   ds      numWORDS*numWORDS       ; a buffer for words (the words)


Download :


Disk image and manual