codecraft 0.1.1

A minimalist 3D game engine built on parts of Bevy (ECS, color) with wgpu and winit: OpenPBR materials, clustered lighting, an immediate-mode UI, audio and gamepad haptics
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
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
//! Sound out of the pad in the player's hands.
//!
//! A DualSense on USB is a sound card as well as a controller. Windows lists
//! it as an output called "Speakers", four channels at 48 kHz: the first two
//! are the pad's own speaker and its headset jack, the other two are the
//! haptic actuators under the grips, which take audio and turn it into
//! whatever is felt. So a gun fired from this pad can crack out of the pad
//! and, in the same instant, thump in the hands holding it — one waveform
//! down channels one and two, and the same waveform low-passed down three
//! and four.
//!
//! Two things have to be worked out for that. Which output is which pad,
//! since every one of them is called "Speakers" and so is the PC's own
//! output — that is [`PadKey`], and on Windows the USB container id behind
//! it. And how a mono clip is spread over four channels — [`Quad`] — because
//! left to itself rodio's mixer puts a mono source on the first two channels
//! and silence on the rest, which is a pad that speaks and never shakes.
//!
//! [`Audio`](super::Audio) opens every pad it finds when it opens the main
//! output, and the game plays a player's own gun on their own pad by key.
use std::ffi::CStr;
use std::time::Duration;

use rodio::Source;
use rodio::buffer::SamplesBuffer;
use rodio::cpal::traits::{DeviceTrait, HostTrait};

use super::synth::OnePole;

/// Where the haptic lane's low-pass sits.
///
/// The actuators are felt rather than heard, and what is felt is the low
/// end: the thump under a gunshot, the roll of an explosion. Above a hundred
/// or so hertz they buzz rather than kick, and the crack that makes the gun
/// sound like one is all up there. 120 Hz keeps the weight and drops the
/// crack.
const HAPTIC_CUTOFF_HZ: f32 = 120.0;

/// How a pad's sound card is told apart from every other output, and matched
/// to the HID device in the same plastic.
///
/// Every pad reports its output as "Speakers", and the PC's own speakers say
/// the same, so a name is no use. What one pad's audio function and its HID
/// function have and nothing else does is a *container id*: Windows stamps
/// every function of one USB composite device with the same one, which is
/// how the Devices and Printers page shows a pad as one thing rather than
/// four. The renderer reads it off the HID path it already has, and off each
/// audio endpoint, and the two that agree are the same pad.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum PadKey {
    /// The USB container id, as the 128 bits of its GUID.
    Container(u128),
    /// The n-th pad-shaped output in the order the host lists them, for when
    /// there is no container id to go on. Not stable across replugging, and
    /// the only key a non-Windows build ever makes.
    Ordinal(usize),
}

/// The container as the 32 hex digits of its GUID, which is how it reads
/// everywhere else it shows up; the derived form would be a 39-digit number.
impl std::fmt::Debug for PadKey {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Container(id) => write!(f, "Container({id:032x})"),
            Self::Ordinal(n) => write!(f, "Ordinal({n})"),
        }
    }
}

impl PadKey {
    /// The key a HID device's sound card will carry, from the HID path
    /// hidapi reports. `None` where the container id cannot be read — which
    /// is always, off Windows.
    pub fn of_hid_path(path: &CStr) -> Option<Self> {
        container_of_hid_path(path).map(Self::Container)
    }
}

/// The container id of the USB device behind a HID interface path, or `None`
/// if the path cannot be walked to one. See [`platform`].
pub fn container_of_hid_path(path: &CStr) -> Option<u128> {
    platform::container_of_hid_path(path)
}

/// One active output endpoint as the host reports it, with the two things
/// cpal does not pass through: the endpoint's full friendly name and its
/// container id.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Endpoint {
    /// Its place among the active render endpoints — see [`endpoints`] for
    /// why that is cpal's numbering too.
    pub index: usize,
    /// The endpoint id string, which is what cpal's `DeviceTrait::id()`
    /// carries on Windows. Matching on this is what actually pairs an
    /// [`Endpoint`] with a cpal device; the index is for reading.
    pub id: String,
    /// "Speakers (2- Wireless Controller)": the name Windows shows in its
    /// sound settings. cpal prefers the shorter device description, which
    /// is just "Speakers".
    pub friendly_name: String,
    pub container: Option<u128>,
}

/// Every active output endpoint, in cpal's order.
///
/// Empty off Windows, and on Windows when the enumerator cannot be made.
pub fn endpoints() -> Vec<Endpoint> {
    platform::endpoints()
}

