opus1 0.3.0

Pure-Rust implementation of the Opus audio codec (RFC 6716), following libopus 1.x
Documentation
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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
//! The weights and the models that a decoder holds: `DnnBlob` (a validated
//! weight blob), `DnnModels` (which model groups are loaded) and
//! `DecoderDnn` (what an `OpusDecoder` stores for its neural network
//! features).
//!
//! libopus loads its built-in models into every decoder (`lpcnet_plc_init`,
//! `silk_InitDecoder`), or, when built with `USE_WEIGHTS_FILE`, from a blob
//! given with `OPUS_SET_DNN_BLOB`. opus1 parses the built-in weights with
//! the same code as a blob given at run time (`plan.md` section 3), and
//! offers `set_dnn_blob` in every build with a model (feature `deep-plc`,
//! which the other neural network features turn on; `dnn` alone has no
//! model and no public API).
//!
//! Where the layers live:
//!
//! - The built-in models are built once per process (`builtin::models()`).
//!   Every decoder points at them, so they cost no memory per decoder.
//! - A model loaded from a blob is built for the decoder that loads it.
//!   With `heap` its layers are in a `Box` and the decoder keeps a handle
//!   to the blob, whose bytes the layers borrow. Without `heap` only blobs
//!   that live for the whole program exist, and the layers are stored in
//!   the decoder itself.

use crate::Error;
use super::arch::{self, DnnArch};
use super::weights::WeightList;
use crate::in_place::write_fields;
#[cfg(feature = "deep-plc")]
use super::{builtin, models};

#[cfg(feature = "heap")]
use alloc::boxed::Box;
#[cfg(all(feature = "heap", target_has_atomic = "ptr"))]
use alloc::sync::Arc;

use super::models::ERR_NO_MODEL;

/// A validated weight blob in the libopus format (`OPUS_SET_DNN_BLOB`).
/// Cloning is cheap: the bytes are not copied.
///
/// A blob may hold any set of models, in any order, like the file that
/// libopus's `dump_weights_blob` writes. Every array must start 4-byte
/// aligned in memory, so that the layers can use the bytes in place.
#[derive(Clone)]
pub struct DnnBlob {
    inner: Inner,
}

#[derive(Clone)]
enum Inner {
    /// The weights built into this build (`builtin::weights()`).
    #[cfg(feature = "deep-plc")]
    Builtin,
    /// A blob that lives for the whole program, already parsed.
    Static(WeightList<'static>),
    /// A copy in 64-byte aligned heap memory, shared by every clone.
    #[cfg(all(feature = "heap", target_has_atomic = "ptr"))]
    Shared(Arc<SharedBlob>),
}

/// The heap copy behind `DnnBlob::from_bytes`.
#[cfg(all(feature = "heap", target_has_atomic = "ptr"))]
struct SharedBlob {
    /// The parsed list. Its slices point into the allocation of `bytes`;
    /// they are typed `'static`, which is sound because that allocation is
    /// never changed or freed while this struct exists (see
    /// `DnnBlob::from_bytes`). The list is never handed out with that
    /// lifetime.
    list: WeightList<'static>,
    /// The blob, in blocks of 64 bytes so that it starts 64-byte aligned.
    /// It is owned through a raw pointer (from `Box::into_raw`) and freed
    /// in `Drop`. A `Box` field would not do: moving a `Box` asserts that
    /// it is the only pointer to its memory, which the slices of `list`
    /// contradict (Rust's aliasing rules, as Miri checks them).
    bytes: *mut [Block],
}

// SAFETY: `SharedBlob` owns its allocation like a `Box<[Block]>` and never
// writes to it after `from_bytes`, so sharing it or moving it to another
// thread is as safe as for `Box<[Block]>` and `WeightList<'static>`.
#[cfg(all(feature = "heap", target_has_atomic = "ptr"))]
unsafe impl Send for SharedBlob {}
// SAFETY: see `Send`; every access after construction is a read.
#[cfg(all(feature = "heap", target_has_atomic = "ptr"))]
unsafe impl Sync for SharedBlob {}

#[cfg(all(feature = "heap", target_has_atomic = "ptr"))]
impl Drop for SharedBlob {
    fn drop(&mut self) {
        // SAFETY: `bytes` comes from `Box::into_raw` in `from_bytes` and is
        // freed only here, once. `list` borrows it, but it is dropped with
        // this struct and never used again.
        drop(unsafe { Box::from_raw(self.bytes) });
    }
}

/// 64 bytes on a 64-byte boundary.
#[cfg(all(feature = "heap", target_has_atomic = "ptr"))]
#[derive(Clone, Copy)]
#[repr(C, align(64))]
struct Block([u8; 64]);

impl DnnBlob {
    /// A blob that lives for the whole program, for example from
    /// `include_bytes!` in a 64-byte aligned wrapper. Fails with
    /// [`BadArg`](crate::ErrorKind::BadArg) when an array does not start 4-byte
    /// aligned (message "DNN blob is not aligned") or a record is malformed
    /// (message "invalid DNN blob").
    pub fn from_static(bytes: &'static [u8]) -> Result<DnnBlob, Error> {
        Ok(DnnBlob { inner: Inner::Static(WeightList::parse(bytes)?) })
    }

