Skip to main content

music21_rs/
articulations.rs

1//! Articulations: music21's `articulations` module.
2//!
3//! An articulation is a mark on a note that says how it is played: how much
4//! louder, how much shorter, and, when a note is split across a tie, which
5//! part keeps the mark. music21 has a class for each; they differ in those
6//! numbers, and a few carry a field or two more -- a fingering's finger, a
7//! string or fret number, a bend -- so here an [`Articulation`] is those
8//! values and an [`ArticulationKind`] names the class they started as.
9//!
10//! music21's hammer-on and pull-off are spanners joining two notes rather
11//! than marks on one, and are not articulations here.
12
13use crate::common::stringtools::camel_case_to_hyphen;
14use crate::defaults::{FloatType, IntegerType};
15use crate::interval::ChromaticInterval;
16
17/// The finger a fingering names: a number, or the letter a guitarist's right
18/// hand is written with (`p`, `i`, `m`, `a`) or anything else a score writes.
19/// music21 keeps whichever the score gave, as a number where it reads as one.
20#[derive(Clone, Debug, PartialEq, Eq, Hash)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub enum Finger {
23    /// A finger by number.
24    Number(IntegerType),
25    /// A finger written some other way.
26    Written(String),
27}
28
29impl Finger {
30    /// A finger as a score writes it: a number where the text reads as one,
31    /// as music21's MusicXML reader has it.
32    pub fn from_written(text: &str) -> Self {
33        text.trim()
34            .parse()
35            .map_or_else(|_| Self::Written(text.to_string()), Self::Number)
36    }
37}
38
39/// What an articulation of one kind starts out as.
40struct KindRow {
41    kind: ArticulationKind,
42    class: &'static str,
43    /// Its ancestors up to `Articulation`, in music21's method resolution
44    /// order.
45    parents: &'static [&'static str],
46    volume_shift: FloatType,
47    length_shift: FloatType,
48    tie_attach: &'static str,
49    /// The fields past the base ones music21's class carries, by music21's
50    /// name for them.
51    fields: &'static [&'static str],
52}
53
54/// One of music21's articulation classes.
55#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
56#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
57pub enum ArticulationKind {
58    /// music21's `articulations.Articulation`.
59    Articulation,
60    /// music21's `articulations.Caesura`.
61    Caesura,
62    /// music21's `articulations.DynamicArticulation`.
63    DynamicArticulation,
64    /// music21's `articulations.LengthArticulation`.
65    LengthArticulation,
66    /// music21's `articulations.PitchArticulation`.
67    PitchArticulation,
68    /// music21's `articulations.TechnicalIndication`.
69    TechnicalIndication,
70    /// music21's `articulations.TimbreArticulation`.
71    TimbreArticulation,
72    /// music21's `articulations.Accent`.
73    Accent,
74    /// music21's `articulations.Bowing`.
75    Bowing,
76    /// music21's `articulations.BreathMark`.
77    BreathMark,
78    /// music21's `articulations.DetachedLegato`.
79    DetachedLegato,
80    /// music21's `articulations.Fingering`.
81    Fingering,
82    /// music21's `articulations.FretIndication`.
83    FretIndication,
84    /// music21's `articulations.HandbellIndication`.
85    HandbellIndication,
86    /// music21's `articulations.Harmonic`.
87    Harmonic,
88    /// music21's `articulations.HarpIndication`.
89    HarpIndication,
90    /// music21's `articulations.IndeterminateSlide`.
91    IndeterminateSlide,
92    /// music21's `articulations.OrganIndication`.
93    OrganIndication,
94    /// music21's `articulations.Staccato`.
95    Staccato,
96    /// music21's `articulations.Tenuto`.
97    Tenuto,
98    /// music21's `articulations.Unstress`.
99    Unstress,
100    /// music21's `articulations.WindIndication`.
101    WindIndication,
102    /// music21's `articulations.BrassIndication`.
103    BrassIndication,
104    /// music21's `articulations.Doit`.
105    Doit,
106    /// music21's `articulations.DownBow`.
107    DownBow,
108    /// music21's `articulations.Falloff`.
109    Falloff,
110    /// music21's `articulations.FretBend`.
111    FretBend,
112    /// music21's `articulations.FretTap`.
113    FretTap,
114    /// music21's `articulations.HarpFingerNails`.
115    HarpFingerNails,
116    /// music21's `articulations.OpenString`.
117    OpenString,
118    /// music21's `articulations.OrganHeel`.
119    OrganHeel,
120    /// music21's `articulations.OrganToe`.
121    OrganToe,
122    /// music21's `articulations.Pizzicato`.
123    Pizzicato,
124    /// music21's `articulations.Plop`.
125    Plop,
126    /// music21's `articulations.Scoop`.
127    Scoop,
128    /// music21's `articulations.Staccatissimo`.
129    Staccatissimo,
130    /// music21's `articulations.Stopped`.
131    Stopped,
132    /// music21's `articulations.Stress`.
133    Stress,
134    /// music21's `articulations.StringIndication`.
135    StringIndication,
136    /// music21's `articulations.StringThumbPosition`.
137    StringThumbPosition,
138    /// music21's `articulations.StrongAccent`.
139    StrongAccent,
140    /// music21's `articulations.TonguingIndication`.
141    TonguingIndication,
142    /// music21's `articulations.UpBow`.
143    UpBow,
144    /// music21's `articulations.WoodwindIndication`.
145    WoodwindIndication,
146    /// music21's `articulations.DoubleTongue`.
147    DoubleTongue,
148    /// music21's `articulations.FrettedPluck`.
149    FrettedPluck,
150    /// music21's `articulations.NailPizzicato`.
151    NailPizzicato,
152    /// music21's `articulations.SnapPizzicato`.
153    SnapPizzicato,
154    /// music21's `articulations.StringHarmonic`.
155    StringHarmonic,
156    /// music21's `articulations.TripleTongue`.
157    TripleTongue,
158    /// music21's `articulations.Spiccato`.
159    Spiccato,
160    /// music21's `articulations.StringFingering`.
161    StringFingering,
162}
163
164/// What an articulation of each kind starts out as, and which of the
165/// fields beside the base ones its class carries.
166const KINDS: [KindRow; 52] = [
167    KindRow {
168        kind: ArticulationKind::Articulation,
169        class: "Articulation",
170        parents: &[],
171        volume_shift: 0.0,
172        length_shift: 1.0,
173        tie_attach: "first",
174        fields: &[],
175    },
176    KindRow {
177        kind: ArticulationKind::Caesura,
178        class: "Caesura",
179        parents: &["Articulation"],
180        volume_shift: 0.0,
181        length_shift: 1.0,
182        tie_attach: "first",
183        fields: &[],
184    },
185    KindRow {
186        kind: ArticulationKind::DynamicArticulation,
187        class: "DynamicArticulation",
188        parents: &["Articulation"],
189        volume_shift: 0.0,
190        length_shift: 1.0,
191        tie_attach: "first",
192        fields: &[],
193    },
194    KindRow {
195        kind: ArticulationKind::LengthArticulation,
196        class: "LengthArticulation",
197        parents: &["Articulation"],
198        volume_shift: 0.0,
199        length_shift: 1.0,
200        tie_attach: "last",
201        fields: &[],
202    },
203    KindRow {
204        kind: ArticulationKind::PitchArticulation,
205        class: "PitchArticulation",
206        parents: &["Articulation"],
207        volume_shift: 0.0,
208        length_shift: 1.0,
209        tie_attach: "first",
210        fields: &[],
211    },
212    KindRow {
213        kind: ArticulationKind::TechnicalIndication,
214        class: "TechnicalIndication",
215        parents: &["Articulation"],
216        volume_shift: 0.0,
217        length_shift: 1.0,
218        tie_attach: "first",
219        fields: &[],
220    },
221    KindRow {
222        kind: ArticulationKind::TimbreArticulation,
223        class: "TimbreArticulation",
224        parents: &["Articulation"],
225        volume_shift: 0.0,
226        length_shift: 1.0,
227        tie_attach: "first",
228        fields: &[],
229    },
230    KindRow {
231        kind: ArticulationKind::Accent,
232        class: "Accent",
233        parents: &["DynamicArticulation", "Articulation"],
234        volume_shift: 0.1,
235        length_shift: 1.0,
236        tie_attach: "first",
237        fields: &[],
238    },
239    KindRow {
240        kind: ArticulationKind::Bowing,
241        class: "Bowing",
242        parents: &["TechnicalIndication", "Articulation"],
243        volume_shift: 0.0,
244        length_shift: 1.0,
245        tie_attach: "first",
246        fields: &[],
247    },
248    KindRow {
249        kind: ArticulationKind::BreathMark,
250        class: "BreathMark",
251        parents: &["LengthArticulation", "Articulation"],
252        volume_shift: 0.0,
253        length_shift: 0.7,
254        tie_attach: "last",
255        fields: &["symbol"],
256    },
257    KindRow {
258        kind: ArticulationKind::DetachedLegato,
259        class: "DetachedLegato",
260        parents: &["LengthArticulation", "Articulation"],
261        volume_shift: 0.0,
262        length_shift: 0.9,
263        tie_attach: "last",
264        fields: &[],
265    },
266    KindRow {
267        kind: ArticulationKind::Fingering,
268        class: "Fingering",
269        parents: &["TechnicalIndication", "Articulation"],
270        volume_shift: 0.0,
271        length_shift: 1.0,
272        tie_attach: "first",
273        fields: &["alternate", "fingerNumber", "substitution"],
274    },
275    KindRow {
276        kind: ArticulationKind::FretIndication,
277        class: "FretIndication",
278        parents: &["TechnicalIndication", "Articulation"],
279        volume_shift: 0.0,
280        length_shift: 1.0,
281        tie_attach: "first",
282        fields: &["number"],
283    },
284    KindRow {
285        kind: ArticulationKind::HandbellIndication,
286        class: "HandbellIndication",
287        parents: &["TechnicalIndication", "Articulation"],
288        volume_shift: 0.0,
289        length_shift: 1.0,
290        tie_attach: "first",
291        fields: &[],
292    },
293    KindRow {
294        kind: ArticulationKind::Harmonic,
295        class: "Harmonic",
296        parents: &["TechnicalIndication", "Articulation"],
297        volume_shift: 0.0,
298        length_shift: 1.0,
299        tie_attach: "first",
300        fields: &[],
301    },
302    KindRow {
303        kind: ArticulationKind::HarpIndication,
304        class: "HarpIndication",
305        parents: &["TechnicalIndication", "Articulation"],
306        volume_shift: 0.0,
307        length_shift: 1.0,
308        tie_attach: "first",
309        fields: &[],
310    },
311    KindRow {
312        kind: ArticulationKind::IndeterminateSlide,
313        class: "IndeterminateSlide",
314        parents: &["PitchArticulation", "Articulation"],
315        volume_shift: 0.0,
316        length_shift: 1.0,
317        tie_attach: "first",
318        fields: &[],
319    },
320    KindRow {
321        kind: ArticulationKind::OrganIndication,
322        class: "OrganIndication",
323        parents: &["TechnicalIndication", "Articulation"],
324        volume_shift: 0.0,
325        length_shift: 1.0,
326        tie_attach: "first",
327        fields: &["substitution"],
328    },
329    KindRow {
330        kind: ArticulationKind::Staccato,
331        class: "Staccato",
332        parents: &["LengthArticulation", "Articulation"],
333        volume_shift: 0.05,
334        length_shift: 0.7,
335        tie_attach: "last",
336        fields: &[],
337    },
338    KindRow {
339        kind: ArticulationKind::Tenuto,
340        class: "Tenuto",
341        parents: &["LengthArticulation", "Articulation"],
342        volume_shift: -0.05,
343        length_shift: 1.1,
344        tie_attach: "last",
345        fields: &[],
346    },
347    KindRow {
348        kind: ArticulationKind::Unstress,
349        class: "Unstress",
350        parents: &["DynamicArticulation", "Articulation"],
351        volume_shift: -0.05,
352        length_shift: 1.0,
353        tie_attach: "first",
354        fields: &[],
355    },
356    KindRow {
357        kind: ArticulationKind::WindIndication,
358        class: "WindIndication",
359        parents: &["TechnicalIndication", "Articulation"],
360        volume_shift: 0.0,
361        length_shift: 1.0,
362        tie_attach: "first",
363        fields: &[],
364    },
365    KindRow {
366        kind: ArticulationKind::BrassIndication,
367        class: "BrassIndication",
368        parents: &["WindIndication", "TechnicalIndication", "Articulation"],
369        volume_shift: 0.0,
370        length_shift: 1.0,
371        tie_attach: "first",
372        fields: &[],
373    },
374    KindRow {
375        kind: ArticulationKind::Doit,
376        class: "Doit",
377        parents: &["IndeterminateSlide", "PitchArticulation", "Articulation"],
378        volume_shift: 0.0,
379        length_shift: 1.0,
380        tie_attach: "last",
381        fields: &[],
382    },
383    KindRow {
384        kind: ArticulationKind::DownBow,
385        class: "DownBow",
386        parents: &["Bowing", "TechnicalIndication", "Articulation"],
387        volume_shift: 0.0,
388        length_shift: 1.0,
389        tie_attach: "first",
390        fields: &[],
391    },
392    KindRow {
393        kind: ArticulationKind::Falloff,
394        class: "Falloff",
395        parents: &["IndeterminateSlide", "PitchArticulation", "Articulation"],
396        volume_shift: 0.0,
397        length_shift: 1.0,
398        tie_attach: "last",
399        fields: &[],
400    },
401    KindRow {
402        kind: ArticulationKind::FretBend,
403        class: "FretBend",
404        parents: &["FretIndication", "TechnicalIndication", "Articulation"],
405        volume_shift: 0.0,
406        length_shift: 1.0,
407        tie_attach: "first",
408        fields: &["bendAlter", "number", "preBend", "release", "withBar"],
409    },
410    KindRow {
411        kind: ArticulationKind::FretTap,
412        class: "FretTap",
413        parents: &["FretIndication", "TechnicalIndication", "Articulation"],
414        volume_shift: 0.0,
415        length_shift: 1.0,
416        tie_attach: "first",
417        fields: &["number"],
418    },
419    KindRow {
420        kind: ArticulationKind::HarpFingerNails,
421        class: "HarpFingerNails",
422        parents: &["HarpIndication", "TechnicalIndication", "Articulation"],
423        volume_shift: 0.0,
424        length_shift: 1.0,
425        tie_attach: "first",
426        fields: &[],
427    },
428    KindRow {
429        kind: ArticulationKind::OpenString,
430        class: "OpenString",
431        parents: &["Bowing", "TechnicalIndication", "Articulation"],
432        volume_shift: 0.0,
433        length_shift: 1.0,
434        tie_attach: "first",
435        fields: &[],
436    },
437    KindRow {
438        kind: ArticulationKind::OrganHeel,
439        class: "OrganHeel",
440        parents: &["OrganIndication", "TechnicalIndication", "Articulation"],
441        volume_shift: 0.0,
442        length_shift: 1.0,
443        tie_attach: "first",
444        fields: &["substitution"],
445    },
446    KindRow {
447        kind: ArticulationKind::OrganToe,
448        class: "OrganToe",
449        parents: &["OrganIndication", "TechnicalIndication", "Articulation"],
450        volume_shift: 0.0,
451        length_shift: 1.0,
452        tie_attach: "first",
453        fields: &["substitution"],
454    },
455    KindRow {
456        kind: ArticulationKind::Pizzicato,
457        class: "Pizzicato",
458        parents: &["Bowing", "TechnicalIndication", "Articulation"],
459        volume_shift: 0.0,
460        length_shift: 1.0,
461        tie_attach: "first",
462        fields: &[],
463    },
464    KindRow {
465        kind: ArticulationKind::Plop,
466        class: "Plop",
467        parents: &["IndeterminateSlide", "PitchArticulation", "Articulation"],
468        volume_shift: 0.0,
469        length_shift: 1.0,
470        tie_attach: "first",
471        fields: &[],
472    },
473    KindRow {
474        kind: ArticulationKind::Scoop,
475        class: "Scoop",
476        parents: &["IndeterminateSlide", "PitchArticulation", "Articulation"],
477        volume_shift: 0.0,
478        length_shift: 1.0,
479        tie_attach: "first",
480        fields: &[],
481    },
482    KindRow {
483        kind: ArticulationKind::Staccatissimo,
484        class: "Staccatissimo",
485        parents: &["Staccato", "LengthArticulation", "Articulation"],
486        volume_shift: 0.05,
487        length_shift: 0.5,
488        tie_attach: "last",
489        fields: &[],
490    },
491    KindRow {
492        kind: ArticulationKind::Stopped,
493        class: "Stopped",
494        parents: &["WindIndication", "TechnicalIndication", "Articulation"],
495        volume_shift: 0.0,
496        length_shift: 1.0,
497        tie_attach: "first",
498        fields: &[],
499    },
500    KindRow {
501        kind: ArticulationKind::Stress,
502        class: "Stress",
503        parents: &["DynamicArticulation", "LengthArticulation", "Articulation"],
504        volume_shift: 0.05,
505        length_shift: 1.1,
506        tie_attach: "last",
507        fields: &[],
508    },
509    KindRow {
510        kind: ArticulationKind::StringIndication,
511        class: "StringIndication",
512        parents: &["Bowing", "TechnicalIndication", "Articulation"],
513        volume_shift: 0.0,
514        length_shift: 1.0,
515        tie_attach: "first",
516        fields: &["number"],
517    },
518    KindRow {
519        kind: ArticulationKind::StringThumbPosition,
520        class: "StringThumbPosition",
521        parents: &["Bowing", "TechnicalIndication", "Articulation"],
522        volume_shift: 0.0,
523        length_shift: 1.0,
524        tie_attach: "first",
525        fields: &[],
526    },
527    KindRow {
528        kind: ArticulationKind::StrongAccent,
529        class: "StrongAccent",
530        parents: &["Accent", "DynamicArticulation", "Articulation"],
531        volume_shift: 0.15,
532        length_shift: 1.0,
533        tie_attach: "first",
534        fields: &["pointDirection"],
535    },
536    KindRow {
537        kind: ArticulationKind::TonguingIndication,
538        class: "TonguingIndication",
539        parents: &["WindIndication", "TechnicalIndication", "Articulation"],
540        volume_shift: 0.0,
541        length_shift: 1.0,
542        tie_attach: "first",
543        fields: &[],
544    },
545    KindRow {
546        kind: ArticulationKind::UpBow,
547        class: "UpBow",
548        parents: &["Bowing", "TechnicalIndication", "Articulation"],
549        volume_shift: 0.0,
550        length_shift: 1.0,
551        tie_attach: "first",
552        fields: &[],
553    },
554    KindRow {
555        kind: ArticulationKind::WoodwindIndication,
556        class: "WoodwindIndication",
557        parents: &["WindIndication", "TechnicalIndication", "Articulation"],
558        volume_shift: 0.0,
559        length_shift: 1.0,
560        tie_attach: "first",
561        fields: &[],
562    },
563    KindRow {
564        kind: ArticulationKind::DoubleTongue,
565        class: "DoubleTongue",
566        parents: &[
567            "TonguingIndication",
568            "WindIndication",
569            "TechnicalIndication",
570            "Articulation",
571        ],
572        volume_shift: 0.0,
573        length_shift: 1.0,
574        tie_attach: "first",
575        fields: &[],
576    },
577    KindRow {
578        kind: ArticulationKind::FrettedPluck,
579        class: "FrettedPluck",
580        parents: &[
581            "FretIndication",
582            "Fingering",
583            "TechnicalIndication",
584            "Articulation",
585        ],
586        volume_shift: 0.0,
587        length_shift: 1.0,
588        tie_attach: "first",
589        fields: &["alternate", "fingerNumber", "number", "substitution"],
590    },
591    KindRow {
592        kind: ArticulationKind::NailPizzicato,
593        class: "NailPizzicato",
594        parents: &["Pizzicato", "Bowing", "TechnicalIndication", "Articulation"],
595        volume_shift: 0.0,
596        length_shift: 1.0,
597        tie_attach: "first",
598        fields: &[],
599    },
600    KindRow {
601        kind: ArticulationKind::SnapPizzicato,
602        class: "SnapPizzicato",
603        parents: &["Pizzicato", "Bowing", "TechnicalIndication", "Articulation"],
604        volume_shift: 0.0,
605        length_shift: 1.0,
606        tie_attach: "first",
607        fields: &[],
608    },
609    KindRow {
610        kind: ArticulationKind::StringHarmonic,
611        class: "StringHarmonic",
612        parents: &["Bowing", "Harmonic", "TechnicalIndication", "Articulation"],
613        volume_shift: 0.0,
614        length_shift: 1.0,
615        tie_attach: "first",
616        fields: &["harmonicType", "pitchType"],
617    },
618    KindRow {
619        kind: ArticulationKind::TripleTongue,
620        class: "TripleTongue",
621        parents: &[
622            "TonguingIndication",
623            "WindIndication",
624            "TechnicalIndication",
625            "Articulation",
626        ],
627        volume_shift: 0.0,
628        length_shift: 1.0,
629        tie_attach: "first",
630        fields: &[],
631    },
632    KindRow {
633        kind: ArticulationKind::Spiccato,
634        class: "Spiccato",
635        parents: &[
636            "Staccato",
637            "LengthArticulation",
638            "Accent",
639            "DynamicArticulation",
640            "Articulation",
641        ],
642        volume_shift: 0.1,
643        length_shift: 0.7,
644        tie_attach: "first",
645        fields: &[],
646    },
647    KindRow {
648        kind: ArticulationKind::StringFingering,
649        class: "StringFingering",
650        parents: &[
651            "StringIndication",
652            "Bowing",
653            "Fingering",
654            "TechnicalIndication",
655            "Articulation",
656        ],
657        volume_shift: 0.0,
658        length_shift: 1.0,
659        tie_attach: "first",
660        fields: &["alternate", "fingerNumber", "number", "substitution"],
661    },
662];
663
664impl ArticulationKind {
665    /// Every kind, parents before children.
666    pub const ALL: [ArticulationKind; 52] = [
667        ArticulationKind::Articulation,
668        ArticulationKind::Caesura,
669        ArticulationKind::DynamicArticulation,
670        ArticulationKind::LengthArticulation,
671        ArticulationKind::PitchArticulation,
672        ArticulationKind::TechnicalIndication,
673        ArticulationKind::TimbreArticulation,
674        ArticulationKind::Accent,
675        ArticulationKind::Bowing,
676        ArticulationKind::BreathMark,
677        ArticulationKind::DetachedLegato,
678        ArticulationKind::Fingering,
679        ArticulationKind::FretIndication,
680        ArticulationKind::HandbellIndication,
681        ArticulationKind::Harmonic,
682        ArticulationKind::HarpIndication,
683        ArticulationKind::IndeterminateSlide,
684        ArticulationKind::OrganIndication,
685        ArticulationKind::Staccato,
686        ArticulationKind::Tenuto,
687        ArticulationKind::Unstress,
688        ArticulationKind::WindIndication,
689        ArticulationKind::BrassIndication,
690        ArticulationKind::Doit,
691        ArticulationKind::DownBow,
692        ArticulationKind::Falloff,
693        ArticulationKind::FretBend,
694        ArticulationKind::FretTap,
695        ArticulationKind::HarpFingerNails,
696        ArticulationKind::OpenString,
697        ArticulationKind::OrganHeel,
698        ArticulationKind::OrganToe,
699        ArticulationKind::Pizzicato,
700        ArticulationKind::Plop,
701        ArticulationKind::Scoop,
702        ArticulationKind::Staccatissimo,
703        ArticulationKind::Stopped,
704        ArticulationKind::Stress,
705        ArticulationKind::StringIndication,
706        ArticulationKind::StringThumbPosition,
707        ArticulationKind::StrongAccent,
708        ArticulationKind::TonguingIndication,
709        ArticulationKind::UpBow,
710        ArticulationKind::WoodwindIndication,
711        ArticulationKind::DoubleTongue,
712        ArticulationKind::FrettedPluck,
713        ArticulationKind::NailPizzicato,
714        ArticulationKind::SnapPizzicato,
715        ArticulationKind::StringHarmonic,
716        ArticulationKind::TripleTongue,
717        ArticulationKind::Spiccato,
718        ArticulationKind::StringFingering,
719    ];
720}
721
722impl ArticulationKind {
723    fn row(self) -> &'static KindRow {
724        KINDS
725            .iter()
726            .find(|row| row.kind == self)
727            .expect("every kind has a row")
728    }
729
730    /// music21's class name for the kind: `"StrongAccent"`.
731    pub fn class_name(self) -> &'static str {
732        self.row().class
733    }
734
735    /// The kind music21 names by that class.
736    pub fn from_class_name(class: &str) -> Option<Self> {
737        KINDS
738            .iter()
739            .find(|row| row.class == class)
740            .map(|row| row.kind)
741    }
742
743    /// The classes above this one, in music21's method resolution order, up
744    /// to `Articulation`. A spiccato is a staccato and an accent both.
745    pub fn parents(self) -> &'static [&'static str] {
746        self.row().parents
747    }
748
749    /// Whether music21's class for the kind carries the field music21 names
750    /// `field`: `"fingerNumber"`, `"number"`, `"bendAlter"` and the rest.
751    pub fn has_field(self, field: &str) -> bool {
752        self.row().fields.contains(&field)
753    }
754}
755
756/// An articulation: how a note is played, and the numbers that say it.
757///
758/// Two articulations are equal when they are of one kind, as music21's are;
759/// where they sit and what else they say does not count.
760///
761/// ```
762/// use music21_rs::articulations::{Articulation, ArticulationKind};
763///
764/// let accent = Articulation::of_kind(ArticulationKind::StrongAccent);
765/// assert_eq!(accent.name(), "strong accent");
766/// assert!(accent.volume_shift() > 0.1);
767/// assert!(accent.is_a("Accent"));
768/// ```
769#[derive(Clone, Debug)]
770#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
771#[must_use]
772pub struct Articulation {
773    kind: ArticulationKind,
774    placement: Option<String>,
775    volume_shift: FloatType,
776    length_shift: FloatType,
777    tie_attach: String,
778    display_text: Option<String>,
779    finger: Option<Finger>,
780    substitution: bool,
781    alternate: bool,
782    number: IntegerType,
783    point_direction: Option<String>,
784    symbol: Option<String>,
785    harmonic_type: Option<String>,
786    pitch_type: Option<String>,
787    bend_alter: Option<ChromaticInterval>,
788    pre_bend: bool,
789    release: Option<FloatType>,
790    with_bar: Option<String>,
791}
792
793impl PartialEq for Articulation {
794    fn eq(&self, other: &Self) -> bool {
795        self.kind == other.kind
796    }
797}
798
799impl Default for Articulation {
800    /// music21's bare `Articulation`.
801    fn default() -> Self {
802        Self::of_kind(ArticulationKind::Articulation)
803    }
804}
805
806impl Articulation {
807    /// An articulation as music21's class for `kind` starts out.
808    pub fn of_kind(kind: ArticulationKind) -> Self {
809        let row = kind.row();
810        Self {
811            kind,
812            placement: None,
813            volume_shift: row.volume_shift,
814            length_shift: row.length_shift,
815            tie_attach: row.tie_attach.to_string(),
816            display_text: None,
817            finger: None,
818            substitution: false,
819            alternate: false,
820            number: 0,
821            point_direction: kind.has_field("pointDirection").then(|| "up".to_string()),
822            symbol: None,
823            harmonic_type: kind
824                .has_field("harmonicType")
825                .then(|| "natural".to_string()),
826            pitch_type: None,
827            bend_alter: None,
828            pre_bend: false,
829            release: None,
830            with_bar: None,
831        }
832    }
833
834    /// The kind of articulation this started as.
835    pub fn kind(&self) -> ArticulationKind {
836        self.kind
837    }
838
839    /// Whether this is an articulation of that music21 class or one below
840    /// it: `is_a("Staccato")` holds for a spiccato.
841    pub fn is_a(&self, class: &str) -> bool {
842        self.kind.class_name() == class || self.kind.parents().contains(&class)
843    }
844
845    /// music21's `name`: the class name as lower-case words, so a snap
846    /// pizzicato is `"snap pizzicato"`.
847    pub fn name(&self) -> String {
848        camel_case_to_hyphen(self.kind.class_name(), ' ')
849    }
850
851    /// Where the mark sits: `"above"`, `"below"`, or nothing said.
852    pub fn placement(&self) -> Option<&str> {
853        self.placement.as_deref()
854    }
855
856    /// Says where the mark sits.
857    pub fn set_placement(&mut self, placement: Option<String>) {
858        self.placement = placement;
859    }
860
861    /// How much the mark shifts the note's volume, between -1 and 1:
862    /// music21's `volumeShift`, which [`crate::Volume::realized_with`]
863    /// adds.
864    pub fn volume_shift(&self) -> FloatType {
865        self.volume_shift
866    }
867
868    /// Changes the volume shift, held between -1 and 1.
869    pub fn set_volume_shift(&mut self, shift: FloatType) {
870        self.volume_shift = shift.clamp(-1.0, 1.0);
871    }
872
873    /// What the note's sounding length is multiplied by: music21's
874    /// `lengthShift`, 0.7 for a staccato.
875    pub fn length_shift(&self) -> FloatType {
876        self.length_shift
877    }
878
879    /// Changes the length shift.
880    pub fn set_length_shift(&mut self, shift: FloatType) {
881        self.length_shift = shift;
882    }
883
884    /// Which part of a note split across a tie keeps the mark: music21's
885    /// `tieAttach`, `"first"`, `"last"` or `"all"`.
886    pub fn tie_attach(&self) -> &str {
887        &self.tie_attach
888    }
889
890    /// Changes which part keeps the mark.
891    pub fn set_tie_attach(&mut self, attach: impl Into<String>) {
892        self.tie_attach = attach.into();
893    }
894
895    /// Text written for the mark instead of its usual sign.
896    pub fn display_text(&self) -> Option<&str> {
897        self.display_text.as_deref()
898    }
899
900    /// Changes the text written for the mark.
901    pub fn set_display_text(&mut self, text: Option<String>) {
902        self.display_text = text;
903    }
904
905    /// A fingering's finger: music21's `fingerNumber`.
906    pub fn finger(&self) -> Option<&Finger> {
907        self.finger.as_ref()
908    }
909
910    /// Changes the finger.
911    pub fn set_finger(&mut self, finger: Option<Finger>) {
912        self.finger = finger;
913    }
914
915    /// Whether a fingering or organ mark is a substitution.
916    pub fn substitution(&self) -> bool {
917        self.substitution
918    }
919
920    /// Says whether it is a substitution.
921    pub fn set_substitution(&mut self, substitution: bool) {
922        self.substitution = substitution;
923    }
924
925    /// Whether a fingering is an alternate one.
926    pub fn alternate(&self) -> bool {
927        self.alternate
928    }
929
930    /// Says whether it is an alternate.
931    pub fn set_alternate(&mut self, alternate: bool) {
932        self.alternate = alternate;
933    }
934
935    /// A string or fret indication's number: music21's `number`, nought when
936    /// none is given.
937    pub fn number(&self) -> IntegerType {
938        self.number
939    }
940
941    /// Changes the string or fret number.
942    pub fn set_number(&mut self, number: IntegerType) {
943        self.number = number;
944    }
945
946    /// Which way a strong accent points: music21's `pointDirection`.
947    pub fn point_direction(&self) -> Option<&str> {
948        self.point_direction.as_deref()
949    }
950
951    /// Changes which way it points.
952    pub fn set_point_direction(&mut self, direction: Option<String>) {
953        self.point_direction = direction;
954    }
955
956    /// A breath mark's symbol.
957    pub fn symbol(&self) -> Option<&str> {
958        self.symbol.as_deref()
959    }
960
961    /// Changes the breath mark's symbol.
962    pub fn set_symbol(&mut self, symbol: Option<String>) {
963        self.symbol = symbol;
964    }
965
966    /// A string harmonic's type: `"natural"` unless said otherwise.
967    pub fn harmonic_type(&self) -> Option<&str> {
968        self.harmonic_type.as_deref()
969    }
970
971    /// Changes the harmonic's type.
972    pub fn set_harmonic_type(&mut self, harmonic_type: Option<String>) {
973        self.harmonic_type = harmonic_type;
974    }
975
976    /// Which pitch a string harmonic's written note stands for.
977    pub fn pitch_type(&self) -> Option<&str> {
978        self.pitch_type.as_deref()
979    }
980
981    /// Changes which pitch the written note stands for.
982    pub fn set_pitch_type(&mut self, pitch_type: Option<String>) {
983        self.pitch_type = pitch_type;
984    }
985
986    /// How far a fret bend bends the string: music21's `bendAlter`.
987    pub fn bend_alter(&self) -> Option<&ChromaticInterval> {
988        self.bend_alter.as_ref()
989    }
990
991    /// Changes how far the string is bent.
992    pub fn set_bend_alter(&mut self, alter: Option<ChromaticInterval>) {
993        self.bend_alter = alter;
994    }
995
996    /// Whether the string is bent before the note sounds.
997    pub fn pre_bend(&self) -> bool {
998        self.pre_bend
999    }
1000
1001    /// Says whether the string is bent beforehand.
1002    pub fn set_pre_bend(&mut self, pre_bend: bool) {
1003        self.pre_bend = pre_bend;
1004    }
1005
1006    /// When the bend is released, in quarter lengths from the start of the
1007    /// note.
1008    pub fn release(&self) -> Option<FloatType> {
1009        self.release
1010    }
1011
1012    /// Changes when the bend is released.
1013    pub fn set_release(&mut self, release: Option<FloatType>) {
1014        self.release = release;
1015    }
1016
1017    /// The whammy-bar movement a bend uses: `"scoop"` or `"dip"`.
1018    pub fn with_bar(&self) -> Option<&str> {
1019        self.with_bar.as_deref()
1020    }
1021
1022    /// Changes the whammy-bar movement.
1023    pub fn set_with_bar(&mut self, with_bar: Option<String>) {
1024        self.with_bar = with_bar;
1025    }
1026}
1027
1028#[cfg(test)]
1029mod tests {
1030    use super::*;
1031
1032    /// Every answer here is music21's own, from its articulation docstrings.
1033    #[test]
1034    fn an_articulation_answers_what_music21_s_answers() {
1035        let staccato = Articulation::of_kind(ArticulationKind::Staccato);
1036        assert_eq!(staccato.name(), "staccato");
1037        assert_eq!(staccato.length_shift(), 0.7);
1038        assert_eq!(staccato.tie_attach(), "last");
1039        assert_eq!(
1040            Articulation::of_kind(ArticulationKind::SnapPizzicato).name(),
1041            "snap pizzicato"
1042        );
1043
1044        let mut first = Articulation::of_kind(ArticulationKind::StrongAccent);
1045        let mut second = Articulation::of_kind(ArticulationKind::StrongAccent);
1046        first.set_placement(Some("above".to_string()));
1047        second.set_placement(Some("below".to_string()));
1048        assert_eq!(first, second);
1049        assert_ne!(first, Articulation::of_kind(ArticulationKind::Accent));
1050        assert_eq!(first.point_direction(), Some("up"));
1051
1052        let spiccato = Articulation::of_kind(ArticulationKind::Spiccato);
1053        assert!(spiccato.is_a("Staccato") && spiccato.is_a("Accent"));
1054        assert_eq!(
1055            (spiccato.volume_shift(), spiccato.length_shift()),
1056            (0.1, 0.7)
1057        );
1058
1059        let mut accent = Articulation::of_kind(ArticulationKind::Accent);
1060        accent.set_volume_shift(3.0);
1061        assert_eq!(accent.volume_shift(), 1.0);
1062        accent.set_volume_shift(-3.0);
1063        assert_eq!(accent.volume_shift(), -1.0);
1064
1065        assert!(ArticulationKind::FrettedPluck.has_field("fingerNumber"));
1066        assert!(ArticulationKind::FrettedPluck.has_field("number"));
1067        assert!(!ArticulationKind::Accent.has_field("number"));
1068        assert_eq!(
1069            Articulation::of_kind(ArticulationKind::StringHarmonic).harmonic_type(),
1070            Some("natural")
1071        );
1072    }
1073}