diff --git a/demos/tapemon/README.md b/demos/tapemon/README.md new file mode 100644 index 0000000..6a7e44c --- /dev/null +++ b/demos/tapemon/README.md @@ -0,0 +1,30 @@ +# TAPEMON + +Tapemon is a Apple-I tool that helps finding the optimal playback volume to +the ACI audio cassette interface. + +## How to use it + +- Load `tapemon.0280.bin` on the Apple-1 and execute it with `280R`. + +- Connect your playback device (PC, Smartphone, iPod etc) to the ACI + +- Play the file `frames.wav` contained in `frame/frames.wav.zip` archive. The file +will send a continous stream of small data packets that are catched by the program +running on the Apple-1. Each packet is 64 bytes long. + +For each received packet a character will be displayed: + +- `*` indicates a good packet +- `.` indicates packet not received +- an hex digit 0-F indicates the packet is partially corrupt + +Adjust the volume level so that you have a lot of `*`. + + + + + + + + diff --git a/demos/tapemon/frame/frames.wav.zip b/demos/tapemon/frame/frames.wav.zip new file mode 100644 index 0000000..704fe2b Binary files /dev/null and b/demos/tapemon/frame/frames.wav.zip differ diff --git a/demos/tapemon/frame/mkframes.js b/demos/tapemon/frame/mkframes.js new file mode 100644 index 0000000..bd7e1a0 --- /dev/null +++ b/demos/tapemon/frame/mkframes.js @@ -0,0 +1,24 @@ +const fs = require("fs"); + +const FRAMELEN = 64; + +let data_block = []; +for(let t=0;t +#include + +const byte *HEX1L = 0x24; // End address of dump block +const byte *HEX1H = 0x25; // +const byte *HEX2L = 0x26; // Begin address of dump block +const byte *HEX2H = 0x27; // +const byte *LASTSTATE = 0x29; // Last input state +const byte *NUMPULSES = 0x30; // Number of long pulses to sync at the header +const byte *TAPEIN = 0xC081; // Tape input + +#define FRAMELEN 64 + +const byte *RX_BUFFER = 0x200; +const byte *RX_BUFFER_END = (0x200+FRAMELEN-1); + +void read_frame() +{ + asm { + // set READ buffer pointers to $0200-$021F (32 characters) + lda #RX_BUFFER_END + lda HEX1H + + lda #RX_BUFFER + lda HEX2H + + // synchronizes with the short header + +syncstart: lda #24 // 24 cycles (3 bytes of $ff) + sta NUMPULSES // count 24 cycles pulses + jsr fullcycle // skip the first full cycle (when looping) +nextsync: ldy #58 // full cycle duration + jsr fullcycle // read a full cycle + bcc syncstart // if short cycle found (c=0), redo from start + dec NUMPULSES // else long cycle found, decrease count + bne nextsync // if not 24 cycles, get next cycle + + // else read bit start and 32 bytes of data normally + // the following routine was copied directly from the ACI ROM + +notstart: ldy #31 // try to detect the much shorter start bit + jsr cmplevel // + bcs notstart // start bit not detected yet! + jsr cmplevel // wait for 2nd phase of start bit + ldy #58 // set threshold value in middle +rdbyte: ldx #8 // receiver 8 bits +rdbit: pha + jsr fullcycle // detect a full cycle + pla + rol // roll new bit into result + ldy #57 // set threshold value in middle + dex // decrement bit counter + bne rdbit // read next bit! + sta ($26,x) // save new byte *** same as "STA (HEX2L,X)" see KickC bug #756 https://gitlab.com/camelot/kickc/-/issues/756 + jsr incaddr // increment address + ldy #53 // compensate threshold with workload + bcc rdbyte // do next byte if not done yet! + bcs restidx // always taken! restore parse index +fullcycle: jsr cmplevel // wait for two level changes +cmplevel: dey // decrement time counter + lda TAPEIN // get tape in data + cmp LASTSTATE // same as before? + beq cmplevel // yes! + sta LASTSTATE // save new data + cpy #128 // compare threshold + rts + // [...] +incaddr: lda HEX2L // compare current address with + cmp HEX1L // end address + lda HEX2H + sbc HEX1H + inc HEX2L // and increment current address + bne nocarry // no carry to msb! + inc HEX2H +nocarry: rts + + // end of read routine "restidx" is the exit point + +restidx: rts + + } +} + +byte data_block[FRAMELEN]; +byte *vmeter = "0123456789ABCDEF"; + +void main() { + + // fill the data block with the known values + for(byte t=0;t>1]); + #endif + #if FRAMELEN == 64 + woz_putc(vmeter[i>>2]); + #endif + } + + // exit with "X" + if(apple1_readkey()=='X') break; + } +} +