    /// Copies `bytes` into 64-byte aligned memory and checks it as
    /// `from_static` does. The copy is shared by the clones of the blob and
    /// by every decoder that loads it.
    ///
    /// It needs the feature `heap`, and a target with atomic
    /// compare-and-swap, because the clones share the copy through an
    /// `Arc`. On a target without it, such as `thumbv6m-none-eabi`, use
    /// [`from_static`](Self::from_static).
    #[cfg(all(feature = "heap", target_has_atomic = "ptr"))]
    pub fn from_bytes(bytes: &[u8]) -> Result<DnnBlob, Error> {
        let blocks = alloc::vec![Block([0; 64]); bytes.len().div_ceil(64)].into_boxed_slice();
        // From here on the allocation is owned through this raw pointer
        // only. `SharedBlob::drop` frees it, and so does the failure path
        // below.
        let raw: *mut [Block] = Box::into_raw(blocks);
        // SAFETY: `raw` holds `64 · len >= bytes.len()` bytes in one live
        // allocation, and `Block` is plain bytes without padding.
        let copy: &mut [u8] = unsafe { core::slice::from_raw_parts_mut(raw.cast::<u8>(), bytes.len()) };
        copy.copy_from_slice(bytes);
        // SAFETY: the allocation stays where it is and is never written
        // again; it is freed only when the `SharedBlob` that owns it is
        // dropped (or right below, if the blob is refused, after the last
        // use of `view`). The `'static` list is only reachable through
        // `DnnBlob` methods that tie it to a borrow of the blob, or through
        // `DecoderDnn`, which keeps a clone of the blob (and so the
        // allocation) next to every layer it builds from it.
        let view: &'static [u8] = unsafe { core::slice::from_raw_parts(raw.cast::<u8>(), bytes.len()) };
        let list = match WeightList::parse(view) {
            Ok(list) => list,
            Err(e) => {
                // SAFETY: `raw` comes from `Box::into_raw` above and nothing
                // refers to it any more.
                drop(unsafe { Box::from_raw(raw) });
                return Err(e);
            }
        };
        Ok(DnnBlob { inner: Inner::Shared(Arc::new(SharedBlob { list, bytes: raw })) })
    }

    /// The libopus 1.6.1 weights of every model this build contains
    /// (`crate::dnn::builtin`). Loading it into a decoder brings back the
    /// built-in models after another blob replaced them.
    #[cfg(feature = "deep-plc")]
    pub fn builtin() -> DnnBlob {
        DnnBlob { inner: Inner::Builtin }
    }

    /// The parsed arrays of the blob.
    ///
    /// Hidden from the documentation: `WeightList` is a type of the
    /// internal `dnn` module, which applications cannot name (tests reach
    /// it through `opus1::internals::dnn`). Only with the test-only feature
    /// `__internals`.
    #[cfg(feature = "__internals")]
    #[doc(hidden)]
    pub fn weights(&self) -> &WeightList<'_> {
        self.list()
    }

    /// The list with the lifetime of its bytes. Private: for a heap blob
    /// that lifetime is only valid while a clone of this blob is kept.
    fn list(&self) -> &WeightList<'static> {
        match &self.inner {
            #[cfg(feature = "deep-plc")]
            Inner::Builtin => builtin::weights(),
            Inner::Static(list) => list,
            #[cfg(all(feature = "heap", target_has_atomic = "ptr"))]
            Inner::Shared(shared) => &shared.list,
        }
    }

    /// Reads one byte of every 4 KiB page, so that the first use of the
    /// weights (often in a lost packet, inside an audio callback) does not
    /// take page faults.
    pub fn prefault(&self) {
        for blob in self.list().blobs() {
            prefault(blob);
        }
    }
}

