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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
use core::fmt;
use std::error;
/// What `getopt` returns for an option it does not know, or one whose
/// value is missing.
const UNRECOGNISED_OPTION: i32 = b'?' as i32;
/// Errors returned by the Bela audio system lifecycle.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Error {
/// `Bela_initAudio` failed with the contained return code.
///
/// The initialisation it failed partway through is not undone, so
/// this is fatal to the process rather than to the one attempt:
/// every later [`Bela::new`](crate::Bela::new) returns
/// [`AudioSystemPoisoned`](Self::AudioSystemPoisoned).
Init(i32),
/// `Bela_startAudio` failed with the contained return code.
Start(i32),
/// The run ended with the contained number of callbacks refused
/// for breaking the protocol the render states rely on.
///
/// libbela made a callback somewhere the crate could not hand out
/// the references [`BelaApplication`](crate::BelaApplication)
/// promises — several `render` calls with the same thread number,
/// or a `render_post` arriving while one was still in flight, which
/// a stop requested mid-block can produce. Each such callback was
/// skipped and a stop requested, so the audio that was rendered is
/// sound and the run ended early rather than going wrong.
///
/// Reported by [`Bela::until_stopped`](crate::Bela::until_stopped)
/// and the `run` methods built on it, so that a run which ended
/// this way is not mistaken for one that was asked to stop. See
/// [`Bela::callback_faults`](crate::Bela::callback_faults).
CallbackFaults(u32),
/// An auxiliary task name contained a NUL byte.
TaskName,
/// `Bela_createAuxiliaryTask` failed, or the crate was built for a
/// target with no audio system to create the task in.
TaskCreate,
/// An auxiliary task was created while an audio system was being
/// torn down, which would have deleted it again immediately.
///
/// This is what a `cleanup` callback gets: it runs inside that
/// teardown.
TaskCreateWhileStopping,
/// `Bela_cpuMonitoringInit` failed.
CpuMonitoring,
/// The requested CPU monitoring acquisition cycle does not fit in a
/// C `int`, which is how libbela takes it.
CpuMonitoringCycle(u32),
/// CPU monitoring was requested with a period size big enough that
/// libbela runs `render` on a different thread from the one it
/// measures.
///
/// See
/// [`MAX_MONITORED_PERIOD_SIZE`](crate::MAX_MONITORED_PERIOD_SIZE).
CpuMonitoringPeriodSize(i32),
/// Another [`Bela`](crate::Bela) audio system already exists in
/// this process.
///
/// The C API is a process-wide singleton, so a second one would
/// share — and reset — the state the first is using.
AudioSystemExists,
/// An earlier `Bela_initAudio` in this process failed partway
/// through, and no audio system can be built after that.
///
/// libbela is left believing the audio system is up and offers no
/// way to put it back: `Bela_cleanupAudio` segfaults on that path.
/// So this is refused rather than attempted — going ahead means a
/// segfault inside libbela, which is what the error replaces.
///
/// Terminal for the process, and only for the process: the board is
/// untouched, so a new one gets a working audio system straight
/// away. See [`Bela::new`](crate::Bela::new).
AudioSystemPoisoned,
/// An argument was not one of Bela's standard command-line options.
///
/// Carries what `Bela_getopt_long` returned: `'?'` for an
/// unrecognised option or one missing its value, which `getopt` has
/// already reported on standard error naming the argument, or an
/// internal option code when libbela rejected a standard option it
/// did recognise — a `--json-file` it could not read, say.
CommandLine(i32),
/// A command-line argument contained a NUL byte, which a C string
/// cannot carry.
CommandLineNul,
/// The settings resolved to an audio sample rate of zero, which
/// libbela reports as a codec that is not enabled.
///
/// Zero is the only rate refused here, which is why this carries
/// nothing: `--sample-rate` reads its value with `atof`, so
/// anything that is not a number arrives as 0, and a negative rate
/// is clamped to the same 0 by the parser. The message libbela
/// prints for it — `Error: audio sampling rate is 0. Is the codec
/// enabled?`, followed by one about a cape — describes hardware for
/// a number the command line supplied.
///
/// One of the checks [`Bela::new_with_args`](crate::Bela::new_with_args)
/// makes before `Bela_initAudio`, where this would otherwise be an
/// [`Init`](Self::Init) that costs the process every later audio
/// system.
SampleRate,
/// The settings named a PRU other than 0 or 1, which are the two
/// libbela can run the audio code on.
///
/// Checked before `Bela_initAudio`, which refuses the same values
/// and leaves the process unable to build another audio system.
PruNumber(i32),
/// The settings asked for a number of multiplexer channels libbela
/// does not take: it accepts 0, which is off, and 2, 4 or 8.
///
/// Checked before `Bela_initAudio`; see
/// [`Bela::new_with_args`](crate::Bela::new_with_args) for what the
/// crate does and does not check about `--mux-channels`.
MultiplexerChannels(i32),
/// The multiplexer was asked for while the audio code was pointed
/// at a PRU other than 1, carried here.
///
/// PRU 0 is a valid setting on its own; only the multiplexer needs
/// PRU 1, which is why the two are separate errors.
MultiplexerPru(i32),
/// The multiplexer was asked for with the analog inputs disabled.
///
/// This is the combination that gets past every check libbela makes
/// on the ARM side and dies in the PRU firmware instead — `Invalid
/// PRU configuration settings`, `PRU timeout`, `McASP error,
/// abort`, with the process ending from inside libbela and nothing
/// returned to the caller. Refusing it beforehand is the only place
/// it can be reported at all.
MultiplexerWithoutAnalog,
/// The multiplexer was asked for with a number of analog input
/// channels other than the 8 it needs, carried here.
///
/// `--analog-channels` snaps what it is given to 8, 4 or 2, so this
/// reports the number the settings resolved to rather than the one
/// that was written on the command line. It is not only about
/// asking for too few:
/// [`Settings::num_analog_in_channels`](crate::Settings::num_analog_in_channels)
/// is passed on as it stands, and 16 is as much a refusal as 4.
///
/// Checked before `Bela_initAudio`, which refuses the same thing in
/// `PRU::initialise` and leaves the process unable to build another
/// audio system.
MultiplexerAnalogChannels(i32),
/// `Bela_setLineOutLevel` failed with the contained return code,
/// e.g. for a channel the codec does not have.
LineOutLevel(i32),
/// `Bela_setHpLevel` failed with the contained return code, e.g.
/// for a channel the codec does not have.
HeadphoneLevel(i32),
/// `Bela_setAudioInputGain` failed with the contained return code.
AudioInputGain(i32),
/// `Bela_muteSpeakers` failed with the contained return code.
MuteSpeakers(i32),
/// A MIDI port name contained a NUL byte.
MidiPortName,
/// A `Midi` object could not be created, or the crate was built for
/// a target with no `libbelaextra` to create one in.
MidiCreate,
/// A MIDI port could not be opened, with what the shim reported.
///
/// [`bela_sys::BELA_MIDI_NO_SUCH_PORT`] for a name no port has —
/// the names are the ones [`midi_ports`](crate::midi_ports) lists,
/// which carry the subdevice — and a negative `errno` when ALSA
/// refused the device itself, `-16` for a port something else
/// already holds. The two are told apart rather than sharing `-1`,
/// which would be the first of them and `EPERM`.
///
/// [`bela_sys::BELA_MIDI_ALREADY_OPEN`] is the third value the
/// shim can report and cannot arrive here: this crate opens each
/// port on an object of its own, and drops it when the open fails.
MidiOpen(i32),
/// A MIDI value did not fit the type it was converted into.
///
/// Carries what was given, the largest that type takes — 127 for a
/// data byte, 15 for a channel, 16383 for a pitch bend — and what
/// the type is. The wire has no room for more, and masking the
/// extra bits off would turn a number that was wrong into a
/// different number that is not.
///
/// The name is there because a note and a velocity are two numbers
/// in the same range, which is the whole reason they are separate
/// types: an error that only said "127" would put them back
/// together.
MidiValue {
/// What was given.
value: u16,
/// The largest the type takes.
max: u16,
/// What the type holds, as it reads in a sentence — `"note
/// number"`, `"velocity"`, `"channel"`.
kind: &'static str,
},
/// A render thread queued more MIDI messages between drains than
/// [`MidiOutput::capacity`](crate::MidiOutput::capacity) allows.
///
/// The message was not queued, and nothing else was affected. This
/// says the program outran the budget it declared and nothing about
/// the device: neither Bela's output pipe nor ALSA reports anything
/// this crate could pass on.
MidiQueueFull,
/// MIDI was sent from a thread that cannot send it.
///
/// [`MidiOutput::send`](crate::MidiOutput::send) and
/// [`flush`](crate::MidiOutput::flush) write to Bela's pipe through
/// an EVL out-of-band call, which only a thread EVL knows about can
/// make. This crate's answer is the thread that opened the port: a
/// write from any other reports success, delivers nothing, and
/// leaves the output stream misaligned for the rest of the run, so
/// it is refused instead.
MidiThread,
/// A level or gain was not a number of decibels libbela can convert
/// into register values: not finite, or larger in magnitude than
/// [`MAX_DECIBELS`](crate::MAX_DECIBELS).
///
/// The conversion on the C side is a cast to `int`, which is
/// undefined behaviour for those values, so they are refused before
/// the call rather than passed on.
Decibels,
}
impl fmt::Display for Error {
#[allow(
clippy::too_many_lines,
reason = "one arm per variant, which is what makes a new variant fail to compile until \
it has a message; splitting the match would need a catch-all and lose that"
)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Init(code) => write!(f, "Bela_initAudio failed with code {code}"),
Self::Start(code) => write!(f, "Bela_startAudio failed with code {code}"),
Self::CallbackFaults(faults) => write!(
f,
"{faults} callback(s) were refused for breaking the protocol the render states \
rely on, and the audio system was asked to stop"
),
Self::TaskName => write!(f, "the auxiliary task name contains a NUL byte"),
Self::TaskCreate => write!(f, "Bela_createAuxiliaryTask failed"),
Self::TaskCreateWhileStopping => write!(
f,
"auxiliary tasks cannot be created while the audio system is stopping"
),
Self::CpuMonitoring => write!(f, "Bela_cpuMonitoringInit failed"),
Self::CpuMonitoringCycle(count) => write!(
f,
"the CPU monitoring cycle is {count} measurements, \
which does not fit in the C int libbela takes"
),
Self::CpuMonitoringPeriodSize(frames) => write!(
f,
"CPU monitoring needs a period size of at most {max} frames, not {frames}: \
above that libbela renders on a separate thread from the one it measures",
max = crate::MAX_MONITORED_PERIOD_SIZE
),
Self::AudioSystemExists => write!(
f,
"a Bela audio system already exists in this process; the C API is a singleton"
),
Self::AudioSystemPoisoned => write!(
f,
"an earlier Bela_initAudio failed in this process, leaving libbela with an audio \
system it will not give back; start a new process"
),
Self::CommandLine(code) if *code == UNRECOGNISED_OPTION => write!(
f,
"an argument is not one of Bela's standard options, or is missing its value"
),
Self::CommandLine(code) => write!(
f,
"the command line was rejected by Bela_getopt_long, which returned {code}"
),
Self::CommandLineNul => {
write!(f, "a command-line argument contains a NUL byte")
}
Self::SampleRate => write!(
f,
"the audio sample rate is 0, which libbela reports as a codec that is not enabled"
),
Self::PruNumber(number) => {
write!(f, "the audio code runs on PRU 0 or PRU 1, not PRU {number}")
}
Self::MultiplexerChannels(channels) => write!(
f,
"{channels} is not a number of multiplexer channels; \
the options are 0 for off, 2, 4 and 8"
),
Self::MultiplexerPru(number) => write!(
f,
"the multiplexer runs on PRU 1, and the audio code was pointed at PRU {number}"
),
Self::MultiplexerWithoutAnalog => write!(
f,
"the multiplexer needs the analog inputs, which are disabled; \
libbela checks neither and the PRU firmware ends the process"
),
Self::MultiplexerAnalogChannels(channels) => write!(
f,
"the multiplexer needs 8 analog input channels, not {channels}"
),
Self::LineOutLevel(code) => {
write!(f, "Bela_setLineOutLevel failed with code {code}")
}
Self::HeadphoneLevel(code) => write!(f, "Bela_setHpLevel failed with code {code}"),
Self::AudioInputGain(code) => {
write!(f, "Bela_setAudioInputGain failed with code {code}")
}
Self::MuteSpeakers(code) => write!(f, "Bela_muteSpeakers failed with code {code}"),
Self::MidiPortName => write!(f, "the MIDI port name contains a NUL byte"),
Self::MidiCreate => write!(f, "a Bela Midi object could not be created"),
Self::MidiOpen(code) if *code == bela_sys::BELA_MIDI_NO_SUCH_PORT => write!(
f,
"no MIDI port has that name; it has to be one of those listed by midi_ports, \
which carry the subdevice, as in hw:0,0,0"
),
Self::MidiOpen(code) if *code == bela_sys::BELA_MIDI_ALREADY_OPEN => {
write!(f, "that direction of this MIDI port is already open")
}
Self::MidiOpen(code) => write!(
f,
"the MIDI port could not be opened ({code}), which is -errno as ALSA reported it"
),
Self::MidiValue { value, max, kind } => {
write!(f, "{value} is more than the {max} a {kind} carries")
}
Self::MidiQueueFull => write!(
f,
"this render thread's MIDI output queue is full; it holds what the program asked \
MidiOutput::open for, per drain"
),
Self::MidiThread => write!(
f,
"MIDI can only be sent from the thread that opened the port; a write from any \
other thread is lost and misaligns the output stream"
),
Self::Decibels => write!(
f,
"a level must be a finite number of decibels of at most {max} in magnitude, \
which is what libbela can convert into register values",
max = crate::MAX_DECIBELS
),
}
}
}
impl error::Error for Error {}