/// Whether an endpoint's friendly name is a PlayStation pad's.
///
/// Sony's USB descriptor calls the whole device "Wireless Controller", and
/// Windows names the endpoint after it: "Speakers (Wireless Controller)",
/// or "Speakers (2- Wireless Controller)" once there are two.
pub fn is_pad_name(name: &str) -> bool {
    name.contains("Wireless Controller")
}

/// A GUID's 128 bits in the order its text form reads: the first field at
/// the top, the eight trailing bytes at the bottom. The same number
/// `windows::core::GUID::to_u128` gives, written out here so that a build
/// without the `windows` crate can still test it.
pub fn guid_to_u128(data1: u32, data2: u16, data3: u16, data4: [u8; 8]) -> u128 {
    (data1 as u128) << 96
        | (data2 as u128) << 80
        | (data3 as u128) << 64
        | u64::from_be_bytes(data4) as u128
}

/// One opened pad sound card.
pub struct PadSpeaker {
    /// Dropping this stops the sound, which is why it is kept even though the
    /// mixer is what gets used.
    _device: rodio::MixerDeviceSink,
    mixer: rodio::mixer::Mixer,
    channels: rodio::ChannelCount,
    sample_rate: rodio::SampleRate,
    pub key: PadKey,
    pub name: String,
}

impl std::fmt::Debug for PadSpeaker {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("PadSpeaker")
            .field("key", &self.key)
            .field("name", &self.name)
            .field("channels", &self.channels)
            .field("sample_rate", &self.sample_rate)
            .finish()
    }
}

impl PadSpeaker {
    /// What the pad's output runs at. A loop made at this rate goes to the
    /// device untouched.
    pub fn sample_rate(&self) -> rodio::SampleRate {
        self.sample_rate
    }

    pub fn channels(&self) -> rodio::ChannelCount {
        self.channels
    }

    /// Plays a mono clip out of the pad: `speaker` is the gain into the
    /// pad's speaker, `haptic` the gain into the actuators.
    ///
    /// The clip need not be at the pad's rate. The mixer wraps everything
    /// it is given in a `UniformSourceIterator`, which resamples to the
    /// device's rate, so a take rendered at the main output's 44.1 kHz plays
    /// right on a 48 kHz pad — through a linear interpolation rather than
    /// untouched, which for a gunshot is nothing anyone will hear.
    pub fn play(&self, clip: SamplesBuffer, speaker: f32, haptic: f32) {
        self.add(clip, speaker, haptic);
    }

    /// Adds any mono source — an engine loop — spread over the pad's lanes.
    ///
    /// A pad that turned out to have only two channels gets the speaker
    /// lanes and no haptic; anything with four or more gets both, and the
    /// mixer pads channels past the fourth with silence.
    pub fn add<S>(&self, source: S, speaker: f32, haptic: f32)
    where
        S: Source<Item = f32> + Send + 'static,
    {
        if self.channels.get() >= 4 {
            self.mixer.add(Quad::new(source, speaker, haptic));
        } else {
            self.mixer.add(Stereo::new(source, speaker, haptic));
        }
    }
}

/// Opens every pad sound card the host has.
///
/// A pad is an output whose endpoint name says "Wireless Controller"; where
/// there is no endpoint information to go on — off Windows, or an endpoint
/// cpal lists that the enumeration here did not — it is an output whose
/// default format has four channels, which nothing else in a living room
/// has. Each is opened at its own default format, four channels and all:
/// rodio's builder would happily ask for two, and Windows' shared-mode
/// engine would then fold the pair into the four, and the actuators would
/// get the speaker's signal instead of their own.
///
/// Nothing here panics. An output that will not open is logged and left.
pub fn open_pad_speakers() -> Vec<PadSpeaker> {
    let endpoints = endpoints();
    let devices = match rodio::cpal::default_host().output_devices() {
        Ok(devices) => devices,
        Err(error) => {
            log::warn!("could not list audio outputs ({error}); no pad speakers");
            return Vec::new();
        }
    };

    let mut pads = Vec::new();
    // Pad-shaped outputs seen so far, opened or not, so an ordinal key
    // means the same thing whether or not an earlier pad failed to open.
    let mut seen = 0;
    for device in devices {
        let Ok(config) = device.default_output_config() else {
            continue;
        };
        let endpoint = device
            .id()
            .ok()
            .and_then(|id| endpoints.iter().find(|endpoint| endpoint.id == id.1));
        let is_pad = match endpoint {
            Some(endpoint) => is_pad_name(&endpoint.friendly_name),
            None => config.channels() == 4,
        };
        if !is_pad {
            continue;
        }

        let name = match endpoint {
            Some(endpoint) => endpoint.friendly_name.clone(),
            None => device
                .description()
                .map(|description| description.name().to_owned())
                .unwrap_or_else(|_| "unnamed output".to_owned()),
        };
        let key = match endpoint.and_then(|endpoint| endpoint.container) {
            Some(container) => PadKey::Container(container),
            None => PadKey::Ordinal(seen),
        };
        seen += 1;

        let opened =
            rodio::DeviceSinkBuilder::from_device(device).and_then(|builder| builder.open_stream());
        let mut device = match opened {
            Ok(device) => device,
            Err(error) => {
                log::warn!("could not open pad speaker {name}: {error}");
                continue;
            }
        };
        // Quitting stops the sound on purpose; rodio need not warn about it.
        device.log_on_drop(false);
        let channels = device.config().channel_count();
        let sample_rate = device.config().sample_rate();
        log::info!("pad speaker: {name}, {channels} ch {sample_rate} Hz");
        pads.push(PadSpeaker {
            mixer: device.mixer().clone(),
            _device: device,
            channels,
            sample_rate,
            key,
            name,
        });
    }
    pads
}