/// Reads one byte of every 4 KiB page of `bytes`.
pub(crate) fn prefault(bytes: &[u8]) {
    for i in (0..bytes.len()).step_by(4096) {
        // SAFETY: `i < bytes.len()`, so the pointer is in bounds and the
        // byte is initialized. The volatile read keeps the compiler from
        // removing it.
        unsafe { core::ptr::read_volatile(bytes.as_ptr().add(i)) };
    }
}

impl core::fmt::Debug for DnnBlob {
    /// The kind of blob and the size of its arrays, without the data.
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        let kind = match &self.inner {
            #[cfg(feature = "deep-plc")]
            Inner::Builtin => "built-in",
            Inner::Static(_) => "static",
            #[cfg(all(feature = "heap", target_has_atomic = "ptr"))]
            Inner::Shared(_) => "copied",
        };
        f.debug_struct("DnnBlob").field("kind", &kind).field("weights", self.list()).finish()
    }
}

/// A set of model groups, as returned by every `set_dnn_blob`
/// (`OpusDecoder`, `OpusEncoder`, `OpusDredDecoder` and
/// `multistream::MultistreamDecoder`): the groups that the call loaded.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct DnnModels(u8);

impl DnnModels {
    /// No model.
    pub const NONE: DnnModels = DnnModels(0);
    /// Deep PLC: PitchDNN, the PLC model and FARGAN, which load together.
    pub const DEEP_PLC: DnnModels = DnnModels(1 << 0);
    /// LACE.
    pub const LACE: DnnModels = DnnModels(1 << 1);
    /// NoLACE.
    pub const NOLACE: DnnModels = DnnModels(1 << 2);
    /// BBWENet, the network of the bandwidth extension.
    pub const BBWENET: DnnModels = DnnModels(1 << 3);
    /// The models of the DRED encoder, which load together: the RDOVAE
    /// encoder and PitchDNN (for `OpusEncoder` with the feature `dred`).
    pub const DRED_ENCODER: DnnModels = DnnModels(1 << 4);
    /// The RDOVAE decoder of DRED (for `OpusDredDecoder`).
    pub const DRED_DECODER: DnnModels = DnnModels(1 << 5);

    /// The bits of the set.
    pub const fn bits(self) -> u8 {
        self.0
    }

    /// Whether every group of `other` is in the set.
    pub const fn contains(self, other: DnnModels) -> bool {
        self.0 & other.0 == other.0
    }

    /// The groups of both sets.
    pub const fn union(self, other: DnnModels) -> DnnModels {
        DnnModels(self.0 | other.0)
    }

    /// Whether the set is empty.
    pub const fn is_empty(self) -> bool {
        self.0 == 0
    }
}

/// Where the layers of one model group come from.
#[cfg(feature = "deep-plc")]
enum Slot<T: 'static> {
    /// Not loaded.
    Empty,
    /// The built-in model, shared by every decoder.
    Builtin(&'static T),
    /// A model built from a blob, with a clone of the blob, which keeps the
    /// bytes its layers borrow alive.
    #[cfg(feature = "heap")]
    Loaded { model: Box<T>, _blob: DnnBlob },
    /// A model built from a blob that lives for the whole program.
    #[cfg(not(feature = "heap"))]
    Loaded(T),
}

#[cfg(feature = "deep-plc")]
impl<T: 'static> Slot<T> {
    fn builtin(model: Option<&'static T>) -> Self {
        model.map_or(Slot::Empty, Slot::Builtin)
    }

    #[cfg(feature = "heap")]
    fn loaded(model: T, blob: &DnnBlob) -> Self {
        Slot::Loaded { model: Box::new(model), _blob: blob.clone() }
    }

    #[cfg(not(feature = "heap"))]
    fn loaded(model: T, _blob: &DnnBlob) -> Self {
        Slot::Loaded(model)
    }

    /// The model, borrowed for as long as the slot is not changed. The
    /// layers come out with that shorter lifetime, so none of them can be
    /// kept after the blob they borrow from is released.
    fn get(&self) -> Option<&T> {
        match self {
            Slot::Empty => None,
            Slot::Builtin(m) => Some(m),
            #[cfg(feature = "heap")]
            Slot::Loaded { model, .. } => Some(model),
            #[cfg(not(feature = "heap"))]
            Slot::Loaded(m) => Some(m),
        }
    }
}

