1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//! Subscription API for the player.
//!
//! A [`PlayerObserver`] is notified every time the player crosses a song-side
//! event boundary: a new row is loaded, a sustained tick fires, the pattern
//! cursor moves to a new entry in the pattern order, or playback ends.
//!
//! The voices engine (the default audio renderer) is registered as an
//! observer internally; any additional observer plugs in next to it via
//! [`crate::xmrsplayer::XmrsPlayer::add_observer`].
//!
//! Observers are called **synchronously** on the thread that drives sample
//! generation. Long-running work or I/O must be deferred (push to a channel
//! drained elsewhere) — otherwise it will stall audio. See `OBSERVERS.md`
//! for the detailed threading contract and recipes.
//!
//! [`PlayerObserver`]: crate::observer::PlayerObserver
use *;
/// Context passed to [`PlayerObserver::on_row`] when a new row is loaded.
///
/// `cells` is borrowed directly from the module — no allocation, no copy —
/// so observers can inspect per-channel notes/instruments/effects at zero
/// cost.
/// Context passed to [`PlayerObserver::on_tick`] for sustained (non-row-start)
/// ticks. Row-start ticks fire `on_row` instead; they never fire `on_tick`.
/// Context passed to [`PlayerObserver::on_pattern_change`].
///
/// Fired when the pattern-order cursor moves to a new entry — either
/// naturally at end-of-pattern or via a jump effect (`Bxx`, `Dxx`, or a call
/// to [`crate::xmrsplayer::XmrsPlayer::goto`]).
/// A subscriber to player events.
///
/// Implement this trait for anything that wants to react to song progress —
/// a UI highlighting the current note, a MIDI bridge, a recording probe…
///
/// ## `Send` requirement
///
/// Observers are stored as `Box<dyn PlayerObserver + Send>` because the
/// typical deployment pattern — `Arc<Mutex<XmrsPlayer>>` handed to a cpal /
/// rodio audio callback — requires the player (and everything it owns) to
/// be sendable between threads. If your observer holds a `!Send` type
/// (raw pointer, `Rc<_>`, etc.), wrap it in `Arc`/`Mutex` or restructure.
/// `Sync` is not required: callbacks are delivered through `&mut self`
/// from a single thread at a time (the one holding the mutex).
///
/// ## Required vs. optional methods
///
/// [`on_row`](Self::on_row) is the only mandatory callback; all others have
/// defaulted no-op bodies, so a typical observer only needs to implement
/// `on_row`.
///
/// ## Opting into ticks
///
/// `on_tick` fires roughly 50 times per second — pointless for most UI use
/// cases. Observers that only care about row changes must leave
/// [`subscribes_to_ticks`](Self::subscribes_to_ticks) at its default `false`
/// value; the player gates `on_tick` delivery on that flag.
///
/// Observers that legitimately need tick-level granularity (per-tick visual
/// effects, vibrato visualisers, etc.) override it to return `true`.
///
/// ## Threading
///
/// Every method runs on the thread that pulls samples from the player. Do
/// not block, allocate on hot paths, or do I/O — push events to a queue
/// drained elsewhere. See `OBSERVERS.md` for concrete patterns.