/// A mono source spread over `N` interleaved lanes.
///
/// Four lanes is a pad: `[speaker, speaker, haptic, haptic]`, the haptic
/// pair being the same sample through a one-pole low-pass at
/// [`HAPTIC_CUTOFF_HZ`]. Two lanes is the same without the haptics, for an
/// output that turned out to be plain stereo. Each is a plain gain on the
/// sample; nothing here limits, so the caller's gains are the ceiling.
///
/// A source with more than one channel is folded to mono first, by
/// averaging, so that a stereo recording handed over by mistake comes out
/// of the pad rather than out of step.
pub struct Spread<S, const N: usize> {
    inner: S,
    speaker: f32,
    haptic: f32,
    filter: OnePole,
    /// The frame being handed out and how much of it is left: one mono
    /// sample in, `N` samples out.
    frame: [f32; N],
    left: usize,
}

/// See [`Spread`]: the four lanes of a pad.
pub type Quad<S> = Spread<S, 4>;

/// See [`Spread`]: speaker lanes only.
pub type Stereo<S> = Spread<S, 2>;

impl<S: Source<Item = f32>, const N: usize> Spread<S, N> {
    pub fn new(inner: S, speaker: f32, haptic: f32) -> Self {
        const {
            assert!(N == 2 || N == 4, "a pad has two lanes or four");
        }
        let filter = OnePole::new(inner.sample_rate().get() as f32, HAPTIC_CUTOFF_HZ);
        Self {
            inner,
            speaker,
            haptic,
            filter,
            frame: [0.0; N],
            left: 0,
        }
    }

    /// One sample of the inner source folded to mono, or `None` when it is
    /// done. A source that ends mid-frame ends there.
    fn mono(&mut self) -> Option<f32> {
        let channels = self.inner.channels().get() as usize;
        let mut sum = self.inner.next()?;
        for _ in 1..channels {
            sum += self.inner.next()?;
        }
        Some(sum / channels as f32)
    }
}

impl<S: Source<Item = f32>, const N: usize> Iterator for Spread<S, N> {
    type Item = f32;

    fn next(&mut self) -> Option<f32> {
        if self.left == 0 {
            let sample = self.mono()?;
            let felt = self.filter.step(sample) * self.haptic;
            let heard = sample * self.speaker;
            self.frame = [heard; N];
            if N == 4 {
                self.frame[2] = felt;
                self.frame[3] = felt;
            }
            self.left = N;
        }
        let sample = self.frame[N - self.left];
        self.left -= 1;
        Some(sample)
    }
}

impl<S: Source<Item = f32>, const N: usize> Source for Spread<S, N> {
    /// The inner's span, in output samples: what is left of the frame in
    /// hand, plus `N` for every mono frame the inner has left in its span.
    ///
    /// The mixer wraps every source in a `UniformSourceIterator`, which
    /// takes exactly this many samples and then rebuilds its rate and
    /// channel converters from the source's current values, so a span that
    /// claimed too many samples would have the converters run into the next
    /// span's format. Ours never changes, but the count is kept honest
    /// anyway; a source with no spans — a loop — reports none.
    fn current_span_len(&self) -> Option<usize> {
        let channels = self.inner.channels().get() as usize;
        self.inner
            .current_span_len()
            .map(|span| span / channels * N + self.left)
    }

    fn channels(&self) -> rodio::ChannelCount {
        rodio::ChannelCount::new(N as u16).expect("N is two or four")
    }

    fn sample_rate(&self) -> rodio::SampleRate {
        self.inner.sample_rate()
    }