/// What a decoder stores for its neural network features: the kernel
/// variant, chosen once at creation as libopus does (`opus_select_arch`),
/// and the layers of every loaded model group.
///
/// `OpusDecoder::reset_state` keeps all of it: unlike libopus, a reset does
/// not reload PitchDNN (`nnet.md`, Review item 4).
pub struct DecoderDnn {
    arch: DnnArch,
    #[cfg(feature = "deep-plc")]
    deep_plc: Slot<models::DeepPlcModels<'static>>,
    #[cfg(feature = "osce")]
    lace: Slot<models::lace::LaceLayers<'static>>,
    #[cfg(feature = "osce")]
    nolace: Slot<models::nolace::NolaceLayers<'static>>,
    #[cfg(feature = "osce-bwe")]
    bbwenet: Slot<models::bbwenet::BbwenetLayers<'static>>,
}

impl DecoderDnn {
    /// A holder with the detected kernel variant and the built-in models of
    /// this build (none in a build with only `dnn`), as a new libopus
    /// decoder has.
    pub fn new() -> DecoderDnn {
        let mut d = core::mem::MaybeUninit::<DecoderDnn>::uninit();
        // SAFETY: `write_new` initializes every field of `d`.
        unsafe {
            DecoderDnn::write_new(d.as_mut_ptr());
            d.assume_init()
        }
    }

    /// Writes the holder of [`new`](Self::new) to `this`, which may be
    /// uninitialized memory, without building it on the stack first (see
    /// `crate::in_place`). Without `heap` a holder has room for models
    /// loaded from a blob, 12 KB with every feature.
    ///
    /// # Safety
    /// `this` must be valid for writes of `Self`.
    pub(crate) unsafe fn write_new(this: *mut DecoderDnn) {
        #[cfg(feature = "deep-plc")]
        let set = builtin::models();
        // SAFETY: the caller guarantees that `this` is valid for writes.
        unsafe {
            write_fields!(this, {
                arch: arch::detect(),
                #[cfg(feature = "deep-plc")]
                deep_plc: Slot::builtin(set.deep_plc.as_ref()),
                #[cfg(feature = "osce")]
                lace: Slot::builtin(set.lace.as_ref()),
                #[cfg(feature = "osce")]
                nolace: Slot::builtin(set.nolace.as_ref()),
                #[cfg(feature = "osce-bwe")]
                bbwenet: Slot::builtin(set.bbwenet.as_ref()),
            });
        }
    }

    /// The kernel variant the decoder uses.
    pub fn arch(&self) -> DnnArch {
        self.arch
    }

    /// The model groups that are loaded.
    pub fn models(&self) -> DnnModels {
        #[allow(unused_mut)]
        let mut m = DnnModels::NONE;
        #[cfg(feature = "deep-plc")]
        if self.deep_plc.get().is_some() {
            m = m.union(DnnModels::DEEP_PLC);
        }
        #[cfg(feature = "osce")]
        if self.lace.get().is_some() {
            m = m.union(DnnModels::LACE);
        }
        #[cfg(feature = "osce")]
        if self.nolace.get().is_some() {
            m = m.union(DnnModels::NOLACE);
        }
        #[cfg(feature = "osce-bwe")]
        if self.bbwenet.get().is_some() {
            m = m.union(DnnModels::BBWENET);
        }
        m
    }

