initial commit: beginnings of descriptor definition
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/target
|
||||
Generated
+219
@@ -0,0 +1,219 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-embedded-io"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed6bb9472871706c9b1f648ca527031e33d647a95706d6ab5659f22ca28d419"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"embedded-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"defmt-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt-macros"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e"
|
||||
dependencies = [
|
||||
"defmt-parser",
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt-parser"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embedded-io"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7"
|
||||
|
||||
[[package]]
|
||||
name = "hash32"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heapless"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
|
||||
dependencies = [
|
||||
"hash32",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "modular-bitfield"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2956e537fc68236d2aa048f55704f231cc93f1c4de42fe1ecb5bd7938061fc4a"
|
||||
dependencies = [
|
||||
"modular-bitfield-impl",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "modular-bitfield-impl"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59b43b4fd69e3437618106f7754f34021b831a514f9e1a98ae863cabcd8d8dad"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "usb-device"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6"
|
||||
dependencies = [
|
||||
"heapless",
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "usbd-uac2"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"byteorder-embedded-io",
|
||||
"defmt",
|
||||
"embedded-io",
|
||||
"modular-bitfield",
|
||||
"usb-device",
|
||||
]
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "usbd-uac2"
|
||||
description = "USB Audio Class 2.0 for usb-device"
|
||||
authors = ["Keenan Tims <ktims@gotroot.ca>"]
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
keywords = ["no-std", "usb-device"]
|
||||
|
||||
[features]
|
||||
defmt = ["dep:defmt"]
|
||||
|
||||
[dependencies]
|
||||
byteorder-embedded-io = { version = "0.1.1", features = ["embedded-io"] }
|
||||
defmt = { version = "1.0.1", optional = true }
|
||||
embedded-io = "0.7.1"
|
||||
modular-bitfield = "0.13.1"
|
||||
usb-device = "0.3"
|
||||
@@ -0,0 +1,490 @@
|
||||
pub const AUDIO: u8 = 0x1;
|
||||
pub const HEADER: u8 = 0x1;
|
||||
|
||||
/// A.2 Audio Function Subclass Codes
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum FunctionSubclass {
|
||||
Undefined = 0,
|
||||
}
|
||||
|
||||
/// A.3 Audio Function Protocol Codes
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum FunctionProtocol {
|
||||
Undefined = 0,
|
||||
Version2 = 0x20,
|
||||
}
|
||||
|
||||
/// A.5 Audio Interface Subclass Codes
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum InterfaceSubclass {
|
||||
Undefined = 0,
|
||||
AudioControl = 1,
|
||||
AudioStreaming = 2,
|
||||
MidiStreaming = 3,
|
||||
}
|
||||
|
||||
/// A.6 Audio Interface Protocol Codes
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum InterfaceProtocol {
|
||||
Undefined = 0,
|
||||
Version2 = 0x20,
|
||||
}
|
||||
|
||||
/// A.7 Audio Function Category Codes
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum FunctionCode {
|
||||
Undefined = 0,
|
||||
DesktopSpeaker = 1,
|
||||
HomeTheater = 2,
|
||||
Microphone = 3,
|
||||
Headset = 4,
|
||||
Telephone = 5,
|
||||
Converter = 6,
|
||||
SoundRecorder = 7,
|
||||
IoBox = 8,
|
||||
MusicalInstrument = 9,
|
||||
ProAudio = 0xa,
|
||||
AudioVideo = 0xb,
|
||||
ControlPanel = 0xc,
|
||||
Other = 0xff,
|
||||
}
|
||||
|
||||
/// A.8 Audio Class-Specific Descriptor Types
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ClassSpecificDescriptorType {
|
||||
Undefined = 0x20,
|
||||
Device = 0x21,
|
||||
Configuration = 0x22,
|
||||
String = 0x23,
|
||||
Interface = 0x24,
|
||||
Endpoint = 0x25,
|
||||
}
|
||||
|
||||
/// A.9 Audio Class-Specific AC Interface Descriptor Subtypes
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ClassSpecificACInterfaceDescriptorSubtype {
|
||||
Undefined = 0,
|
||||
Header = 0x01,
|
||||
InputTerminal = 0x02,
|
||||
OutputTerminal = 0x03,
|
||||
MixerUnit = 0x04,
|
||||
SelectorUnit = 0x05,
|
||||
FeatureUnit = 0x06,
|
||||
EffectUnit = 0x07,
|
||||
ProcessingUnit = 0x08,
|
||||
ExtensionUnit = 0x09,
|
||||
ClockSource = 0x0A,
|
||||
ClockSelector = 0x0B,
|
||||
ClockMultiplier = 0x0C,
|
||||
SampleRateConverter = 0x0D,
|
||||
}
|
||||
|
||||
/// A.10 Audio Class-Specific AS Interface Descriptor Subtypes
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ClassSpecificASInterfaceDescriptorSubtype {
|
||||
Undefined = 0,
|
||||
General = 1,
|
||||
FormatType = 2,
|
||||
Encoder = 3,
|
||||
Decoder = 4,
|
||||
}
|
||||
|
||||
/// A.11 Effect Unit Effect Types
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum EffectUnitEffectType {
|
||||
Undefined = 0,
|
||||
ParamEqSection = 1,
|
||||
Reverb = 2,
|
||||
ModDelay = 3,
|
||||
DynRangeComp = 4,
|
||||
}
|
||||
|
||||
/// A.12 Processing Unit Process Types
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ProcessingUnitProcessType {
|
||||
Undefined = 0,
|
||||
UpDownMix = 1,
|
||||
DolbyPrologic = 2,
|
||||
StereoExtender = 3,
|
||||
}
|
||||
|
||||
/// A.13 Audio Class-Specific Endpoint Descriptor Subtypes
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ClassSpecificEndpointDescriptorSubtype {
|
||||
Undefined = 0,
|
||||
General = 1,
|
||||
}
|
||||
|
||||
/// A.14 Audio Class-Specific Request Codes
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ClassSpecificRequest {
|
||||
Undefined = 0,
|
||||
Cur = 1,
|
||||
Range = 2,
|
||||
Mem = 3,
|
||||
}
|
||||
|
||||
/// A.15 Encoder Type Codes
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum EncoderType {
|
||||
Undefined = 0,
|
||||
Other = 1,
|
||||
Mpeg = 2,
|
||||
Ac3 = 3,
|
||||
Wma = 4,
|
||||
Dts = 5,
|
||||
}
|
||||
/// A.16 Decoder Type Codes
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum DecoderType {
|
||||
Undefined = 0,
|
||||
Other = 1,
|
||||
Mpeg = 2,
|
||||
Ac3 = 3,
|
||||
Wma = 4,
|
||||
Dts = 5,
|
||||
}
|
||||
|
||||
/// A.17.1 Clock Source Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ClockSourceControlSelector {
|
||||
Undefined = 0,
|
||||
SamFreqControl = 1,
|
||||
ClockValidControl = 2,
|
||||
}
|
||||
/// A.17.2 Clock Selector Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ClockSelectorControlSelector {
|
||||
ControlUndefined = 0,
|
||||
ClockSelectorControl = 1,
|
||||
}
|
||||
|
||||
/// A.17.3 Clock Multiplier Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ClockMultiplierControlSelector {
|
||||
Undefined = 0,
|
||||
NumeratorControl = 1,
|
||||
DenominatorControl = 2,
|
||||
}
|
||||
|
||||
/// A.17.4 Terminal Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum TerminalControlSelector {
|
||||
Undefined = 0,
|
||||
CopyProtect = 1,
|
||||
Connector = 2,
|
||||
Overload = 3,
|
||||
Cluster = 4,
|
||||
Underflow = 5,
|
||||
Overflow = 6,
|
||||
Latency = 7,
|
||||
PhantomPower = 8,
|
||||
}
|
||||
|
||||
/// A.17.5 Mixer Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum MixerControlSelector {
|
||||
Undefined = 0,
|
||||
Mixer = 1,
|
||||
Cluster = 2,
|
||||
Underflow = 3,
|
||||
Overflow = 4,
|
||||
Latency = 5,
|
||||
}
|
||||
|
||||
/// A.17.6 Selector Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum SelectorControlSelector {
|
||||
Undefined = 0,
|
||||
Selector = 1,
|
||||
Latency = 2,
|
||||
}
|
||||
|
||||
/// A.17.7 Feature Unit Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum FeatureUnitControlSelector {
|
||||
Undefined = 0,
|
||||
Mute = 0x01,
|
||||
Volume = 0x02,
|
||||
Bass = 0x03,
|
||||
Mid = 0x04,
|
||||
Treble = 0x05,
|
||||
GraphicEqualizer = 0x06,
|
||||
AutomaticGain = 0x07,
|
||||
Delay = 0x08,
|
||||
BassBoost = 0x09,
|
||||
Loudness = 0x0A,
|
||||
InputGain = 0x0B,
|
||||
InputGainPad = 0x0C,
|
||||
PhaseInverter = 0x0D,
|
||||
Underflow = 0x0E,
|
||||
Overflow = 0x0F,
|
||||
Latency = 0x10,
|
||||
HighpassFilter = 0x11,
|
||||
}
|
||||
/// A.17.8.1 Parametric Equalizer Section Effect Unit Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ParametricEqSectionEffectUnitControlSelector {
|
||||
Undefined = 0,
|
||||
Enable = 1,
|
||||
CenterFreq = 2,
|
||||
QFactor = 3,
|
||||
Gain = 4,
|
||||
Underflow = 5,
|
||||
Overflow = 6,
|
||||
Latency = 7,
|
||||
}
|
||||
|
||||
/// A.17.8.2 Reverberation Effect Unit Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ReverbEffectUnitControlSelector {
|
||||
Undefined = 0,
|
||||
Enable = 1,
|
||||
Type = 2,
|
||||
Level = 3,
|
||||
Time = 4,
|
||||
Feedback = 5,
|
||||
PreDelay = 6,
|
||||
Density = 7,
|
||||
HiFreqRolloff = 8,
|
||||
Underflow = 9,
|
||||
Overflow = 0xa,
|
||||
Latency = 0xb,
|
||||
}
|
||||
|
||||
/// A.17.8.3 Modulation Delay Effect Unit Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ModDelayEffectUnitControlSelector {
|
||||
Undefined = 0,
|
||||
Enable = 1,
|
||||
Balance = 2,
|
||||
Rate = 3,
|
||||
Depth = 4,
|
||||
Time = 5,
|
||||
Feedback = 6,
|
||||
Underflow = 7,
|
||||
Overflow = 8,
|
||||
Latency = 9,
|
||||
}
|
||||
|
||||
/// A.17.8.4 Dynamic Range Compressor Effect Unit Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum DynamicRangeCompressorEffectUnitControlSelector {
|
||||
Undefined = 0,
|
||||
Enable = 1,
|
||||
CompressionRate = 2,
|
||||
MaxAmplitude = 3,
|
||||
Threshold = 4,
|
||||
AttackTime = 5,
|
||||
ReleaseTime = 6,
|
||||
Underflow = 7,
|
||||
Overflow = 8,
|
||||
Latency = 9,
|
||||
}
|
||||
|
||||
/// A.17.9.1 Up/Down-mix Processing Unit Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum UpDownMixProcessingUnitControlSelector {
|
||||
Undefined = 0,
|
||||
Enable = 1,
|
||||
ModeSelect = 2,
|
||||
Cluster = 3,
|
||||
Underflow = 4,
|
||||
Overflow = 5,
|
||||
Latency = 6,
|
||||
}
|
||||
|
||||
/// A.17.9.2 Dolby Prologic Processing Unit Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum DolbyProcessingUnitControlSelectors {
|
||||
Undefined = 0,
|
||||
Enable = 1,
|
||||
ModeSelect = 2,
|
||||
Cluster = 3,
|
||||
Underflow = 4,
|
||||
Overflow = 5,
|
||||
Latency = 6,
|
||||
}
|
||||
|
||||
/// A.17.9.3 Stereo Extender Processing Unit Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum StereoExtenderProcessingUnitControlSelector {
|
||||
Undefined = 0,
|
||||
Enable = 1,
|
||||
Width = 2,
|
||||
Underflow = 3,
|
||||
Overflow = 4,
|
||||
Latency = 5,
|
||||
}
|
||||
|
||||
/// A.17.10 Extension Unit Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ExtensionUnitControlSelector {
|
||||
Undefined = 0,
|
||||
Enable = 1,
|
||||
Cluster = 2,
|
||||
Underflow = 3,
|
||||
Overflow = 4,
|
||||
Latency = 5,
|
||||
}
|
||||
|
||||
/// A.17.11 AudioStreaming Interface Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum AudioStreamingInterfaceControlSelector {
|
||||
Undefined = 0,
|
||||
ActAltSetting = 1,
|
||||
ValAltSetting = 2,
|
||||
AudioDataFormat = 3,
|
||||
}
|
||||
|
||||
/// A.17.12 Encoder Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum EncoderControlSelector {
|
||||
Undefined = 0,
|
||||
BitRate = 0x01,
|
||||
Quality = 0x02,
|
||||
Vbr = 0x03,
|
||||
Type = 0x04,
|
||||
Underflow = 0x05,
|
||||
Overflow = 0x06,
|
||||
EncoderError = 0x07,
|
||||
Param1 = 0x08,
|
||||
Param2 = 0x09,
|
||||
Param3 = 0x0A,
|
||||
Param4 = 0x0B,
|
||||
Param5 = 0x0C,
|
||||
Param6 = 0x0D,
|
||||
Param7 = 0x0E,
|
||||
Param8 = 0x0F,
|
||||
}
|
||||
|
||||
/// A.17.13.1 MPEG Decoder Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum MpegDecoderControlSelector {
|
||||
Undefined = 0,
|
||||
DualChannel = 1,
|
||||
SecondStereo = 2,
|
||||
Multilingual = 3,
|
||||
DynRange = 4,
|
||||
Scaling = 5,
|
||||
HiloScaling = 6,
|
||||
Underflow = 7,
|
||||
Overflow = 8,
|
||||
DecoderError = 9,
|
||||
}
|
||||
|
||||
/// A.17.13.2 AC-3 Decoder Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Ac3DecoderControlSelector {
|
||||
Undefined = 0,
|
||||
Mode = 1,
|
||||
DynRange = 2,
|
||||
Scaling = 3,
|
||||
HiloScaling = 4,
|
||||
Underflow = 5,
|
||||
Overflow = 6,
|
||||
DecoderError = 7,
|
||||
}
|
||||
|
||||
/// A.17.13.3 WMA Decoder Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum WmaDecoderControlSelector {
|
||||
Undefined = 0,
|
||||
Underflow = 1,
|
||||
Overflow = 2,
|
||||
DecoderError = 3,
|
||||
}
|
||||
|
||||
/// A.17.13.4 DTS Decoder Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum DtsDecoderControlSelector {
|
||||
Undefined = 0,
|
||||
Underflow = 1,
|
||||
Overflow = 2,
|
||||
DecoderError = 3,
|
||||
}
|
||||
|
||||
/// A.17.14 Endpoint Control Selectors
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum EndpointControlSelector {
|
||||
Undefined = 0,
|
||||
Pitch = 1,
|
||||
DataOverrun = 2,
|
||||
DataUnderrun = 3,
|
||||
}
|
||||
|
||||
/// Universal Serial Bus Device Class Definition for Terminal Types
|
||||
#[repr(u16)]
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum TerminalType {
|
||||
// USB Terminal Types
|
||||
UsbUndefined = 0x0100,
|
||||
UsbStreaming = 0x0101,
|
||||
UsbVendor = 0x01ff,
|
||||
|
||||
// Input Terminal Types
|
||||
InUndefined = 0x0200,
|
||||
InMicrophone = 0x0201,
|
||||
InDesktopMicrophone = 0x0202,
|
||||
InPersonalMicrophone = 0x0203,
|
||||
InOmniDirectionalMicrophone = 0x0204,
|
||||
InMicrophoneArray = 0x0205,
|
||||
InProcessingMicrophoneArray = 0x0206,
|
||||
|
||||
// Output Terminal Types
|
||||
OutUndefined = 0x0300,
|
||||
OutSpeaker = 0x0301,
|
||||
OutHeadphones = 0x0302,
|
||||
OutHeadMountedDisplayAudio = 0x0303,
|
||||
OutDesktopSpeaker = 0x0304,
|
||||
OutRoomSpeaker = 0x0305,
|
||||
OutCommunicationSpeaker = 0x0306,
|
||||
OutLowFrequencyEffectsSpeaker = 0x0307,
|
||||
|
||||
// External Terminal Types
|
||||
ExtUndefined = 0x0600,
|
||||
ExtAnalogConnector = 0x0601,
|
||||
ExtDigitalAudioInterface = 0x0602,
|
||||
ExtLineConnector = 0x0603,
|
||||
ExtLegacyAudioConnector = 0x0604,
|
||||
ExtSpdifConnector = 0x0605,
|
||||
Ext1394DaStream = 0x0606,
|
||||
Ext1394DvStreamSoundtrack = 0x0607,
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
// Copy most of embedded_io_cursor here to avoid multiple embedded-io versions in dep tree
|
||||
|
||||
use core::cmp;
|
||||
use embedded_io::{BufRead, Error, ErrorKind, ErrorType, Read, Seek, SeekFrom, Write};
|
||||
|
||||
#[derive(Debug, Default, Eq, PartialEq)]
|
||||
pub struct Cursor<T> {
|
||||
inner: T,
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<T> Cursor<T> {
|
||||
/// Creates a new cursor wrapping the provided underlying in-memory buffer.
|
||||
///
|
||||
/// Cursor initial position is `0` even if underlying buffer (e.g., `Vec`)
|
||||
/// is not empty. So writing to cursor starts with overwriting `Vec`
|
||||
/// content, not with appending to it.
|
||||
pub const fn new(inner: T) -> Cursor<T> {
|
||||
Cursor { pos: 0, inner }
|
||||
}
|
||||
|
||||
/// Consumes this cursor, returning the underlying value.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner
|
||||
}
|
||||
|
||||
/// Gets a reference to the underlying value in this cursor.
|
||||
pub const fn get_ref(&self) -> &T {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the underlying value in this cursor.
|
||||
///
|
||||
/// Care should be taken to avoid modifying the internal I/O state of the
|
||||
/// underlying value as it may corrupt this cursor's position.
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.inner
|
||||
}
|
||||
|
||||
/// Returns the current position of this cursor.
|
||||
pub const fn position(&self) -> usize {
|
||||
self.pos
|
||||
}
|
||||
|
||||
/// Sets the position of this cursor.
|
||||
pub fn set_position(&mut self, pos: usize) {
|
||||
self.pos = pos;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Cursor<T>
|
||||
where
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
/// Returns the remaining slice from the current position.
|
||||
///
|
||||
/// This method returns the portion of the underlying buffer that
|
||||
/// can still be read from the current cursor position.
|
||||
pub fn remaining_slice(&self) -> &[u8] {
|
||||
let pos = cmp::min(self.pos, self.inner.as_ref().len());
|
||||
&self.inner.as_ref()[pos..]
|
||||
}
|
||||
|
||||
/// Returns `true` if there are no more bytes to read from the cursor.
|
||||
///
|
||||
/// This is equivalent to checking if `remaining_slice().is_empty()`.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.pos >= self.inner.as_ref().len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Cursor<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
Cursor {
|
||||
inner: self.inner.clone(),
|
||||
pos: self.pos,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clone_from(&mut self, other: &Self) {
|
||||
self.inner.clone_from(&other.inner);
|
||||
self.pos = other.pos;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ErrorType for Cursor<T> {
|
||||
type Error = ErrorKind;
|
||||
}
|
||||
|
||||
// Read implementation for AsRef<[u8]> types
|
||||
impl<T> Read for Cursor<T>
|
||||
where
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||
let remaining = self.remaining_slice();
|
||||
let n = cmp::min(buf.len(), remaining.len());
|
||||
|
||||
if n > 0 {
|
||||
buf[..n].copy_from_slice(&remaining[..n]);
|
||||
}
|
||||
|
||||
self.pos += n;
|
||||
Ok(n)
|
||||
}
|
||||
}
|
||||
|
||||
// BufRead implementation for AsRef<[u8]> types
|
||||
impl<T> BufRead for Cursor<T>
|
||||
where
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
fn fill_buf(&mut self) -> Result<&[u8], Self::Error> {
|
||||
Ok(self.remaining_slice())
|
||||
}
|
||||
|
||||
fn consume(&mut self, amt: usize) {
|
||||
self.pos += amt;
|
||||
}
|
||||
}
|
||||
|
||||
// Seek implementation for AsRef<[u8]> types
|
||||
impl<T> Seek for Cursor<T>
|
||||
where
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
fn seek(&mut self, style: SeekFrom) -> Result<u64, Self::Error> {
|
||||
let (base_pos, offset) = match style {
|
||||
SeekFrom::Start(n) => {
|
||||
self.pos = n as usize;
|
||||
return Ok(n);
|
||||
}
|
||||
SeekFrom::End(n) => (self.inner.as_ref().len() as u64, n),
|
||||
SeekFrom::Current(n) => (self.pos as u64, n),
|
||||
};
|
||||
|
||||
match base_pos.checked_add_signed(offset) {
|
||||
Some(n) => {
|
||||
self.pos = n as usize;
|
||||
Ok(self.pos as u64)
|
||||
}
|
||||
None => Err(ErrorKind::InvalidInput),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function for writing to fixed-size slices
|
||||
fn slice_write(pos_mut: &mut usize, slice: &mut [u8], buf: &[u8]) -> Result<usize, ErrorKind> {
|
||||
let pos = cmp::min(*pos_mut, slice.len()) as usize;
|
||||
let amt = (&mut slice[pos..]).write(buf).map_err(|err| err.kind())?;
|
||||
*pos_mut += amt;
|
||||
Ok(amt)
|
||||
}
|
||||
|
||||
// Write implementation for &mut [u8]
|
||||
impl Write for Cursor<&mut [u8]> {
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||
slice_write(&mut self.pos, self.inner, buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+1422
File diff suppressed because it is too large
Load Diff
+285
@@ -0,0 +1,285 @@
|
||||
#![no_std]
|
||||
#![allow(dead_code)]
|
||||
|
||||
mod constants;
|
||||
mod cursor;
|
||||
mod descriptors;
|
||||
|
||||
use constants::*;
|
||||
use descriptors::*;
|
||||
|
||||
use usb_device::control::{Recipient, Request, RequestType};
|
||||
use usb_device::device::DEFAULT_ALTERNATE_SETTING;
|
||||
use usb_device::endpoint::{self, Endpoint, EndpointDirection, In, Out};
|
||||
use usb_device::{UsbDirection, class_prelude::*};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Format {
|
||||
/// Signed, 16 bits per subframe, little endian
|
||||
S16le,
|
||||
/// Signed, 24 bits per subframe, little endian
|
||||
S24le,
|
||||
/// Signed, 32 bits per subframe, little endian
|
||||
S32le,
|
||||
}
|
||||
|
||||
/// Sampling rates that shall be supported by an steaming endpoint
|
||||
#[derive(Debug)]
|
||||
pub enum Rates<'a> {
|
||||
/// A continuous range of sampling rates in samples/second defined by a
|
||||
/// tuple including a minimum value and a maximum value. The maximum value
|
||||
/// must be greater than the minimum value.
|
||||
Continuous(u32, u32),
|
||||
/// A set of discrete sampling rates in samples/second
|
||||
Discrete(&'a [u32]),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StreamConfig<'a> {
|
||||
format: Format,
|
||||
channels: u8,
|
||||
rates: Rates<'a>,
|
||||
terminal_type: TerminalType,
|
||||
/// ISO endpoint size calculated from format, channels and rates (may be
|
||||
/// removed in future)
|
||||
ep_size: u16,
|
||||
}
|
||||
|
||||
impl StreamConfig<'_> {
|
||||
/// Create a stream configuration with one or more discrete sampling rates
|
||||
/// indicated in samples/second. An input stream or an output stream will
|
||||
/// have an Input Terminal or Output Terminal of Terminal Type
|
||||
/// `terminal_type`, respectively.
|
||||
pub fn new_discrete(
|
||||
format: Format,
|
||||
channels: u8,
|
||||
rates: &'_ [u32],
|
||||
terminal_type: TerminalType,
|
||||
) -> Result<StreamConfig<'_>> {
|
||||
let max_rate = rates.iter().max().unwrap();
|
||||
let ep_size = Self::ep_size(format, channels, *max_rate)?;
|
||||
let rates = Rates::Discrete(rates);
|
||||
Ok(StreamConfig {
|
||||
format,
|
||||
channels,
|
||||
rates,
|
||||
terminal_type,
|
||||
ep_size,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a stream configuration with a continuous range of supported
|
||||
/// sampling rates indicated in samples/second. An input stream or an output
|
||||
/// stream will have an Input Terminal or Output Terminal of Terminal Type
|
||||
/// `terminal_type`, respectively.
|
||||
pub fn new_continuous(
|
||||
format: Format,
|
||||
channels: u8,
|
||||
min_rate: u32,
|
||||
max_rate: u32,
|
||||
terminal_type: TerminalType,
|
||||
) -> Result<StreamConfig<'static>> {
|
||||
if min_rate >= max_rate {
|
||||
return Err(Error::InvalidValue);
|
||||
}
|
||||
let ep_size = Self::ep_size(format, channels, max_rate)?;
|
||||
let rates = Rates::Continuous(min_rate, max_rate);
|
||||
Ok(StreamConfig {
|
||||
format,
|
||||
channels,
|
||||
rates,
|
||||
terminal_type,
|
||||
ep_size,
|
||||
})
|
||||
}
|
||||
|
||||
/// calculate ISO endpoint size from format, channels and rates
|
||||
fn ep_size(format: Format, channels: u8, max_rate: u32) -> Result<u16> {
|
||||
let octets_per_frame = channels as u32
|
||||
* match format {
|
||||
Format::S16le => 2,
|
||||
Format::S24le => 3,
|
||||
Format::S32le => 4,
|
||||
};
|
||||
let ep_size = octets_per_frame * max_rate / 1000;
|
||||
// if ep_size > MAX_ISO_EP_SIZE {
|
||||
// return Err(Error::BandwidthExceeded);
|
||||
// }
|
||||
Ok(ep_size as u16)
|
||||
}
|
||||
}
|
||||
|
||||
/// USB audio errors, including possible USB Stack errors
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
InvalidValue,
|
||||
BandwidthExceeded,
|
||||
StreamNotInitialized,
|
||||
UsbError(usb_device::UsbError),
|
||||
}
|
||||
|
||||
impl From<UsbError> for Error {
|
||||
fn from(err: UsbError) -> Self {
|
||||
Error::UsbError(err)
|
||||
}
|
||||
}
|
||||
type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
struct AudioStream<'a, B: UsbBus, D: EndpointDirection> {
|
||||
stream_config: StreamConfig<'a>,
|
||||
interface: InterfaceNumber,
|
||||
endpoint: Endpoint<'a, B, D>,
|
||||
alt_setting: u8,
|
||||
}
|
||||
|
||||
impl<B: UsbBus> AudioStream<'_, B, endpoint::In> {
|
||||
fn input_terminal_desc(&self, id: u8, clock_source: u8) -> InputTerminal {
|
||||
let channel_config = ChannelConfig::default_chans(self.stream_config.channels);
|
||||
InputTerminal {
|
||||
id,
|
||||
terminal_type: TerminalType::UsbStreaming,
|
||||
assoc_terminal: 0,
|
||||
clock_source,
|
||||
num_channels: self.stream_config.channels,
|
||||
channel_config,
|
||||
channel_names: 0,
|
||||
copy_protect_control: AccessControl::NotPresent,
|
||||
connector_control: AccessControl::NotPresent,
|
||||
overload_control: AccessControl::NotPresent,
|
||||
cluster_control: AccessControl::NotPresent,
|
||||
underflow_control: AccessControl::NotPresent,
|
||||
overflow_control: AccessControl::NotPresent,
|
||||
phantom_power_control: AccessControl::NotPresent,
|
||||
string: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: UsbBus> AudioStream<'_, B, endpoint::Out> {
|
||||
fn output_terminal_desc(&self, id: u8, source_id: u8, clock_source: u8) -> OutputTerminal {
|
||||
OutputTerminal {
|
||||
id,
|
||||
terminal_type: TerminalType::UsbStreaming,
|
||||
assoc_terminal: 0,
|
||||
source_id,
|
||||
clock_source,
|
||||
copy_protect_control: AccessControl::NotPresent,
|
||||
connector_control: AccessControl::NotPresent,
|
||||
overload_control: AccessControl::NotPresent,
|
||||
underflow_control: AccessControl::NotPresent,
|
||||
overflow_control: AccessControl::NotPresent,
|
||||
string: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AudioClass<'a, B: UsbBus> {
|
||||
control_iface: InterfaceNumber,
|
||||
input: Option<AudioStream<'a, B, In>>,
|
||||
output: Option<AudioStream<'a, B, Out>>,
|
||||
function: FunctionCode,
|
||||
clock_type: ClockType,
|
||||
input_type: Option<TerminalType>,
|
||||
output_type: Option<TerminalType>,
|
||||
}
|
||||
|
||||
impl<B: UsbBus> AudioClass<'_, B> {}
|
||||
|
||||
impl<B: UsbBus> UsbClass<B> for AudioClass<'_, B> {
|
||||
fn get_configuration_descriptors(
|
||||
&self,
|
||||
writer: &mut DescriptorWriter,
|
||||
) -> usb_device::Result<()> {
|
||||
// Build the necessary descriptors
|
||||
// Clock Source - id 1
|
||||
// USB Input Terminal - id 2
|
||||
// Audio Output Terminal - id 3
|
||||
// USB Output Terminal - id 4
|
||||
// Audio Input Terminal - id 5
|
||||
let clock_source = ClockSource {
|
||||
id: 1,
|
||||
clock_type: self.clock_type,
|
||||
sof_sync: false,
|
||||
frequency_access: if self.clock_type == ClockType::InternalProgrammable {
|
||||
AccessControl::Programmable
|
||||
} else {
|
||||
AccessControl::NotPresent
|
||||
},
|
||||
validity_access: AccessControl::ReadOnly,
|
||||
assoc_terminal: 0,
|
||||
string: 0,
|
||||
};
|
||||
let in_terminals = match &self.input {
|
||||
Some(i) => Some((
|
||||
i.input_terminal_desc(2, 1),
|
||||
OutputTerminal {
|
||||
id: 3,
|
||||
terminal_type: self.output_type.unwrap_or(TerminalType::OutUndefined),
|
||||
assoc_terminal: 0,
|
||||
source_id: 2,
|
||||
clock_source: 1,
|
||||
copy_protect_control: AccessControl::NotPresent,
|
||||
connector_control: AccessControl::NotPresent,
|
||||
overload_control: AccessControl::NotPresent,
|
||||
underflow_control: AccessControl::NotPresent,
|
||||
overflow_control: AccessControl::NotPresent,
|
||||
string: 0,
|
||||
},
|
||||
)),
|
||||
None => None,
|
||||
};
|
||||
let out_terminals = match &self.output {
|
||||
Some(i) => Some((
|
||||
i.output_terminal_desc(4, 5, 1),
|
||||
InputTerminal {
|
||||
id: 5,
|
||||
terminal_type: self.input_type.unwrap_or(TerminalType::InUndefined),
|
||||
assoc_terminal: 0,
|
||||
clock_source: 1,
|
||||
num_channels: i.stream_config.channels,
|
||||
channel_config: ChannelConfig::default_chans(i.stream_config.channels),
|
||||
channel_names: 0,
|
||||
copy_protect_control: AccessControl::NotPresent,
|
||||
connector_control: AccessControl::NotPresent,
|
||||
overload_control: AccessControl::NotPresent,
|
||||
cluster_control: AccessControl::NotPresent,
|
||||
underflow_control: AccessControl::NotPresent,
|
||||
overflow_control: AccessControl::NotPresent,
|
||||
phantom_power_control: AccessControl::NotPresent,
|
||||
string: 0,
|
||||
},
|
||||
)),
|
||||
None => None,
|
||||
};
|
||||
let n_interfaces = match (&self.input, &self.output) {
|
||||
(Some(_), Some(_)) => 3, // two audio, one control
|
||||
(Some(_), None) | (None, Some(_)) => 2, // one audio, one control
|
||||
(None, None) => 1, // no audio (?!), one control
|
||||
};
|
||||
writer.iad(
|
||||
self.control_iface,
|
||||
n_interfaces,
|
||||
AUDIO,
|
||||
FunctionSubclass::Undefined as u8,
|
||||
FunctionProtocol::Version2 as u8,
|
||||
None,
|
||||
)?;
|
||||
writer.interface(
|
||||
self.control_iface,
|
||||
AUDIO,
|
||||
InterfaceSubclass::AudioControl as u8,
|
||||
InterfaceProtocol::Version2 as u8,
|
||||
)?;
|
||||
|
||||
if let Some(terminals) = in_terminals {
|
||||
terminals.0.write_descriptor(writer)?;
|
||||
terminals.1.write_descriptor(writer)?;
|
||||
}
|
||||
if let Some(terminals) = out_terminals {
|
||||
terminals.0.write_descriptor(writer)?;
|
||||
terminals.1.write_descriptor(writer)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user