    fn total_duration(&self) -> Option<Duration> {
        self.inner.total_duration()
    }
}

/// The Windows side: the device tree, and the audio endpoints with the
/// properties cpal leaves behind.
///
/// Two walks. From a HID interface path, `CM_Get_Device_Interface_PropertyW`
/// gives the instance id of the device node behind it
/// (`HID\VID_054C&PID_0CE6&MI_03\...`), `CM_Locate_DevNodeW` opens that node,
/// and `CM_Get_DevNode_PropertyW` reads its container id. From an audio
/// endpoint, the same container id is a property in the endpoint's own
/// store; failing that, the endpoint's topology names the adapter device it
/// hangs off, whose interface path can be walked like the HID one.
#[cfg(windows)]
mod platform {
    use std::ffi::CStr;

    use windows::Win32::Devices::DeviceAndDriverInstallation::{
        CM_Get_DevNode_PropertyW, CM_Get_Device_Interface_PropertyW, CM_LOCATE_DEVNODE_NORMAL,
        CM_Locate_DevNodeW, CR_BUFFER_SMALL, CR_SUCCESS,
    };
    use windows::Win32::Devices::FunctionDiscovery::PKEY_Device_FriendlyName;
    use windows::Win32::Devices::Properties::{
        DEVPKEY_Device_ContainerId, DEVPKEY_Device_InstanceId, DEVPROP_TYPE_GUID,
        DEVPROP_TYPE_STRING, DEVPROPTYPE,
    };
    use windows::Win32::Foundation::{DEVPROPKEY, PROPERTYKEY, RPC_E_CHANGED_MODE};
    use windows::Win32::Media::Audio::{
        DEVICE_STATE_ACTIVE, IDeviceTopology, IMMDevice, IMMDeviceEnumerator, IMMEndpoint,
        MMDeviceEnumerator, eAll, eRender,
    };
    use windows::Win32::System::Com::StructuredStorage::PropVariantClear;
    use windows::Win32::System::Com::{
        CLSCTX_ALL, COINIT_MULTITHREADED, CoCreateInstance, CoInitializeEx, CoTaskMemFree,
        CoUninitialize, STGM_READ,
    };
    use windows::Win32::System::Variant::{VT_CLSID, VT_LPWSTR};
    use windows::Win32::UI::Shell::PropertiesSystem::IPropertyStore;
    use windows::core::{GUID, HRESULT, Interface, PCWSTR, PWSTR};

    use super::{Endpoint, guid_to_u128};

    /// The container id as an endpoint's property store files it: the same
    /// GUID and index as the device-tree key, under the other key type.
    const PKEY_DEVICE_CONTAINER_ID: PROPERTYKEY = PROPERTYKEY {
        fmtid: DEVPKEY_Device_ContainerId.fmtid,
        pid: DEVPKEY_Device_ContainerId.pid,
    };

    /// COM, initialised once per thread and released when the thread ends.
    ///
    /// Multithreaded is asked for; if cpal got to this thread first it will
    /// have made it an apartment thread and the call comes back
    /// `RPC_E_CHANGED_MODE`, which is fine — COM marshals between the two —
    /// and must not be paired with an uninitialise.
    struct ComInit(HRESULT);

    impl ComInit {
        fn usable(&self) -> bool {
            self.0.is_ok() || self.0 == RPC_E_CHANGED_MODE
        }
    }

    impl Drop for ComInit {
        fn drop(&mut self) {
            if self.0.is_ok() {
                unsafe { CoUninitialize() };
            }
        }
    }

