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