Skip to main content

music21_rs/
volume.rs

1//! How loud a note is: music21's `volume.Volume`.
2//!
3//! A volume is one number held two ways — a MIDI velocity from 0 to 127 and
4//! the same value as a scalar from 0 to 1 — plus the rule for reading it
5//! against the dynamic marks around it. A volume does not know the note it
6//! belongs to, so [`Volume::realized_with`] takes the dynamic scalar and the
7//! articulation shift as arguments; the marks themselves are found in a
8//! stream by [`dynamic_context`], [`dynamics_in_force`] and
9//! [`realize_volume`].
10
11use std::cmp::Ordering;
12use std::fmt;
13
14use crate::{
15    defaults::{FloatType, IntegerType},
16    dynamics::{Dynamic, dynamic_str_from_decimal as dynamic_name},
17    error::{Error, Result},
18    stream::{Stream, StreamElement, StreamKind},
19};
20
21/// The velocity music21 assumes when none was set, as a scalar. It is the
22/// `0.5` base level shifted by `0.20866`, which is what makes an unset note
23/// sound at a natural mezzo-forte rather than at half volume.
24const UNSET_VELOCITY_SHIFT: FloatType = 0.20866;
25
26/// The middle of the dynamic range that scalars move away from.
27const BASE_LEVEL: FloatType = 0.5;
28
29/// How loud a note is: music21's `volume.Volume`.
30#[derive(Clone, Debug, PartialEq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32#[must_use]
33pub struct Volume {
34    velocity_scalar: Option<FloatType>,
35    velocity_is_relative: bool,
36}
37
38impl Default for Volume {
39    /// A volume with no velocity set, which reads as music21's default
40    /// loudness rather than as silence.
41    fn default() -> Self {
42        Self {
43            velocity_scalar: None,
44            velocity_is_relative: true,
45        }
46    }
47}
48
49impl Volume {
50    /// A volume with no velocity of its own.
51    pub fn new() -> Self {
52        Self::default()
53    }
54
55    /// Takes everything another volume says, the velocity and whether it is
56    /// relative: music21's `mergeAttributes`.
57    pub fn merge_attributes(&mut self, other: &Volume) {
58        self.velocity_scalar = other.velocity_scalar;
59        self.velocity_is_relative = other.velocity_is_relative;
60    }
61
62    /// A volume at a MIDI velocity, `0` to `127`, clamped to that range.
63    pub fn from_velocity(velocity: IntegerType) -> Self {
64        let mut volume = Self::new();
65        volume.set_velocity(Some(velocity));
66        volume
67    }
68
69    /// A volume at a scalar between `0` and `1`, clamped to that range.
70    pub fn from_velocity_scalar(scalar: FloatType) -> Result<Self> {
71        let mut volume = Self::new();
72        volume.set_velocity_scalar(Some(scalar))?;
73        Ok(volume)
74    }
75
76    /// The MIDI velocity, `0` to `127`, or `None` when none was set.
77    pub fn velocity(&self) -> Option<IntegerType> {
78        let scalar = self.velocity_scalar?;
79        let velocity = (scalar * 127.0).clamp(0.0, 127.0);
80        Some(velocity.round_ties_even() as IntegerType)
81    }
82
83    /// Sets the MIDI velocity, clamped to `0` through `127`, or clears it.
84    pub fn set_velocity(&mut self, velocity: Option<IntegerType>) {
85        self.velocity_scalar = velocity.map(|velocity| {
86            if velocity <= 0 {
87                0.0
88            } else if velocity >= 127 {
89                1.0
90            } else {
91                FloatType::from(velocity) / 127.0
92            }
93        });
94    }
95
96    /// The velocity as a scalar between `0` and `1`, or `None` when none was
97    /// set.
98    pub fn velocity_scalar(&self) -> Option<FloatType> {
99        self.velocity_scalar
100    }
101
102    /// Sets the velocity from a scalar, clamped to `0` through `1`, or
103    /// clears it. A value that is not a number is an error, as in music21.
104    pub fn set_velocity_scalar(&mut self, scalar: Option<FloatType>) -> Result<()> {
105        match scalar {
106            None => self.velocity_scalar = None,
107            Some(scalar) if scalar.is_nan() => {
108                return Err(Error::Volume(
109                    "value provided for velocityScalar must be a number, not NaN".to_string(),
110                ));
111            }
112            Some(scalar) => self.velocity_scalar = Some(scalar.clamp(0.0, 1.0)),
113        }
114        Ok(())
115    }
116
117    /// Whether the velocity shifts the dynamics around it (music21's
118    /// default) or fixes the loudness on its own, as a velocity read from a
119    /// MIDI file does.
120    pub fn velocity_is_relative(&self) -> bool {
121        self.velocity_is_relative
122    }
123
124    /// Sets whether the velocity is relative to its context.
125    pub fn set_velocity_is_relative(&mut self, relative: bool) {
126        self.velocity_is_relative = relative;
127    }
128
129    /// Whether a velocity was ever set: music21's `hasVolumeInformation` on
130    /// the note that owns it.
131    pub fn has_velocity_information(&self) -> bool {
132        self.velocity_scalar.is_some()
133    }
134
135    /// The loudness this volume comes to on its own, between `0` and `1`:
136    /// music21's `realized` with no dynamic or articulation context.
137    pub fn realized(&self) -> FloatType {
138        self.realized_with(None, 0.0, BASE_LEVEL, true)
139    }
140
141    /// The loudness this volume comes to against the dynamic in force and
142    /// whatever the articulations add: music21's `getRealized`, with the
143    /// context search it would do through a stream replaced by its answers.
144    ///
145    /// A relative velocity doubles the scalar range, so `0.5` leaves the base
146    /// level alone and `0.7` raises it; an absolute one decides the answer by
147    /// itself. The dynamic scales; an articulation *shifts*, which is why an
148    /// accent on a note nobody has marked lifts it rather than doubling it.
149    /// `clip` holds the result inside `0` to `1`.
150    pub fn realized_with(
151        &self,
152        dynamic_scalar: Option<FloatType>,
153        articulation_shift: FloatType,
154        base_level: FloatType,
155        clip: bool,
156    ) -> FloatType {
157        self.realize(true, dynamic_scalar, articulation_shift, base_level, clip)
158    }
159
160    /// [`Volume::realized_with`] leaving this volume's velocity out: music21's
161    /// `getRealized(useVelocity=False)`. The base level stands in for the
162    /// velocity, with no shift for its being unset, and the dynamic and the
163    /// articulations then act on it as they would.
164    pub fn realized_without_velocity(
165        &self,
166        dynamic_scalar: Option<FloatType>,
167        articulation_shift: FloatType,
168        base_level: FloatType,
169        clip: bool,
170    ) -> FloatType {
171        self.realize(false, dynamic_scalar, articulation_shift, base_level, clip)
172    }
173
174    fn realize(
175        &self,
176        use_velocity: bool,
177        dynamic_scalar: Option<FloatType>,
178        articulation_shift: FloatType,
179        base_level: FloatType,
180        clip: bool,
181    ) -> FloatType {
182        let mut value = base_level;
183        if use_velocity {
184            match self.velocity_scalar {
185                Some(scalar) if !self.velocity_is_relative => value = scalar,
186                Some(scalar) => value *= scalar * 2.0,
187                None => value += UNSET_VELOCITY_SHIFT,
188            }
189        }
190        if self.velocity_is_relative {
191            if let Some(dynamic_scalar) = dynamic_scalar {
192                value *= dynamic_scalar * 2.0;
193            }
194            value += articulation_shift;
195        }
196        if clip {
197            value = value.clamp(0.0, 1.0);
198        }
199        value
200    }
201
202    /// The realized loudness as one of music21's dynamic marks, `ppp`
203    /// through `fff`: `dynamics.dynamicStrFromDecimal` of the realized value.
204    pub fn realized_dynamic(&self) -> &'static str {
205        dynamic_name(self.realized())
206    }
207
208    /// The realized loudness written out to two places, which is what
209    /// music21's `getRealizedStr` and `cachedRealizedStr` answer.
210    pub fn realized_str(&self) -> String {
211        rounded_str(self.realized())
212    }
213}
214
215/// A loudness written out the way Python writes `str(round(value, 2))`: to
216/// two places, with the trailing zeros dropped but never the point.
217pub fn rounded_str(value: FloatType) -> String {
218    let rounded = (value * 100.0).round() / 100.0;
219    let written = format!("{rounded}");
220    if written.contains('.') {
221        written
222    } else {
223        format!("{written}.0")
224    }
225}
226
227/// Where [`realize_volume`] looks for the dynamic a note sounds under:
228/// music21's `useDynamicContext`, which is `True`, `False` or a `Dynamic`.
229#[derive(Clone, Debug, Default, PartialEq)]
230pub enum DynamicContext {
231    /// The dynamics in the stream, each in force until the next. A note
232    /// before the first of them sounds under none.
233    #[default]
234    FromStream,
235    /// No dynamic at all.
236    Ignored,
237    /// One dynamic for everything, whatever the stream holds.
238    Fixed(Dynamic),
239}
240
241/// Sets how loud every note and chord of a stream sounds, from the dynamics
242/// in force: music21's `realizeVolume` with `setAbsoluteVelocity`.
243///
244/// Each note's volume is realized against the dynamic it stands under and
245/// the answer is written back as its velocity, no longer relative, so that a
246/// stream played from its velocities alone sounds as marked. Without
247/// `use_velocity` a note's own velocity is left out of the realizing and the
248/// dynamic alone decides.
249///
250/// music21 without `setAbsoluteVelocity` only fills each volume's cache of
251/// its realized value. There is no such cache here, so this is always the
252/// call that writes. The crate carries no articulations, so none shifts a
253/// note; [`Volume::realized_with`] takes that shift as an argument for a
254/// caller that has them.
255///
256/// ```
257/// use music21_rs::{volume::{realize_volume, DynamicContext}, Dynamic, Note, Stream};
258///
259/// let mut stream = Stream::new();
260/// stream.insert(0.0, Dynamic::new("pp"));
261/// stream.insert(0.0, Note::from_name("G3")?);
262/// stream.insert(1.0, Dynamic::new("ff"));
263/// stream.insert(1.0, Note::from_name("G3")?);
264/// realize_volume(&mut stream, &DynamicContext::FromStream, true);
265/// let velocities: Vec<_> = stream
266///     .notes()
267///     .iter()
268///     .map(|(_, note)| match note {
269///         music21_rs::StreamElement::Note(note) => note.volume().velocity(),
270///         _ => None,
271///     })
272///     .collect();
273/// assert_eq!(velocities, [Some(45), Some(127)]);
274/// # Ok::<(), music21_rs::Error>(())
275/// ```
276pub fn realize_volume(stream: &mut Stream, context: &DynamicContext, use_velocity: bool) {
277    let in_force: Vec<Option<FloatType>> = match context {
278        DynamicContext::Ignored => vec![None; stream.leaves().len()],
279        DynamicContext::Fixed(dynamic) => {
280            vec![Some(dynamic.volume_scalar()); stream.leaves().len()]
281        }
282        DynamicContext::FromStream => {
283            let leaves = stream.leaves();
284            dynamics_in_force(stream)
285                .into_iter()
286                .map(|found| match found.map(|position| leaves[position].1) {
287                    Some(StreamElement::Dynamic(dynamic)) => Some(dynamic.volume_scalar()),
288                    _ => None,
289                })
290                .collect()
291        }
292    };
293
294    let realized = |volume: &Volume, dynamic: Option<FloatType>| -> Volume {
295        let value = if use_velocity {
296            volume.realized_with(dynamic, 0.0, BASE_LEVEL, true)
297        } else {
298            volume.realized_without_velocity(dynamic, 0.0, BASE_LEVEL, true)
299        };
300        let mut absolute = volume.clone();
301        absolute.set_velocity_is_relative(false);
302        absolute
303            .set_velocity_scalar(Some(value))
304            .expect("a clipped loudness is within the range a velocity takes");
305        absolute
306    };
307    let mut position = 0;
308    stream.for_each_mut(&mut |_, element| {
309        let dynamic = in_force[position];
310        position += 1;
311        match element {
312            StreamElement::Note(note) => {
313                let volume = realized(&note.volume(), dynamic);
314                note.set_volume(Some(volume));
315            }
316            StreamElement::Chord(chord) => {
317                let volume = realized(&chord.volume(), dynamic);
318                chord.set_volume(Some(volume));
319            }
320            _ => {}
321        }
322    });
323}
324
325/// The dynamic each element of a stream stands under: for every element
326/// [`Stream::leaves`] lists, the position in that list of the dynamic in
327/// force where it stands, or `None` where no dynamic is.
328///
329/// A dynamic is in force from where it stands until the next, the last until
330/// the end of the stream, which is music21's `extendDuration`; a note before
331/// the first dynamic stands under none, and of two dynamics at one offset the
332/// later-listed is the one in force. This is the search [`realize_volume`]
333/// realizes against, for a caller that keeps something of its own beside
334/// each element.
335///
336/// ```
337/// use music21_rs::{volume::dynamics_in_force, Dynamic, Note, Stream};
338///
339/// let mut stream = Stream::new();
340/// stream.insert(0.0, Note::from_name("G3")?);
341/// stream.insert(1.0, Dynamic::new("ff"));
342/// stream.insert(1.0, Note::from_name("G3")?);
343/// assert_eq!(dynamics_in_force(&stream), [None, Some(1), Some(1)]);
344/// # Ok::<(), music21_rs::Error>(())
345/// ```
346pub fn dynamics_in_force(stream: &Stream) -> Vec<Option<usize>> {
347    let leaves = stream.leaves();
348    // The leaves in the order music21's `flatten` gives them.
349    let order = Stream::flatten_order(&leaves);
350    let dynamics: Vec<(Held, usize)> = order
351        .iter()
352        .filter(|position| matches!(leaves[**position].1, StreamElement::Dynamic(_)))
353        .map(|position| (Held::of(leaves[*position].0), *position))
354        .collect();
355    // `extendDuration`: each dynamic lasts until the next, the last until
356    // the end, each span snapped as `opFrac` snaps it. music21 then adds a
357    // span to a start, and where either is a float that sum is a float, so
358    // a span can end a hair past where the next one starts.
359    let total = Held::of(stream.end_offset());
360    let mut spans: Vec<((Held, Held), usize)> = Vec::new();
361    for (index, &(start, position)) in dynamics.iter().enumerate() {
362        let next = dynamics.get(index + 1).map_or(total, |(next, _)| *next);
363        let key = (start, start.add(next.span_from(start)));
364        // Two dynamics with one span: the later is the one kept.
365        match spans
366            .iter_mut()
367            .find(|(held, _)| held.0.same(key.0) && held.1.same(key.1))
368        {
369            Some(held) => held.1 = position,
370            None => spans.push((key, position)),
371        }
372    }
373    spans.sort_by(|left, right| {
374        left.0
375            .0
376            .compare(right.0.0)
377            .then(left.0.1.compare(right.0.1))
378    });
379
380    let mut answers: Vec<Option<usize>> = vec![None; leaves.len()];
381    let mut last = 0;
382    for &position in &order {
383        let at = Held::of(leaves[position].0);
384        let found = spans[last.min(spans.len())..]
385            .iter()
386            .position(|((start, end), _)| {
387                end.compare(at) == Ordering::Greater && at.compare(*start) != Ordering::Less
388            })
389            .map(|index| index + last);
390        if crate::makenotation::is_not_rest(leaves[position].1)
391            && let Some(index) = found
392        {
393            last = index;
394        }
395        answers[position] = found.map(|index| spans[index].1);
396    }
397    answers
398}
399
400/// An offset as music21 holds one once `opFrac` has had it: a float where it
401/// is a binary fraction, and the exact fraction otherwise.
402#[derive(Clone, Copy, Debug)]
403enum Held {
404    Float(FloatType),
405    Exact(i128, i128),
406}
407
408impl Held {
409    fn of(value: FloatType) -> Self {
410        let snapped = crate::makenotation::op_frac(value);
411        match crate::duration::limited_fraction(snapped, 65535) {
412            Some((numerator, denominator)) if !is_power_of_two(denominator) => {
413                Self::Exact(numerator, denominator)
414            }
415            _ => Self::Float(snapped),
416        }
417    }
418
419    fn float(self) -> FloatType {
420        match self {
421            Self::Float(value) => value,
422            Self::Exact(numerator, denominator) => {
423                numerator as FloatType / denominator as FloatType
424            }
425        }
426    }
427
428    /// Python's `+`: exact for two fractions, a float otherwise.
429    fn add(self, other: Self) -> Self {
430        match (self, other) {
431            (Self::Exact(a, b), Self::Exact(c, d)) => reduced(a * d + c * b, b * d),
432            _ => Self::Float(self.float() + other.float()),
433        }
434    }
435
436    /// `opFrac(self - start)`: the distance snapped to a float where it is a
437    /// binary fraction.
438    fn span_from(self, start: Self) -> Self {
439        match (self, start) {
440            (Self::Exact(a, b), Self::Exact(c, d)) => match reduced(a * d - c * b, b * d) {
441                Self::Exact(numerator, denominator)
442                    if is_power_of_two(denominator) || denominator > 65535 =>
443                {
444                    Self::of(numerator as FloatType / denominator as FloatType)
445                }
446                exact => exact,
447            },
448            _ => Self::of(self.float() - start.float()),
449        }
450    }
451
452    /// The two compared exactly, as Python compares a float with a fraction.
453    fn compare(self, other: Self) -> Ordering {
454        let (a, b) = self.ratio();
455        let (c, d) = other.ratio();
456        match (a.checked_mul(d), c.checked_mul(b)) {
457            (Some(left), Some(right)) => left.cmp(&right),
458            _ => self.float().total_cmp(&other.float()),
459        }
460    }
461
462    fn same(self, other: Self) -> bool {
463        self.compare(other) == Ordering::Equal
464    }
465
466    /// The exact value as a fraction, a float being a binary one.
467    fn ratio(self) -> (i128, i128) {
468        match self {
469            Self::Exact(numerator, denominator) => (numerator, denominator),
470            Self::Float(value) => {
471                let mut exact = value;
472                let mut denominator: i128 = 1;
473                while exact.fract() != 0.0 && denominator < (1 << 70) {
474                    exact *= 2.0;
475                    denominator *= 2;
476                }
477                (exact as i128, denominator)
478            }
479        }
480    }
481}
482
483fn reduced(numerator: i128, denominator: i128) -> Held {
484    let (mut a, mut b) = (numerator.abs(), denominator.abs());
485    while b != 0 {
486        (a, b) = (b, a % b);
487    }
488    let divisor = a.max(1);
489    Held::Exact(numerator / divisor, denominator / divisor)
490}
491
492fn is_power_of_two(value: i128) -> bool {
493    value > 0 && value & (value - 1) == 0
494}
495
496/// The dynamic a note's volume is read against: music21's
497/// `getDynamicContext`, which asks the note for the nearest dynamic at or
498/// before it.
499///
500/// `position` is the note's place in [`Stream::leaves`], and the answer is
501/// the dynamic's place there, or `None` where no dynamic comes before it.
502/// The search starts in the stream holding the note and works outwards, as
503/// music21's `getContextByClass` does: a measure, a voice or a plain stream
504/// is searched first among its own elements and then through the streams it
505/// holds, a part only through them, and a score or an opus only among its
506/// own elements -- so a dynamic in another part is never the one, while a
507/// dynamic nearer the note in its own voice wins over a later one in
508/// another voice. A dynamic at the note's own offset is before it.
509///
510/// ```
511/// use music21_rs::{volume::dynamic_context, Dynamic, Note, Stream, StreamKind};
512///
513/// let mut measure = Stream::with_kind(StreamKind::Measure);
514/// measure.insert(0.0, Dynamic::new("ff"));
515/// measure.insert(0.0, Note::from_name("C4")?);
516/// measure.insert(1.0, Note::from_name("D4")?);
517/// assert_eq!(dynamic_context(&measure, 2), Some(0));
518/// assert_eq!(dynamic_context(&Stream::new(), 0), None);
519/// # Ok::<(), music21_rs::Error>(())
520/// ```
521pub fn dynamic_context(stream: &Stream, position: usize) -> Option<usize> {
522    // The streams holding the note, outermost first: each with the offset it
523    // starts at and the position of its first leaf.
524    let mut chain: Vec<(&Stream, FloatType, usize)> = Vec::new();
525    let at = holders_of(stream, 0.0, 0, position, &mut chain)?;
526    for &(site, start, first) in chain.iter().rev() {
527        let at = at - start;
528        let own_first = !matches!(site.kind(), StreamKind::Part | StreamKind::PartStaff);
529        let then_inside = !matches!(site.kind(), StreamKind::Score | StreamKind::Opus);
530        if own_first && let Some(found) = last_dynamic(site, first, at, false) {
531            return Some(found);
532        }
533        if then_inside && let Some(found) = last_dynamic(site, first, at, true) {
534            return Some(found);
535        }
536    }
537    None
538}
539
540/// Walks down to the leaf at `position`, pushing every stream on the way,
541/// and answers the leaf's offset; `None` where there is no such leaf.
542fn holders_of<'a>(
543    stream: &'a Stream,
544    start: FloatType,
545    first: usize,
546    position: usize,
547    chain: &mut Vec<(&'a Stream, FloatType, usize)>,
548) -> Option<FloatType> {
549    chain.push((stream, start, first));
550    let mut next = first;
551    for event in stream.events() {
552        let offset = start + event.offset();
553        match event.element() {
554            StreamElement::Stream(inner) => {
555                let count = inner.leaves().len();
556                if position < next + count {
557                    return holders_of(inner, offset, next, position, chain);
558                }
559                next += count;
560            }
561            _ => {
562                if next == position {
563                    return Some(offset);
564                }
565                next += 1;
566            }
567        }
568    }
569    None
570}
571
572/// The last dynamic at or before `at` among a stream's own elements, or
573/// among everything it holds where `inside`, as its place among the leaves.
574fn last_dynamic(stream: &Stream, first: usize, at: FloatType, inside: bool) -> Option<usize> {
575    let mut found: Option<(FloatType, usize)> = None;
576    let mut consider = |offset: FloatType, position: usize| {
577        if offset <= at + 1e-9 && found.is_none_or(|(best, _)| offset >= best) {
578            found = Some((offset, position));
579        }
580    };
581    if inside {
582        for (position, (offset, element)) in stream.leaves().into_iter().enumerate() {
583            if matches!(element, StreamElement::Dynamic(_)) {
584                consider(offset, first + position);
585            }
586        }
587    } else {
588        let mut next = first;
589        for event in stream.events() {
590            match event.element() {
591                StreamElement::Stream(inner) => next += inner.leaves().len(),
592                StreamElement::Dynamic(_) => {
593                    consider(event.offset(), next);
594                    next += 1;
595                }
596                _ => next += 1,
597            }
598        }
599    }
600    found.map(|(_, position)| position)
601}
602
603impl fmt::Display for Volume {
604    /// music21's `repr` body, `realized=0.71`.
605    ///
606    /// music21 writes `round(self.realized, 2)` into an f-string, so Python's
607    /// float formatting drops a trailing zero the way `{:.2}` does not: half
608    /// velocity reads `realized=0.5`, not `realized=0.50`.
609    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
610        let mut rounded = format!("{:.2}", self.realized());
611        while rounded.ends_with('0') && !rounded.ends_with(".0") {
612            rounded.pop();
613        }
614        write!(f, "realized={rounded}")
615    }
616}
617
618#[cfg(test)]
619mod tests {
620    use super::*;
621
622    #[test]
623    fn merging_takes_the_other_volumes_velocity() {
624        let mut loud = Volume::from_velocity(111);
625        loud.set_velocity_is_relative(false);
626        let mut volume = Volume::new();
627        volume.merge_attributes(&loud);
628        assert_eq!(volume.velocity(), Some(111));
629        assert!(!volume.velocity_is_relative());
630    }
631
632    #[test]
633    fn velocity_and_scalar_are_the_same_number() {
634        let mut volume = Volume::new();
635        assert_eq!(volume.velocity(), None);
636        assert_eq!(volume.velocity_scalar(), None);
637        assert!(!volume.has_velocity_information());
638
639        volume.set_velocity(Some(64));
640        assert_eq!(volume.velocity(), Some(64));
641        assert!(volume.has_velocity_information());
642        assert!((volume.velocity_scalar().unwrap() - 64.0 / 127.0).abs() < 1e-12);
643
644        volume.set_velocity(Some(200));
645        assert_eq!(volume.velocity(), Some(127));
646        volume.set_velocity(Some(-5));
647        assert_eq!(volume.velocity(), Some(0));
648        volume.set_velocity(None);
649        assert_eq!(volume.velocity(), None);
650    }
651
652    #[test]
653    fn scalars_clamp_and_round_trip() {
654        let volume = Volume::from_velocity_scalar(0.5).unwrap();
655        assert_eq!(volume.velocity(), Some(64));
656        assert_eq!(
657            Volume::from_velocity_scalar(2.0).unwrap().velocity_scalar(),
658            Some(1.0)
659        );
660        assert_eq!(
661            Volume::from_velocity_scalar(-1.0)
662                .unwrap()
663                .velocity_scalar(),
664            Some(0.0)
665        );
666        assert!(Volume::from_velocity_scalar(FloatType::NAN).is_err());
667        assert_eq!(Volume::from_velocity(127).velocity_scalar(), Some(1.0));
668    }
669
670    #[test]
671    fn realized_matches_music21() {
672        // An unset velocity realizes at the shifted base level, which is
673        // music21's default loudness for a note nobody has marked.
674        let unset = Volume::new();
675        assert!((unset.realized() - 0.70866).abs() < 1e-9);
676        assert_eq!(unset.realized_dynamic(), "f");
677        assert_eq!(unset.realized_str(), "0.71");
678
679        // A relative velocity doubles its scalar against the base level, so
680        // half velocity leaves the base level where it is.
681        let half = Volume::from_velocity_scalar(0.5).unwrap();
682        assert!((half.realized() - 0.5).abs() < 1e-9);
683        assert_eq!(half.realized_dynamic(), "mf");
684        assert_eq!(half.realized_str(), "0.5");
685
686        // An absolute velocity decides the answer by itself.
687        let mut absolute = Volume::from_velocity_scalar(0.5).unwrap();
688        absolute.set_velocity_is_relative(false);
689        assert!((absolute.realized() - 0.5).abs() < 1e-9);
690        let mut loud = Volume::from_velocity_scalar(1.0).unwrap();
691        loud.set_velocity_is_relative(false);
692        assert!((loud.realized() - 1.0).abs() < 1e-9);
693
694        // Relative velocities scale with the dynamic around them, and clip.
695        assert!((half.realized_with(Some(0.5), 0.0, 0.5, true) - 0.5).abs() < 1e-9);
696        assert!((half.realized_with(Some(1.0), 0.0, 0.5, true) - 1.0).abs() < 1e-9);
697        assert!((half.realized_with(Some(1.0), 0.0, 0.5, false) - 1.0).abs() < 1e-9);
698        assert!(loud.realized_with(Some(1.0), 0.0, 0.5, false) > 0.99);
699
700        // An articulation shifts rather than scales, so an accent on a note
701        // nobody has marked lifts it a little.
702        let unmarked = Volume::new();
703        assert!((unmarked.realized_with(None, 0.1, 0.5, true) - 0.80866).abs() < 1e-9);
704        // Read off music21: a note nobody has marked, realized under pp and
705        // ff with its velocity left out, is the base level scaled and no more.
706        assert!(
707            (unmarked.realized_without_velocity(Some(0.25), 0.0, 0.5, true) - 0.25).abs() < 1e-9
708        );
709        assert!(
710            (unmarked.realized_without_velocity(Some(0.85), 0.0, 0.5, true) - 0.85).abs() < 1e-9
711        );
712    }
713
714    #[test]
715    fn realized_names_cover_the_dynamic_range() {
716        assert_eq!(dynamic_name(0.0), "n");
717        assert_eq!(dynamic_name(0.05), "pppp");
718        assert_eq!(dynamic_name(0.12), "ppp");
719        assert_eq!(dynamic_name(0.2), "pp");
720        assert_eq!(dynamic_name(0.3), "p");
721        assert_eq!(dynamic_name(0.4), "mp");
722        assert_eq!(dynamic_name(0.6), "mf");
723        assert_eq!(dynamic_name(0.7), "f");
724        assert_eq!(dynamic_name(0.85), "ff");
725        assert_eq!(dynamic_name(1.0), "fff");
726        assert_eq!(Volume::from_velocity(64).to_string(), "realized=0.5");
727        assert_eq!(Volume::new().to_string(), "realized=0.71");
728        let mut silent = Volume::new();
729        silent.set_velocity(Some(0));
730        silent.set_velocity_is_relative(false);
731        assert_eq!(silent.to_string(), "realized=0.0");
732    }
733
734    /// Every number here is music21's: the first run is its own
735    /// `testRealizeVolumeA`, the rest were read off it.
736    #[test]
737    fn a_stream_is_realized_against_the_dynamics_in_force() {
738        use crate::{Note, Stream, StreamElement};
739
740        let velocities = |stream: &Stream| -> Vec<Option<IntegerType>> {
741            stream
742                .notes()
743                .iter()
744                .map(|(_, element)| match element {
745                    StreamElement::Note(note) => note.volume().velocity(),
746                    _ => None,
747                })
748                .collect()
749        };
750        let notes = |count: usize| {
751            let mut stream = Stream::new();
752            for _ in 0..count {
753                stream.push(Note::from_name("G3").unwrap());
754            }
755            stream
756        };
757
758        let mut stream = notes(16);
759        for (index, mark) in ["pp", "p", "mp", "f", "mf", "ff", "ppp", "mf"]
760            .into_iter()
761            .enumerate()
762        {
763            stream.insert(index as FloatType * 2.0, Dynamic::new(mark));
764        }
765        realize_volume(&mut stream, &DynamicContext::FromStream, true);
766        assert_eq!(
767            velocities(&stream),
768            [
769                45, 45, 63, 63, 81, 81, 126, 126, 99, 99, 127, 127, 27, 27, 99, 99
770            ]
771            .map(Some)
772        );
773
774        // A note before the first dynamic sounds under none, a velocity of
775        // its own scales the answer, and an absolute one decides it.
776        let mut stream = notes(4);
777        stream.insert(1.0, Dynamic::new("p"));
778        stream.insert(3.0, Dynamic::new("ff"));
779        let mut position = 0;
780        stream.for_each_mut(&mut |_, element| {
781            if let StreamElement::Note(note) = element {
782                let mut volume = note.volume();
783                match position {
784                    2 => volume.set_velocity(Some(100)),
785                    3 => {
786                        volume.set_velocity(Some(64));
787                        volume.set_velocity_is_relative(false);
788                    }
789                    _ => {}
790                }
791                note.set_volume(Some(volume));
792                position += 1;
793            }
794        });
795        realize_volume(&mut stream, &DynamicContext::FromStream, true);
796        assert_eq!(velocities(&stream), [90, 63, 70, 64].map(Some));
797
798        let mut stream = notes(2);
799        stream.insert(0.0, Dynamic::new("p"));
800        stream.for_each_mut(&mut |offset, element| {
801            if let (true, StreamElement::Note(note)) = (offset == 1.0, element) {
802                note.set_volume(Some(Volume::from_velocity(100)));
803            }
804        });
805        realize_volume(&mut stream, &DynamicContext::FromStream, false);
806        assert_eq!(velocities(&stream), [44, 44].map(Some));
807
808        let mut stream = notes(2);
809        stream.insert(0.0, Dynamic::new("p"));
810        realize_volume(
811            &mut stream,
812            &DynamicContext::Fixed(Dynamic::new("sfz")),
813            true,
814        );
815        assert_eq!(velocities(&stream), [126, 126].map(Some));
816
817        let mut stream = notes(2);
818        stream.insert(0.0, Dynamic::new("p"));
819        realize_volume(&mut stream, &DynamicContext::Ignored, true);
820        assert_eq!(velocities(&stream), [90, 90].map(Some));
821    }
822}