Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 42 additions & 12 deletions src/cw_audio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,21 @@ impl CWAudioPCM {
pub fn get_duration(s: &str, wpm: f32) -> std::time::Duration {
let mut length = 2;

for c in s.chars() {
if c == ' ' {
for morse_code in crate::morse::get_morse_str(s.to_string()) {
if morse_code.0 == 0 {
// space
length += 4;
continue;
}

let (n, b) = crate::morse::get_morse(c);
for i in 0..n {
if b & (1 << i) != 0 {
length += 4;
} else {
length += 2;
} else {
let (n, b) = morse_code;
for i in 0..n {
if b & (1 << i) != 0 {
length += 4;
} else {
length += 2;
}
}
length += 2;
}
length += 2;
}

crate::morse::dot_time(wpm) * length
Expand Down Expand Up @@ -142,3 +142,33 @@ impl std::io::Seek for CWAudioPCM {
unreachable!();
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_cw_audio_brackets() {
// Test that bracket notation creates the correct audio events
// This validates that the audio generation correctly uses the morse module
let audio1 = CWAudioPCM::new("AR".to_string(), 20.0, 600.0, 48000);
let audio2 = CWAudioPCM::new("[AR]".to_string(), 20.0, 600.0, 48000);

// AR should have more events than [AR] because AR has a space between letters
// AR: A + space + R = multiple events
// [AR]: combined pattern = fewer events
assert!(audio1.events.len() > audio2.events.len(),
"AR should have more events than [AR] due to the space between letters");
}

#[test]
fn test_cw_duration_brackets() {
// Test that duration calculation works correctly with brackets
let duration1 = CWAudioPCM::get_duration("AR", 20.0);
let duration2 = CWAudioPCM::get_duration("[AR]", 20.0);

// [AR] should be shorter than "AR" because there's no inter-character space
assert!(duration2 < duration1,
"[AR] should have shorter duration than AR due to no inter-character space");
}
}
154 changes: 149 additions & 5 deletions src/morse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,18 +124,81 @@ pub fn get_morse(c: char) -> (u8, u8) {

pub fn get_morse_str(s: String) -> Vec<(u8, u8)> {
let s = UCSStr::from_str(&s).upper_case().katakana().to_string();

let s = s.nfkd().collect::<String>();

let mut v = Vec::<(u8, u8)>::new();
let mut i = 0;
let chars: Vec<char> = s.chars().collect();

while i < chars.len() {
if chars[i] == '[' {
// Find the closing bracket
if let Some(close_pos) = find_closing_bracket(&chars, i) {
// Extract characters inside brackets and concatenate their Morse codes
let bracket_content: String = chars[i+1..close_pos].iter().collect();
if let Some(concatenated) = concatenate_morse_codes(&bracket_content) {
v.push(concatenated);
}
i = close_pos + 1;
} else {
// No closing bracket found, treat '[' as a regular character
let m = get_morse(chars[i]);
if m.0 == 0 && v.last().map(|x| x.0 == 0).unwrap_or(false) {
i += 1;
continue;
}
v.push(m);
i += 1;
}
} else {
let m = get_morse(chars[i]);
if m.0 == 0 && v.last().map(|x| x.0 == 0).unwrap_or(false) {
i += 1;
continue;
}
v.push(m);
i += 1;
}
}
v
}

fn find_closing_bracket(chars: &[char], start: usize) -> Option<usize> {
for i in start + 1..chars.len() {
if chars[i] == ']' {
return Some(i);
}
}
None
}

fn concatenate_morse_codes(s: &str) -> Option<(u8, u8)> {
let mut total_length = 0u8;
let mut combined_pattern = 0u8;

for c in s.chars() {
let m = get_morse(c);
if m.0 == 0 && v.last().map(|x| x.0 == 0).unwrap_or(false) {
let (length, pattern) = get_morse(c);
if length == 0 {
// Skip whitespace characters inside brackets
continue;
}
v.push(m);

// Check if we can fit this pattern
if total_length + length > 8 {
// Pattern would be too long for u8, return None
return None;
}

// Shift the existing pattern left and add the new pattern
combined_pattern = (combined_pattern << length) | pattern;
total_length += length;
}

if total_length == 0 {
None
} else {
Some((total_length, combined_pattern))
}
v
}

pub fn dot_time(wpm: f32) -> std::time::Duration {
Expand Down Expand Up @@ -197,4 +260,85 @@ mod tests {
]
);
}

#[test]
fn test_morse_brackets() {
// Test basic bracket notation - [AR] should concatenate A (.-) and R (.-.) into (.-.-)
// A = (2, 0b01) = .-
// R = (3, 0b010) = .-.
// [AR] = (5, 0b01010) = .-.-
assert_eq!(
get_morse_str("[AR]".to_string()),
[(5, 0b01010)]
);

// Test bracket with space before/after
assert_eq!(
get_morse_str("A [AR] B".to_string()),
[
(2, 0b01), // A
(0, 0), // space
(5, 0b01010), // [AR]
(0, 0), // space
(4, 0b1000), // B
]
);

// Test multiple brackets
assert_eq!(
get_morse_str("[AR] [SK]".to_string()),
[
(5, 0b01010), // [AR] = .-.-
(0, 0), // space
(6, 0b000101), // [SK] = ...-.
]
);
}

#[test]
fn test_morse_brackets_japanese() {
// Test Japanese characters in brackets - ホレ should be concatenated
// ホ = (3, 0b100) = -..
// レ = (3, 0b111) = ---
// [ホレ] = (6, 0b100111) = -..---
assert_eq!(
get_morse_str("[ホレ]".to_string()),
[(6, 0b100111)]
);
}

#[test]
fn test_morse_brackets_edge_cases() {
// Test empty brackets
assert_eq!(
get_morse_str("[]".to_string()),
[]
);

// Test brackets with spaces inside (should ignore spaces)
assert_eq!(
get_morse_str("[A R]".to_string()),
[(5, 0b01010)] // Same as [AR]
);

// Test unmatched opening bracket
assert_eq!(
get_morse_str("[AR".to_string()),
[
(0, 0), // '[' -> unknown character -> (0, 0)
(2, 0b01), // A
(3, 0b010), // R
]
);

// Test normal text with bracket characters not forming pairs
assert_eq!(
get_morse_str("A[R".to_string()),
[
(2, 0b01), // A
(0, 0), // '[' -> unknown character -> (0, 0)
(3, 0b010), // R
]
);
}
}