1use std::sync::Arc;
4
5use sim_kernel::{
6 Args, Callable, ClassRef, Cx, Error, Export, ExportKind, ExportRecord, ExportState, Expr, Lib,
7 LibManifest, Linker, LoadCx, Object, ObjectCompat, RawArgs, Result, RuntimeId, ShapeRef,
8 Symbol, Value,
9};
10use sim_lib_core::{SurfaceField, SurfacePackLib, SurfacePackSpec, SurfaceValueSpec, install_once};
11use sim_lib_music_shapes::{MusicScoreDescriptor, encode_score, install_music_shapes_lib};
12use sim_shape::{AnyShape, ExactExprShape, ListShape, shape_value};
13
14use crate::{
15 MusicXmlLimits, NotationIdentityKind, NotationLossKind, import_musicxml_partwise_report,
16};
17
18const MUSIC_NOTATION_LIB_ID: &str = "music-notation";
19const EXPORT_KIND_NAME: &str = "NotationCodec";
20
21pub struct MusicNotationLib;
24
25impl Lib for MusicNotationLib {
26 fn manifest(&self) -> LibManifest {
27 let mut manifest = music_notation_pack().manifest();
28 manifest.exports.push(Export::Function {
29 symbol: notation_import_symbol(),
30 function_id: None,
31 });
32 manifest
33 }
34
35 fn load(&self, cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
36 music_notation_pack().load(cx, linker)?;
37 linker.function_value(
38 notation_import_symbol(),
39 cx.factory().opaque(Arc::new(NotationImportFunction))?,
40 )?;
41 Ok(())
42 }
43}
44
45pub fn install_music_notation_lib(cx: &mut Cx) -> Result<()> {
49 install_music_shapes_lib(cx)?;
50 if !install_once(cx, &MusicNotationLib)? {
51 return Ok(());
52 }
53 let lib = Symbol::new(MUSIC_NOTATION_LIB_ID);
54 cx.registry_mut().append_export_record(
55 &lib,
56 ExportRecord {
57 kind: ExportKind::named(EXPORT_KIND_NAME),
58 symbol: notation_symbol(),
59 state: ExportState::Resolved {
60 id: RuntimeId::Value,
61 },
62 },
63 )?;
64 Ok(())
65}
66
67pub fn notation_import_symbol() -> Symbol {
69 Symbol::qualified("music/notation", "import")
70}
71
72fn notation_symbol() -> Symbol {
73 Symbol::qualified("music", "LilyPondSubsetCodec")
74}
75
76fn notation_value_spec() -> SurfaceValueSpec {
77 SurfaceValueSpec {
78 symbol: notation_symbol(),
79 fields: vec![
80 (
81 Symbol::new("symbol"),
82 SurfaceField::Symbol(notation_symbol()),
83 ),
84 (Symbol::new("layer"), SurfaceField::Str("music".to_owned())),
85 (Symbol::new("kind"), SurfaceField::Str("plugin".to_owned())),
86 (
87 Symbol::new("shape"),
88 SurfaceField::Symbol(Symbol::qualified("music", "NotationCodec")),
89 ),
90 (
91 Symbol::new("dependencies"),
92 SurfaceField::Strs(vec![
93 "music-core".to_owned(),
94 "music-shapes".to_owned(),
95 "pitch-core".to_owned(),
96 ]),
97 ),
98 (Symbol::new("lossless"), SurfaceField::Bool(false)),
99 (Symbol::new("capabilities"), SurfaceField::Symbols(vec![])),
100 (
101 Symbol::new("surface"),
102 SurfaceField::Str("lilypond-subset,musicxml-partwise".to_owned()),
103 ),
104 ],
105 }
106}
107
108fn music_notation_pack() -> SurfacePackLib {
109 SurfacePackLib {
110 spec: SurfacePackSpec {
111 lib_id: Symbol::new(MUSIC_NOTATION_LIB_ID),
112 values: vec![notation_value_spec()],
113 },
114 }
115}
116
117struct NotationImportFunction;
118
119impl Object for NotationImportFunction {
120 fn display(&self, _cx: &mut Cx) -> Result<String> {
121 Ok("#<function music/notation/import>".to_owned())
122 }
123
124 fn as_any(&self) -> &dyn std::any::Any {
125 self
126 }
127}
128
129impl ObjectCompat for NotationImportFunction {
130 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
131 cx.factory().class_stub(
132 sim_kernel::CORE_FUNCTION_CLASS_ID,
133 Symbol::qualified("core", "Function"),
134 )
135 }
136
137 fn as_callable(&self) -> Option<&dyn Callable> {
138 Some(self)
139 }
140}
141
142impl Callable for NotationImportFunction {
143 fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
144 let exprs = args
145 .into_vec()
146 .into_iter()
147 .map(|value| value.object().as_expr(cx))
148 .collect::<Result<Vec<_>>>()?;
149 import_call(cx, &exprs, false)
150 }
151
152 fn call_exprs(&self, cx: &mut Cx, args: RawArgs) -> Result<Value> {
153 import_call(cx, args.exprs(), true)
154 }
155
156 fn browse_args_shape(&self, _cx: &mut Cx) -> Result<Option<ShapeRef>> {
157 let keyword = |name| {
158 Arc::new(ExactExprShape::new(Expr::Symbol(Symbol::new(name))))
159 as Arc<dyn sim_shape::Shape>
160 };
161 Ok(Some(shape_value(
162 Symbol::qualified("music/notation/import", "args"),
163 Arc::new(ListShape::tuple(vec![
164 keyword(":format"),
165 Arc::new(AnyShape),
166 keyword(":source"),
167 Arc::new(AnyShape),
168 keyword(":limits"),
169 Arc::new(AnyShape),
170 ])),
171 )))
172 }
173
174 fn browse_result_shape(&self, _cx: &mut Cx) -> Result<Option<ShapeRef>> {
175 Ok(Some(shape_value(
176 Symbol::qualified("music/notation/import", "result"),
177 Arc::new(AnyShape),
178 )))
179 }
180}
181
182fn import_call(cx: &mut Cx, args: &[Expr], evaluate_values: bool) -> Result<Value> {
183 let [format_key, format, source_key, source, limits_key, limits] = args else {
184 return Err(Error::Eval(
185 "music/notation/import expects :format FORMAT :source BYTES :limits MAP".to_owned(),
186 ));
187 };
188 expect_keyword(format_key, "format")?;
189 expect_keyword(source_key, "source")?;
190 expect_keyword(limits_key, "limits")?;
191 if symbolish(format)? != "musicxml-partwise" {
192 return Err(Error::Eval(
193 "music/notation/import supports only 'musicxml-partwise".to_owned(),
194 ));
195 }
196 let source = value_expr(cx, source, evaluate_values)?;
197 let source = match source {
198 Expr::Bytes(bytes) => bytes,
199 Expr::String(text) => text.into_bytes(),
200 other => {
201 return Err(Error::TypeMismatch {
202 expected: "MusicXML bytes or string",
203 found: expr_kind(&other),
204 });
205 }
206 };
207 let limits = value_expr(cx, limits, evaluate_values)?;
208 let limits = parse_limits(&limits)?;
209 let report = import_musicxml_partwise_report(&source, limits)
210 .map_err(|error| Error::Eval(error.to_string()))?;
211 let score_form = encode_score(&report.value).map_err(|error| Error::Eval(error.to_string()))?;
212 let score = MusicScoreDescriptor::read_construct_expr_from_text(&score_form)?;
213 let identities = report
214 .identities
215 .into_iter()
216 .map(|identity| {
217 map(vec![
218 (
219 "kind",
220 Expr::Symbol(Symbol::new(match identity.kind {
221 NotationIdentityKind::Part => "part",
222 NotationIdentityKind::Event => "event",
223 })),
224 ),
225 ("path", Expr::String(identity.canonical_path)),
226 ("id", Expr::String(identity.xml_id)),
227 ])
228 })
229 .collect();
230 let losses = report
231 .losses
232 .into_iter()
233 .map(|loss| {
234 map(vec![
235 (
236 "kind",
237 Expr::Symbol(Symbol::new(match loss.kind {
238 NotationLossKind::Clef => "clef",
239 NotationLossKind::PartName => "part-name",
240 NotationLossKind::PitchSpelling => "pitch-spelling",
241 NotationLossKind::DefaultedTempo => "defaulted-tempo",
242 NotationLossKind::DefaultedTimeSignature => "defaulted-time-signature",
243 NotationLossKind::Velocity => "velocity",
244 NotationLossKind::Channel => "channel",
245 })),
246 ),
247 ("path", loss.canonical_path.map_or(Expr::Nil, Expr::String)),
248 ("detail", Expr::String(loss.detail)),
249 ])
250 })
251 .collect();
252 cx.factory().expr(map(vec![
253 ("format", Expr::Symbol(Symbol::new("musicxml-partwise"))),
254 ("score", score),
255 ("identities", Expr::Vector(identities)),
256 ("losses", Expr::Vector(losses)),
257 ]))
258}
259
260fn value_expr(cx: &mut Cx, expr: &Expr, evaluate: bool) -> Result<Expr> {
261 if evaluate {
262 cx.eval_expr(expr.clone())?.object().as_expr(cx)
263 } else {
264 Ok(expr.clone())
265 }
266}
267
268fn parse_limits(expr: &Expr) -> Result<MusicXmlLimits> {
269 let Expr::Map(entries) = unquote_ref(expr) else {
270 return Err(Error::TypeMismatch {
271 expected: "MusicXML limits map",
272 found: expr_kind(expr),
273 });
274 };
275 let mut limits = MusicXmlLimits::default();
276 for (key, value) in entries {
277 let name = keyword_name(key)?;
278 let parsed = usize_expr(value)?;
279 match name.as_str() {
280 "bytes" => limits.bytes = parsed,
281 "nodes" => limits.nodes = parsed,
282 "depth" => limits.depth = parsed,
283 "text" => limits.text = parsed,
284 "parts" => limits.parts = parsed,
285 "events" => limits.events = parsed,
286 other => {
287 return Err(Error::Eval(format!(
288 "unknown music/notation/import limit :{other}"
289 )));
290 }
291 }
292 }
293 Ok(limits)
294}
295
296fn expect_keyword(expr: &Expr, expected: &str) -> Result<()> {
297 if keyword_name(expr)? == expected {
298 Ok(())
299 } else {
300 Err(Error::Eval(format!(
301 "music/notation/import expected :{expected}"
302 )))
303 }
304}
305
306fn keyword_name(expr: &Expr) -> Result<String> {
307 match unquote_ref(expr) {
308 Expr::Symbol(symbol) => Ok(symbol
309 .name
310 .strip_prefix(':')
311 .unwrap_or(symbol.name.as_ref())
312 .to_owned()),
313 _ => Err(Error::TypeMismatch {
314 expected: "keyword symbol",
315 found: expr_kind(expr),
316 }),
317 }
318}
319
320fn symbolish(expr: &Expr) -> Result<String> {
321 match unquote_ref(expr) {
322 Expr::Symbol(symbol) => Ok(symbol.name.to_string()),
323 Expr::String(value) => Ok(value.clone()),
324 _ => Err(Error::TypeMismatch {
325 expected: "format symbol",
326 found: expr_kind(expr),
327 }),
328 }
329}
330
331fn usize_expr(expr: &Expr) -> Result<usize> {
332 let text = match unquote_ref(expr) {
333 Expr::Number(number) => number.canonical.as_str(),
334 Expr::String(value) => value.as_str(),
335 _ => {
336 return Err(Error::TypeMismatch {
337 expected: "non-negative integer",
338 found: expr_kind(expr),
339 });
340 }
341 };
342 text.parse()
343 .map_err(|_| Error::Eval(format!("invalid MusicXML limit {text:?}")))
344}
345
346fn unquote_ref(expr: &Expr) -> &Expr {
347 match expr {
348 Expr::Quote { expr, .. } => expr,
349 other => other,
350 }
351}
352
353fn map(entries: Vec<(&str, Expr)>) -> Expr {
354 Expr::Map(
355 entries
356 .into_iter()
357 .map(|(key, value)| (Expr::Symbol(Symbol::new(key)), value))
358 .collect(),
359 )
360}
361
362fn expr_kind(expr: &Expr) -> &'static str {
363 match expr {
364 Expr::Nil => "nil",
365 Expr::Bool(_) => "bool",
366 Expr::Number(_) => "number",
367 Expr::Symbol(_) => "symbol",
368 Expr::Local(_) => "local",
369 Expr::String(_) => "string",
370 Expr::Bytes(_) => "bytes",
371 Expr::List(_) => "list",
372 Expr::Vector(_) => "vector",
373 Expr::Map(_) => "map",
374 Expr::Set(_) => "set",
375 Expr::Call { .. } => "call",
376 Expr::Infix { .. } => "infix",
377 Expr::Prefix { .. } => "prefix",
378 Expr::Postfix { .. } => "postfix",
379 Expr::Block(_) => "block",
380 Expr::Quote { .. } => "quote",
381 Expr::Annotated { .. } => "annotated",
382 Expr::Extension { .. } => "extension",
383 }
384}