    /// Loads every model group of this build that `blob` holds completely.
    /// Each group is replaced only when all of its arrays load; the others
    /// keep what they had. Returns the groups loaded now, or `"DNN blob
    /// holds no model of this build"` (and nothing changes).
    ///
    /// libopus differs (`nnet.md` sections 4.3 and 4.4): it returns 1 on
    /// any failure, can leave a model half-replaced, and a blob without
    /// BBWENet also leaves LACE and NoLACE unloaded in a BWE build.
    pub fn load(&mut self, blob: &DnnBlob) -> Result<DnnModels, Error> {
        #[allow(unused_mut)]
        let mut loaded = DnnModels::NONE;
        #[cfg(feature = "deep-plc")]
        {
            if let Inner::Builtin = blob.inner {
                let set = builtin::models();
                if let Some(m) = set.deep_plc.as_ref() {
                    self.deep_plc = Slot::Builtin(m);
                    loaded = loaded.union(DnnModels::DEEP_PLC);
                }
                #[cfg(feature = "osce")]
                if let Some(m) = set.lace.as_ref() {
                    self.lace = Slot::Builtin(m);
                    loaded = loaded.union(DnnModels::LACE);
                }
                #[cfg(feature = "osce")]
                if let Some(m) = set.nolace.as_ref() {
                    self.nolace = Slot::Builtin(m);
                    loaded = loaded.union(DnnModels::NOLACE);
                }
                #[cfg(feature = "osce-bwe")]
                if let Some(m) = set.bbwenet.as_ref() {
                    self.bbwenet = Slot::Builtin(m);
                    loaded = loaded.union(DnnModels::BBWENET);
                }
            } else {
                let list = blob.list();
                if let Ok(m) = models::DeepPlcModels::init(list) {
                    self.deep_plc = Slot::loaded(m, blob);
                    loaded = loaded.union(DnnModels::DEEP_PLC);
                }
                #[cfg(feature = "osce")]
                if let Ok(m) = models::lace::LaceLayers::init(list) {
                    self.lace = Slot::loaded(m, blob);
                    loaded = loaded.union(DnnModels::LACE);
                }
                #[cfg(feature = "osce")]
                if let Ok(m) = models::nolace::NolaceLayers::init(list) {
                    self.nolace = Slot::loaded(m, blob);
                    loaded = loaded.union(DnnModels::NOLACE);
                }
                #[cfg(feature = "osce-bwe")]
                if let Ok(m) = models::bbwenet::BbwenetLayers::init(list) {
                    self.bbwenet = Slot::loaded(m, blob);
                    loaded = loaded.union(DnnModels::BBWENET);
                }
            }
        }
        if loaded.is_empty() {
            return Err(ERR_NO_MODEL);
        }
        blob.prefault();
        Ok(loaded)
    }

    /// The deep PLC models, when loaded.
    #[cfg(feature = "deep-plc")]
    pub fn deep_plc(&self) -> Option<&models::DeepPlcModels<'_>> {
        self.deep_plc.get()
    }

    /// LACE, when loaded.
    #[cfg(feature = "osce")]
    pub fn lace(&self) -> Option<&models::lace::LaceLayers<'_>> {
        self.lace.get()
    }

    /// NoLACE, when loaded.
    #[cfg(feature = "osce")]
    pub fn nolace(&self) -> Option<&models::nolace::NolaceLayers<'_>> {
        self.nolace.get()
    }

    /// BBWENet, when loaded.
    #[cfg(feature = "osce-bwe")]
    pub fn bbwenet(&self) -> Option<&models::bbwenet::BbwenetLayers<'_>> {
        self.bbwenet.get()
    }
}

impl Default for DecoderDnn {
    fn default() -> DecoderDnn {
        DecoderDnn::new()
    }
}

/// What an `OpusDredDecoder` stores: the kernel variant and the layers of
/// the RDOVAE decoder (libopus: `model`, `loaded` and `arch` of
/// `OpusDREDDecoder`).
#[cfg(feature = "dred")]
pub(crate) struct DredDecoderDnn {
    arch: DnnArch,
    model: Slot<models::dred_rdovae_dec::RdovaeDec<'static>>,
}

#[cfg(feature = "dred")]
impl DredDecoderDnn {
    /// The detected kernel variant and the built-in model.
    pub(crate) fn new() -> Self {
        DredDecoderDnn { arch: arch::detect(), model: Slot::builtin(builtin::rdovae_dec()) }
    }

    /// Loads the RDOVAE decoder from `blob` and returns
    /// [`DnnModels::DRED_DECODER`]. When the blob lacks it, or one of its
    /// arrays does not load, returns [`ERR_NO_MODEL`] and keeps the model it
    /// had.
    pub(crate) fn load(&mut self, blob: &DnnBlob) -> Result<DnnModels, Error> {
        if let Inner::Builtin = blob.inner {
            self.model = Slot::Builtin(builtin::rdovae_dec().ok_or(ERR_NO_MODEL)?);
        } else {
            let model = models::dred_rdovae_dec::RdovaeDec::init(blob.list()).map_err(|_| ERR_NO_MODEL)?;
            self.model = Slot::loaded(model, blob);
        }
        blob.prefault();
        Ok(DnnModels::DRED_DECODER)
    }

    /// The model, when loaded.
    pub(crate) fn model(&self) -> Option<&models::dred_rdovae_dec::RdovaeDec<'_>> {
        self.model.get()
    }

    /// The kernel variant.
    pub(crate) fn arch(&self) -> DnnArch {
        self.arch
    }
}

