ass_editor/commands/karaoke_commands/
apply_impl.rs1use super::{ApplyKaraokeCommand, KaraokeTemplate, KaraokeType};
4use crate::commands::{CommandResult, EditorCommand};
5use crate::core::{EditorDocument, Position, Range, Result};
6
7#[cfg(not(feature = "std"))]
8use alloc::{
9 format,
10 string::{String, ToString},
11 vec,
12 vec::Vec,
13};
14
15impl EditorCommand for ApplyKaraokeCommand {
16 fn execute(&self, document: &mut EditorDocument) -> Result<CommandResult> {
17 let original_text = document.text_range(self.event_range)?;
18 let karaoke_text = self.apply_karaoke_template(&original_text, document)?;
19
20 document.replace_raw(self.event_range, &karaoke_text)?;
21
22 let end_pos = Position::new(self.event_range.start.offset + karaoke_text.len());
23 let range = Range::new(self.event_range.start, end_pos);
24
25 Ok(CommandResult::success_with_change(range, end_pos))
26 }
27
28 fn description(&self) -> &str {
29 match &self.karaoke_template {
30 KaraokeTemplate::Equal { .. } => "Apply equal karaoke timing",
31 KaraokeTemplate::Beat { .. } => "Apply beat-based karaoke timing",
32 KaraokeTemplate::Pattern { .. } => "Apply pattern-based karaoke timing",
33 KaraokeTemplate::ImportFrom { .. } => "Import karaoke timing",
34 }
35 }
36
37 fn memory_usage(&self) -> usize {
38 let template_size = match &self.karaoke_template {
39 KaraokeTemplate::Pattern { durations, .. } => {
40 durations.len() * core::mem::size_of::<u32>()
41 }
42 _ => 0,
43 };
44 core::mem::size_of::<Self>() + template_size
45 }
46}
47
48impl ApplyKaraokeCommand {
49 fn apply_karaoke_template(&self, text: &str, _document: &EditorDocument) -> Result<String> {
51 let clean_text = self.extract_clean_text(text);
53 let syllables = self.detect_syllables(&clean_text);
54
55 match &self.karaoke_template {
56 KaraokeTemplate::Equal {
57 syllable_duration,
58 karaoke_type,
59 } => self.apply_equal_timing(&syllables, *syllable_duration, *karaoke_type),
60 KaraokeTemplate::Beat {
61 beats_per_minute,
62 beats_per_syllable,
63 karaoke_type,
64 } => self.apply_beat_timing(
65 &syllables,
66 *beats_per_minute,
67 *beats_per_syllable,
68 *karaoke_type,
69 ),
70 KaraokeTemplate::Pattern {
71 durations,
72 karaoke_type,
73 } => self.apply_pattern_timing(&syllables, durations, *karaoke_type),
74 KaraokeTemplate::ImportFrom {
75 source_event_index: _,
76 } => {
77 Ok(text.to_string())
79 }
80 }
81 }
82
83 fn extract_clean_text(&self, text: &str) -> String {
85 let mut result = String::new();
86 let mut chars = text.chars();
87
88 while let Some(ch) = chars.next() {
89 if ch == '{' {
90 let mut brace_count = 1;
92 for inner_ch in chars.by_ref() {
93 if inner_ch == '{' {
94 brace_count += 1;
95 } else if inner_ch == '}' {
96 brace_count -= 1;
97 if brace_count == 0 {
98 break;
99 }
100 }
101 }
102 } else {
103 result.push(ch);
104 }
105 }
106
107 result
108 }
109
110 fn detect_syllables(&self, text: &str) -> Vec<String> {
112 text.split_whitespace()
114 .flat_map(|word| {
115 vec![word.to_string()]
118 })
119 .collect()
120 }
121
122 fn apply_equal_timing(
124 &self,
125 syllables: &[String],
126 duration: u32,
127 karaoke_type: KaraokeType,
128 ) -> Result<String> {
129 let tag = karaoke_type.tag_string();
130 let mut result = String::new();
131
132 for (i, syllable) in syllables.iter().enumerate() {
133 if i > 0 {
134 result.push(' '); }
136 result.push_str(&format!("{{\\{tag}{duration}}}{syllable}"));
137 }
138
139 Ok(result)
140 }
141
142 fn apply_beat_timing(
144 &self,
145 syllables: &[String],
146 bpm: u32,
147 beats_per_syllable: f32,
148 karaoke_type: KaraokeType,
149 ) -> Result<String> {
150 let duration = ((60.0 / bpm as f32) * beats_per_syllable * 100.0) as u32;
152 self.apply_equal_timing(syllables, duration, karaoke_type)
153 }
154
155 fn apply_pattern_timing(
157 &self,
158 syllables: &[String],
159 durations: &[u32],
160 karaoke_type: KaraokeType,
161 ) -> Result<String> {
162 let tag = karaoke_type.tag_string();
163 let mut result = String::new();
164
165 for (i, syllable) in syllables.iter().enumerate() {
166 if i > 0 {
167 result.push(' ');
168 }
169 let duration = durations.get(i % durations.len()).copied().unwrap_or(50);
170 result.push_str(&format!("{{\\{tag}{duration}}}{syllable}"));
171 }
172
173 Ok(result)
174 }
175}