    thread_local! {
        static COM: ComInit = ComInit(unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) });
    }

    fn com_usable() -> bool {
        COM.with(ComInit::usable)
    }

    /// A nul-terminated UTF-16 copy, for the W entry points.
    fn wide(text: &str) -> Vec<u16> {
        text.encode_utf16().chain(std::iter::once(0)).collect()
    }

    /// Takes a string COM handed us and frees the memory it came in.
    unsafe fn take_string(text: PWSTR) -> Option<String> {
        if text.is_null() {
            return None;
        }
        let owned = unsafe { text.to_string() }.ok();
        unsafe { CoTaskMemFree(Some(text.as_ptr() as *const _)) };
        owned
    }

    /// A string property of a device interface, by its path.
    fn interface_string(path: &[u16], key: &DEVPROPKEY) -> Option<String> {
        let mut kind = DEVPROPTYPE::default();
        let mut size = 0u32;
        let asked = unsafe {
            CM_Get_Device_Interface_PropertyW(
                PCWSTR(path.as_ptr()),
                key,
                &mut kind,
                None,
                &mut size,
                0,
            )
        };
        if asked != CR_BUFFER_SMALL || kind != DEVPROP_TYPE_STRING || size < 2 {
            return None;
        }
        let mut buffer = vec![0u16; size as usize / 2];
        let read = unsafe {
            CM_Get_Device_Interface_PropertyW(
                PCWSTR(path.as_ptr()),
                key,
                &mut kind,
                Some(buffer.as_mut_ptr() as *mut u8),
                &mut size,
                0,
            )
        };
        if read != CR_SUCCESS {
            return None;
        }
        let end = buffer.iter().position(|&c| c == 0).unwrap_or(buffer.len());
        Some(String::from_utf16_lossy(&buffer[..end]))
    }

    /// A GUID property of a device node.
    fn devnode_guid(devinst: u32, key: &DEVPROPKEY) -> Option<u128> {
        let mut kind = DEVPROPTYPE::default();
        let mut guid = GUID::default();
        let mut size = std::mem::size_of::<GUID>() as u32;
        let read = unsafe {
            CM_Get_DevNode_PropertyW(
                devinst,
                key,
                &mut kind,
                Some(&mut guid as *mut GUID as *mut u8),
                &mut size,
                0,
            )
        };
        if read != CR_SUCCESS || kind != DEVPROP_TYPE_GUID {
            return None;
        }
        Some(guid_to_u128(guid.data1, guid.data2, guid.data3, guid.data4))
    }

    /// The container id of the device behind any interface path, HID or
    /// audio: `\\?\hid#vid_054c&pid_0ce6&mi_03#8&b600a7c&0&0000#{...}`.
    fn container_of_interface(path: &str) -> Option<u128> {
        let instance = interface_string(&wide(path), &DEVPKEY_Device_InstanceId)?;
        let instance = wide(&instance);
        let mut devinst = 0u32;
        let located = unsafe {
            CM_Locate_DevNodeW(
                &mut devinst,
                PCWSTR(instance.as_ptr()),
                CM_LOCATE_DEVNODE_NORMAL,
            )
        };
        if located != CR_SUCCESS {
            return None;
        }
        devnode_guid(devinst, &DEVPKEY_Device_ContainerId)
    }

    pub fn container_of_hid_path(path: &CStr) -> Option<u128> {
        container_of_interface(&path.to_string_lossy())
    }

    /// A string from an endpoint's property store.
    unsafe fn store_string(store: &IPropertyStore, key: &PROPERTYKEY) -> Option<String> {
        let mut value = unsafe { store.GetValue(key) }.ok()?;
        let inner = unsafe { &value.Anonymous.Anonymous };
        let text = if inner.vt == VT_LPWSTR {
            unsafe { inner.Anonymous.pwszVal.to_string() }.ok()
        } else {
            None
        };
        unsafe { PropVariantClear(&mut value) }.ok();
        text
    }

    /// A GUID from an endpoint's property store.
    unsafe fn store_guid(store: &IPropertyStore, key: &PROPERTYKEY) -> Option<u128> {
        let mut value = unsafe { store.GetValue(key) }.ok()?;
        let inner = unsafe { &value.Anonymous.Anonymous };
        let guid = if inner.vt == VT_CLSID {
            let pointer = unsafe { inner.Anonymous.puuid };
            (!pointer.is_null()).then(|| unsafe { *pointer })
        } else {
            None
        };
        unsafe { PropVariantClear(&mut value) }.ok();
        guid.map(|guid| guid_to_u128(guid.data1, guid.data2, guid.data3, guid.data4))
    }

    /// The device interface path of the adapter an endpoint hangs off, from
    /// its topology: the endpoint's one connector is wired to the adapter's
    /// filter, whose id is `{2}.\\?\usb#vid_054c&pid_0ce6&mi_00#...#{guid}\...`
    /// — a device interface path with a prefix and a pin name around it.
    pub fn adapter_path_of(device: &IMMDevice) -> Option<String> {
        unsafe {
            let topology: IDeviceTopology = device.Activate(CLSCTX_ALL, None).ok()?;
            let connector = topology.GetConnector(0).ok()?;
            let id = take_string(connector.GetDeviceIdConnectedTo().ok()?)?;
            let path = id.strip_prefix("{2}.").unwrap_or(&id);
            let end = path.rfind('}')?;
            Some(path[..=end].to_owned())
        }
    }

    /// Every active render endpoint, in the order cpal numbers them.
    ///
    /// cpal's `Devices` is `EnumAudioEndpoints(eAll, DEVICE_STATE_ACTIVE)`
    /// walked in order, and its `output_devices()` keeps those whose data
    /// flow is `eRender` (that is what `supports_output` comes to on WASAPI:
    /// the render check, then a format query that only fails for a device
    /// that has just gone away). This is the same call with the same filter,
    /// so index *i* here is cpal's *i*-th output device, up to the one case
    /// of an endpoint dying between the two enumerations — which is why the
    /// caller pairs on the endpoint id and only reads the index.
    pub fn endpoints() -> Vec<Endpoint> {
        if !com_usable() {
            log::warn!("COM would not initialise; pads cannot be told apart");
            return Vec::new();
        }
        let mut found = Vec::new();
        unsafe {
            let enumerator: IMMDeviceEnumerator =
                match CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_ALL) {
                    Ok(enumerator) => enumerator,
                    Err(error) => {
                        log::warn!("no audio endpoint enumerator ({error})");
                        return found;
                    }
                };
            let Ok(collection) = enumerator.EnumAudioEndpoints(eAll, DEVICE_STATE_ACTIVE) else {
                return found;
            };
            let count = collection.GetCount().unwrap_or(0);
            for item in 0..count {
                let Ok(device) = collection.Item(item) else {
                    continue;
                };
                let flow = device
                    .cast::<IMMEndpoint>()
                    .and_then(|endpoint| endpoint.GetDataFlow());
                if flow != Ok(eRender) {
                    continue;
                }
                let Some(id) = device.GetId().ok().and_then(|id| take_string(id)) else {
                    continue;
                };
                let store = device.OpenPropertyStore(STGM_READ).ok();
                let friendly_name = store
                    .as_ref()
                    .and_then(|store| store_string(store, &PKEY_Device_FriendlyName))
                    .unwrap_or_default();
                let container = store
                    .as_ref()
                    .and_then(|store| store_guid(store, &PKEY_DEVICE_CONTAINER_ID))
                    .or_else(|| {
                        adapter_path_of(&device).and_then(|path| container_of_interface(&path))
                    });
                found.push(Endpoint {
                    index: found.len(),
                    id,
                    friendly_name,
                    container,
                });
            }
        }
        found
    }
}

