summaryrefslogtreecommitdiff
path: root/src/main.rs
blob: c711d3dbc0daa75f3720e01ca52c2e746ab2a879 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
use anyhow::{Result, bail};
use clap::Parser;
use std::path::PathBuf;
use std::time::Instant;

mod generator;

#[derive(Parser)]
#[command(
    name = "ditdah",
    version,
    about = "High-performance Morse code decoder",
    long_about = "A high-performance Rust implementation of a Morse code decoder that can process WAV audio files and decode them into text with 100% accuracy on the comprehensive test suite."
)]
struct Cli {
    /// Path to the input WAV file
    #[arg(value_name = "FILE", help = "WAV file containing Morse code audio")]
    wav_file: Option<PathBuf>,
    
    /// Show detailed processing information
    #[arg(short, long, help = "Enable verbose output")]
    verbose: bool,
    
    /// Show timing information
    #[arg(short, long, help = "Show processing time")]
    time: bool,
    
    /// Generate a test WAV file instead of decoding
    #[arg(long, value_name = "TEXT", help = "Generate test WAV file with given text")]
    generate: Option<String>,
    
    /// Output file for generation (default: output.wav)
    #[arg(short, long, value_name = "FILE", help = "Output file for generated WAV")]
    output: Option<PathBuf>,
    
    /// Frequency for generated audio (default: 600 Hz)
    #[arg(long, value_name = "HZ", default_value = "600", help = "Frequency in Hz for generated audio")]
    frequency: f32,
    
    /// Words per minute for generated audio (default: 20 WPM)
    #[arg(long, value_name = "WPM", default_value = "20", help = "Words per minute for generated audio")]
    wpm: f32,
}

fn main() -> Result<()> {
    let cli = Cli::parse();
    
    // Set up logging - user can control with RUST_LOG environment variable
    env_logger::try_init().ok();
    
    // Handle generation mode
    if let Some(text) = &cli.generate {
        return generate_wav_file(&cli, text);
    }
    
    // Validate we have an input file for decoding
    let wav_file = cli.wav_file.as_ref()
        .ok_or_else(|| anyhow::anyhow!("No input file specified. Use --help for usage information."))?;
    
    // Validate input file exists
    if !wav_file.exists() {
        bail!("File not found: {}", wav_file.display());
    }
    
    let start_time = Instant::now();
    
    if cli.verbose {
        println!("Processing: {}", wav_file.display());
    }
    
    // Use the new high-level API
    let decoded_text = ditdah::decode_wav_file(wav_file)
        .map_err(|e| {
            // Provide user-friendly error messages
            match e.to_string().as_str() {
                s if s.contains("No such file") => anyhow::anyhow!("File not found: {}", wav_file.display()),
                s if s.contains("Unsupported sample format") => anyhow::anyhow!("Unsupported audio format. Please use 16-bit or 32-bit WAV files."),
                s if s.contains("Could not find a dominant frequency") => anyhow::anyhow!("No Morse code signal detected. Check that the file contains clear Morse audio."),
                _ => e
            }
        })?;
    
    let duration = start_time.elapsed();
    
    // Output results
    if decoded_text.is_empty() {
        println!("No Morse code detected");
        if !cli.verbose {
            println!("Try using --verbose flag for detailed processing information");
        }
    } else {
        println!("Decoded: {}", decoded_text);
        
        if cli.time {
            println!("Processing time: {:.2?}", duration);
        }
        
        if cli.verbose {
            println!("Length: {} characters", decoded_text.len());
            if decoded_text.len() > 50 {
                println!("Text: {}...", &decoded_text[..50]);
            }
        }
    }
    
    Ok(())
}

fn generate_wav_file(cli: &Cli, text: &str) -> Result<()> {
    use generator::MorseGenerator;
    
    let output_path = cli.output.as_ref()
        .map(|p| p.clone())
        .unwrap_or_else(|| PathBuf::from("output.wav"));
    
    if cli.verbose {
        println!("Generating: '{}' at {} Hz, {} WPM", text, cli.frequency, cli.wpm);
        println!("Output: {}", output_path.display());
    }
    
    let generator = MorseGenerator::new(12000, cli.frequency, cli.wpm);
    generator.generate_wav_file(text, &output_path)?;
    
    println!("Generated: {}", output_path.display());
    
    if cli.verbose {
        println!("Settings: {} Hz, {} WPM, 12kHz sample rate", cli.frequency, cli.wpm);
        
        // Show what the Morse pattern looks like
        let morse_pattern = text_to_morse_pattern(text);
        if !morse_pattern.is_empty() {
            println!("Morse: {}", morse_pattern);
        }
    }
    
    Ok(())
}

fn text_to_morse_pattern(text: &str) -> String {
    let morse_map = [
        ('A', ".-"), ('B', "-..."), ('C', "-.-."), ('D', "-.."), ('E', "."),
        ('F', "..-."), ('G', "--."), ('H', "...."), ('I', ".."), ('J', ".---"),
        ('K', "-.-"), ('L', ".-.."), ('M', "--"), ('N', "-."), ('O', "---"),
        ('P', ".--."), ('Q', "--.-"), ('R', ".-."), ('S', "..."), ('T', "-"),
        ('U', "..-"), ('V', "...-"), ('W', ".--"), ('X', "-..-"), ('Y', "-.--"),
        ('Z', "--.."), ('1', ".----"), ('2', "..---"), ('3', "...--"), ('4', "....-"),
        ('5', "....."), ('6', "-...."), ('7', "--..."), ('8', "---.."), ('9', "----."),
        ('0', "-----"),
    ].into_iter().collect::<std::collections::HashMap<_, _>>();
    
    text.to_uppercase()
        .chars()
        .filter_map(|c| {
            if c == ' ' {
                Some("  ".to_string())
            } else {
                morse_map.get(&c).map(|&morse| format!("{} ", morse))
            }
        })
        .collect::<String>()
        .trim()
        .to_string()
}