/// What the DRED part of an Opus encoder stores: the kernel variant and
/// the models of the DRED encoder (libopus: `model`, `loaded` and the
/// PitchDNN model of `lpcnet_enc_state` in `DREDEnc`, and the encoder's
/// `arch`).
#[cfg(feature = "dred")]
pub(crate) struct DredEncoderDnn {
    arch: DnnArch,
    model: Slot<models::DredEncModels<'static>>,
}

#[cfg(feature = "dred")]
impl DredEncoderDnn {
    /// The detected kernel variant and the built-in models.
    pub(crate) fn new() -> Self {
        DredEncoderDnn { arch: arch::detect(), model: Slot::builtin(builtin::dred_enc()) }
    }

    /// Loads the RDOVAE encoder and PitchDNN from `blob` and returns
    /// [`DnnModels::DRED_ENCODER`]. When the blob lacks one of them, or one
    /// of their arrays does not load, returns [`ERR_NO_MODEL`] and keeps the
    /// models it had.
    pub(crate) fn load(&mut self, blob: &DnnBlob) -> Result<DnnModels, Error> {
        if let Inner::Builtin = blob.inner {
            self.model = Slot::Builtin(builtin::dred_enc().ok_or(ERR_NO_MODEL)?);
        } else {
            let model = models::DredEncModels::init(blob.list()).map_err(|_| ERR_NO_MODEL)?;
            self.model = Slot::loaded(model, blob);
        }
        blob.prefault();
        Ok(DnnModels::DRED_ENCODER)
    }

    /// The models, when loaded.
    pub(crate) fn model(&self) -> Option<&models::DredEncModels<'_>> {
        self.model.get()
    }

    /// The kernel variant.
    pub(crate) fn arch(&self) -> DnnArch {
        self.arch
    }
}

#[cfg(all(test, feature = "deep-plc"))]
mod tests {
    use super::*;

    #[test]
    fn a_new_holder_has_every_builtin_model() {
        let d = DecoderDnn::new();
        assert_eq!(d.models(), builtin::models().models());
        assert!(d.models().contains(DnnModels::DEEP_PLC));
        assert!(d.deep_plc().is_some());
    }

    #[test]
    fn a_failed_load_changes_nothing() {
        let mut d = DecoderDnn::new();
        let before = d.models();
        let empty = DnnBlob::from_static(&[]).unwrap();
        assert_eq!(d.load(&empty), Err(ERR_NO_MODEL));
        assert_eq!(d.models(), before);
        // The deep PLC models still point at the built-in ones.
        let shared = builtin::models().deep_plc.as_ref().unwrap();
        assert!(core::ptr::eq(d.deep_plc().unwrap(), shared));
    }

    #[cfg(all(feature = "heap", target_has_atomic = "ptr"))]
    #[test]
    fn a_copied_blob_outlives_its_source_and_is_kept_by_the_decoder() {
        let mut d = DecoderDnn::new();
        let mut bytes = std::vec::Vec::new();
        for &(_, blob) in builtin::blobs() {
            bytes.extend_from_slice(blob);
        }
        // Start the copy at an odd address of the source, to show that the
        // source alignment does not matter.
        let mut shifted = std::vec![0u8; 1];
        shifted.extend_from_slice(&bytes);
        let blob = DnnBlob::from_bytes(&shifted[1..]).unwrap();
        drop(shifted);
        let got = d.load(&blob).unwrap();
        assert_eq!(got, builtin::models().models());
        drop(blob);
        // The layers still read the copy, which the decoder keeps alive.
        let plc = &d.deep_plc().unwrap().plc;
        let builtin_plc = &builtin::models().deep_plc.as_ref().unwrap().plc;
        assert_eq!(plc.plc_dense_in.bias, builtin_plc.plc_dense_in.bias);
        assert!(!core::ptr::eq(plc, builtin_plc));
    }

    #[test]
    fn the_builtin_blob_brings_back_the_shared_models() {
        let mut d = DecoderDnn::new();
        let blob = DnnBlob::from_static(builtin::plc()).unwrap();
        // The PLC blob alone is not a complete deep PLC group.
        assert_eq!(d.load(&blob), Err(ERR_NO_MODEL));
        assert_eq!(d.load(&DnnBlob::builtin()), Ok(builtin::models().models()));
    }
}