midi_reader_writer/
midly_0_5.rs

1//     Facilitate reading and writing midi files.
2//
3//     Copyright (C) 2021 Pieter Penninckx
4//
5//     `midi-reader-writer` is licensed under the Apache License, Version 2.0
6//     or the MIT license, at your option.
7//
8//     For the application of the MIT license, the examples included in the doc comments are not
9//     considered "substantial portions of this Software".
10//
11//     License texts can be found:
12//     * for the Apache License, Version 2.0: <LICENSE-APACHE.txt> or
13//         <http://www.apache.org/licenses/LICENSE-2.0>
14//     * for the MIT license: <LICENSE-MIT.txt> or
15//         <http://opensource.org/licenses/MIT>.
16//
17//
18
19//! Everything specific to using this crate with the [`midly`](https://crates.io/crates/midly) crate,
20//! version 0.5.x, behind the `engine-midly-0-5` feature.
21//!
22//! This module also adds some extra functionality to be used with the `midly` crate:
23//! * [`merge_tracks`]: Create an iterator over all tracks, merged (behind the `read` feature)
24//! * [`TrackSeparator`]: Separate the tracks again.
25
26/// Re-exports from the [`midly`](https://crates.io/crates/midly) crate, version 0.5.x.
27pub mod exports {
28    pub use midly_0_5::*;
29}
30
31use self::exports::{num::u28, MetaMessage, Track, TrackEvent, TrackEventKind};
32#[cfg(all(test, feature = "convert-time"))]
33use self::exports::{
34    num::{u15, u24},
35    Format,
36};
37#[cfg(all(test, any(feature = "read", feature = "convert-time")))]
38use self::exports::{
39    num::{u4, u7},
40    MidiMessage,
41};
42#[cfg(feature = "convert-time")]
43use self::exports::{Fps, Header, Timing};
44#[cfg(feature = "convert-time")]
45use crate::{
46    ConvertMicroSecondsToTicks, ConvertTicksToMicroseconds, MidiEvent, TimeConversionError,
47};
48#[cfg(feature = "read")]
49use itertools::Itertools; // TODO: remove if not needed.
50use std::iter::FromIterator;
51#[cfg(feature = "convert-time")]
52use std::num::NonZeroU64;
53use std::num::TryFromIntError;
54use std::{
55    convert::TryFrom,
56    error::Error,
57    fmt::{Display, Formatter},
58};
59#[cfg(feature = "convert-time")]
60use timestamp_stretcher::TimestampStretcher;
61
62#[cfg(feature = "convert-time")]
63const MICROSECONDS_PER_SECOND: u64 = 1_000_000;
64#[cfg(feature = "convert-time")]
65const SECONDS_PER_MINUTE: u64 = 60;
66#[cfg(feature = "convert-time")]
67const MICROSECONDS_PER_MINUTE: u64 = SECONDS_PER_MINUTE * MICROSECONDS_PER_SECOND;
68#[cfg(feature = "convert-time")]
69const DEFAULT_BEATS_PER_MINUTE: u64 = 120;
70
71#[cfg(feature = "read")]
72/// Create an iterator over all the tracks, merged.
73/// The item has type `(u64, usize, TrackEventKind)`,
74/// where the first element of the triple is the timing of the event relative to the beginning
75/// of the tracks, in ticks,
76/// the second item of the triple is the track index and the last item is the event itself.
77///
78/// # Example
79/// ```
80/// use midi_reader_writer::midly_0_5::{
81///     merge_tracks,
82///     exports::{
83///         MidiMessage,
84///         TrackEvent,
85///         TrackEventKind,
86///         num::{u15, u4, u7, u28}
87///     }
88/// };
89///
90/// // Create a note on event with the given channel
91/// fn note_on_with_channel(channel: u8) -> TrackEventKind<'static> {
92///     // ...
93/// #     TrackEventKind::Midi {
94/// #         channel: u4::new(channel),
95/// #         message: MidiMessage::NoteOn {
96/// #             key: u7::new(1),
97/// #             vel: u7::new(1),
98/// #         },
99/// #     }
100/// }
101///
102/// // Create a note on event with the given delta and channel.
103/// fn note_on_with_delta_and_channel(delta: u32, channel: u8) -> TrackEvent<'static> {
104///     // ...
105/// #     TrackEvent {
106/// #        delta: u28::new(delta),
107/// #        kind: note_on_with_channel(channel),
108/// #    }
109/// }
110///
111/// let tracks = vec![
112///     vec![
113///         note_on_with_delta_and_channel(2, 0),
114///         note_on_with_delta_and_channel(100, 1),
115///     ],
116///     vec![note_on_with_delta_and_channel(30, 2)],
117/// ];
118/// let result: Vec<_> = merge_tracks(&tracks[..]).collect();
119/// assert_eq!(
120///     result,
121///     vec![
122///         (2, 0, note_on_with_channel(0)),
123///         (30, 1, note_on_with_channel(2)),
124///         (102, 0, note_on_with_channel(1)),
125///     ]
126/// )
127/// ```
128pub fn merge_tracks<'a, 'b>(
129    tracks: &'b [Track<'a>],
130) -> impl Iterator<Item = (u64, usize, TrackEventKind<'a>)> + 'b {
131    let mut track_index = 0;
132    tracks
133        .iter()
134        .map(|t| {
135            let mut offset = 0;
136            let result = t.iter().map(move |e| {
137                offset += e.delta.as_int() as u64;
138                (offset, track_index, e.kind)
139            });
140            track_index += 1;
141            result
142        })
143        .kmerge_by(|(t1, _, _), (t2, _, _)| t1 < t2)
144}
145
146#[cfg(feature = "read")]
147#[test]
148fn merge_tracks_has_sufficiently_flexible_lifetime_annotation() {
149    fn wrap(bytes: &[u8]) -> Track {
150        let event = TrackEvent {
151            delta: u28::new(123),
152            kind: TrackEventKind::SysEx(bytes),
153        };
154        merge_tracks(&[vec![event]])
155            .map(|(_, _, kind)| TrackEvent {
156                delta: u28::new(0),
157                kind,
158            })
159            .collect()
160    }
161
162    let bytes = "abc".to_string(); // Force a non-static lifetime.
163    wrap(bytes.as_bytes());
164}
165
166#[cfg(feature = "read")]
167#[test]
168fn merge_tracks_works() {
169    fn kind(channel: u8) -> TrackEventKind<'static> {
170        TrackEventKind::Midi {
171            channel: u4::new(channel),
172            message: MidiMessage::NoteOn {
173                key: u7::new(1),
174                vel: u7::new(1),
175            },
176        }
177    }
178    fn track_event(delta: u32, channel: u8) -> TrackEvent<'static> {
179        TrackEvent {
180            delta: u28::new(delta),
181            kind: kind(channel),
182        }
183    }
184
185    let tracks = vec![
186        vec![track_event(1, 0), track_event(2, 1), track_event(4, 2)],
187        vec![track_event(2, 3), track_event(2, 4), track_event(5, 5)],
188    ];
189    let result: Vec<_> = merge_tracks(&tracks[..]).collect();
190    assert_eq!(
191        result,
192        vec![
193            (1, 0, kind(0)),
194            (2, 1, kind(3)),
195            (3, 0, kind(1)),
196            (4, 1, kind(4)),
197            (7, 0, kind(2)),
198            (9, 1, kind(5))
199        ]
200    )
201}
202
203#[cfg(feature = "convert-time")]
204impl TryFrom<Header> for ConvertTicksToMicroseconds {
205    type Error = TimeConversionError;
206
207    /// Create a new `ConvertTicksToMicrosecond` with the given header.
208    ///
209    /// The parameter `header` is used to determine the meaning of "tick", since this is stored
210    /// in the header in a midi file.
211    fn try_from(header: Header) -> Result<Self, Self::Error> {
212        let time_stretcher;
213        let ticks_per_beat;
214        use TimeConversionError::*;
215        match header.timing {
216            Timing::Metrical(t) => {
217                let tpb = NonZeroU64::new(t.as_int() as u64).ok_or(ZeroTicksPerBeatNotSupported)?;
218                time_stretcher = TimestampStretcher::new(
219                    MICROSECONDS_PER_MINUTE / DEFAULT_BEATS_PER_MINUTE,
220                    tpb,
221                );
222                ticks_per_beat = Some(tpb);
223            }
224            Timing::Timecode(Fps::Fps29, ticks_per_frame) => {
225                // Frames per second = 30 / 1.001 = 30000 / 1001
226                // microseconds = ticks * microseconds_per_second / (ticks_per_frame * frames_per_second) ;
227                time_stretcher = TimestampStretcher::new(
228                    MICROSECONDS_PER_SECOND * 1001,
229                    NonZeroU64::new((ticks_per_frame as u64) * 30000)
230                        .ok_or(ZeroTicksPerFrameNotSupported)?,
231                );
232                ticks_per_beat = None;
233            }
234            Timing::Timecode(fps, ticks_per_frame) => {
235                // microseconds = ticks * microseconds_per_second / (ticks_per_frame * frames_per_second) ;
236                time_stretcher = TimestampStretcher::new(
237                    MICROSECONDS_PER_SECOND,
238                    NonZeroU64::new((fps.as_int() as u64) * (ticks_per_frame as u64))
239                        .ok_or(ZeroTicksPerFrameNotSupported)?,
240                );
241                ticks_per_beat = None;
242            }
243        }
244        Ok(Self {
245            time_stretcher,
246            ticks_per_beat,
247        })
248    }
249}
250
251#[cfg(feature = "convert-time")]
252impl<'a> MidiEvent for TrackEventKind<'a> {
253    fn tempo(&self) -> Option<u32> {
254        if let TrackEventKind::Meta(MetaMessage::Tempo(tempo)) = self {
255            Some(tempo.as_int())
256        } else {
257            None
258        }
259    }
260}
261
262#[cfg(feature = "convert-time")]
263#[test]
264pub fn convert_ticks_to_microseconds_works_with_one_event() {
265    // 120 beats per minute
266    // = 120 beats per 60 seconds
267    // = 120 beats per 60 000 000 microseconds
268    // so the tempo is
269    //   60 000 000 / 120 microseconds per beat
270    //   = 10 000 000 / 20 microseconds per beat
271    //   =    500 000 microseconds per beat
272    let tempo_in_microseconds_per_beat = 500000;
273    let ticks_per_beat = 32;
274    // One event after 1 second.
275    // One second corresponds to two beats, so to 64 ticks.
276    let event_time_in_ticks: u64 = 64;
277    let header = Header {
278        timing: Timing::Metrical(u15::from(ticks_per_beat)),
279        format: Format::SingleTrack,
280    };
281    let mut converter =
282        ConvertTicksToMicroseconds::try_from(header).expect("No error expected at this point.");
283    let microseconds = converter.convert(
284        0,
285        &TrackEventKind::Meta(MetaMessage::Tempo(u24::from(
286            tempo_in_microseconds_per_beat,
287        ))),
288    );
289    assert_eq!(microseconds, 0);
290    let microseconds = converter.convert(
291        event_time_in_ticks,
292        &TrackEventKind::Midi {
293            channel: u4::from(0),
294            message: MidiMessage::NoteOn {
295                key: u7::from(60),
296                vel: u7::from(90),
297            },
298        },
299    );
300    assert_eq!(microseconds, 1000000);
301}
302
303#[cfg(feature = "convert-time")]
304impl From<Header> for ConvertMicroSecondsToTicks {
305    /// Create a new `ConvertMicroSecondsToTicks` with the given header.
306    ///
307    /// The parameter `header` is used to determine the meaning of "tick", since this is stored
308    /// in the header in a midi file.
309    fn from(header: Header) -> Self {
310        let time_stretcher;
311        let ticks_per_beat;
312        match header.timing {
313            Timing::Metrical(t) => {
314                let tpb = t.as_int() as u64;
315                time_stretcher = TimestampStretcher::new(
316                    tpb,
317                    NonZeroU64::new(MICROSECONDS_PER_MINUTE / DEFAULT_BEATS_PER_MINUTE)
318                        .expect("Bug: MICROSECONDS_PER_MINUTE / DEFAULT_BEATS_PER_MINUTE should not be zero."),
319                );
320                ticks_per_beat = Some(tpb);
321            }
322            Timing::Timecode(Fps::Fps29, ticks_per_frame) => {
323                // Frames per second = 30 / 1.001 = 30000 / 1001
324                // ticks = microseconds * ticks_per_frame * frames_per_second / microseconds_per_second
325                time_stretcher = TimestampStretcher::new(
326                    (ticks_per_frame as u64) * 30000,
327                    NonZeroU64::new(MICROSECONDS_PER_SECOND * 1001)
328                        .expect("Bug: MICROSECONDS_PER_SECOND * 1001 should not be zero."),
329                );
330                ticks_per_beat = None;
331            }
332            Timing::Timecode(fps, ticks_per_frame) => {
333                // ticks = microseconds * ticks_per_frame * frames_per_second / microseconds_per_second
334                time_stretcher = TimestampStretcher::new(
335                    (fps.as_int() as u64) * (ticks_per_frame as u64),
336                    NonZeroU64::new(MICROSECONDS_PER_SECOND)
337                        .expect("Bug: MICROSECONDS_PER_SECOND should not be zero."),
338                );
339                ticks_per_beat = None;
340            }
341        }
342        Self {
343            time_stretcher,
344            ticks_per_beat,
345        }
346    }
347}
348
349#[cfg(feature = "convert-time")]
350#[test]
351pub fn convert_microseconds_to_ticks_works_with_one_event() {
352    // 120 beats per minute
353    // = 120 beats per 60 seconds
354    // = 120 beats per 60 000 000 microseconds
355    // so the tempo is
356    //   60 000 000 / 120 microseconds per beat
357    //   = 10 000 000 / 20 microseconds per beat
358    //   =    500 000 microseconds per beat
359    let tempo_in_microseconds_per_beat = 500000;
360    let ticks_per_beat = 32;
361    // One event after 1 second.
362    // One second corresponds to two beats, so to 64 ticks.
363    let expected_vent_time_in_ticks: u64 = 64;
364    let event_time_in_microseconds: u64 = 1000000;
365    let header = Header {
366        timing: Timing::Metrical(u15::from(ticks_per_beat)),
367        format: Format::SingleTrack,
368    };
369    let mut converter = ConvertMicroSecondsToTicks::from(header);
370    let microseconds = converter
371        .convert(
372            0,
373            &TrackEventKind::Meta(MetaMessage::Tempo(u24::from(
374                tempo_in_microseconds_per_beat,
375            ))),
376        )
377        .expect("No error expected at this point.");
378    assert_eq!(microseconds, 0);
379    let observed_event_time_in_ticks = converter
380        .convert(
381            event_time_in_microseconds,
382            &TrackEventKind::Midi {
383                channel: u4::from(0),
384                message: MidiMessage::NoteOn {
385                    key: u7::from(60),
386                    vel: u7::from(90),
387                },
388            },
389        )
390        .expect("No error expected at this point");
391    assert_eq!(expected_vent_time_in_ticks, observed_event_time_in_ticks);
392}
393
394/// The error type returned by the [`TrackSeparator::push()`] method.
395#[derive(Debug)]
396#[non_exhaustive]
397pub enum SeparateTracksError {
398    /// The specified time overflows what can be specified.
399    TimeOverflow,
400    /// Time decreased from one event to a next event.
401    TimeCanOnlyIncrease,
402}
403
404impl From<TryFromIntError> for SeparateTracksError {
405    fn from(_: TryFromIntError) -> Self {
406        SeparateTracksError::TimeOverflow
407    }
408}
409
410impl Display for SeparateTracksError {
411    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
412        match self {
413            SeparateTracksError::TimeOverflow => {
414                write!(
415                    f,
416                    "the time overflows what can be represented in a midi file."
417                )
418            }
419            SeparateTracksError::TimeCanOnlyIncrease => {
420                write!(f, "subsequent events with decreasing time found.")
421            }
422        }
423    }
424}
425
426impl Error for SeparateTracksError {
427    fn source(&self) -> Option<&(dyn Error + 'static)> {
428        None
429    }
430}
431
432/// Write to a track, keeping ... well, keeping "track" of the timing relative to the beginning.
433#[derive(Clone)]
434struct TrackWriter<'a> {
435    track: Track<'a>,
436    ticks: u64,
437}
438
439impl<'a> TrackWriter<'a> {
440    fn new() -> Self {
441        TrackWriter {
442            track: Vec::new(),
443            ticks: 0,
444        }
445    }
446
447    fn push(&mut self, ticks: u64, kind: TrackEventKind<'a>) -> Result<(), SeparateTracksError> {
448        use SeparateTracksError::*;
449        if self.ticks > ticks {
450            return Err(TimeCanOnlyIncrease);
451        }
452        let delta = ticks - self.ticks;
453        let delta = u28::try_from(u32::try_from(delta)?).ok_or(TimeOverflow)?;
454        self.ticks = ticks;
455        self.track.push(TrackEvent { delta, kind });
456        Ok(())
457    }
458}
459
460/// Separate events into different tracks.
461pub struct TrackSeparator<'a> {
462    tracks: Vec<TrackWriter<'a>>,
463}
464
465impl<'a> TrackSeparator<'a> {
466    /// Create a new `TrackSeparator`.
467    ///
468    /// # Example
469    /// ```
470    /// use midi_reader_writer::midly_0_5::TrackSeparator;
471    /// let track_separator = TrackSeparator::new();
472    /// let tracks : Vec<_> = track_separator.collect();
473    /// assert!(tracks.is_empty());
474    /// ```
475    #[inline]
476    pub fn new() -> Self {
477        TrackSeparator { tracks: Vec::new() }
478    }
479
480    /// Push a new event.
481    ///
482    /// # Parameters
483    /// * `ticks`: the time in midi ticks, relative to the beginning
484    ///   of the tracks
485    /// * `track_index`: the index of the track to which the event belongs
486    /// * `event`: the event
487    ///
488    /// # Example
489    /// ```
490    /// use midi_reader_writer::midly_0_5::{TrackSeparator, exports::{TrackEventKind, TrackEvent, MetaMessage}};
491    /// # use midi_reader_writer::midly_0_5::exports::{
492    /// #     MidiMessage,
493    /// #     num::{u4, u7, u28}
494    /// # };
495    ///
496    /// // Create a note on event with the given channel
497    /// fn note_on_with_channel(channel: u8) -> TrackEventKind<'static> {
498    ///     // ...
499    /// #     TrackEventKind::Midi {
500    /// #         channel: u4::new(channel),
501    /// #         message: MidiMessage::NoteOn {
502    /// #             key: u7::new(1),
503    /// #             vel: u7::new(1),
504    /// #         },
505    /// #     }
506    /// }
507    ///
508    /// // Create a note on event with the given delta and channel.
509    /// fn note_on_with_delta_and_channel(delta: u32, channel: u8) -> TrackEvent<'static> {
510    ///     // ...
511    /// #     TrackEvent {
512    /// #        delta: u28::new(delta),
513    /// #        kind: note_on_with_channel(channel),
514    /// #    }
515    /// }
516    ///
517    /// // Create an end-of-track event with the given delta.
518    /// fn end_of_track(delta: u32) -> TrackEvent<'static> {
519    ///     // ...
520    /// #     TrackEvent {
521    /// #       delta: u28::new(delta),
522    /// #       kind: TrackEventKind::Meta(MetaMessage::EndOfTrack)
523    /// #    }
524    /// }
525    ///
526    /// let mut track_separator = TrackSeparator::new();
527    /// track_separator.push(5, 0, note_on_with_channel(0));
528    /// track_separator.push(0, 1, note_on_with_channel(1));
529    /// track_separator.push(10, 0, note_on_with_channel(2));
530    /// track_separator.push(3, 1, TrackEventKind::Meta(MetaMessage::EndOfTrack));
531    /// let tracks : Vec<_> = track_separator.collect();
532    /// assert_eq!(tracks.len(), 2);
533    /// assert_eq!(
534    ///             tracks[0],
535    ///             vec![
536    ///                 note_on_with_delta_and_channel(5, 0),
537    ///                 note_on_with_delta_and_channel(5, 2),
538    ///                 end_of_track(0)
539    ///             ]
540    /// );
541    /// assert_eq!(tracks[1], vec![note_on_with_delta_and_channel(0, 1), end_of_track(3)]);
542    /// ```
543
544    #[inline]
545    pub fn push(
546        &mut self,
547        ticks: u64,
548        track_index: usize,
549        event: TrackEventKind<'a>,
550    ) -> Result<(), SeparateTracksError> {
551        if self.tracks.len() <= track_index {
552            self.tracks.resize(track_index + 1, TrackWriter::new());
553        }
554        self.tracks[track_index].push(ticks, event)
555    }
556
557    /// Append all events from an iterator of triples of type `(u64, usize, TrackEventKind)`,
558    /// where
559    /// * the first item of the triple (of type `u64`) is the time in midi ticks, relative to the beginning
560    ///   of the tracks
561    /// * the second item of the triple (of type `usize`) is the track index.
562    /// * the last item of the triple is the event itself.
563    /// # Example
564    /// ```
565    /// use midi_reader_writer::midly_0_5::{TrackSeparator, exports::{TrackEventKind, TrackEvent}};
566    /// # use midi_reader_writer::midly_0_5::exports::{MetaMessage, MidiMessage, num::{u4, u7, u28}};
567    ///
568    /// fn note_on_with_channel(channel: u8) -> TrackEventKind<'static> {
569    ///     // ...
570    /// #    TrackEventKind::Midi {
571    /// #        channel: u4::new(channel),
572    /// #        message: MidiMessage::NoteOn {
573    /// #            key: u7::new(1),
574    /// #            vel: u7::new(1),
575    /// #        },
576    /// #    }
577    /// }
578    ///
579    /// fn note_on_with_channel_and_delta_time(delta: u32, channel: u8) -> TrackEvent<'static> {
580    ///     // ...
581    /// #    TrackEvent {
582    /// #        delta: u28::new(delta),
583    /// #        kind: note_on_with_channel(channel),
584    /// #    }
585    /// }
586    /// // Create an end-of-track event with the given delta.
587    /// fn end_of_track(delta: u32) -> TrackEvent<'static> {
588    ///     // ...
589    /// #     TrackEvent {
590    /// #       delta: u28::new(delta),
591    /// #       kind: TrackEventKind::Meta(MetaMessage::EndOfTrack)
592    /// #    }
593    /// }
594    ///
595    /// let events : Vec<(u64, usize, _)>= vec![
596    ///     (1, 0, note_on_with_channel(0)),
597    ///     (2, 1, note_on_with_channel(3)),
598    ///     (3, 0, note_on_with_channel(1)),
599    ///     (4, 1, note_on_with_channel(4)),
600    ///     (7, 0, note_on_with_channel(2)),
601    ///     (9, 1, note_on_with_channel(5)),
602    /// ];
603    ///
604    /// let mut separator = TrackSeparator::new();
605    /// separator.extend(events).expect("No error should occur here.");
606    /// let separated : Vec<_> = separator.collect();
607    ///
608    /// assert_eq!(
609    ///     separated,
610    ///     vec![
611    ///         vec![
612    ///             note_on_with_channel_and_delta_time(1, 0),
613    ///             note_on_with_channel_and_delta_time(2, 1),
614    ///             note_on_with_channel_and_delta_time(4, 2),
615    ///             end_of_track(0)
616    ///         ],
617    ///         vec![
618    ///             note_on_with_channel_and_delta_time(2, 3),
619    ///             note_on_with_channel_and_delta_time(2, 4),
620    ///             note_on_with_channel_and_delta_time(5, 5),
621    ///             end_of_track(0)
622    ///         ],
623    ///     ]
624    /// );
625    /// ```
626    #[inline]
627    pub fn extend<I>(&mut self, iter: I) -> Result<(), SeparateTracksError>
628    where
629        I: IntoIterator<Item = (u64, usize, TrackEventKind<'a>)>,
630    {
631        for (ticks, track_index, event) in iter {
632            self.push(ticks, track_index, event)?
633        }
634        Ok(())
635    }
636
637    /// Create a collection containing all the tracks.
638    ///
639    /// The number of tracks is determined by the highest track index.
640    ///
641    /// An [`EndOfTrack`](exports::MetaMessage::EndOfTrack) event is added to the tracks that do not end with an `EndOfTrack` event.
642    pub fn collect<B>(self) -> B
643    where
644        B: FromIterator<Track<'a>>,
645    {
646        self.tracks
647            .into_iter()
648            .map(|t| {
649                let mut track = t.track;
650                let end_of_track_is_marked_with_event = if let Some(last_event) = track.last() {
651                    last_event.kind == TrackEventKind::Meta(MetaMessage::EndOfTrack)
652                } else {
653                    false
654                };
655                if !end_of_track_is_marked_with_event {
656                    track.push(TrackEvent {
657                        kind: TrackEventKind::Meta(MetaMessage::EndOfTrack),
658                        delta: u28::new(0),
659                    });
660                }
661                track
662            })
663            .collect()
664    }
665}