diff --git a/apps/casette/encode.py b/apps/casette/encode.py index 93dfad6..57286ce 100644 --- a/apps/casette/encode.py +++ b/apps/casette/encode.py @@ -159,9 +159,9 @@ def main() -> None: parser = argparse.ArgumentParser(description="Encode a binary file as Manchester two-tone FSK WAV.") parser.add_argument("input", type=Path, help="binary input file") parser.add_argument("output", type=Path, nargs="?", help="output WAV; default: INPUT.wav") - parser.add_argument("--baud", type=int, default=600) - parser.add_argument("--low", type=float, default=2000.0, help="low tone in Hz") - parser.add_argument("--high", type=float, default=4000.0, help="high tone in Hz") + parser.add_argument("--baud", type=int, default=150) + parser.add_argument("--low", type=float, default=1000.0, help="low tone in Hz") + parser.add_argument("--high", type=float, default=2000.0, help="high tone in Hz") parser.add_argument("--sample-rate", type=int, default=48000) parser.add_argument("--leader", type=float, default=4.0, help="leader seconds") parser.add_argument("--preamble", type=int, default=64, help="number of 0x55 preamble bytes") diff --git a/apps/casette/tape_loader.asm b/apps/casette/tape_loader.asm index 93463f2..9117e61 100644 --- a/apps/casette/tape_loader.asm +++ b/apps/casette/tape_loader.asm @@ -5,28 +5,25 @@ ; ===================================================================== -; Cassette training diagnostic +; Cassette Manchester decoder diagnostic ; -; CPU: Z80 @ 1 MHz -; Comparator: port 0x18 +; CPU: Z80 @ 1 MHz +; Comparator port: 0x18 ; -; Comparator register: -; D0 = comparator output -; D7..D1 = 0 +; This stage: ; -; This program DOES NOT decode data yet. +; 1. Calibrates HIGH carrier from leader +; 2. Detects LOW carrier at start of 0x55 preamble +; 3. Learns FULL-bit HIGH/LOW run lengths from 0x55 +; 4. Derives HALF/FULL run thresholds +; 5. Manchester-decodes bits +; 6. Searches bit-by-bit for sync word D3 91 +; 7. Captures 30 following bytes +; 8. Prints the decoded bytes ; -; It: -; 1. Finds a stable leader -; 2. Measures leader timing -; 3. Waits for 3 consecutive slower-carrier periods -; 4. Captures 128 raw comparator half-periods -; 5. Prints the raw values -; 6. Classifies them as H/L -; 7. Prints carrier run lengths +; Nothing is written to 0x8000 yet. ; -; All sampling happens with interrupts disabled. -; Printing happens only before or after sampling. +; Interrupts remain disabled during tape reception. ; ===================================================================== @@ -46,10 +43,32 @@ LEADER_MAX = WORK_BASE + 0x05 ; byte TONE_THRESHOLD = WORK_BASE + 0x06 ; byte LOW_TRIGGER = WORK_BASE + 0x07 ; byte -RETURN_CODE = WORK_BASE + 0x08 ; byte +CURRENT_TONE = WORK_BASE + 0x08 ; byte +OLD_TONE = WORK_BASE + 0x09 ; byte +TEMP_TONE = WORK_BASE + 0x0A ; byte + +RUN_COUNT = WORK_BASE + 0x0B ; byte + +HIGH_RUN_SUM = WORK_BASE + 0x0C ; word +LOW_RUN_SUM = WORK_BASE + 0x0E ; word + +HIGH_RUN_AVG = WORK_BASE + 0x10 ; byte +LOW_RUN_AVG = WORK_BASE + 0x11 ; byte + +HIGH_RUN_THRESH = WORK_BASE + 0x12 ; byte +LOW_RUN_THRESH = WORK_BASE + 0x13 ; byte + +TRAIN_REMAIN = WORK_BASE + 0x14 ; byte + +RETURN_CODE = WORK_BASE + 0x15 ; byte + + +; Buffer contains: +; +; D3 91 + 30 following decoded bytes CAPTURE_BUF = 0xB100 -CAPTURE_COUNT = 128 +CAPTURE_COUNT = 32 ; ===================================================================== @@ -58,18 +77,12 @@ CAPTURE_COUNT = 128 tape_loader: - ; ------------------------------------------------------------- - ; Save caller stack. - ; ------------------------------------------------------------- - ld (OLD_SP),sp - ; ------------------------------------------------------------- - ; Save interrupt state. - ; - ; LD A,I copies IFF2 into P/V. - ; ------------------------------------------------------------- +; --------------------------------------------------------------------- +; Save interrupt state. +; --------------------------------------------------------------------- ld a,i jp po,iff_was_off @@ -77,46 +90,36 @@ tape_loader: ld a,#1 jr save_iff + iff_was_off: xor a + save_iff: ld (OLD_IFF),a - - ; ------------------------------------------------------------- - ; Use our own stack. - ; ------------------------------------------------------------- - di ld sp,#STACK_TOP - ; ------------------------------------------------------------- - ; Startup message. - ; - ; BIOS printing requires interrupts. - ; print_with_irqs returns with interrupts disabled. - ; ------------------------------------------------------------- +; --------------------------------------------------------------------- +; Initial message. +; --------------------------------------------------------------------- ld hl,#msg_start call print_with_irqs ; ===================================================================== -; Find stable leader +; STEP 1 ; -; The first measurement becomes our reference. +; Find stable leader. ; -; Require another 31 measurements within +/-2 counts of that reference. -; -; At 1 MHz we only get a few loop counts per carrier half-period, so -; allowing +/-2 is intentionally generous. -; -; We also keep track of the maximum observed leader count. +; First period is the reference. +; Require another 31 periods within +/-2 counts. ; ===================================================================== find_leader: @@ -128,14 +131,13 @@ find_leader: ld (LEADER_MAX),a - ; HL = running sum. + ; HL = running sum ld l,a ld h,#0 - ; We already have one sample. - ; Need 31 more for 32 total. + ; One sample already obtained. ld b,#31 @@ -150,13 +152,9 @@ leader_stability_loop: ld e,a - ; ------------------------------------------------------------- - ; Calculate: - ; - ; abs(sample - reference) - ; - ; without destroying E. - ; ------------------------------------------------------------- +; --------------------------------------------------------------------- +; abs(measurement - reference) +; --------------------------------------------------------------------- ld a,(LEADER_REF) @@ -164,7 +162,7 @@ leader_stability_loop: jr nc,leader_ref_ge_sample - ; sample > reference + ; measurement > reference ld a,e ld c,a @@ -180,25 +178,20 @@ leader_stability_loop: leader_ref_ge_sample: - ; A still contains reference. - sub e leader_diff_ready: - ; Difference 0, 1, or 2 accepted. - ; - ; >= 3 means this isn't a stable leader. - ; Start over. + ; Difference 0, 1 or 2 accepted. cp #3 - jr nc,find_leader + jp nc,find_leader - ; ------------------------------------------------------------- - ; Update maximum leader period. - ; ------------------------------------------------------------- +; --------------------------------------------------------------------- +; Update maximum observed leader period. +; --------------------------------------------------------------------- ld a,(LEADER_MAX) @@ -212,9 +205,7 @@ leader_diff_ready: leader_max_ok: - ; ------------------------------------------------------------- - ; Add sample to 16-bit sum. - ; ------------------------------------------------------------- + ; Add E to HL. ld d,#0 @@ -225,11 +216,19 @@ leader_max_ok: ; ===================================================================== -; Calculate leader average. +; Rounded leader average. ; -; 32 samples -> divide by 32. +; 32 samples. +; +; Add 16 before dividing by 32: +; +; average = (sum + 16) / 32 ; ===================================================================== + ld de,#16 + add hl,de + + srl h rr l @@ -249,32 +248,48 @@ leader_max_ok: ld a,l or a - jr z,find_leader + jp z,find_leader ld (LEADER_AVG),a ; ===================================================================== -; Initial carrier threshold. +; Initial carrier discrimination. ; -; Everything up to LEADER_MAX is treated as the HIGH-frequency carrier. +; From our real measurements: ; -; To decide that the LOW-frequency carrier has appeared, require: +; HIGH carrier = 03 / 04 +; LOW carrier = 05 / 06 ; -; measurement >= LEADER_MAX + 1 +; Therefore: ; -; AND require this to happen three times consecutively. +; threshold = rounded leader average + 1 +; low trigger = threshold + 1 ; -; Example from your previous capture: +; Expected: ; -; leader average ~= 02 -; leader max ~= 03 or 04 +; LEADER AVG = 03 +; CARRIER THRESHOLD = 04 +; LOW TRIGGER = 05 ; -; A single 04 can therefore no longer falsely trigger acquisition. +; Classification: +; +; <= threshold : HIGH +; > threshold : LOW ; ===================================================================== - ld a,(LEADER_MAX) + ld a,(LEADER_AVG) + + inc a + + jr nz,tone_threshold_ok + + + ld a,#0xFE + + +tone_threshold_ok: ld (TONE_THRESHOLD),a @@ -284,8 +299,6 @@ leader_max_ok: jr nz,low_trigger_ok - ; Protect against overflow, although this should never happen. - ld a,#0xFF @@ -295,11 +308,12 @@ low_trigger_ok: ; ===================================================================== -; Wait for the beginning of the slower carrier. +; STEP 2 ; -; We require THREE consecutive periods >= LOW_TRIGGER. +; Wait for THREE consecutive definitely-LOW measurements. ; -; This is the important change from the previous diagnostic. +; This prevents a single slightly-long leader measurement from +; triggering the preamble detector. ; ===================================================================== wait_for_low_restart: @@ -311,31 +325,22 @@ wait_for_low: call measure_halfperiod + ld e,a ld a,(LOW_TRIGGER) - - ; We want: - ; - ; E >= LOW_TRIGGER - ; - ; CP does A - E. - ; - ; A < E -> carry - ; A = E -> zero - cp e + + ; E >= LOW_TRIGGER ? + jr z,low_candidate jr c,low_candidate - ; Too short: still leader/noise. - ; Restart the consecutive counter. - - jr wait_for_low_restart + jp wait_for_low_restart low_candidate: @@ -344,88 +349,326 @@ low_candidate: ; ===================================================================== -; We have now observed three consecutive slow periods. +; We are now somewhere inside the first LOW run of the preamble. ; -; Capture 128 FRESH measurements. +; Discard the rest of that partial run. ; -; We deliberately do not print anything here because the tape stream -; has no gaps. +; scan_run returns after detecting the transition to HIGH. ; ===================================================================== + xor a ; LOW + + call scan_run + + + ; Must have changed to HIGH. + + or a + jp z,training_error + + + ld (CURRENT_TONE),a + + +; ===================================================================== +; STEP 3 +; +; Train using 16 complete carrier runs from the 0x55 preamble. +; +; 0x55 produces alternating FULL runs: +; +; HIGH FULL +; LOW FULL +; HIGH FULL +; LOW FULL +; ... +; +; 16 runs gives: +; +; 8 HIGH +; 8 LOW +; ===================================================================== + + ld hl,#0x0000 + + ld (HIGH_RUN_SUM),hl + ld (LOW_RUN_SUM),hl + + + ld a,#16 + ld (TRAIN_REMAIN),a + + +train_run_loop: + + ld a,(CURRENT_TONE) + ld (OLD_TONE),a + + + call scan_run + + + ; A = new tone. + + ld (TEMP_TONE),a + + + ; Reject empty run. + + ld a,(RUN_COUNT) + + or a + jp z,training_error + + + ; DE = run count. + + ld e,a + ld d,#0 + + + ld a,(OLD_TONE) + + or a + jr z,train_low_run + + +; --------------------------------------------------------------------- +; HIGH full run. +; --------------------------------------------------------------------- + + ld hl,(HIGH_RUN_SUM) + + add hl,de + + ld (HIGH_RUN_SUM),hl + + jr train_run_added + + +; --------------------------------------------------------------------- +; LOW full run. +; --------------------------------------------------------------------- + +train_low_run: + + ld hl,(LOW_RUN_SUM) + + add hl,de + + ld (LOW_RUN_SUM),hl + + +train_run_added: + + ld a,(TEMP_TONE) + ld (CURRENT_TONE),a + + + ld a,(TRAIN_REMAIN) + + dec a + + ld (TRAIN_REMAIN),a + + jp nz,train_run_loop + + +; ===================================================================== +; Average 8 HIGH and 8 LOW runs. +; ===================================================================== + + ld hl,(HIGH_RUN_SUM) + + call divide_hl_by_8 + + + ld a,l + + or a + jp z,training_error + + + ld (HIGH_RUN_AVG),a + + + ld hl,(LOW_RUN_SUM) + + call divide_hl_by_8 + + + ld a,l + + or a + jp z,training_error + + + ld (LOW_RUN_AVG),a + + +; ===================================================================== +; Derive HALF/FULL discriminator. +; +; threshold = ceil(3/4 * full_run) +; +; Expected from our current measurements: +; +; HIGH FULL ~= 3 +; HIGH threshold = 3 +; +; LOW FULL ~= 2 +; LOW threshold = 2 +; +; Thus: +; +; HIGH 1/2 -> HALF, 3+ -> FULL +; LOW 1 -> HALF, 2+ -> FULL +; ===================================================================== + + ld a,(HIGH_RUN_AVG) + + call make_run_threshold + + ld (HIGH_RUN_THRESH),a + + + ld a,(LOW_RUN_AVG) + + call make_run_threshold + + ld (LOW_RUN_THRESH),a + + +; ===================================================================== +; We are currently at a Manchester midpoint in the remaining 0x55 +; preamble. +; +; CURRENT_TONE is the carrier immediately following that midpoint. +; +; Search the decoded BIT STREAM for: +; +; D3 91 +; +; Searching bit-by-bit means we do not need to know byte alignment. +; +; HL = 16-bit shift register +; DE = timeout counter +; ===================================================================== + + ld hl,#0x0000 + + ld de,#8192 + + +sync_search_loop: + + call get_bit + + + ; C = decoded bit + + ld c,a + + + ; Shift previous bits left. + + add hl,hl + + + ; Append new bit. + + ld a,l + + or c + + ld l,a + + + ; D3 91 ? + + ld a,h + + cp #0xD3 + jr nz,sync_not_found + + + ld a,l + + cp #0x91 + jp z,sync_found + + +sync_not_found: + + dec de + + + ; DEC DE doesn't affect flags. + + ld a,d + or e + + jp nz,sync_search_loop + + + jp sync_error + + +; ===================================================================== +; Sync found. +; +; We are exactly byte-aligned immediately after the 0x91. +; +; Store D3 91 and then decode another 30 bytes. +; ===================================================================== + +sync_found: + ld hl,#CAPTURE_BUF - ld b,#CAPTURE_COUNT + + ld a,#0xD3 + ld (hl),a + inc hl -capture_loop: + ld a,#0x91 + ld (hl),a + inc hl - call measure_halfperiod + + ld b,#30 + + +capture_decoded_bytes: + + call get_byte ld (hl),a inc hl - djnz capture_loop + + djnz capture_decoded_bytes ; ===================================================================== -; Capture complete. +; Reception complete. ; -; Timing no longer matters, so enable interrupts for BIOS output. +; We can now enable interrupts and print. ; ===================================================================== ei -; ===================================================================== -; Print calibration summary. -; ===================================================================== - - ld hl,#msg_leader_avg + ld hl,#msg_sync_ok call print_string - ld a,(LEADER_AVG) - call print_hex8 - call print_crlf + call print_training_values - ld hl,#msg_leader_max - call print_string - - ld a,(LEADER_MAX) - call print_hex8 - - call print_crlf - - - ld hl,#msg_low_trigger - call print_string - - ld a,(LOW_TRIGGER) - call print_hex8 - - call print_crlf - - - ld hl,#msg_threshold - call print_string - - ld a,(TONE_THRESHOLD) - call print_hex8 - - call print_crlf - - call print_crlf - - -; ===================================================================== -; Print raw half-period measurements. -; -; 16 per line. -; ===================================================================== - - ld hl,#msg_raw + ld hl,#msg_bytes call print_string call print_crlf @@ -437,9 +680,10 @@ capture_loop: ld c,#0 -print_raw_loop: +print_byte_loop: ld a,(hl) + call print_hex8 @@ -451,239 +695,90 @@ print_raw_loop: inc c - ; New line every 16 samples. + ; 16 bytes per line. ld a,c + and #0x0F - jr nz,raw_no_newline + jr nz,print_byte_no_crlf call print_crlf -raw_no_newline: +print_byte_no_crlf: - djnz print_raw_loop + djnz print_byte_loop call print_crlf -; ===================================================================== -; Print H/L classification. -; -; H: -; sample <= TONE_THRESHOLD -; -; L: -; sample > TONE_THRESHOLD -; -; 64 characters per line. -; ===================================================================== - - ld hl,#msg_class - call print_string - - call print_crlf - - - ld hl,#CAPTURE_BUF - - ld b,#CAPTURE_COUNT - ld c,#0 - - -print_class_loop: - - ld a,(hl) - - call classify_sample - - - or a - jr z,class_print_low - - - ld a,#'H' - jr class_print_char - - -class_print_low: - - ld a,#'L' - - -class_print_char: - - call BIOS_PUTC - - - inc hl - inc c - - - ; New line every 64 classifications. - - ld a,c - and #0x3F - - jr nz,class_no_newline - - - call print_crlf - - -class_no_newline: - - djnz print_class_loop - - - call print_crlf - - -; ===================================================================== -; Print carrier run lengths. -; -; Example: -; -; L06 H0D L07 H0C ... -; -; This is the most interesting output for the 0x55 preamble. -; -; For nominal 2 kHz / 4 kHz at 600 baud we might expect something -; roughly around: -; -; LOW run: 6-7 comparator half-periods -; HIGH run: 12-14 comparator half-periods -; -; Exact values don't matter. -; Consistency is what matters. -; ===================================================================== - - ld hl,#msg_runs - call print_string - - call print_crlf - - - ld hl,#CAPTURE_BUF - - - ; ------------------------------------------------------------- - ; Establish first carrier. - ; ------------------------------------------------------------- - - ld a,(hl) - call classify_sample - - - ; D = current carrier - ; - ; 0 = LOW - ; 1 = HIGH - - ld d,a - - - ; C = current run length - - ld c,#1 - - - inc hl - - - ; 127 measurements remain. - - ld b,#127 - - -run_scan_loop: - - ld a,(hl) - - ; classify_sample preserves BC and DE. - - call classify_sample - - - cp d - jr nz,run_changed - - - ; Same carrier. - - inc c - - jr run_scan_next - - -run_changed: - - ; ------------------------------------------------------------- - ; A = new carrier. - ; - ; Save it while printing the old run. - ; ------------------------------------------------------------- - - ld e,a - - - ld a,d - call print_tone_letter - - - ld a,c - call print_hex8 - - - ld a,#' ' - call BIOS_PUTC - - - ; Start new run. - - ld d,e - ld c,#1 - - -run_scan_next: - - inc hl - - djnz run_scan_loop - - - ; ------------------------------------------------------------- - ; Print final run. - ; ------------------------------------------------------------- - - ld a,d - call print_tone_letter - - - ld a,c - call print_hex8 - - - call print_crlf - call print_crlf - - - ld hl,#msg_done - call print_string - - xor a ld (RETURN_CODE),a + jp diagnostic_exit + ; ===================================================================== -; Return to monitor / BIOS caller. +; Error paths ; ===================================================================== -training_exit: +training_error: + + ld a,#1 + ld (RETURN_CODE),a + + + ei + + + ld hl,#msg_training_error + call print_string + + call print_training_values + + jp diagnostic_exit + + +sync_error: + + ld a,#2 + ld (RETURN_CODE),a + + + ei + + + ld hl,#msg_sync_error + call print_string + + call print_training_values + + jp diagnostic_exit + + +timing_error: + + ld a,#3 + ld (RETURN_CODE),a + + + ei + + + ld hl,#msg_timing_error + call print_string + + call print_training_values + + +; ===================================================================== +; Exit +; ===================================================================== + +diagnostic_exit: di @@ -695,9 +790,7 @@ training_exit: ld c,a - ; ------------------------------------------------------------- - ; Restore caller interrupt state. - ; ------------------------------------------------------------- + ; Restore caller's interrupt state. ld a,(OLD_IFF) @@ -706,17 +799,14 @@ training_exit: ld a,c - jr z,training_return_di + jr z,diagnostic_return_di - ; EI becomes active after the following instruction. - ; RET serves as that instruction. - ei ret -training_return_di: +diagnostic_return_di: ret @@ -724,24 +814,22 @@ training_return_di: ; ===================================================================== ; measure_halfperiod ; -; 1. Wait for a comparator edge. -; 2. Starting exactly from that edge, count polling iterations until -; the next comparator edge. +; Wait for one comparator edge, then measure the COMPLETE interval +; between that edge and the following edge. ; ; Returns: ; -; A = count, 1..255 +; A = count 1..255 ; -; If the interval is very long the counter saturates at FF instead of -; wrapping. +; Saturates at FF. ; +; NOTE: ; -; At 1 MHz your observed leader currently gives roughly: +; Each invocation first synchronizes to an edge and then measures the +; next interval, so sequential calls effectively skip one raw +; half-period between measured intervals. ; -; 02 / 03 counts -; -; which is perfectly usable as long as the two carrier clusters remain -; distinct. +; That's okay because training and decoding use the same mechanism. ; ===================================================================== measure_halfperiod: @@ -752,7 +840,7 @@ measure_halfperiod: ; --------------------------------------------------------------------- -; Wait for first edge. +; Synchronize to first edge. ; --------------------------------------------------------------------- mhp_wait_first_edge: @@ -772,7 +860,7 @@ mhp_wait_first_edge: ; --------------------------------------------------------------------- -; Measure to following edge. +; Measure until next edge. ; --------------------------------------------------------------------- mhp_measure_loop: @@ -787,8 +875,6 @@ mhp_measure_loop: jr nz,mhp_done - ; Saturate at FF. - ld a,c cp #0xFF @@ -797,8 +883,7 @@ mhp_measure_loop: ; --------------------------------------------------------------------- -; Saturated: -; just wait for the edge without incrementing further. +; Saturated. ; --------------------------------------------------------------------- mhp_saturated: @@ -827,18 +912,17 @@ mhp_done: ; ; Input: ; -; A = measured half-period +; A = measured carrier period ; ; Output: ; -; A = 1 HIGH-frequency / short-period carrier -; A = 0 LOW-frequency / long-period carrier +; A = 1 HIGH-frequency carrier +; A = 0 LOW-frequency carrier ; -; Preserves: +; sample <= TONE_THRESHOLD : HIGH +; sample > TONE_THRESHOLD : LOW ; -; BC -; DE -; HL +; Preserves BC, DE, HL. ; ===================================================================== classify_sample: @@ -854,9 +938,9 @@ classify_sample: cp c - ; threshold < sample -> LOW-frequency carrier + ; threshold < sample -> LOW - jr c,class_sample_low + jr c,classify_low ld a,#1 @@ -866,7 +950,7 @@ classify_sample: ret -class_sample_low: +classify_low: xor a @@ -876,31 +960,480 @@ class_sample_low: ; ===================================================================== -; print_tone_letter +; scan_run ; ; Input: ; -; A = 0 -> L -; A = 1 -> H +; A = carrier currently present +; +; Output: +; +; RUN_COUNT = measured run length +; A = newly detected carrier +; +; +; IMPORTANT: +; +; The previous scan_run consumed the first measurement belonging to +; this run while detecting the previous transition. +; +; Therefore the run starts at count 1, not 0. +; +; This was the important fix which changed training from: +; +; H=2 L=1 +; +; to: +; +; H=3 L=2 ; ===================================================================== -print_tone_letter: +scan_run: + + ld e,a ; expected tone + + + ; First sample of this run was already consumed by the + ; previous transition detection. + + ld b,#1 + + +scan_run_loop: + + call measure_halfperiod + + call classify_sample + + + cp e + + jr nz,scan_run_changed + + + inc b + + + ; Protect against wraparound. + + jp z,timing_error + + + jr scan_run_loop + + +scan_run_changed: + + ; A contains new tone. + + ld (TEMP_TONE),a + + + ld a,b + + ld (RUN_COUNT),a + + + ld a,(TEMP_TONE) + + ret + + +; ===================================================================== +; divide_hl_by_8 +; ===================================================================== + +divide_hl_by_8: + + srl h + rr l + + srl h + rr l + + srl h + rr l + + ret + + +; ===================================================================== +; make_run_threshold +; +; Input: +; +; A = learned FULL run length +; +; Output: +; +; A = ceil(3/4 * FULL) +; +; Formula: +; +; (3*FULL + 3) / 4 +; +; Examples: +; +; full = 2 -> threshold = 2 +; full = 3 -> threshold = 3 +; full = 4 -> threshold = 3 +; full = 5 -> threshold = 4 +; full = 6 -> threshold = 5 +; ===================================================================== + +make_run_threshold: + + ld b,a + + + add a,a ; 2 * full + + add a,b ; 3 * full + + add a,#3 + + + srl a + srl a + + + or a + ret nz + + + ld a,#1 + + ret + + +; ===================================================================== +; run_is_full +; +; Input: +; +; A = carrier of run just measured +; +; Uses RUN_COUNT. +; +; Output: +; +; A = 0 HALF +; A = 1 FULL +; ===================================================================== + +run_is_full: or a - jr z,print_tone_low + jr z,run_check_low - ld a,#'H' + ld a,(HIGH_RUN_THRESH) - jp BIOS_PUTC + jr run_do_compare -print_tone_low: +run_check_low: - ld a,#'L' + ld a,(LOW_RUN_THRESH) - jp BIOS_PUTC + +run_do_compare: + + ld b,a + + + ld a,(RUN_COUNT) + + cp b + + + ; run_count < threshold -> HALF + + jr c,run_is_half + + + ld a,#1 + + ret + + +run_is_half: + + xor a + + ret + + +; ===================================================================== +; get_bit +; +; Entry: +; +; positioned immediately after a known Manchester midpoint +; +; CURRENT_TONE = carrier following that midpoint +; +; +; If the next carrier run is FULL: +; +; its ending transition is the next midpoint. +; +; +; If the next carrier run is HALF: +; +; its ending transition is a bit boundary. +; +; Consume another HALF run to reach the midpoint. +; +; +; At a midpoint: +; +; LOW -> HIGH = bit 0 +; HIGH -> LOW = bit 1 +; +; Since TEMP_TONE is the tone AFTER the midpoint: +; +; new HIGH = bit 0 +; new LOW = bit 1 +; +; Returns: +; +; A = 0 or 1 +; +; Preserves BC, DE, HL. +; ===================================================================== + +get_bit: + + push bc + push de + push hl + + + ld a,(CURRENT_TONE) + + ld (OLD_TONE),a + + +; --------------------------------------------------------------------- +; Measure first run after previous midpoint. +; --------------------------------------------------------------------- + + call scan_run + + + ; A = new tone. + + ld (TEMP_TONE),a + + + ; Was old run FULL or HALF? + + ld a,(OLD_TONE) + + call run_is_full + + + or a + + jr nz,get_bit_at_midpoint + + +; --------------------------------------------------------------------- +; HALF: +; +; First transition was a bit boundary. +; +; Consume the second HALF run. +; --------------------------------------------------------------------- + + ld a,(TEMP_TONE) + + ld (CURRENT_TONE),a + + + call scan_run + + + ld (TEMP_TONE),a + + + ; This second run should also be HALF. + + ld a,(CURRENT_TONE) + + call run_is_full + + + or a + + jp nz,timing_error + + +; --------------------------------------------------------------------- +; We are now at a Manchester midpoint. +; --------------------------------------------------------------------- + +get_bit_at_midpoint: + + ld a,(TEMP_TONE) + + ld (CURRENT_TONE),a + + + ; New HIGH means LOW -> HIGH = bit 0. + + or a + + jr z,get_bit_one + + + xor a + + jr get_bit_done + + +get_bit_one: + + ld a,#1 + + +get_bit_done: + + pop hl + pop de + pop bc + + ret + + +; ===================================================================== +; get_byte +; +; Decode eight bits, MSB first. +; ===================================================================== + +get_byte: + + push bc + push de + + + ld b,#8 + ld e,#0 + + +get_byte_loop: + + call get_bit + + + ld d,a + + + sla e + + + ld a,d + + or e + + ld e,a + + + djnz get_byte_loop + + + ld a,e + + + pop de + pop bc + + ret + + +; ===================================================================== +; Diagnostic printing +; ===================================================================== + +print_training_values: + + ld hl,#msg_leader + call print_string + + ld a,(LEADER_AVG) + call print_hex8 + + call print_crlf + + + ld hl,#msg_leader_max + call print_string + + ld a,(LEADER_MAX) + call print_hex8 + + call print_crlf + + + ld hl,#msg_low_trigger + call print_string + + ld a,(LOW_TRIGGER) + call print_hex8 + + call print_crlf + + + ld hl,#msg_tone_threshold + call print_string + + ld a,(TONE_THRESHOLD) + call print_hex8 + + call print_crlf + + + ld hl,#msg_high_run + call print_string + + ld a,(HIGH_RUN_AVG) + call print_hex8 + + call print_crlf + + + ld hl,#msg_low_run + call print_string + + ld a,(LOW_RUN_AVG) + call print_hex8 + + call print_crlf + + + ld hl,#msg_high_threshold + call print_string + + ld a,(HIGH_RUN_THRESH) + call print_hex8 + + call print_crlf + + + ld hl,#msg_low_threshold + call print_string + + ld a,(LOW_RUN_THRESH) + call print_hex8 + + call print_crlf + + call print_crlf + + ret ; ===================================================================== @@ -918,8 +1451,6 @@ print_with_irqs: ret -; --------------------------------------------------------------------- - print_string: ld a,(hl) @@ -937,8 +1468,6 @@ print_string: jr print_string -; --------------------------------------------------------------------- - print_crlf: ld a,#13 @@ -952,7 +1481,7 @@ print_crlf: ; ===================================================================== ; print_hex8 ; -; A -> two hexadecimal characters. +; Print A as two hexadecimal characters. ; ===================================================================== print_hex8: @@ -975,7 +1504,7 @@ print_hex8: and #0x0F -; Fall through. +; fall through print_hex_nibble: @@ -998,58 +1527,88 @@ print_hex_digit: ; ===================================================================== -; ROM strings +; Strings ; ===================================================================== msg_start: - .ascii "TAPE TRAINING - PLAY TAPE" + .ascii "TAPE DECODE TEST - PLAY TAPE" .db 13,10,0 -msg_leader_avg: +msg_sync_ok: - .ascii "LEADER AVG = " + .ascii "SYNC D3 91 FOUND" + .db 13,10,0 + + +msg_training_error: + + .ascii "TAPE TRAINING ERROR" + .db 13,10,0 + + +msg_sync_error: + + .ascii "TAPE SYNC ERROR" + .db 13,10,0 + + +msg_timing_error: + + .ascii "TAPE MANCHESTER TIMING ERROR" + .db 13,10,0 + + +msg_leader: + + .ascii "LEADER AVG = " .db 0 msg_leader_max: - .ascii "LEADER MAX = " + .ascii "LEADER MAX = " .db 0 msg_low_trigger: - .ascii "LOW TRIGGER = " + .ascii "LOW TRIGGER = " .db 0 -msg_threshold: +msg_tone_threshold: - .ascii "H/L THRESHOLD = " + .ascii "CARRIER THRESHOLD = " .db 0 -msg_raw: +msg_high_run: - .ascii "RAW HALF-PERIOD COUNTS:" + .ascii "HIGH FULL RUN = " .db 0 -msg_class: +msg_low_run: - .ascii "CLASSIFICATION:" + .ascii "LOW FULL RUN = " .db 0 -msg_runs: +msg_high_threshold: - .ascii "RUN LENGTHS:" + .ascii "HIGH RUN THRESH = " .db 0 -msg_done: +msg_low_threshold: - .ascii "TRAINING CAPTURE DONE" - .db 13,10,0 \ No newline at end of file + .ascii "LOW RUN THRESH = " + .db 0 + + +msg_bytes: + + .ascii "DECODED BYTES:" + .db 0 \ No newline at end of file diff --git a/test.wav b/test.wav index 13fd33f..5dd4eaa 100644 Binary files a/test.wav and b/test.wav differ