/// Anywhere else there is no container id to read and no endpoint store to
/// ask, so a pad is any four-channel output and its key is its ordinal.
#[cfg(not(windows))]
mod platform {
    use std::ffi::CStr;

    use super::Endpoint;

    pub fn container_of_hid_path(_path: &CStr) -> Option<u128> {
        None
    }

    pub fn endpoints() -> Vec<Endpoint> {
        Vec::new()
    }
}

#[cfg(test)]
mod tests {
    use std::f32::consts::TAU;

    use super::*;

    const RATE: u32 = 48000;

    fn rate() -> rodio::SampleRate {
        rodio::SampleRate::new(RATE).unwrap()
    }

    fn mono(samples: Vec<f32>) -> SamplesBuffer {
        SamplesBuffer::new(rodio::ChannelCount::MIN, rate(), samples)
    }

    fn tone(hz: f32, seconds: f32) -> SamplesBuffer {
        let count = (RATE as f32 * seconds) as usize;
        mono(
            (0..count)
                .map(|i| (TAU * hz * i as f32 / RATE as f32).sin())
                .collect(),
        )
    }

    /// The loudest sample in one lane of an interleaved stream, after
    /// `skip` frames have gone by.
    fn lane_peak(samples: &[f32], lanes: usize, lane: usize, skip: usize) -> f32 {
        samples
            .iter()
            .skip(skip * lanes + lane)
            .step_by(lanes)
            .fold(0.0f32, |peak, s| peak.max(s.abs()))
    }

    #[test]
    fn a_mono_impulse_comes_out_as_four_samples_with_the_right_gains() {
        let quad = Quad::new(mono(vec![1.0, 0.0]), 0.5, 0.8);
        assert_eq!(quad.channels().get(), 4);
        assert_eq!(quad.sample_rate().get(), RATE);

        let out: Vec<f32> = quad.collect();
        assert_eq!(out.len(), 8, "four lanes per mono sample");
        // The speaker lanes are the sample as it came, at speaker gain.
        assert_eq!(out[0], 0.5);
        assert_eq!(out[1], 0.5);
        // The haptic lanes are the sample through the low-pass, which after
        // one step of an impulse is the filter's coefficient, at haptic gain.
        let coefficient = 1.0 - (-TAU * HAPTIC_CUTOFF_HZ / RATE as f32).exp();
        assert!((out[2] - 0.8 * coefficient).abs() < 1e-6, "{}", out[2]);
        assert_eq!(out[2], out[3], "both hands get the same thump");
        assert!(
            out[2] > 0.0 && out[2] < 0.05,
            "and it is well under the speaker's"
        );
        // The second frame: the speaker goes to nothing at once, the haptic
        // is still ringing down.
        assert_eq!(out[4], 0.0);
        assert!(out[6] > 0.0 && out[6] < out[2]);
    }

