#!/usr/bin/env python3 """ ============================================================================== MSX .CAS to .WAV Converter Engine Adapted via Gemini (Google) on July 5, 2026. Technical Specifications Derived From: - MSX Assembly Page: Tape Data Structure Spec (1200/2400Hz FSK Hardware Layer) - CAS File Format Definition (8-byte header token: 1F A6 DE BA CC 13 7D 74) ============================================================================== The code provided in this file is entirely public domain, meaning you are legally free to do whatever you want with it. Because it was generated by an AI assistant based on public, historical technical specifications, it is not bound by any pre-existing software license. ## Acknowledgments * **MSX Technical Specifications:** Data protocol rules based on the original MSX 1 Hardware BIOS specification for Frequency Shift Keying (FSK) cassette modulation. * **Implementation Engine:** Code structure and cycle-exact sample synthesis generated in collaboration with Google AI Search (July 2026). """ import math, os, struct, sys, wave CAS_HEADER_MARKER = b"\x1f\xa6\xde\xba\xcc\x13\x7d\x74" SAMPLE_RATE = 22050 PEAK_AMPLITUDE = 0.7 FIRST_LEADER_DURATION = 4.0 NORMAL_LEADER_DURATION = 2.0 FINAL_SILENCE_DURATION = 2.0 BAUD_RATE = 1200 def generate_cycle(freq): samples_in_cycle = int(SAMPLE_RATE / freq) cycle = [] for i in range(samples_in_cycle): value = PEAK_AMPLITUDE * math.sin(2 * math.pi * (i / samples_in_cycle)) cycle.append(int(value * 32767.0)) return cycle def generate_silence(duration): samples_in_silence = int(SAMPLE_RATE * duration) return [0] * samples_in_silence def encode_bit(bit_value): samples = [] if bit_value == 0: samples.extend(generate_cycle(BAUD_RATE)) else: samples.extend(generate_cycle(2 * BAUD_RATE)) samples.extend(generate_cycle(2 * BAUD_RATE)) return samples def encode_byte(byte_val): samples = [] samples.extend(encode_bit(0)) for i in range(8): bit = (byte_val >> i) & 1 samples.extend(encode_bit(bit)) samples.extend(encode_bit(1)) samples.extend(encode_bit(1)) return samples def generate_pilot_leader(duration_seconds): samples = [] cycles_needed = int(2 * BAUD_RATE * duration_seconds) for _ in range(cycles_needed): samples.extend(generate_cycle(2 * BAUD_RATE)) return samples def cas_to_wav(cas_filename, wav_filename): # this is a general purpose data converter. the extension # validation by assertion is reasonable, but dictating other # aspects of the input and output paths would not be, so we # suppress sonarqube validation/taint analysis assert os.path.splitext(cas_filename)[-1].lower() == ".cas" assert os.path.splitext(wav_filename)[-1].lower() == ".wav" with open(cas_filename, "rb") as f: # NOSONAR cas_data = f.read() pcm_frames = [] idx = 0 total_bytes = len(cas_data) is_first_header = True while idx < total_bytes: if cas_data[idx : idx + 8] == CAS_HEADER_MARKER: leader_duration = ( FIRST_LEADER_DURATION if is_first_header else NORMAL_LEADER_DURATION ) pcm_frames.extend(generate_pilot_leader(leader_duration)) is_first_header = False idx += 8 else: pcm_frames.extend(encode_byte(cas_data[idx])) idx += 1 pcm_frames.extend(generate_silence(FINAL_SILENCE_DURATION)) with wave.open(wav_filename, "wb") as wav_file: # NOSONAR wav_file.setnchannels(1) wav_file.setsampwidth(2) wav_file.setframerate(SAMPLE_RATE) packed_data = struct.pack(f"{len(pcm_frames)}h", *pcm_frames) wav_file.writeframes(packed_data) if __name__ == "__main__": if ( len(sys.argv) != 3 or os.path.splitext(sys.argv[1])[-1].lower() != ".cas" or os.path.splitext(sys.argv[2])[-1].lower() != ".wav" ): print("Usage: python cas2wav.py ") else: cas_to_wav(sys.argv[1], sys.argv[2])