1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3use std::fmt;
4use std::str::FromStr;
5
6use crate::error::Error;
7use crate::expression::ExpressionDescriptor;
8use crate::part::{VoicePart, WavePart};
9use crate::time::{Tempo, TimeSignature};
10use crate::track::Track;
11use crate::version::{CURRENT_VERSION, Version};
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
18#[serde(rename_all = "snake_case")]
19pub struct Project {
20 #[serde(default = "default_project_name")]
22 pub name: String,
23 #[serde(default)]
25 pub comment: String,
26 #[serde(default = "default_output_dir")]
28 pub output_dir: String,
29 #[serde(default = "default_cache_dir")]
31 pub cache_dir: String,
32 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub ustx_version: Option<Version>,
35 #[serde(default = "default_resolution")]
37 pub resolution: i32,
38 #[serde(default = "default_bpm")]
40 pub bpm: f64,
41 #[serde(default = "default_beat_per_bar")]
43 pub beat_per_bar: i32,
44 #[serde(default = "default_beat_unit")]
46 pub beat_unit: i32,
47 #[serde(default)]
49 pub expressions: BTreeMap<String, ExpressionDescriptor>,
50 #[serde(default = "default_exp_selectors")]
52 pub exp_selectors: Vec<String>,
53 #[serde(default)]
55 pub exp_primary: i32,
56 #[serde(default = "default_exp_secondary")]
58 pub exp_secondary: i32,
59 #[serde(default)]
61 pub key: i32,
62 #[serde(default = "default_time_signatures")]
64 pub time_signatures: Vec<TimeSignature>,
65 #[serde(default = "default_tempos")]
67 pub tempos: Vec<Tempo>,
68 #[serde(default = "default_tracks")]
70 pub tracks: Vec<Track>,
71 #[serde(default)]
73 pub voice_parts: Vec<VoicePart>,
74 #[serde(default)]
76 pub wave_parts: Vec<WavePart>,
77}
78
79impl Project {
80 #[inline]
82 pub fn from_yaml_str(input: &str) -> Result<Self, Error> {
83 let mut documents = serde_yaml::Deserializer::from_str(input);
84 let document = documents.next().ok_or_else(|| Error::MissingDocument)?;
85 let project = Project::deserialize(document)?;
86 Ok(project)
87 }
88
89 #[inline]
91 pub fn to_yaml_string(&self) -> Result<String, Error> {
92 serde_yaml::to_string(self).map_err(Error::from)
93 }
94
95 #[inline]
100 pub fn from_yaml_str_with_compat(input: &str) -> Result<Self, Error> {
101 let mut project = Self::from_yaml_str(input)?;
102 project.convert_to(CURRENT_VERSION)?;
103 Ok(project)
104 }
105
106 #[inline]
108 pub fn to_yaml_string_with_compat(&self) -> Result<String, Error> {
109 let mut project = self.clone();
110 project.convert_to(CURRENT_VERSION)?;
111 serde_yaml::to_string(&project).map_err(Error::from)
112 }
113
114 pub fn convert_to(&mut self, target: Version) -> Result<(), Error> {
116 let detected = self.ustx_version.unwrap_or_else(Version::zero);
117
118 if target > CURRENT_VERSION {
119 return Err(Error::unsupported_version(target.to_string()));
120 }
121 if detected > CURRENT_VERSION {
122 return Err(Error::unsupported_version(detected.to_string()));
123 }
124 if detected >= target {
125 self.ustx_version = Some(target);
126 return Ok(());
127 }
128
129 if detected < VERSION_0_4 && target >= VERSION_0_4 {
130 self.convert_pre_0_4();
131 }
132 if detected < VERSION_0_5 && target >= VERSION_0_5 {
133 self.convert_pre_0_5();
134 }
135 if detected < VERSION_0_6 && target >= VERSION_0_6 {
136 self.convert_pre_0_6();
137 }
138 if detected < VERSION_0_7 && target >= VERSION_0_7 {
139 self.convert_pre_0_7();
140 }
141
142 self.ustx_version = Some(target);
143 Ok(())
144 }
145}
146
147impl fmt::Display for Project {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 self.to_yaml_string_with_compat()
150 .map_or(Err(fmt::Error), |text| f.write_str(&text))
151 }
152}
153
154impl FromStr for Project {
155 type Err = Error;
156
157 #[inline]
158 fn from_str(s: &str) -> Result<Self, Self::Err> {
159 Self::from_yaml_str_with_compat(s)
160 }
161}
162
163#[inline]
164fn default_project_name() -> String {
165 String::from("New Project")
166}
167
168#[inline]
169fn default_output_dir() -> String {
170 String::from("Vocal")
171}
172
173#[inline]
174fn default_cache_dir() -> String {
175 String::from("UCache")
176}
177
178#[inline]
179const fn default_resolution() -> i32 {
180 480
181}
182
183#[inline]
184const fn default_bpm() -> f64 {
185 120.0
186}
187
188#[inline]
189const fn default_beat_per_bar() -> i32 {
190 4
191}
192
193#[inline]
194const fn default_beat_unit() -> i32 {
195 4
196}
197
198#[inline]
199fn default_exp_selectors() -> Vec<String> {
200 vec![
201 "dyn".into(),
202 "pitd".into(),
203 "clr".into(),
204 "eng".into(),
205 "vel".into(),
206 "vol".into(),
207 "atk".into(),
208 "dec".into(),
209 "gen".into(),
210 "bre".into(),
211 ]
212}
213
214#[inline]
215const fn default_exp_secondary() -> i32 {
216 1
217}
218
219#[inline]
220fn default_time_signatures() -> Vec<TimeSignature> {
221 vec![TimeSignature::default()]
222}
223
224#[inline]
225fn default_tempos() -> Vec<Tempo> {
226 vec![Tempo::default()]
227}
228
229#[inline]
230fn default_tracks() -> Vec<Track> {
231 vec![Track::default()]
232}
233
234const VERSION_0_4: Version = Version::new(0, 4, 0);
235const VERSION_0_5: Version = Version::new(0, 5, 0);
236const VERSION_0_6: Version = Version::new(0, 6, 0);
237const VERSION_0_7: Version = Version::new(0, 7, 0);
238
239const OLD_ACCENT_ABBR: &str = "acc";
240const NEW_ACCENT_ABBR: &str = "atk";
241const NEW_ACCENT_NAME: &str = "attack";
242const DEFAULT_SELECTORS: [&str; 10] = [
243 "dyn", "pitd", "clr", "eng", "vel", "vol", "atk", "dec", "gen", "bre",
244];
245
246impl Project {
247 fn convert_pre_0_4(&mut self) {
248 if self
249 .expressions
250 .get(OLD_ACCENT_ABBR)
251 .is_none_or(|descriptor| descriptor.name != "accent")
252 {
253 return;
254 }
255 let Some(mut descriptor) = self.expressions.remove(OLD_ACCENT_ABBR) else {
256 return;
257 };
258 descriptor.abbr = String::from(NEW_ACCENT_ABBR);
259 descriptor.name = String::from(NEW_ACCENT_NAME);
260 self.expressions
261 .insert(String::from(NEW_ACCENT_ABBR), descriptor);
262
263 for part in &mut self.voice_parts {
264 for note in &mut part.notes {
265 for expression in &mut note.phoneme_expressions {
266 if expression.abbr == OLD_ACCENT_ABBR {
267 expression.abbr = String::from(NEW_ACCENT_ABBR);
268 }
269 }
270 }
271 }
272 }
273
274 fn convert_pre_0_5(&mut self) {
275 for part in &mut self.voice_parts {
276 for note in &mut part.notes {
277 if note.lyric.starts_with("...") {
278 note.lyric = note.lyric.replace("...", "+");
279 }
280 }
281 }
282 }
283
284 fn convert_pre_0_6(&mut self) {
285 let beat_per_bar = if self.beat_per_bar > 0 {
286 self.beat_per_bar
287 } else {
288 4
289 };
290 let beat_unit = if self.beat_unit > 0 {
291 self.beat_unit
292 } else {
293 4
294 };
295 let bpm = if self.bpm > 0.0 { self.bpm } else { 120.0 };
296
297 self.time_signatures = vec![TimeSignature {
298 bar_position: 0,
299 beat_per_bar,
300 beat_unit,
301 }];
302 self.tempos = vec![Tempo { position: 0, bpm }];
303 }
304
305 fn convert_pre_0_7(&mut self) {
306 if self.exp_selectors.len() >= DEFAULT_SELECTORS.len() {
307 return;
308 }
309 let mut selectors = DEFAULT_SELECTORS
310 .iter()
311 .map(|&value| String::from(value))
312 .collect::<Vec<_>>();
313 for (index, existing) in self.exp_selectors.iter().enumerate() {
314 if let Some(target) = selectors.get_mut(index) {
315 target.clone_from(existing);
316 } else {
317 break;
318 }
319 }
320 self.exp_selectors = selectors;
321 }
322}