    #[test]
    fn the_haptic_lane_keeps_a_thump_and_drops_a_crack() {
        // A 5 kHz crack is what makes a gun sound like a gun; in the hands
        // it would only buzz. A 30 Hz thump is what a hand is for.
        let low: Vec<f32> = Quad::new(tone(30.0, 0.5), 1.0, 1.0).collect();
        let high: Vec<f32> = Quad::new(tone(5000.0, 0.5), 1.0, 1.0).collect();
        let settle = RATE as usize / 10;
        let low_felt = lane_peak(&low, 4, 2, settle);
        let high_felt = lane_peak(&high, 4, 2, settle);
        assert!(low_felt > 0.9, "the thump got through at {low_felt}");
        assert!(high_felt < 0.05, "the crack got through at {high_felt}");
        // While the speaker lanes carry both untouched.
        assert!(lane_peak(&high, 4, 0, settle) > 0.99);
        assert!(lane_peak(&low, 4, 0, settle) > 0.99);
    }

    #[test]
    fn a_stereo_output_gets_the_speaker_lanes_and_nothing_felt() {
        let stereo = Stereo::new(mono(vec![1.0, -0.5]), 0.5, 0.8);
        assert_eq!(stereo.channels().get(), 2);
        let out: Vec<f32> = stereo.collect();
        assert_eq!(out, vec![0.5, 0.5, -0.25, -0.25]);
    }

    #[test]
    fn a_stereo_source_is_folded_to_mono_before_it_is_spread() {
        let clip = SamplesBuffer::new(
            rodio::ChannelCount::new(2).unwrap(),
            rate(),
            vec![1.0, 0.0, 0.5, 0.5],
        );
        let out: Vec<f32> = Stereo::new(clip, 1.0, 0.0).collect();
        assert_eq!(out, vec![0.5, 0.5, 0.5, 0.5]);
    }

    /// A mono source that says how much of its one span is left, which is
    /// what the `Source` contract asks for. rodio's own `SamplesBuffer`
    /// reports its whole length until it is spent, so the mid-span
    /// arithmetic cannot be checked against it.
    struct Remaining(Vec<f32>);

    impl Iterator for Remaining {
        type Item = f32;
        fn next(&mut self) -> Option<f32> {
            (!self.0.is_empty()).then(|| self.0.remove(0))
        }
    }

    impl Source for Remaining {
        fn current_span_len(&self) -> Option<usize> {
            Some(self.0.len())
        }
        fn channels(&self) -> rodio::ChannelCount {
            rodio::ChannelCount::MIN
        }
        fn sample_rate(&self) -> rodio::SampleRate {
            rate()
        }
        fn total_duration(&self) -> Option<Duration> {
            Some(Duration::from_millis(7))
        }
    }

    #[test]
    fn the_span_is_counted_in_output_samples() {
        // The mixer takes exactly this many before it rebuilds its
        // converters, so it has to be the number of samples that will come.
        let mut quad = Quad::new(Remaining(vec![0.1, 0.2, 0.3]), 1.0, 1.0);
        assert_eq!(quad.current_span_len(), Some(12));
        quad.next();
        assert_eq!(
            quad.current_span_len(),
            Some(11),
            "three of the frame left, and two frames"
        );
        let rest = quad.by_ref().count();
        assert_eq!(rest, 11);
        assert_eq!(quad.current_span_len(), Some(0));
        assert_eq!(quad.total_duration(), Some(Duration::from_millis(7)));

        // And at the start of a span the number is right for rodio's own
        // buffer too, which is the only time the mixer asks.
        let quad = Quad::new(mono(vec![0.1, 0.2, 0.3]), 1.0, 1.0);
        assert_eq!(quad.current_span_len(), Some(12));
        assert_eq!(quad.count(), 12);
    }

    #[test]
    fn a_loop_with_no_spans_reports_none() {
        struct Endless;
        impl Iterator for Endless {
            type Item = f32;
            fn next(&mut self) -> Option<f32> {
                Some(0.0)
            }
        }
        impl Source for Endless {
            fn current_span_len(&self) -> Option<usize> {
                None
            }
            fn channels(&self) -> rodio::ChannelCount {
                rodio::ChannelCount::MIN
            }
            fn sample_rate(&self) -> rodio::SampleRate {
                rate()
            }
            fn total_duration(&self) -> Option<Duration> {
                None
            }
        }
        let quad = Quad::new(Endless, 1.0, 1.0);
        assert_eq!(quad.current_span_len(), None);
        assert_eq!(quad.total_duration(), None);
    }

    #[test]
    fn a_key_is_its_container_and_nothing_else() {
        assert_eq!(PadKey::Container(7), PadKey::Container(7));
        assert_ne!(PadKey::Container(7), PadKey::Container(8));
        assert_ne!(PadKey::Container(1), PadKey::Ordinal(1));
        assert_eq!(PadKey::Ordinal(0), PadKey::Ordinal(0));
        assert_eq!(
            format!(
                "{:?}",
                PadKey::Container(0x8c7ed206_3f8a_4827_b3ab_ae9e1faefc6c)
            ),
            "Container(8c7ed2063f8a4827b3abae9e1faefc6c)",
            "a log line shows the GUID's digits, not a decimal",
        );
        assert_eq!(format!("{:?}", PadKey::Ordinal(2)), "Ordinal(2)");
        let mut set = std::collections::HashSet::new();
        set.insert(PadKey::Container(7));
        assert!(set.contains(&PadKey::Container(7)), "and it hashes");
    }

    #[test]
    fn a_pad_is_known_by_the_name_sony_gave_the_usb_device() {
        assert!(is_pad_name("Speakers (Wireless Controller)"));
        assert!(is_pad_name("Speakers (2- Wireless Controller)"));
        assert!(is_pad_name("Headphones (3- Wireless Controller)"));
        assert!(!is_pad_name("Speakers"));
        assert!(!is_pad_name("Speakers (Realtek(R) Audio)"));
        assert!(!is_pad_name("DELL U4320Q"));
        assert!(!is_pad_name(""));
    }

    #[test]
    fn a_guid_reads_as_the_number_its_text_form_writes() {
        // DEVPKEY_Device_ContainerId's own GUID: {8c7ed206-3f8a-4827-b3ab-ae9e1faefc6c}.
        let number = guid_to_u128(
            0x8c7ed206,
            0x3f8a,
            0x4827,
            [0xb3, 0xab, 0xae, 0x9e, 0x1f, 0xae, 0xfc, 0x6c],
        );
        assert_eq!(number, 0x8c7ed206_3f8a_4827_b3ab_ae9e1faefc6c);
        assert_eq!(guid_to_u128(0, 0, 0, [0; 8]), 0);
        assert_eq!(guid_to_u128(0, 0, 0, [0, 0, 0, 0, 0, 0, 0, 1]), 1);
        #[cfg(windows)]
        assert_eq!(
            number,
            windows::core::GUID::from_u128(0x8c7ed206_3f8a_4827_b3ab_ae9e1faefc6c).to_u128(),
            "and it is the same number the windows crate makes",
        );
    }

    /// Not a test of anything: a printout of what this machine has, for
    /// checking the pairing by eye. `cargo test -p codecraft --lib pads::probe
    /// -- --ignored --nocapture`.
    #[test]
    #[ignore]
    fn probe() {
        println!("cpal output devices:");
        if let Ok(devices) = rodio::cpal::default_host().output_devices() {
            for (index, device) in devices.enumerate() {
                let name = device
                    .description()
                    .map(|description| description.name().to_owned())
                    .unwrap_or_else(|error| format!("<{error}>"));
                let id = device
                    .id()
                    .map(|id| id.1)
                    .unwrap_or_else(|error| format!("<{error}>"));
                match device.default_output_config() {
                    Ok(config) => println!(
                        "  {index}: {name:?}  {} ch {} Hz {:?}  id {id}",
                        config.channels(),
                        config.sample_rate(),
                        config.sample_format(),
                    ),
                    Err(error) => println!("  {index}: {name:?}  <{error}>  id {id}"),
                }
            }
        }

        println!("render endpoints:");
        for endpoint in endpoints() {
            println!(
                "  {}: {:?}  container {}  id {}",
                endpoint.index,
                endpoint.friendly_name,
                endpoint
                    .container
                    .map(|c| format!("{c:032x}"))
                    .unwrap_or_else(|| "none".to_owned()),
                endpoint.id,
            );
        }

        println!("PlayStation HID interfaces:");
        if let Ok(api) = hidapi::HidApi::new() {
            for info in api.device_list().filter(|info| info.vendor_id() == 0x054C) {
                println!(
                    "  pid {:04x} interface {}: {:?}  container {}",
                    info.product_id(),
                    info.interface_number(),
                    info.path(),
                    container_of_hid_path(info.path())
                        .map(|c| format!("{c:032x}"))
                        .unwrap_or_else(|| "none".to_owned()),
                );
            }
        }

        println!("pad speakers opened:");
        for pad in open_pad_speakers() {
            println!("  {pad:?}");
        }
    }
}