initial commit

Signed-off-by: James Calligeros <jcalligeros99@gmail.com>
This commit is contained in:
James Calligeros 2022-12-02 13:45:15 +10:00
commit e8a37a847b
33 changed files with 13634 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/target
*.kate-swp
*/target

88
Cargo.lock generated Normal file
View file

@ -0,0 +1,88 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "alsa"
version = "0.7.0"
dependencies = [
"alsa-sys",
"bitflags",
"libc",
"nix",
]
[[package]]
name = "alsa-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
dependencies = [
"libc",
"pkg-config",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "configparser"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5458d9d1a587efaf5091602c59d299696a3877a439c8f6d461a2d3cce11df87a"
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "half"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad6a9459c9c30b177b925162351f97e7d967c7ea8bab3b8352805327daf45554"
dependencies = [
"crunchy",
]
[[package]]
name = "libc"
version = "0.2.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
[[package]]
name = "nix"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc"
dependencies = [
"bitflags",
"cfg-if",
"libc",
]
[[package]]
name = "pkg-config"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]]
name = "speakersafety-2"
version = "0.1.0"
dependencies = [
"alsa",
"configparser",
"half",
]

11
Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "speakersafety-2"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
half = "^2.1.0"
alsa = { path = "./alsa" }
configparser = "^3.0.2"

42
README.md Normal file
View file

@ -0,0 +1,42 @@
## Asahi Linux speaker safety daemon
This is still very much a work in progress, is probably not "proper" Rust,
and almost definitely makes competent developers extremely sad.
We currently rely on a local version of the `alsa` crate, pending the merge of
bindings to `snd_ctl_elem_value_{read,write}` and `snd_ctl_elem_set_id`.
## What works
* Parsing config file
* All borrows seem to work fine
* Volume getting/setting
## Needs improvement
* Probably everything
## Need to implement
* Daemonise and loop
* Threading (should probably make sure it works as intended first)
* Getting V/ISENSE (pending changes to the codec drivers, we have mock implementations)
* Actually fail safe (see below)
## On failing safe
We need a way to guarantee safety on _any_ fail condition. The TAS codecs have a safe
mode which cuts all outputs down by 18 dB. This works out to being about half their
full output capabiltiy. It might be worth having the `macaudio` driver start them
explicitly in this mode, and only unlock full output capability with an IOCTL that
can be sent by `speakersafetyd` when it's sure it has started correctly. We would
then of course also need an IOCTL to do the opposite if we encounter a runtime error.
It was suggested by someone on IRC that this would be conducive to some sort of
keepalive IOCTL, where the driver would automatically put the codecs into safe mode
if it didn't hear from us for a while. This seems like it would suck to implement.
Like any SLA, it is likely that we will never be able to guarantee 100% safety for all
nonstandard setups. The reference PipeWire DSP graph plus this should be enough for 99% of
users, but I feel at some point those who insist on using Pulse or raw ALSA are just going
to have to put up with a best effort service and accept the (small) risk of this failing.
## Sundry
The `alsa` crate is Copyright (c) 2015-2021 David Henningsson, and other
contributors. Redistributed under the MIT license.

58
alsa/Cargo.lock generated Normal file
View file

@ -0,0 +1,58 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "alsa"
version = "0.7.0"
dependencies = [
"alsa-sys",
"bitflags",
"libc",
"nix",
]
[[package]]
name = "alsa-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
dependencies = [
"libc",
"pkg-config",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "libc"
version = "0.2.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
[[package]]
name = "nix"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc"
dependencies = [
"bitflags",
"cfg-if",
"libc",
]
[[package]]
name = "pkg-config"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"

24
alsa/Cargo.toml Normal file
View file

@ -0,0 +1,24 @@
[package]
name = "alsa"
version = "0.7.0"
authors = ["David Henningsson <coding@diwic.se>"]
description = "Thin but safe wrappers for ALSA (Linux sound API)"
repository = "https://github.com/diwic/alsa-rs"
documentation = "http://docs.rs/alsa"
keywords = ["ALSA", "audio", "sound"]
license = "Apache-2.0/MIT"
categories = ["multimedia::audio", "api-bindings"]
readme = "README.md"
edition = "2021"
include = ["README.md", "LICENSE-*", "Cargo.toml", "src/"]
[dependencies]
libc = "0.2"
alsa-sys = "0.3.1"
bitflags = "1.3.2"
nix = { version = "^0.24", default-features = false, features = ["ioctl"] }
[badges]
is-it-maintained-issue-resolution = { repository = "diwic/alsa-rs" }
is-it-maintained-open-issues = { repository = "diwic/alsa-rs" }

177
alsa/LICENSE-APACHE Normal file
View file

@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

21
alsa/LICENSE-MIT Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2015-2021 David Henningsson, and other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

62
alsa/README.md Normal file
View file

@ -0,0 +1,62 @@
ALSA bindings for Rust
=======================
Thin but safe wrappers for [ALSA](https://alsa-project.org), the most
common API for accessing audio devices on Linux.
[![crates.io](https://img.shields.io/crates/v/alsa.svg)](https://crates.io/crates/alsa)
[![API documentation](https://docs.rs/alsa/badge.svg)](https://docs.rs/alsa)
[![license](https://img.shields.io/crates/l/alsa.svg)](https://crates.io/crates/alsa)
The ALSA API is rather big, so everything is not covered yet, but expect the following to work:
* Audio Playback (example in `pcm` module docs)
* Audio Recording
* Mixer controls
* HCtl API (jack detection example in `hctl` module docs)
* Raw midi
* Midi sequencer (most of it)
* Ctl API
* Device name hints (example in `device_name` module docs)
* Enumerations of all of the above
* Poll and/or wait for all of the above
The following is not yet implemented (mostly because nobody asked for them) :
* Separate timer API (snd_timer_*)
* Config API (snd_config_*)
* Plug-in API
Quickstart guide / API design:
* Most functions map 1-to-1 to alsa-lib functions, e g, `ctl::CardInfo::get_id()` is a wrapper around
`snd_ctl_card_info_get_id` and the [alsa-lib documentation](https://www.alsa-project.org/alsa-doc/alsa-lib/)
can be consulted for additional information.
* Structs are RAII and closed/freed on drop, e g, when a `PCM` struct is dropped, `snd_pcm_close` is called.
* To read and write buffers, call the `io_*` methods. It will return a separate struct from which you can
read or write, and which can also be used for mmap (if supported by the driver).
* Error handling - most alsa-lib functions can return errors, so the return value from these is a `Result`.
* Enumeration of cards, devices etc is done through structs implementing `Iterator`.
* Many structs implement `poll::Descriptors`, to combine with poll or mio.
Or just use `wait` if you don't need non-blocking functionality.
Notes:
* To run the tests successfully, there must be a "default" sound card configured. This is usually not a problem when running on normal hardware, but some CI systems, docker images etc, might not have that configured by default.

50
alsa/examples/record.rs Normal file
View file

@ -0,0 +1,50 @@
//! Example that continously reads data and displays its RMS volume.
use alsa::pcm::*;
use alsa::{Direction, ValueOr, Error};
fn start_capture(device: &str) -> Result<PCM, Error> {
let pcm = PCM::new(device, Direction::Capture, false)?;
{
// For this example, we assume 44100Hz, one channel, 16 bit audio.
let hwp = HwParams::any(&pcm)?;
hwp.set_channels(1)?;
hwp.set_rate(44100, ValueOr::Nearest)?;
hwp.set_format(Format::s16())?;
hwp.set_access(Access::RWInterleaved)?;
pcm.hw_params(&hwp)?;
}
pcm.start()?;
Ok(pcm)
}
// Calculates RMS (root mean square) as a way to determine volume
fn rms(buf: &[i16]) -> f64 {
if buf.len() == 0 { return 0f64; }
let mut sum = 0f64;
for &x in buf {
sum += (x as f64) * (x as f64);
}
let r = (sum / (buf.len() as f64)).sqrt();
// Convert value to decibels
20.0 * (r / (i16::MAX as f64)).log10()
}
fn read_loop(pcm: &PCM) -> Result<(), Error> {
let io = pcm.io_i16()?;
let mut buf = [0i16; 8192];
loop {
// Block while waiting for 8192 samples to be read from the device.
assert_eq!(io.readi(&mut buf)?, buf.len());
let r = rms(&buf);
println!("RMS: {:.1} dB", r);
}
}
fn main() {
// The "default" device is usually directed to the sound server process,
// e g PulseAudio or PipeWire.
let capture = start_capture("default").unwrap();
read_loop(&capture).unwrap();
}

54
alsa/src/card.rs Normal file
View file

@ -0,0 +1,54 @@
//! Sound card enumeration
use libc::{c_int, c_char};
use super::error::*;
use crate::alsa;
use std::ffi::CStr;
/// An ALSA sound card, uniquely identified by its index.
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Card(c_int);
/// Iterate over existing sound cards.
pub struct Iter(c_int);
impl Iter {
pub fn new() -> Iter { Iter(-1) }
}
impl Iterator for Iter {
type Item = Result<Card>;
fn next(&mut self) -> Option<Result<Card>> {
match acheck!(snd_card_next(&mut self.0)) {
Ok(_) if self.0 == -1 => None,
Ok(_) => Some(Ok(Card(self.0))),
Err(e) => Some(Err(e)),
}
}
}
impl Card {
pub fn new(index: c_int) -> Card { Card(index) }
pub fn from_str(s: &CStr) -> Result<Card> {
acheck!(snd_card_get_index(s.as_ptr())).map(Card)
}
pub fn get_name(&self) -> Result<String> {
let mut c: *mut c_char = ::std::ptr::null_mut();
acheck!(snd_card_get_name(self.0, &mut c))
.and_then(|_| from_alloc("snd_card_get_name", c))
}
pub fn get_longname(&self) -> Result<String> {
let mut c: *mut c_char = ::std::ptr::null_mut();
acheck!(snd_card_get_longname(self.0, &mut c))
.and_then(|_| from_alloc("snd_card_get_longname", c))
}
pub fn get_index(&self) -> c_int { self.0 }
}
#[test]
fn print_cards() {
for a in Iter::new().map(|a| a.unwrap()) {
println!("Card #{}: {} ({})", a.get_index(), a.get_name().unwrap(), a.get_longname().unwrap())
}
}

148
alsa/src/chmap.rs Normal file
View file

@ -0,0 +1,148 @@
use crate::alsa;
use std::{fmt, mem, slice};
use super::error::*;
alsa_enum!(
/// [SND_CHMAP_TYPE_xxx](http://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html) constants
ChmapType, ALL_CHMAP_TYPES[4],
None = SND_CHMAP_TYPE_NONE,
Fixed = SND_CHMAP_TYPE_FIXED,
Var = SND_CHMAP_TYPE_VAR,
Paired = SND_CHMAP_TYPE_PAIRED,
);
alsa_enum!(
/// [SND_CHMAP_xxx](http://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html) constants
ChmapPosition, ALL_CHMAP_POSITIONS[33],
Unknown = SND_CHMAP_UNKNOWN,
NA = SND_CHMAP_NA,
Mono = SND_CHMAP_MONO,
FL = SND_CHMAP_FL,
FR = SND_CHMAP_FR,
RL = SND_CHMAP_RL,
SR = SND_CHMAP_SR,
RC = SND_CHMAP_RC,
FLC = SND_CHMAP_FLC,
FRC = SND_CHMAP_FRC,
RLC = SND_CHMAP_RLC,
RRC = SND_CHMAP_RRC,
FLW = SND_CHMAP_FLW,
FRW = SND_CHMAP_FRW,
FLH = SND_CHMAP_FLH,
FCH = SND_CHMAP_FCH,
FRH = SND_CHMAP_FRH,
TC = SND_CHMAP_TC,
TFL = SND_CHMAP_TFL,
TFR = SND_CHMAP_TFR,
TFC = SND_CHMAP_TFC,
TRL = SND_CHMAP_TRL,
TRR = SND_CHMAP_TRR,
TRC = SND_CHMAP_TRC,
TFLC = SND_CHMAP_TFLC,
TFRC = SND_CHMAP_TFRC,
TSL = SND_CHMAP_TSL,
TSR = SND_CHMAP_TSR,
LLFE = SND_CHMAP_LLFE,
RLFE = SND_CHMAP_RLFE,
BC = SND_CHMAP_BC,
BLC = SND_CHMAP_BLC,
BRC = SND_CHMAP_BRC,
);
impl fmt::Display for ChmapPosition {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = unsafe { alsa::snd_pcm_chmap_long_name(*self as libc::c_uint) };
let s = from_const("snd_pcm_chmap_long_name", s)?;
write!(f, "{}", s)
}
}
/// [snd_pcm_chmap_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html) wrapper
pub struct Chmap(*mut alsa::snd_pcm_chmap_t, bool);
impl Drop for Chmap {
fn drop(&mut self) { if self.1 { unsafe { libc::free(self.0 as *mut libc::c_void) }}}
}
impl Chmap {
fn set_channels(&mut self, c: libc::c_uint) { unsafe { (*self.0) .channels = c }}
fn as_slice_mut(&mut self) -> &mut [libc::c_uint] {
unsafe { slice::from_raw_parts_mut((*self.0).pos.as_mut_ptr(), (*self.0).channels as usize) }
}
fn as_slice(&self) -> &[libc::c_uint] {
unsafe { slice::from_raw_parts((*self.0).pos.as_ptr(), (*self.0).channels as usize) }
}
}
impl fmt::Display for Chmap {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut buf: Vec<libc::c_char> = vec![0; 512];
acheck!(snd_pcm_chmap_print(self.0, buf.len() as libc::size_t, buf.as_mut_ptr()))?;
let s = from_const("snd_pcm_chmap_print", buf.as_mut_ptr())?;
write!(f, "{}", s)
}
}
impl<'a> From<&'a [ChmapPosition]> for Chmap {
fn from(a: &'a [ChmapPosition]) -> Chmap {
let p = unsafe { libc::malloc((mem::size_of::<alsa::snd_pcm_chmap_t>() + mem::size_of::<libc::c_uint>() * a.len()) as libc::size_t) };
if p.is_null() { panic!("Out of memory") }
let mut r = Chmap(p as *mut alsa::snd_pcm_chmap_t, true);
r.set_channels(a.len() as libc::c_uint);
for (i,v) in r.as_slice_mut().iter_mut().enumerate() { *v = a[i] as libc::c_uint }
r
}
}
impl<'a> From<&'a Chmap> for Vec<ChmapPosition> {
fn from(a: &'a Chmap) -> Vec<ChmapPosition> {
a.as_slice().iter().map(|&v| ChmapPosition::from_c_int(v as libc::c_int, "").unwrap()).collect()
}
}
pub fn chmap_new(a: *mut alsa::snd_pcm_chmap_t) -> Chmap { Chmap(a, true) }
pub fn chmap_handle(a: &Chmap) -> *mut alsa::snd_pcm_chmap_t { a.0 }
/// Iterator over available channel maps - see [snd_pcm_chmap_query_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html)
pub struct ChmapsQuery(*mut *mut alsa::snd_pcm_chmap_query_t, isize);
impl Drop for ChmapsQuery {
fn drop(&mut self) { unsafe { alsa::snd_pcm_free_chmaps(self.0) }}
}
pub fn chmaps_query_new(a: *mut *mut alsa::snd_pcm_chmap_query_t) -> ChmapsQuery { ChmapsQuery(a, 0) }
impl Iterator for ChmapsQuery {
type Item = (ChmapType, Chmap);
fn next(&mut self) -> Option<Self::Item> {
if self.0.is_null() { return None; }
let p = unsafe { *self.0.offset(self.1) };
if p.is_null() { return None; }
self.1 += 1;
let t = ChmapType::from_c_int(unsafe { (*p).type_ } as libc::c_int, "snd_pcm_query_chmaps").unwrap();
let m = Chmap(unsafe { &mut (*p).map }, false);
Some((t, m))
}
}
#[test]
fn chmap_for_first_pcm() {
use super::*;
use std::ffi::CString;
use crate::device_name::HintIter;
let i = HintIter::new(None, &*CString::new("pcm").unwrap()).unwrap();
for p in i.map(|n| n.name.unwrap()) {
println!("Chmaps for {:?}:", p);
match PCM::open(&CString::new(p).unwrap(), Direction::Playback, false) {
Ok(a) => for c in a.query_chmaps() {
println!(" {:?}, {}", c.0, c.1);
},
Err(a) => println!(" {}", a) // It's okay to have entries in the name hint array that can't be opened
}
}
}

455
alsa/src/ctl_int.rs Normal file
View file

@ -0,0 +1,455 @@
use crate::alsa;
use std::ffi::{CStr, CString};
use super::error::*;
use super::mixer::MilliBel;
use super::Round;
use std::{ptr, mem, fmt, cmp};
use crate::{Card, poll};
use std::cell::UnsafeCell;
use libc::{c_uint, c_void, size_t, c_long, c_int, pollfd, c_short};
/// We prefer not to allocate for every ElemId, ElemInfo or ElemValue.
/// But we don't know if these will increase in the future or on other platforms.
/// Unfortunately, Rust does not support alloca, so hard-code the sizes for now.
const ELEM_ID_SIZE: usize = 64;
// const ELEM_VALUE_SIZE: usize = 1224;
// const ELEM_INFO_SIZE: usize = 272;
/// [snd_ctl_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper
pub struct Ctl(*mut alsa::snd_ctl_t);
unsafe impl Send for Ctl {}
impl Ctl {
/// Wrapper around open that takes a &str instead of a &CStr
pub fn new(c: &str, nonblock: bool) -> Result<Self> {
Self::open(&CString::new(c).unwrap(), nonblock)
}
/// Open does not support async mode (it's not very Rustic anyway)
pub fn open(c: &CStr, nonblock: bool) -> Result<Ctl> {
let mut r = ptr::null_mut();
let flags = if nonblock { 1 } else { 0 }; // FIXME: alsa::SND_CTL_NONBLOCK does not exist in alsa-sys
acheck!(snd_ctl_open(&mut r, c.as_ptr(), flags)).map(|_| Ctl(r))
}
pub fn from_card(c: &Card, nonblock: bool) -> Result<Ctl> {
let s = format!("hw:{}", c.get_index());
Ctl::open(&CString::new(s).unwrap(), nonblock)
}
pub fn card_info(&self) -> Result<CardInfo> { CardInfo::new().and_then(|c|
acheck!(snd_ctl_card_info(self.0, c.0)).map(|_| c)) }
pub fn wait(&self, timeout_ms: Option<u32>) -> Result<bool> {
acheck!(snd_ctl_wait(self.0, timeout_ms.map(|x| x as c_int).unwrap_or(-1))).map(|i| i == 1) }
pub fn get_db_range(&self, id: &ElemId) -> Result<(MilliBel, MilliBel)> {
let mut min: c_long = 0;
let mut max: c_long = 0;
acheck!(snd_ctl_get_dB_range(self.0, elem_id_ptr(id), &mut min, &mut max))
.map(|_| (MilliBel(min as i64), MilliBel(max as i64)))
}
pub fn convert_to_db(&self, id: &ElemId, volume: i64) -> Result<MilliBel> {
let mut m: c_long = 0;
acheck!(snd_ctl_convert_to_dB(self.0, elem_id_ptr(id), volume as c_long, &mut m))
.map(|_| (MilliBel(m as i64)))
}
pub fn convert_from_db(&self, id: &ElemId, mb: MilliBel, dir: Round) -> Result<i64> {
let mut m: c_long = 0;
acheck!(snd_ctl_convert_from_dB(self.0, elem_id_ptr(id), mb.0 as c_long, &mut m, dir as c_int))
.map(|_| m as i64)
}
pub fn elem_read(&self, val: &mut ElemValue) -> Result<()> {
acheck!(snd_ctl_elem_read(self.0, elem_value_ptr(val))).map(|_| ())
}
pub fn elem_write(&self, val: &ElemValue) -> Result<()> {
acheck!(snd_ctl_elem_write(self.0, elem_value_ptr(val))).map(|_| ())
}
/// Note: According to alsa-lib documentation, you're also supposed to have functionality for
/// returning whether or not you are subscribed. This does not work in practice, so I'm not
/// including that here.
pub fn subscribe_events(&self, subscribe: bool) -> Result<()> {
acheck!(snd_ctl_subscribe_events(self.0, if subscribe { 1 } else { 0 })).map(|_| ())
}
pub fn read(&self) -> Result<Option<Event>> {
let e = event_new()?;
acheck!(snd_ctl_read(self.0, e.0)).map(|r| if r == 1 { Some(e) } else { None })
}
}
impl Drop for Ctl {
fn drop(&mut self) { unsafe { alsa::snd_ctl_close(self.0) }; }
}
impl poll::Descriptors for Ctl {
fn count(&self) -> usize {
unsafe { alsa::snd_ctl_poll_descriptors_count(self.0) as usize }
}
fn fill(&self, p: &mut [pollfd]) -> Result<usize> {
let z = unsafe { alsa::snd_ctl_poll_descriptors(self.0, p.as_mut_ptr(), p.len() as c_uint) };
from_code("snd_ctl_poll_descriptors", z).map(|_| z as usize)
}
fn revents(&self, p: &[pollfd]) -> Result<poll::Flags> {
let mut r = 0;
let z = unsafe { alsa::snd_ctl_poll_descriptors_revents(self.0, p.as_ptr() as *mut pollfd, p.len() as c_uint, &mut r) };
from_code("snd_ctl_poll_descriptors_revents", z).map(|_| poll::Flags::from_bits_truncate(r as c_short))
}
}
pub fn ctl_ptr(a: &Ctl) -> *mut alsa::snd_ctl_t { a.0 }
/// [snd_ctl_card_info_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper
pub struct CardInfo(*mut alsa::snd_ctl_card_info_t);
impl Drop for CardInfo {
fn drop(&mut self) { unsafe { alsa::snd_ctl_card_info_free(self.0) }}
}
impl CardInfo {
fn new() -> Result<CardInfo> {
let mut p = ptr::null_mut();
acheck!(snd_ctl_card_info_malloc(&mut p)).map(|_| CardInfo(p))
}
pub fn get_id(&self) -> Result<&str> {
from_const("snd_ctl_card_info_get_id", unsafe { alsa::snd_ctl_card_info_get_id(self.0) })}
pub fn get_driver(&self) -> Result<&str> {
from_const("snd_ctl_card_info_get_driver", unsafe { alsa::snd_ctl_card_info_get_driver(self.0) })}
pub fn get_components(&self) -> Result<&str> {
from_const("snd_ctl_card_info_get_components", unsafe { alsa::snd_ctl_card_info_get_components(self.0) })}
pub fn get_longname(&self) -> Result<&str> {
from_const("snd_ctl_card_info_get_longname", unsafe { alsa::snd_ctl_card_info_get_longname(self.0) })}
pub fn get_name(&self) -> Result<&str> {
from_const("snd_ctl_card_info_get_name", unsafe { alsa::snd_ctl_card_info_get_name(self.0) })}
pub fn get_mixername(&self) -> Result<&str> {
from_const("snd_ctl_card_info_get_mixername", unsafe { alsa::snd_ctl_card_info_get_mixername(self.0) })}
pub fn get_card(&self) -> Card { Card::new(unsafe { alsa::snd_ctl_card_info_get_card(self.0) })}
}
alsa_enum!(
/// [SND_CTL_ELEM_IFACE_xxx](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) constants
ElemIface, ALL_ELEMIFACE[7],
Card = SND_CTL_ELEM_IFACE_CARD,
Hwdep = SND_CTL_ELEM_IFACE_HWDEP,
Mixer = SND_CTL_ELEM_IFACE_MIXER,
PCM = SND_CTL_ELEM_IFACE_PCM,
Rawmidi = SND_CTL_ELEM_IFACE_RAWMIDI,
Timer = SND_CTL_ELEM_IFACE_TIMER,
Sequencer = SND_CTL_ELEM_IFACE_SEQUENCER,
);
alsa_enum!(
/// [SND_CTL_ELEM_TYPE_xxx](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) constants
ElemType, ALL_ELEMTYPE[7],
None = SND_CTL_ELEM_TYPE_NONE,
Boolean = SND_CTL_ELEM_TYPE_BOOLEAN,
Integer = SND_CTL_ELEM_TYPE_INTEGER,
Enumerated = SND_CTL_ELEM_TYPE_ENUMERATED,
Bytes = SND_CTL_ELEM_TYPE_BYTES,
IEC958 = SND_CTL_ELEM_TYPE_IEC958,
Integer64 = SND_CTL_ELEM_TYPE_INTEGER64,
);
/// [snd_ctl_elem_value_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper
pub struct ElemValue {
ptr: *mut alsa::snd_ctl_elem_value_t,
etype: ElemType,
count: u32,
}
impl Drop for ElemValue {
fn drop(&mut self) { unsafe { alsa::snd_ctl_elem_value_free(self.ptr) }; }
}
pub fn elem_value_ptr(a: &ElemValue) -> *mut alsa::snd_ctl_elem_value_t { a.ptr }
pub fn elem_value_new(t: ElemType, count: u32) -> Result<ElemValue> {
let mut p = ptr::null_mut();
acheck!(snd_ctl_elem_value_malloc(&mut p))
.map(|_| ElemValue { ptr: p, etype: t, count })
}
impl ElemValue {
pub fn set_id(&mut self, id: &ElemId) {
unsafe { alsa::snd_ctl_elem_value_set_id(self.ptr, elem_id_ptr(id)) }
}
// Note: The get_bytes hands out a reference to inside the object. Therefore, we can't treat
// the content as "cell"ed, but must take a "&mut self" (to make sure the reference
// from get_bytes has been dropped when calling a set_* function).
pub fn get_boolean(&self, idx: u32) -> Option<bool> {
if self.etype != ElemType::Boolean || idx >= self.count { None }
else { Some( unsafe { alsa::snd_ctl_elem_value_get_boolean(self.ptr, idx as c_uint) } != 0) }
}
pub fn set_boolean(&mut self, idx: u32, val: bool) -> Option<()> {
if self.etype != ElemType::Boolean || idx >= self.count { None }
else { unsafe { alsa::snd_ctl_elem_value_set_boolean(self.ptr, idx as c_uint, if val {1} else {0}) }; Some(()) }
}
pub fn get_integer(&self, idx: u32) -> Option<i32> {
if self.etype != ElemType::Integer || idx >= self.count { None }
else { Some( unsafe { alsa::snd_ctl_elem_value_get_integer(self.ptr, idx as c_uint) } as i32) }
}
pub fn set_integer(&mut self, idx: u32, val: i32) -> Option<()> {
if self.etype != ElemType::Integer || idx >= self.count { None }
else { unsafe { alsa::snd_ctl_elem_value_set_integer(self.ptr, idx as c_uint, val as c_long) }; Some(()) }
}
pub fn get_integer64(&self, idx: u32) -> Option<i64> {
if self.etype != ElemType::Integer64 || idx >= self.count { None }
else { Some( unsafe { alsa::snd_ctl_elem_value_get_integer64(self.ptr, idx as c_uint) } as i64) }
}
pub fn set_integer64(&mut self, idx: u32, val: i64) -> Option<()> {
if self.etype != ElemType::Integer || idx >= self.count { None }
else { unsafe { alsa::snd_ctl_elem_value_set_integer64(self.ptr, idx as c_uint, val) }; Some(()) }
}
pub fn get_enumerated(&self, idx: u32) -> Option<u32> {
if self.etype != ElemType::Enumerated || idx >= self.count { None }
else { Some( unsafe { alsa::snd_ctl_elem_value_get_enumerated(self.ptr, idx as c_uint) } as u32) }
}
pub fn set_enumerated(&mut self, idx: u32, val: u32) -> Option<()> {
if self.etype != ElemType::Enumerated || idx >= self.count { None }
else { unsafe { alsa::snd_ctl_elem_value_set_enumerated(self.ptr, idx as c_uint, val as c_uint) }; Some(()) }
}
pub fn get_byte(&self, idx: u32) -> Option<u8> {
if self.etype != ElemType::Bytes || idx >= self.count { None }
else { Some( unsafe { alsa::snd_ctl_elem_value_get_byte(self.ptr, idx as c_uint) } as u8) }
}
pub fn set_byte(&mut self, idx: u32, val: u8) -> Option<()> {
if self.etype != ElemType::Bytes || idx >= self.count { None }
else { unsafe { alsa::snd_ctl_elem_value_set_byte(self.ptr, idx as c_uint, val) }; Some(()) }
}
pub fn get_bytes(&self) -> Option<&[u8]> {
if self.etype != ElemType::Bytes { None }
else { Some( unsafe { ::std::slice::from_raw_parts(
alsa::snd_ctl_elem_value_get_bytes(self.ptr) as *const u8, self.count as usize) } ) }
}
pub fn set_bytes(&mut self, val: &[u8]) -> Option<()> {
if self.etype != ElemType::Bytes || val.len() != self.count as usize { None }
// Note: the alsa-lib function definition is broken. First, the pointer is declared as mut even
// though it's const, and second, there is a "value" missing between "elem" and "set_bytes".
else { unsafe { alsa::snd_ctl_elem_set_bytes(self.ptr, val.as_ptr() as *mut c_void, val.len() as size_t) }; Some(()) }
}
/// Creates a new ElemValue.
pub fn new(t: ElemType) -> Result<ElemValue> {
// See max length in include/uapi/sound/asound.h in linux kernel for these values
let count = match t {
ElemType::None => 1,
ElemType::Boolean => 128,
ElemType::Integer => 128,
ElemType::Enumerated => 128,
ElemType::Bytes => 512,
ElemType::IEC958 => 1,
ElemType::Integer64 => 64,
};
// if count > maxcount { return Err(Error::new(Some("ElemValue::new - count too large".into()), 1)) }
let ev = elem_value_new(t, count)?;
unsafe { alsa::snd_ctl_elem_value_clear(elem_value_ptr(&ev)) };
Ok(ev)
}
}
impl fmt::Debug for ElemValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::ElemType::*;
write!(f, "ElemValue({:?}", self.etype)?;
for a in 0..self.count { match self.etype {
Boolean => write!(f, ",{:?}", self.get_boolean(a).unwrap()),
Integer => write!(f, ",{:?}", self.get_integer(a).unwrap()),
Integer64 => write!(f, ",{:?}", self.get_integer64(a).unwrap()),
Enumerated => write!(f, ",{:?}", self.get_enumerated(a).unwrap()),
Bytes => write!(f, ",{:?}", self.get_byte(a).unwrap()),
_ => Ok(()),
}?};
write!(f, ")")
}
}
/// [snd_ctl_elem_info_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper
pub struct ElemInfo(*mut alsa::snd_ctl_elem_info_t);
pub fn elem_info_ptr(a: &ElemInfo) -> *mut alsa::snd_ctl_elem_info_t { a.0 }
impl Drop for ElemInfo {
fn drop(&mut self) { unsafe { alsa::snd_ctl_elem_info_free(self.0) }; }
}
pub fn elem_info_new() -> Result<ElemInfo> {
let mut p = ptr::null_mut();
acheck!(snd_ctl_elem_info_malloc(&mut p)).map(|_| ElemInfo(p))
}
impl ElemInfo {
pub fn get_type(&self) -> ElemType { ElemType::from_c_int(
unsafe { alsa::snd_ctl_elem_info_get_type(self.0) } as c_int, "snd_ctl_elem_info_get_type").unwrap() }
pub fn get_count(&self) -> u32 { unsafe { alsa::snd_ctl_elem_info_get_count(self.0) as u32 } }
}
//
// Non-allocating version of ElemId
//
/// [snd_ctl_elem_id_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper
pub struct ElemId(UnsafeCell<[u8; ELEM_ID_SIZE]>);
pub fn elem_id_new() -> Result<ElemId> {
assert!(unsafe { alsa::snd_ctl_elem_id_sizeof() } as usize <= ELEM_ID_SIZE);
Ok(ElemId(UnsafeCell::new(unsafe { mem::zeroed() })))
}
#[inline]
pub fn elem_id_ptr(a: &ElemId) -> *mut alsa::snd_ctl_elem_id_t { a.0.get() as *mut _ as *mut alsa::snd_ctl_elem_id_t }
unsafe impl Send for ElemId {}
impl Clone for ElemId {
fn clone(&self) -> Self {
ElemId(UnsafeCell::new(unsafe { *self.0.get() }))
}
}
//
// Allocating version of ElemId
//
/*
/// [snd_ctl_elem_id_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper
pub struct ElemId(*mut alsa::snd_ctl_elem_id_t);
impl Drop for ElemId {
fn drop(&mut self) { unsafe { alsa::snd_ctl_elem_id_free(self.0) }; }
}
pub fn elem_id_new() -> Result<ElemId> {
let mut p = ptr::null_mut();
acheck!(snd_ctl_elem_id_malloc(&mut p)).map(|_| ElemId(p))
}
pub fn elem_id_ptr(a: &ElemId) -> *mut alsa::snd_ctl_elem_id_t { a.0 }
*/
impl ElemId {
pub fn get_name(&self) -> Result<&str> {
from_const("snd_hctl_elem_id_get_name", unsafe { alsa::snd_ctl_elem_id_get_name(elem_id_ptr(self)) })}
pub fn get_device(&self) -> u32 { unsafe { alsa::snd_ctl_elem_id_get_device(elem_id_ptr(self)) as u32 }}
pub fn get_subdevice(&self) -> u32 { unsafe { alsa::snd_ctl_elem_id_get_subdevice(elem_id_ptr(self)) as u32 }}
pub fn get_numid(&self) -> u32 { unsafe { alsa::snd_ctl_elem_id_get_numid(elem_id_ptr(self)) as u32 }}
pub fn get_index(&self) -> u32 { unsafe { alsa::snd_ctl_elem_id_get_index(elem_id_ptr(self)) as u32 }}
pub fn get_interface(&self) -> ElemIface { ElemIface::from_c_int(
unsafe { alsa::snd_ctl_elem_id_get_interface(elem_id_ptr(self)) } as c_int, "snd_ctl_elem_id_get_interface").unwrap() }
pub fn set_device(&mut self, v: u32) { unsafe { alsa::snd_ctl_elem_id_set_device(elem_id_ptr(self), v) }}
pub fn set_subdevice(&mut self, v: u32) { unsafe { alsa::snd_ctl_elem_id_set_subdevice(elem_id_ptr(self), v) }}
pub fn set_numid(&mut self, v: u32) { unsafe { alsa::snd_ctl_elem_id_set_numid(elem_id_ptr(self), v) }}
pub fn set_index(&mut self, v: u32) { unsafe { alsa::snd_ctl_elem_id_set_index(elem_id_ptr(self), v) }}
pub fn set_interface(&mut self, v: ElemIface) { unsafe { alsa::snd_ctl_elem_id_set_interface(elem_id_ptr(self), v as u32) }}
pub fn set_name(&mut self, v: &CStr) { unsafe { alsa::snd_ctl_elem_id_set_name(elem_id_ptr(self), v.as_ptr()) }}
/// Creates a new ElemId.
///
/// To ensure safety (i e make sure we never have an invalid interface enum), we need to supply it to the "new" function.
pub fn new(iface: ElemIface) -> Self {
let mut r = elem_id_new().unwrap();
r.set_interface(iface);
r
}
}
impl cmp::Eq for ElemId {}
impl cmp::PartialEq for ElemId {
fn eq(&self, a: &ElemId) -> bool {
self.get_numid() == a.get_numid() && self.get_interface() == a.get_interface() &&
self.get_index() == a.get_index() && self.get_device() == a.get_device() &&
self.get_subdevice() == a.get_subdevice() && self.get_name().ok() == a.get_name().ok()
}
}
impl fmt::Debug for ElemId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let index = self.get_index();
let device = self.get_device();
let subdevice = self.get_subdevice();
write!(f, "ElemId(#{}, {:?}, {:?}", self.get_numid(), self.get_interface(), self.get_name())?;
if index > 0 { write!(f, ", index={}", index)? };
if device > 0 || subdevice > 0 { write!(f, ", device={}", device)? };
if subdevice > 0 { write!(f, ", subdevice={}", device)? };
write!(f, ")")
}
}
/// [snd_ctl_event_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper
pub struct Event(*mut alsa::snd_ctl_event_t);
impl Drop for Event {
fn drop(&mut self) { unsafe { alsa::snd_ctl_event_free(self.0) }; }
}
pub fn event_new() -> Result<Event> {
let mut p = ptr::null_mut();
acheck!(snd_ctl_event_malloc(&mut p)).map(|_| Event(p))
}
impl Event {
pub fn get_mask(&self) -> EventMask { EventMask(unsafe { alsa::snd_ctl_event_elem_get_mask(self.0) as u32 })}
pub fn get_id(&self) -> ElemId {
let r = elem_id_new().unwrap();
unsafe { alsa::snd_ctl_event_elem_get_id(self.0, elem_id_ptr(&r)) };
r
}
}
/// [SND_CTL_EVENT_MASK_XXX](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) bitmask
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct EventMask(pub u32);
impl EventMask {
pub fn remove(&self) -> bool { return self.0 & 0xffffffff == 0xffffffff }
pub fn value(&self) -> bool { return (!self.remove()) && (self.0 & (1 << 0) != 0); }
pub fn info(&self) -> bool { return (!self.remove()) && (self.0 & (1 << 1) != 0); }
pub fn add(&self) -> bool { return (!self.remove()) && (self.0 & (1 << 2) != 0); }
pub fn tlv(&self) -> bool { return (!self.remove()) && (self.0 & (1 << 3) != 0); }
}
#[test]
fn print_sizeof() {
let elemid = unsafe { alsa::snd_ctl_elem_id_sizeof() } as usize;
let elemvalue = unsafe { alsa::snd_ctl_elem_value_sizeof() } as usize;
let eleminfo = unsafe { alsa::snd_ctl_elem_info_sizeof() } as usize;
assert!(elemid <= ELEM_ID_SIZE);
// assert!(elemvalue <= ELEM_VALUE_SIZE);
// assert!(eleminfo <= ELEM_INFO_SIZE);
println!("Elem id: {}, Elem value: {}, Elem info: {}", elemid, elemvalue, eleminfo);
}

90
alsa/src/device_name.rs Normal file
View file

@ -0,0 +1,90 @@
//! Enumerate devices in the alsa library configuration
//!
//! # Example
//! Print all devices found in various categories.
//!
//! ```
//! use std::ffi::CString;
//! use alsa::device_name::HintIter;
//!
//! for t in &["pcm", "ctl", "rawmidi", "timer", "seq", "hwdep"] {
//! println!("{} devices:", t);
//! let i = HintIter::new(None, &*CString::new(*t).unwrap()).unwrap();
//! for a in i { println!(" {:?}", a) }
//! }
//! ```
use std::ptr;
use libc::{c_void, c_int};
use crate::alsa;
use super::{Card, Direction};
use super::error::*;
use std::ffi::{CStr, CString};
/// [snd_device_name_hint](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper
pub struct HintIter(*mut *mut c_void, isize);
impl Drop for HintIter {
fn drop(&mut self) { unsafe { alsa::snd_device_name_free_hint(self.0); }}
}
impl HintIter {
/// typical interfaces are: "pcm", "ctl", "rawmidi", "timer", "seq" and "hwdep".
pub fn new(card: Option<&Card>, iface: &CStr) -> Result<HintIter> {
let mut p = ptr::null_mut();
let cnr = card.map(|c| c.get_index()).unwrap_or(-1) as c_int;
acheck!(snd_device_name_hint(cnr, iface.as_ptr(), &mut p))
.map(|_| HintIter(p, 0))
}
/// A constructor variant that takes the interface as a Rust string slice.
pub fn new_str(card: Option<&Card>, iface: &str) -> Result<HintIter> {
HintIter::new(card, &CString::new(iface).unwrap())
}
}
impl Iterator for HintIter {
type Item = Hint;
fn next(&mut self) -> Option<Hint> {
if self.0.is_null() { return None; }
let p = unsafe { *self.0.offset(self.1) };
if p.is_null() { return None; }
self.1 += 1;
Some(Hint::new(p))
}
}
/// [snd_device_name_get_hint](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper
#[derive(Debug, Clone)]
pub struct Hint {
pub name: Option<String>,
pub desc: Option<String>,
pub direction: Option<Direction>,
}
impl Hint {
fn get_str(p: *const c_void, name: &str) -> Option<String> {
let name = CString::new(name).unwrap();
let c = unsafe { alsa::snd_device_name_get_hint(p, name.as_ptr()) };
from_alloc("snd_device_name_get_hint", c).ok()
}
fn new(p: *const c_void) -> Hint {
let d = Hint::get_str(p, "IOID").and_then(|x| match &*x {
"Input" => Some(Direction::Capture),
"Output" => Some(Direction::Playback),
_ => None,
});
Hint { name: Hint::get_str(p, "NAME"), desc: Hint::get_str(p, "DESC"), direction: d }
}
}
#[test]
fn print_hints() {
for t in &["pcm", "ctl", "rawmidi", "timer", "seq", "hwdep"] {
println!("{} devices:", t);
let i = HintIter::new(None, &*CString::new(*t).unwrap()).unwrap();
for a in i { println!(" {:?}", a) }
}
}

5
alsa/src/direct.rs Normal file
View file

@ -0,0 +1,5 @@
//! Functions that bypass alsa-lib and talk directly to the kernel.
pub mod pcm;
mod ffi;

File diff suppressed because it is too large Load diff

79
alsa/src/direct/ffi.rs Normal file
View file

@ -0,0 +1,79 @@
// Some definitions from the kernel headers
// const SNDRV_PCM_MMAP_OFFSET_DATA: c_uint = 0x00000000;
pub const SNDRV_PCM_MMAP_OFFSET_STATUS: libc::c_uint = 0x80000000;
pub const SNDRV_PCM_MMAP_OFFSET_CONTROL: libc::c_uint = 0x81000000;
pub const SNDRV_PCM_SYNC_PTR_HWSYNC: libc::c_uint = 1;
pub const SNDRV_PCM_SYNC_PTR_APPL: libc::c_uint = 2;
pub const SNDRV_PCM_SYNC_PTR_AVAIL_MIN: libc::c_uint = 4;
// #[repr(C)]
#[allow(non_camel_case_types)]
pub type snd_pcm_state_t = libc::c_int;
// #[repr(C)]
#[allow(non_camel_case_types)]
pub type snd_pcm_uframes_t = libc::c_ulong;
// I think?! Not sure how this will work with X32 ABI?!
#[allow(non_camel_case_types)]
pub type __kernel_off_t = libc::c_long;
#[repr(C)]
#[derive(Copy, Clone)]
pub struct snd_pcm_mmap_status {
pub state: snd_pcm_state_t, /* RO: state - SNDRV_PCM_STATE_XXXX */
pub pad1: libc::c_int, /* Needed for 64 bit alignment */
pub hw_ptr: snd_pcm_uframes_t, /* RO: hw ptr (0...boundary-1) */
pub tstamp: libc::timespec, /* Timestamp */
pub suspended_state: snd_pcm_state_t, /* RO: suspended stream state */
pub audio_tstamp: libc::timespec, /* from sample counter or wall clock */
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct snd_pcm_mmap_control {
pub appl_ptr: snd_pcm_uframes_t, /* RW: appl ptr (0...boundary-1) */
pub avail_min: snd_pcm_uframes_t, /* RW: min available frames for wakeup */
}
#[repr(C)]
#[derive(Debug)]
pub struct snd_pcm_channel_info {
pub channel: libc::c_uint,
pub offset: __kernel_off_t, /* mmap offset */
pub first: libc::c_uint, /* offset to first sample in bits */
pub step: libc::c_uint, /* samples distance in bits */
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union snd_pcm_mmap_status_r {
pub status: snd_pcm_mmap_status,
pub reserved: [libc::c_uchar; 64],
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union snd_pcm_mmap_control_r {
pub control: snd_pcm_mmap_control,
pub reserved: [libc::c_uchar; 64],
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct snd_pcm_sync_ptr {
pub flags: libc::c_uint,
pub s: snd_pcm_mmap_status_r,
pub c: snd_pcm_mmap_control_r,
}
ioctl_read!(sndrv_pcm_ioctl_channel_info, b'A', 0x32, snd_pcm_channel_info);
ioctl_readwrite!(sndrv_pcm_ioctl_sync_ptr, b'A', 0x23, snd_pcm_sync_ptr);
pub fn pagesize() -> usize {
unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
}

630
alsa/src/direct/pcm.rs Normal file
View file

@ -0,0 +1,630 @@
/*!
This module bypasses alsa-lib and directly read and write into memory mapped kernel memory.
In case of the sample memory, this is in many cases the DMA buffers that is transferred to the sound card.
The reasons for doing this are:
* Minimum overhead where it matters most: let alsa-lib do the code heavy setup -
then steal its file descriptor and deal with sample streaming from Rust.
* RT-safety to the maximum extent possible. Creating/dropping any of these structs causes syscalls,
but function calls on these are just read and write from memory. No syscalls, no memory allocations,
not even loops (with the exception of `MmapPlayback::write` that loops over samples to write).
* Possibility to allow Send + Sync for structs
* It's a fun experiment and an interesting deep dive into how alsa-lib does things.
Note: Not all sound card drivers support this direct method of communication; although almost all
modern/common ones do. It only works with hardware devices though (such as "hw:xxx" device strings),
don't expect it to work with, e g, the PulseAudio plugin or so.
For an example of how to use this mode, look in the "synth-example" directory.
*/
use libc;
use std::{mem, ptr, fmt, cmp};
use crate::error::{Error, Result};
use std::os::unix::io::RawFd;
use crate::{pcm, PollDescriptors, Direction};
use crate::pcm::Frames;
use std::marker::PhantomData;
use super::ffi::*;
/// Read PCM status via a simple kernel syscall, bypassing alsa-lib.
///
/// If Status is not available on your architecture, this is the second best option.
pub struct SyncPtrStatus(snd_pcm_mmap_status);
impl SyncPtrStatus {
/// Executes sync_ptr syscall.
///
/// Unsafe because
/// - setting appl_ptr and avail_min might make alsa-lib confused
/// - no check that the fd is really a PCM
pub unsafe fn sync_ptr(fd: RawFd, hwsync: bool, appl_ptr: Option<pcm::Frames>, avail_min: Option<pcm::Frames>) -> Result<Self> {
let mut data = snd_pcm_sync_ptr {
flags: (if hwsync { SNDRV_PCM_SYNC_PTR_HWSYNC } else { 0 }) +
(if appl_ptr.is_some() { SNDRV_PCM_SYNC_PTR_APPL } else { 0 }) +
(if avail_min.is_some() { SNDRV_PCM_SYNC_PTR_AVAIL_MIN } else { 0 }),
c: snd_pcm_mmap_control_r {
control: snd_pcm_mmap_control {
appl_ptr: appl_ptr.unwrap_or(0) as snd_pcm_uframes_t,
avail_min: avail_min.unwrap_or(0) as snd_pcm_uframes_t,
}
},
s: mem::zeroed()
};
sndrv_pcm_ioctl_sync_ptr(fd, &mut data).map_err(|_|
Error::new("SNDRV_PCM_IOCTL_SYNC_PTR", nix::errno::Errno::last() as i32))?;
let i = data.s.status.state;
if (i >= (pcm::State::Open as snd_pcm_state_t)) && (i <= (pcm::State::Disconnected as snd_pcm_state_t)) {
Ok(SyncPtrStatus(data.s.status))
} else {
Err(Error::unsupported("SNDRV_PCM_IOCTL_SYNC_PTR returned broken state"))
}
}
pub fn hw_ptr(&self) -> pcm::Frames { self.0.hw_ptr as pcm::Frames }
pub fn state(&self) -> pcm::State { unsafe { mem::transmute(self.0.state as u8) } /* valid range checked in sync_ptr */ }
pub fn htstamp(&self) -> libc::timespec { self.0.tstamp }
}
/// Read PCM status directly from memory, bypassing alsa-lib.
///
/// This means that it's
/// 1) less overhead for reading status (no syscall, no allocations, no virtual dispatch, just a read from memory)
/// 2) Send + Sync, and
/// 3) will only work for "hw" / "plughw" devices (not e g PulseAudio plugins), and not
/// all of those are supported, although all common ones are (as of 2017, and a kernel from the same decade).
/// Kernel supported archs are: x86, PowerPC, Alpha. Use "SyncPtrStatus" for other archs.
///
/// The values are updated every now and then by the kernel. Many functions will force an update to happen,
/// e g `PCM::avail()` and `PCM::delay()`.
///
/// Note: Even if you close the original PCM device, ALSA will not actually close the device until all
/// Status structs are dropped too.
///
#[derive(Debug)]
pub struct Status(DriverMemory<snd_pcm_mmap_status>);
fn pcm_to_fd(p: &pcm::PCM) -> Result<RawFd> {
let mut fds: [libc::pollfd; 1] = unsafe { mem::zeroed() };
let c = PollDescriptors::fill(p, &mut fds)?;
if c != 1 {
return Err(Error::unsupported("snd_pcm_poll_descriptors returned wrong number of fds"))
}
Ok(fds[0].fd)
}
impl Status {
pub fn new(p: &pcm::PCM) -> Result<Self> { Status::from_fd(pcm_to_fd(p)?) }
pub fn from_fd(fd: RawFd) -> Result<Self> {
DriverMemory::new(fd, 1, SNDRV_PCM_MMAP_OFFSET_STATUS as libc::off_t, false).map(Status)
}
/// Current PCM state.
pub fn state(&self) -> pcm::State {
unsafe {
let i = ptr::read_volatile(&(*self.0.ptr).state);
assert!((i >= (pcm::State::Open as snd_pcm_state_t)) && (i <= (pcm::State::Disconnected as snd_pcm_state_t)));
mem::transmute(i as u8)
}
}
/// Number of frames hardware has read or written
///
/// This number is updated every now and then by the kernel.
/// Calling most functions on the PCM will update it, so will usually a period interrupt.
/// No guarantees given.
///
/// This value wraps at "boundary" (a large value you can read from SwParams).
pub fn hw_ptr(&self) -> pcm::Frames {
unsafe {
ptr::read_volatile(&(*self.0.ptr).hw_ptr) as pcm::Frames
}
}
/// Timestamp - fast version of alsa-lib's Status::get_htstamp
///
/// Note: This just reads the actual value in memory.
/// Unfortunately, the timespec is too big to be read atomically on most archs.
/// Therefore, this function can potentially give bogus result at times, at least in theory...?
pub fn htstamp(&self) -> libc::timespec {
unsafe {
ptr::read_volatile(&(*self.0.ptr).tstamp)
}
}
/// Audio timestamp - fast version of alsa-lib's Status::get_audio_htstamp
///
/// Note: This just reads the actual value in memory.
/// Unfortunately, the timespec is too big to be read atomically on most archs.
/// Therefore, this function can potentially give bogus result at times, at least in theory...?
pub fn audio_htstamp(&self) -> libc::timespec {
unsafe {
ptr::read_volatile(&(*self.0.ptr).audio_tstamp)
}
}
}
/// Write PCM appl ptr directly, bypassing alsa-lib.
///
/// Provides direct access to appl ptr and avail min, without the overhead of
/// alsa-lib or a syscall. Caveats that apply to Status applies to this struct too.
#[derive(Debug)]
pub struct Control(DriverMemory<snd_pcm_mmap_control>);
impl Control {
pub fn new(p: &pcm::PCM) -> Result<Self> { Self::from_fd(pcm_to_fd(p)?) }
pub fn from_fd(fd: RawFd) -> Result<Self> {
DriverMemory::new(fd, 1, SNDRV_PCM_MMAP_OFFSET_CONTROL as libc::off_t, true).map(Control)
}
/// Read number of frames application has read or written
///
/// This value wraps at "boundary" (a large value you can read from SwParams).
pub fn appl_ptr(&self) -> pcm::Frames {
unsafe {
ptr::read_volatile(&(*self.0.ptr).appl_ptr) as pcm::Frames
}
}
/// Set number of frames application has read or written
///
/// When the kernel wakes up due to a period interrupt, this value will
/// be checked by the kernel. An XRUN will happen in case the application
/// has not read or written enough data.
pub fn set_appl_ptr(&self, value: pcm::Frames) {
unsafe {
ptr::write_volatile(&mut (*self.0.ptr).appl_ptr, value as snd_pcm_uframes_t)
}
}
/// Read minimum number of frames in buffer in order to wakeup process
pub fn avail_min(&self) -> pcm::Frames {
unsafe {
ptr::read_volatile(&(*self.0.ptr).avail_min) as pcm::Frames
}
}
/// Write minimum number of frames in buffer in order to wakeup process
pub fn set_avail_min(&self, value: pcm::Frames) {
unsafe {
ptr::write_volatile(&mut (*self.0.ptr).avail_min, value as snd_pcm_uframes_t)
}
}
}
struct DriverMemory<S> {
ptr: *mut S,
size: libc::size_t,
}
impl<S> fmt::Debug for DriverMemory<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "DriverMemory({:?})", self.ptr) }
}
impl<S> DriverMemory<S> {
fn new(fd: RawFd, count: usize, offs: libc::off_t, writable: bool) -> Result<Self> {
let mut total = count * mem::size_of::<S>();
let ps = pagesize();
assert!(total > 0);
if total % ps != 0 { total += ps - total % ps };
let flags = if writable { libc::PROT_WRITE | libc::PROT_READ } else { libc::PROT_READ };
let p = unsafe { libc::mmap(ptr::null_mut(), total, flags, libc::MAP_FILE | libc::MAP_SHARED, fd, offs) };
if p.is_null() || p == libc::MAP_FAILED {
Err(Error::new("mmap (of driver memory)", nix::errno::Errno::last() as i32))
} else {
Ok(DriverMemory { ptr: p as *mut S, size: total })
}
}
}
unsafe impl<S> Send for DriverMemory<S> {}
unsafe impl<S> Sync for DriverMemory<S> {}
impl<S> Drop for DriverMemory<S> {
fn drop(&mut self) {
unsafe {{ libc::munmap(self.ptr as *mut libc::c_void, self.size); } }
}
}
#[derive(Debug)]
struct SampleData<S> {
mem: DriverMemory<S>,
frames: pcm::Frames,
channels: u32,
}
impl<S> SampleData<S> {
pub fn new(p: &pcm::PCM) -> Result<Self> {
let params = p.hw_params_current()?;
let bufsize = params.get_buffer_size()?;
let channels = params.get_channels()?;
if params.get_access()? != pcm::Access::MMapInterleaved {
return Err(Error::unsupported("Not MMAP interleaved data"))
}
let fd = pcm_to_fd(p)?;
let info = unsafe {
let mut info: snd_pcm_channel_info = mem::zeroed();
sndrv_pcm_ioctl_channel_info(fd, &mut info).map_err(|_|
Error::new("SNDRV_PCM_IOCTL_CHANNEL_INFO", nix::errno::Errno::last() as i32))?;
info
};
// println!("{:?}", info);
if (info.step != channels * mem::size_of::<S>() as u32 * 8) || (info.first != 0) {
return Err(Error::unsupported("MMAP data size mismatch"))
}
Ok(SampleData {
mem: DriverMemory::new(fd, (bufsize as usize) * (channels as usize), info.offset as libc::off_t, true)?,
frames: bufsize,
channels,
})
}
}
/// Dummy trait for better generics
pub trait MmapDir: fmt::Debug {
const DIR: Direction;
fn avail(hwptr: Frames, applptr: Frames, buffersize: Frames, boundary: Frames) -> Frames;
}
/// Dummy struct for better generics
#[derive(Copy, Clone, Debug)]
pub struct Playback;
impl MmapDir for Playback {
const DIR: Direction = Direction::Playback;
#[inline]
fn avail(hwptr: Frames, applptr: Frames, buffersize: Frames, boundary: Frames) -> Frames {
let r = hwptr.wrapping_add(buffersize).wrapping_sub(applptr);
let r = if r < 0 { r.wrapping_add(boundary) } else { r };
if r as usize >= boundary as usize { r.wrapping_sub(boundary) } else { r }
}
}
/// Dummy struct for better generics
#[derive(Copy, Clone, Debug)]
pub struct Capture;
impl MmapDir for Capture {
const DIR: Direction = Direction::Capture;
#[inline]
fn avail(hwptr: Frames, applptr: Frames, _buffersize: Frames, boundary: Frames) -> Frames {
let r = hwptr.wrapping_sub(applptr);
if r < 0 { r.wrapping_add(boundary) } else { r }
}
}
pub type MmapPlayback<S> = MmapIO<S, Playback>;
pub type MmapCapture<S> = MmapIO<S, Capture>;
#[derive(Debug)]
/// Struct containing direct I/O functions shared between playback and capture.
pub struct MmapIO<S, D> {
data: SampleData<S>,
c: Control,
ss: Status,
bound: Frames,
dir: PhantomData<*const D>,
}
#[derive(Debug, Clone, Copy)]
/// A raw pointer to samples, and the amount of samples readable or writable.
pub struct RawSamples<S> {
pub ptr: *mut S,
pub frames: Frames,
pub channels: u32,
}
impl<S> RawSamples<S> {
#[inline]
/// Returns `frames` * `channels`, i e the amount of samples (of type `S`) that can be read/written.
pub fn samples(&self) -> isize { self.frames as isize * (self.channels as isize) }
/// Writes samples from an iterator.
///
/// Returns true if iterator was depleted, and the number of samples written.
/// This is just raw read/write of memory.
pub unsafe fn write_samples<I: Iterator<Item=S>>(&self, i: &mut I) -> (bool, isize) {
let mut z = 0;
let max_samples = self.samples();
while z < max_samples {
let b = if let Some(b) = i.next() { b } else { return (true, z) };
ptr::write_volatile(self.ptr.offset(z), b);
z += 1;
};
(false, z)
}
}
impl<S, D: MmapDir> MmapIO<S, D> {
fn new(p: &pcm::PCM) -> Result<Self> {
if p.info()?.get_stream() != D::DIR {
return Err(Error::unsupported("Wrong direction"));
}
let boundary = p.sw_params_current()?.get_boundary()?;
Ok(MmapIO {
data: SampleData::new(p)?,
c: Control::new(p)?,
ss: Status::new(p)?,
bound: boundary,
dir: PhantomData,
})
}
}
pub (crate) fn new_mmap<S, D: MmapDir>(p: &pcm::PCM) -> Result<MmapIO<S, D>> { MmapIO::new(p) }
impl<S, D: MmapDir> MmapIO<S, D> {
/// Read current status
pub fn status(&self) -> &Status { &self.ss }
/// Read current number of frames committed by application
///
/// This number wraps at 'boundary'.
#[inline]
pub fn appl_ptr(&self) -> Frames { self.c.appl_ptr() }
/// Read current number of frames read / written by hardware
///
/// This number wraps at 'boundary'.
#[inline]
pub fn hw_ptr(&self) -> Frames { self.ss.hw_ptr() }
/// The number at which hw_ptr and appl_ptr wraps.
#[inline]
pub fn boundary(&self) -> Frames { self.bound }
/// Total number of frames in hardware buffer
#[inline]
pub fn buffer_size(&self) -> Frames { self.data.frames }
/// Number of channels in stream
#[inline]
pub fn channels(&self) -> u32 { self.data.channels }
/// Notifies the kernel that frames have now been read / written by the application
///
/// This will allow the kernel to write new data into this part of the buffer.
pub fn commit(&self, v: Frames) {
let mut z = self.appl_ptr() + v;
if z + v >= self.boundary() { z -= self.boundary() };
self.c.set_appl_ptr(z)
}
/// Number of frames available to read / write.
///
/// In case of an underrun, this value might be bigger than the buffer size.
pub fn avail(&self) -> Frames { D::avail(self.hw_ptr(), self.appl_ptr(), self.buffer_size(), self.boundary()) }
/// Returns raw pointers to data to read / write.
///
/// Use this if you want to read/write data yourself (instead of using iterators). If you do,
/// using `write_volatile` or `read_volatile` is recommended, since it's DMA memory and can
/// change at any time.
///
/// Since this is a ring buffer, there might be more data to read/write in the beginning
/// of the buffer as well. If so this is returned as the second return value.
pub fn data_ptr(&self) -> (RawSamples<S>, Option<RawSamples<S>>) {
let (hwptr, applptr) = (self.hw_ptr(), self.appl_ptr());
let c = self.channels();
let bufsize = self.buffer_size();
// These formulas mostly mimic the behaviour of
// snd_pcm_mmap_begin (in alsa-lib/src/pcm/pcm.c).
let offs = applptr % bufsize;
let mut a = D::avail(hwptr, applptr, bufsize, self.boundary());
a = cmp::min(a, bufsize);
let b = bufsize - offs;
let more_data = if b < a {
let z = a - b;
a = b;
Some( RawSamples { ptr: self.data.mem.ptr, frames: z, channels: c })
} else { None };
let p = unsafe { self.data.mem.ptr.offset(offs as isize * self.data.channels as isize) };
(RawSamples { ptr: p, frames: a, channels: c }, more_data)
}
}
impl<S> MmapPlayback<S> {
/// Write samples to the kernel ringbuffer.
pub fn write<I: Iterator<Item=S>>(&mut self, i: &mut I) -> Frames {
let (data, more_data) = self.data_ptr();
let (iter_end, samples) = unsafe { data.write_samples(i) };
let mut z = samples / data.channels as isize;
if !iter_end {
if let Some(data2) = more_data {
let (_, samples2) = unsafe { data2.write_samples(i) };
z += samples2 / data2.channels as isize;
}
}
let z = z as Frames;
self.commit(z);
z
}
}
impl<S> MmapCapture<S> {
/// Read samples from the kernel ringbuffer.
///
/// When the iterator is dropped or depleted, the read samples will be committed, i e,
/// the kernel can then write data to the location again. So do this ASAP.
pub fn iter(&mut self) -> CaptureIter<S> {
let (data, more_data) = self.data_ptr();
CaptureIter {
m: self,
samples: data,
p_offs: 0,
read_samples: 0,
next_p: more_data,
}
}
}
/// Iterator over captured samples
pub struct CaptureIter<'a, S: 'static> {
m: &'a MmapCapture<S>,
samples: RawSamples<S>,
p_offs: isize,
read_samples: isize,
next_p: Option<RawSamples<S>>,
}
impl<'a, S: 'static + Copy> CaptureIter<'a, S> {
fn handle_max(&mut self) {
self.p_offs = 0;
if let Some(p2) = self.next_p.take() {
self.samples = p2;
} else {
self.m.commit((self.read_samples / self.samples.channels as isize) as Frames);
self.read_samples = 0;
self.samples.frames = 0; // Shortcut to "None" in case anyone calls us again
}
}
}
impl<'a, S: 'static + Copy> Iterator for CaptureIter<'a, S> {
type Item = S;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.p_offs >= self.samples.samples() {
self.handle_max();
if self.samples.frames <= 0 { return None; }
}
let s = unsafe { ptr::read_volatile(self.samples.ptr.offset(self.p_offs)) };
self.p_offs += 1;
self.read_samples += 1;
Some(s)
}
}
impl<'a, S: 'static> Drop for CaptureIter<'a, S> {
fn drop(&mut self) {
self.m.commit((self.read_samples / self.m.data.channels as isize) as Frames);
}
}
#[test]
#[ignore] // Not everyone has a recording device on plughw:1. So let's ignore this test by default.
fn record_from_plughw_rw() {
use crate::pcm::*;
use crate::{ValueOr, Direction};
use std::ffi::CString;
let pcm = PCM::open(&*CString::new("plughw:1").unwrap(), Direction::Capture, false).unwrap();
let ss = self::Status::new(&pcm).unwrap();
let c = self::Control::new(&pcm).unwrap();
let hwp = HwParams::any(&pcm).unwrap();
hwp.set_channels(2).unwrap();
hwp.set_rate(44100, ValueOr::Nearest).unwrap();
hwp.set_format(Format::s16()).unwrap();
hwp.set_access(Access::RWInterleaved).unwrap();
pcm.hw_params(&hwp).unwrap();
{
let swp = pcm.sw_params_current().unwrap();
swp.set_tstamp_mode(true).unwrap();
pcm.sw_params(&swp).unwrap();
}
assert_eq!(ss.state(), State::Prepared);
pcm.start().unwrap();
assert_eq!(c.appl_ptr(), 0);
println!("{:?}, {:?}", ss, c);
let mut buf = [0i16; 512*2];
assert_eq!(pcm.io_i16().unwrap().readi(&mut buf).unwrap(), 512);
assert_eq!(c.appl_ptr(), 512);
assert_eq!(ss.state(), State::Running);
assert!(ss.hw_ptr() >= 512);
let t2 = ss.htstamp();
assert!(t2.tv_sec > 0 || t2.tv_nsec > 0);
}
#[test]
#[ignore] // Not everyone has a record device on plughw:1. So let's ignore this test by default.
fn record_from_plughw_mmap() {
use crate::pcm::*;
use crate::{ValueOr, Direction};
use std::ffi::CString;
use std::{thread, time};
let pcm = PCM::open(&*CString::new("plughw:1").unwrap(), Direction::Capture, false).unwrap();
let hwp = HwParams::any(&pcm).unwrap();
hwp.set_channels(2).unwrap();
hwp.set_rate(44100, ValueOr::Nearest).unwrap();
hwp.set_format(Format::s16()).unwrap();
hwp.set_access(Access::MMapInterleaved).unwrap();
pcm.hw_params(&hwp).unwrap();
let ss = unsafe { SyncPtrStatus::sync_ptr(pcm_to_fd(&pcm).unwrap(), false, None, None).unwrap() };
assert_eq!(ss.state(), State::Prepared);
let mut m = pcm.direct_mmap_capture::<i16>().unwrap();
assert_eq!(m.status().state(), State::Prepared);
assert_eq!(m.appl_ptr(), 0);
assert_eq!(m.hw_ptr(), 0);
println!("{:?}", m);
let now = time::Instant::now();
pcm.start().unwrap();
while m.avail() < 256 { thread::sleep(time::Duration::from_millis(1)) };
assert!(now.elapsed() >= time::Duration::from_millis(256 * 1000 / 44100));
let (ptr1, md) = m.data_ptr();
assert_eq!(ptr1.channels, 2);
assert!(ptr1.frames >= 256);
assert!(md.is_none());
println!("Has {:?} frames at {:?} in {:?}", m.avail(), ptr1.ptr, now.elapsed());
let samples: Vec<i16> = m.iter().collect();
assert!(samples.len() >= ptr1.frames as usize * 2);
println!("Collected {} samples", samples.len());
let (ptr2, _md) = m.data_ptr();
assert!(unsafe { ptr1.ptr.offset(256 * 2) } <= ptr2.ptr);
}
#[test]
#[ignore]
fn playback_to_plughw_mmap() {
use crate::pcm::*;
use crate::{ValueOr, Direction};
use std::ffi::CString;
let pcm = PCM::open(&*CString::new("plughw:1").unwrap(), Direction::Playback, false).unwrap();
let hwp = HwParams::any(&pcm).unwrap();
hwp.set_channels(2).unwrap();
hwp.set_rate(44100, ValueOr::Nearest).unwrap();
hwp.set_format(Format::s16()).unwrap();
hwp.set_access(Access::MMapInterleaved).unwrap();
pcm.hw_params(&hwp).unwrap();
let mut m = pcm.direct_mmap_playback::<i16>().unwrap();
assert_eq!(m.status().state(), State::Prepared);
assert_eq!(m.appl_ptr(), 0);
assert_eq!(m.hw_ptr(), 0);
println!("{:?}", m);
let mut i = (0..(m.buffer_size() * 2)).map(|i|
(((i / 2) as f32 * 2.0 * ::std::f32::consts::PI / 128.0).sin() * 8192.0) as i16);
m.write(&mut i);
assert_eq!(m.appl_ptr(), m.buffer_size());
pcm.start().unwrap();
pcm.drain().unwrap();
assert_eq!(m.appl_ptr(), m.buffer_size());
assert!(m.hw_ptr() >= m.buffer_size());
}

100
alsa/src/error.rs Normal file
View file

@ -0,0 +1,100 @@
#![macro_use]
use libc::{c_void, c_int, c_char, free};
use std::{fmt, str};
use std::ffi::CStr;
use std::error::Error as StdError;
/// ALSA error
///
/// Most ALSA functions can return a negative error code.
/// If so, then that error code is wrapped into this `Error` struct.
/// An Error is also returned in case ALSA returns a string that
/// cannot be translated into Rust's UTF-8 strings.
#[derive(Debug, Clone, PartialEq, Copy)]
pub struct Error(&'static str, nix::Error);
pub type Result<T> = ::std::result::Result<T, Error>;
macro_rules! acheck {
($f: ident ( $($x: expr),* ) ) => {{
let r = unsafe { alsa::$f( $($x),* ) };
if r < 0 { Err(Error::new(stringify!($f), -r as ::libc::c_int)) }
else { Ok(r) }
}}
}
pub fn from_const<'a>(func: &'static str, s: *const c_char) -> Result<&'a str> {
if s.is_null() { return Err(invalid_str(func)) };
let cc = unsafe { CStr::from_ptr(s) };
str::from_utf8(cc.to_bytes()).map_err(|_| invalid_str(func))
}
pub fn from_alloc(func: &'static str, s: *mut c_char) -> Result<String> {
if s.is_null() { return Err(invalid_str(func)) };
let c = unsafe { CStr::from_ptr(s) };
let ss = str::from_utf8(c.to_bytes()).map_err(|_| {
unsafe { free(s as *mut c_void); }
invalid_str(func)
})?.to_string();
unsafe { free(s as *mut c_void); }
Ok(ss)
}
pub fn from_code(func: &'static str, r: c_int) -> Result<c_int> {
if r < 0 { Err(Error::new(func, r)) }
else { Ok(r) }
}
impl Error {
pub fn new(func: &'static str, res: c_int) -> Error {
let errno = nix::errno::Errno::from_i32(res as i32);
Error(func, errno)
}
pub fn unsupported(func: &'static str) -> Error {
Error(func, nix::Error::ENOTSUP)
}
/// The function which failed.
pub fn func(&self) -> &'static str { self.0 }
/// Underlying error
///
/// Match this against the re-export of `nix::Error` in this crate, not against a specific version
/// of the nix crate. The nix crate version might be updated with minor updates of this library.
pub fn errno(&self) -> nix::Error { self.1 }
/// Underlying error
///
/// Match this against the re-export of `nix::Error` in this crate, not against a specific version
/// of the nix crate. The nix crate version might be updated with minor updates of this library.
pub fn nix_error(&self) -> nix::Error { self.1 }
}
pub fn invalid_str(func: &'static str) -> Error { Error(func, nix::Error::EILSEQ) }
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> { Some(&self.1) }
fn description(&self) -> &str { "ALSA error" }
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ALSA function '{}' failed with error '{}'", self.0, self.1)
}
}
impl From<Error> for fmt::Error {
fn from(_: Error) -> fmt::Error { fmt::Error }
}
#[test]
fn broken_pcm_name() {
use std::ffi::CString;
let e = crate::PCM::open(&*CString::new("this_PCM_does_not_exist").unwrap(), crate::Direction::Playback, false).err().unwrap();
assert_eq!(e.func(), "snd_pcm_open");
assert_eq!(e.errno(), nix::errno::Errno::ENOENT);
}

162
alsa/src/hctl.rs Normal file
View file

@ -0,0 +1,162 @@
//! HCtl API - for mixer control and jack detection
//!
//! # Example
//! Print all jacks and their status
//!
//! ```
//! for a in ::alsa::card::Iter::new().map(|x| x.unwrap()) {
//! use std::ffi::CString;
//! use alsa::hctl::HCtl;
//! let h = HCtl::open(&CString::new(format!("hw:{}", a.get_index())).unwrap(), false).unwrap();
//! h.load().unwrap();
//! for b in h.elem_iter() {
//! use alsa::ctl::ElemIface;
//! let id = b.get_id().unwrap();
//! if id.get_interface() != ElemIface::Card { continue; }
//! let name = id.get_name().unwrap();
//! if !name.ends_with(" Jack") { continue; }
//! if name.ends_with(" Phantom Jack") {
//! println!("{} is always present", &name[..name.len()-13])
//! }
//! else { println!("{} is {}", &name[..name.len()-5],
//! if b.read().unwrap().get_boolean(0).unwrap() { "plugged in" } else { "unplugged" })
//! }
//! }
//! }
//! ```
use crate::alsa;
use std::ffi::{CStr, CString};
use super::error::*;
use std::ptr;
use super::{ctl_int, poll};
use libc::{c_short, c_uint, c_int, pollfd};
/// [snd_hctl_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___h_control.html) wrapper
pub struct HCtl(*mut alsa::snd_hctl_t);
unsafe impl Send for HCtl {}
impl Drop for HCtl {
fn drop(&mut self) { unsafe { alsa::snd_hctl_close(self.0) }; }
}
impl HCtl {
/// Wrapper around open that takes a &str instead of a &CStr
pub fn new(c: &str, nonblock: bool) -> Result<HCtl> {
Self::open(&CString::new(c).unwrap(), nonblock)
}
/// Open does not support async mode (it's not very Rustic anyway)
/// Note: You probably want to call `load` afterwards.
pub fn open(c: &CStr, nonblock: bool) -> Result<HCtl> {
let mut r = ptr::null_mut();
let flags = if nonblock { 1 } else { 0 }; // FIXME: alsa::SND_CTL_NONBLOCK does not exist in alsa-sys
acheck!(snd_hctl_open(&mut r, c.as_ptr(), flags))
.map(|_| HCtl(r))
}
pub fn load(&self) -> Result<()> { acheck!(snd_hctl_load(self.0)).map(|_| ()) }
pub fn elem_iter(&self) -> ElemIter { ElemIter(self, ptr::null_mut()) }
pub fn find_elem(&self, id: &ctl_int::ElemId) -> Option<Elem> {
let p = unsafe { alsa::snd_hctl_find_elem(self.0, ctl_int::elem_id_ptr(id)) };
if p.is_null() { None } else { Some(Elem(self, p)) }
}
pub fn handle_events(&self) -> Result<u32> {
acheck!(snd_hctl_handle_events(self.0)).map(|x| x as u32)
}
pub fn wait(&self, timeout_ms: Option<u32>) -> Result<bool> {
acheck!(snd_hctl_wait(self.0, timeout_ms.map(|x| x as c_int).unwrap_or(-1))).map(|i| i == 1) }
}
impl poll::Descriptors for HCtl {
fn count(&self) -> usize {
unsafe { alsa::snd_hctl_poll_descriptors_count(self.0) as usize }
}
fn fill(&self, p: &mut [pollfd]) -> Result<usize> {
let z = unsafe { alsa::snd_hctl_poll_descriptors(self.0, p.as_mut_ptr(), p.len() as c_uint) };
from_code("snd_hctl_poll_descriptors", z).map(|_| z as usize)
}
fn revents(&self, p: &[pollfd]) -> Result<poll::Flags> {
let mut r = 0;
let z = unsafe { alsa::snd_hctl_poll_descriptors_revents(self.0, p.as_ptr() as *mut pollfd, p.len() as c_uint, &mut r) };
from_code("snd_hctl_poll_descriptors_revents", z).map(|_| poll::Flags::from_bits_truncate(r as c_short))
}
}
/// Iterates over elements for a `HCtl`
pub struct ElemIter<'a>(&'a HCtl, *mut alsa::snd_hctl_elem_t);
impl<'a> Iterator for ElemIter<'a> {
type Item = Elem<'a>;
fn next(&mut self) -> Option<Elem<'a>> {
self.1 = if self.1.is_null() { unsafe { alsa::snd_hctl_first_elem((self.0).0) }}
else { unsafe { alsa::snd_hctl_elem_next(self.1) }};
if self.1.is_null() { None }
else { Some(Elem(self.0, self.1)) }
}
}
/// [snd_hctl_elem_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___h_control.html) wrapper
pub struct Elem<'a>(&'a HCtl, *mut alsa::snd_hctl_elem_t);
impl<'a> Elem<'a> {
pub fn get_id(&self) -> Result<ctl_int::ElemId> {
let v = ctl_int::elem_id_new()?;
unsafe { alsa::snd_hctl_elem_get_id(self.1, ctl_int::elem_id_ptr(&v)) };
Ok(v)
}
pub fn info(&self) -> Result<ctl_int::ElemInfo> {
let v = ctl_int::elem_info_new()?;
acheck!(snd_hctl_elem_info(self.1, ctl_int::elem_info_ptr(&v))).map(|_| v)
}
pub fn read(&self) -> Result<ctl_int::ElemValue> {
let i = self.info()?;
let v = ctl_int::elem_value_new(i.get_type(), i.get_count())?;
acheck!(snd_hctl_elem_read(self.1, ctl_int::elem_value_ptr(&v))).map(|_| v)
}
pub fn write(&self, v: &ctl_int::ElemValue) -> Result<bool> {
acheck!(snd_hctl_elem_write(self.1, ctl_int::elem_value_ptr(v))).map(|e| e > 0)
}
}
#[test]
fn print_hctls() {
for a in super::card::Iter::new().map(|x| x.unwrap()) {
use std::ffi::CString;
let h = HCtl::open(&CString::new(format!("hw:{}", a.get_index())).unwrap(), false).unwrap();
h.load().unwrap();
println!("Card {}:", a.get_name().unwrap());
for b in h.elem_iter() {
println!(" {:?} - {:?}", b.get_id().unwrap(), b.read().unwrap());
}
}
}
#[test]
fn print_jacks() {
for a in super::card::Iter::new().map(|x| x.unwrap()) {
use std::ffi::CString;
let h = HCtl::open(&CString::new(format!("hw:{}", a.get_index())).unwrap(), false).unwrap();
h.load().unwrap();
for b in h.elem_iter() {
let id = b.get_id().unwrap();
if id.get_interface() != super::ctl_int::ElemIface::Card { continue; }
let name = id.get_name().unwrap();
if !name.ends_with(" Jack") { continue; }
if name.ends_with(" Phantom Jack") {
println!("{} is always present", &name[..name.len()-13])
}
else { println!("{} is {}", &name[..name.len()-5],
if b.read().unwrap().get_boolean(0).unwrap() { "plugged in" } else { "unplugged" })
}
}
}
}

49
alsa/src/io.rs Normal file
View file

@ -0,0 +1,49 @@
use crate::alsa;
use super::error::*;
use std::{slice, ptr, fmt};
/// [snd_output_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___output.html) wrapper
pub struct Output(*mut alsa::snd_output_t);
unsafe impl Send for Output {}
impl Drop for Output {
fn drop(&mut self) { unsafe { alsa::snd_output_close(self.0) }; }
}
impl Output {
pub fn buffer_open() -> Result<Output> {
let mut q = ptr::null_mut();
acheck!(snd_output_buffer_open(&mut q)).map(|_| Output(q))
}
pub fn buffer_string<T, F: FnOnce(&[u8]) -> T>(&self, f: F) -> T {
let b = unsafe {
let mut q = ptr::null_mut();
let s = alsa::snd_output_buffer_string(self.0, &mut q);
slice::from_raw_parts(q as *const u8, s as usize)
};
f(b)
}
}
impl fmt::Debug for Output {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Output(")?;
fmt::Display::fmt(self, f)?;
write!(f, ")")
/* self.buffer_string(|b| f.write_str(try!(str::from_utf8(b).map_err(|_| fmt::Error)))) */
}
}
impl fmt::Display for Output {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.buffer_string(|b| {
let s = String::from_utf8_lossy(b);
f.write_str(&*s)
})
}
}
pub fn output_handle(o: &Output) -> *mut alsa::snd_output_t { o.0 }

140
alsa/src/lib.rs Normal file
View file

@ -0,0 +1,140 @@
//! Thin but safe wrappers for [ALSA](https://alsa-project.org).
//!
//! [GitHub repo](https://github.com/diwic/alsa-rs)
//!
//! [Crates.io](https://crates.io/crates/alsa)
//!
//! This ALSA API wrapper/binding is WIP - the ALSA API is huge, and new
//! functions and structs might be added as requested.
//!
//! Most functions map 1-to-1 to alsa-lib functions, e g, `ctl::CardInfo::get_id()` is a wrapper around
//! `snd_ctl_card_info_get_id` and the [alsa-lib documentation](https://www.alsa-project.org/alsa-doc/alsa-lib/)
//! can be consulted for additional information.
//!
//! Enjoy!
#![allow(clippy::all)]
#![warn(clippy::correctness, clippy::suspicious, clippy::perf)]
extern crate alsa_sys as alsa;
extern crate libc;
#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate nix as nix_the_crate;
macro_rules! alsa_enum {
($(#[$attr:meta])+ $name:ident, $static_name:ident [$count:expr], $( $a:ident = $b:ident),* ,) =>
{
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
$(#[$attr])*
pub enum $name {
$(
$a = alsa::$b as isize,
)*
}
static $static_name: [$name; $count] =
[ $( $name::$a, )* ];
impl $name {
/// Returns a slice of all possible values; useful for iteration
pub fn all() -> &'static [$name] { &$static_name[..] }
#[allow(dead_code)]
fn from_c_int(c: ::libc::c_int, s: &'static str) -> Result<$name> {
Self::all().iter().find(|&&x| c == x as ::libc::c_int).map(|&x| x)
.ok_or_else(|| Error::unsupported(s))
}
}
}
}
/// Replaces constants ending with PLAYBACK/CAPTURE as well as
/// INPUT/OUTPUT
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Direction {
Playback,
Capture
}
impl Direction {
#[inline]
pub fn input() -> Direction { Direction::Capture }
#[inline]
pub fn output() -> Direction { Direction::Playback }
}
/// Used to restrict hw parameters. In case the submitted
/// value is unavailable, in which direction should one search
/// for available values?
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum ValueOr {
/// The value set is the submitted value, or less
Less = -1,
/// The value set is the submitted value, or the nearest
Nearest = 0,
/// The value set is the submitted value, or greater
Greater = 1,
}
/// Rounding mode (used in some mixer related calls)
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Round {
/// Round down (towards negative infinity)
Floor = 0,
/// Round up (towards positive infinity)
Ceil = 1,
}
mod error;
pub use crate::error::{Error, Result};
pub mod card;
pub use crate::card::Card as Card;
mod ctl_int;
pub mod ctl {
//! Control device API
pub use super::ctl_int::{Ctl, CardInfo, ElemIface, ElemId, ElemType, ElemValue, ElemInfo};
}
pub use crate::ctl::Ctl as Ctl;
pub mod hctl;
pub use crate::hctl::HCtl as HCtl;
pub mod pcm;
pub use crate::pcm::PCM as PCM;
pub mod rawmidi;
pub use crate::rawmidi::Rawmidi as Rawmidi;
pub mod device_name;
pub mod poll;
pub use crate::poll::Descriptors as PollDescriptors;
pub mod mixer;
pub use crate::mixer::Mixer as Mixer;
pub mod seq;
pub use crate::seq::Seq as Seq;
mod io;
pub use crate::io::Output;
// Reexported inside PCM module
mod chmap;
pub mod direct;
/// Re-exports from the nix crate.
///
/// Use these re-exports instead of also depending on the nix crate. There
/// is no guarantee that these will match a specific nix version, it may
/// change between minor updates of the library.
pub mod nix {
pub use nix_the_crate::Error;
pub use nix_the_crate::errno;
}

639
alsa/src/mixer.rs Normal file
View file

@ -0,0 +1,639 @@
//! Mixer API - Simple Mixer API for mixer control
//!
use std::ffi::{CStr, CString};
use std::{ptr, mem, fmt, ops};
use libc::{c_long, c_int, c_uint, c_short, pollfd};
use crate::poll;
use crate::alsa;
use super::Round;
use super::error::*;
const SELEM_ID_SIZE: usize = 64;
/// wraps [snd_mixer_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___mixer.html)
#[derive(Debug)]
pub struct Mixer(*mut alsa::snd_mixer_t);
unsafe impl Send for Mixer {}
impl Mixer {
/// Opens a mixer and attaches it to a card identified by its name (like hw:0) and loads the
/// mixer after registering a Selem.
pub fn new(name: &str, nonblock: bool) -> Result<Mixer> {
let mut mixer = Mixer::open(nonblock)?;
mixer.attach(&CString::new(name).unwrap())?;
Selem::register(&mut mixer)?;
mixer.load()?;
Ok(mixer)
}
/// Creates a Selem by looking for a specific selem by name given a mixer (of a card)
pub fn find_selem(&self, id: &SelemId) -> Option<Selem> {
let selem = unsafe { alsa::snd_mixer_find_selem(self.0, id.as_ptr()) };
if selem.is_null() { None }
else { Some(Selem(Elem {handle: selem, _mixer: self})) }
}
pub fn open(nonblock: bool) -> Result<Mixer> {
let mut r = ptr::null_mut();
let flags = if nonblock { 1 } else { 0 }; // FIXME: alsa::SND_CTL_NONBLOCK does not exist in alsa-sys
acheck!(snd_mixer_open(&mut r, flags)).map(|_| Mixer(r))
}
pub fn attach(&mut self, name: &CStr) -> Result<()> {
acheck!(snd_mixer_attach(self.0, name.as_ptr())).map(|_| ())
}
pub fn load(&mut self) -> Result<()> {
acheck!(snd_mixer_load(self.0)).map(|_| ())
}
pub fn iter(&self) -> Iter {
Iter {
last_handle: ptr::null_mut(),
mixer: self
}
}
pub fn handle_events(&self) -> Result<u32> {
acheck!(snd_mixer_handle_events(self.0)).map(|x| x as u32)
}
pub fn wait(&self, timeout_ms: Option<u32>) -> Result<bool> {
acheck!(snd_mixer_wait(self.0, timeout_ms.map(|x| x as c_int).unwrap_or(-1))).map(|i| i == 1) }
}
/// Closes mixer and frees used resources
impl Drop for Mixer {
fn drop(&mut self) {
unsafe { alsa::snd_mixer_close(self.0) };
}
}
impl poll::Descriptors for Mixer {
fn count(&self) -> usize {
unsafe { alsa::snd_mixer_poll_descriptors_count(self.0) as usize }
}
fn fill(&self, p: &mut [pollfd]) -> Result<usize> {
let z = unsafe { alsa::snd_mixer_poll_descriptors(self.0, p.as_mut_ptr(), p.len() as c_uint) };
from_code("snd_mixer_poll_descriptors", z).map(|_| z as usize)
}
fn revents(&self, p: &[pollfd]) -> Result<poll::Flags> {
let mut r = 0;
let z = unsafe { alsa::snd_mixer_poll_descriptors_revents(self.0, p.as_ptr() as *mut pollfd, p.len() as c_uint, &mut r) };
from_code("snd_mixer_poll_descriptors_revents", z).map(|_| poll::Flags::from_bits_truncate(r as c_short))
}
}
/// Wrapper for a mB (millibel) value.
///
/// Despite some ALSA functions named "dB", they actually take mB values instead.
/// This is a wrapper type to help with those calculations. Its interior is the
/// actual mB value.
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MilliBel(pub i64);
impl MilliBel {
pub fn to_db(self) -> f32 { (self.0 as f32) / 100.0 }
pub fn from_db(db: f32) -> Self { MilliBel((db * 100.0) as i64) }
}
impl ops::Deref for MilliBel {
type Target = i64;
fn deref(&self) -> &i64 { &self.0 }
}
impl ops::Add for MilliBel {
type Output = MilliBel;
fn add(self, rhs: Self) -> Self { MilliBel(self.0 + rhs.0) }
}
impl ops::AddAssign for MilliBel {
fn add_assign(&mut self, rhs: Self) { self.0 += rhs.0 }
}
impl ops::Sub for MilliBel {
type Output = MilliBel;
fn sub(self, rhs: Self) -> Self { MilliBel(self.0 - rhs.0) }
}
impl ops::SubAssign for MilliBel {
fn sub_assign(&mut self, rhs: Self) { self.0 -= rhs.0 }
}
/// Wraps [snd_mixer_elem_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___mixer.html)
#[derive(Copy, Clone, Debug)]
pub struct Elem<'a>{
handle: *mut alsa::snd_mixer_elem_t,
_mixer: &'a Mixer
}
/// Iterator for all elements of mixer
#[derive(Copy, Clone)]
pub struct Iter<'a>{
last_handle: *mut alsa::snd_mixer_elem_t,
mixer: &'a Mixer
}
impl<'a> Iterator for Iter<'a> {
type Item = Elem<'a>;
fn next(&mut self) -> Option<Elem<'a>> {
let elem = if self.last_handle.is_null() {
unsafe { alsa::snd_mixer_first_elem(self.mixer.0) }
} else {
unsafe { alsa::snd_mixer_elem_next(self.last_handle) }
};
if elem.is_null() {
None
} else {
self.last_handle = elem;
Some(Elem { handle: elem, _mixer: self.mixer})
}
}
}
/// Wrapper for [snd_mixer_selem_id_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___simple_mixer.html)
/// No allocation (uses fixed array)
// #[derive(Copy, Clone, Debug)]
pub struct SelemId([u8; SELEM_ID_SIZE]);
impl SelemId {
pub fn new(name: &str, index: u32) -> SelemId {
let mut s = SelemId::empty();
s.set_name(&CString::new(name).unwrap());
s.set_index(index);
s
}
/// Returns an empty (zeroed) SelemId. This id is not a usable id and need to be initialized
/// like `SelemId::new()` does
pub fn empty() -> SelemId {
assert!(unsafe { alsa::snd_mixer_selem_id_sizeof() } as usize <= SELEM_ID_SIZE);
// Create empty selem_id and fill from mixer
SelemId(unsafe { mem::zeroed() })
}
/// Convert SelemId into ``*mut snd_mixer_selem_id_t` that the alsa call needs.
/// See [snd_mixer_selem_id_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___simple_mixer.html)
#[inline]
fn as_ptr(&self) -> *mut alsa::snd_mixer_selem_id_t {
self.0.as_ptr() as *const _ as *mut alsa::snd_mixer_selem_id_t
}
pub fn get_name(&self) -> Result<&str> {
let c = unsafe { alsa::snd_mixer_selem_id_get_name(self.as_ptr()) };
from_const("snd_mixer_selem_id_get_name", c)
}
pub fn get_index(&self) -> u32 {
unsafe { alsa::snd_mixer_selem_id_get_index(self.as_ptr()) }
}
pub fn set_name(&mut self, name: &CStr) {
unsafe { alsa::snd_mixer_selem_id_set_name(self.as_ptr(), name.as_ptr()) };
}
pub fn set_index(&mut self, index: u32) {
unsafe { alsa::snd_mixer_selem_id_set_index(self.as_ptr(), index) };
}
}
/// Wraps an Elem as a Selem
// #[derive(Copy, Clone)]
pub struct Selem<'a>(Elem<'a>);
impl<'a> Selem<'a> {
/// Creates a Selem by wrapping `elem`.
pub fn new(elem: Elem<'a>) -> Option<Selem<'a>> {
if unsafe { alsa::snd_mixer_elem_get_type(elem.handle) } == alsa::SND_MIXER_ELEM_SIMPLE
{ Some(Selem(elem)) } else { None }
}
/// TODO: This function might change to support regopt and to return the mixer class
pub fn register(mixer: &mut Mixer) -> Result<()> {
acheck!(snd_mixer_selem_register(mixer.0, ptr::null_mut(), ptr::null_mut())).map(|_| ())
}
pub fn get_id(&self) -> SelemId {
let id = SelemId::empty();
unsafe { alsa::snd_mixer_selem_get_id(self.handle, id.as_ptr()) };
id
}
pub fn has_capture_volume(&self) -> bool {
unsafe { alsa::snd_mixer_selem_has_capture_volume(self.handle) > 0 }
}
pub fn has_capture_switch(&self) -> bool {
unsafe { alsa::snd_mixer_selem_has_capture_switch(self.handle) > 0 }
}
pub fn has_playback_volume(&self) -> bool {
unsafe { alsa::snd_mixer_selem_has_playback_volume(self.handle) > 0 }
}
pub fn has_playback_switch(&self) -> bool {
unsafe { alsa::snd_mixer_selem_has_playback_switch(self.handle) > 0 }
}
pub fn can_capture(&self) -> bool {
self.has_capture_volume() || self.has_capture_switch()
}
pub fn can_playback(&self) -> bool {
self.has_playback_volume() || self.has_playback_switch()
}
pub fn has_volume(&self) -> bool {
self.has_capture_volume() || self.has_playback_volume()
}
/// returns range for capture volume as (min, max) values
pub fn get_capture_volume_range(&self) -> (i64, i64) {
let mut min: c_long = 0;
let mut max: c_long = 0;
unsafe { alsa::snd_mixer_selem_get_capture_volume_range(self.handle, &mut min, &mut max) };
(min as i64, max as i64)
}
/// returns (min, max) values.
pub fn get_capture_db_range(&self) -> (MilliBel, MilliBel) {
let mut min: c_long = 0;
let mut max: c_long = 0;
unsafe { alsa::snd_mixer_selem_get_capture_dB_range(self.handle, &mut min, &mut max) };
(MilliBel(min as i64), MilliBel(max as i64))
}
/// returns (min, max) values.
pub fn get_playback_volume_range(&self) -> (i64, i64) {
let mut min: c_long = 0;
let mut max: c_long = 0;
unsafe { alsa::snd_mixer_selem_get_playback_volume_range(self.handle, &mut min, &mut max) };
(min as i64, max as i64)
}
/// returns (min, max) values.
pub fn get_playback_db_range(&self) -> (MilliBel, MilliBel) {
let mut min: c_long = 0;
let mut max: c_long = 0;
unsafe { alsa::snd_mixer_selem_get_playback_dB_range(self.handle, &mut min, &mut max) };
(MilliBel(min as i64), MilliBel(max as i64))
}
pub fn is_playback_mono(&self) -> bool {
unsafe { alsa::snd_mixer_selem_is_playback_mono(self.handle) == 1 }
}
pub fn has_capture_channel(&self, channel: SelemChannelId) -> bool {
unsafe { alsa::snd_mixer_selem_has_capture_channel(self.handle, channel as i32) > 0 }
}
pub fn has_playback_channel(&self, channel: SelemChannelId) -> bool {
unsafe { alsa::snd_mixer_selem_has_playback_channel(self.handle, channel as i32) > 0 }
}
/// Gets name from snd_mixer_selem_channel_name
pub fn channel_name(channel: SelemChannelId) -> Result<&'static str> {
let c = unsafe { alsa::snd_mixer_selem_channel_name(channel as i32) };
from_const("snd_mixer_selem_channel_name", c)
}
pub fn get_playback_volume(&self, channel: SelemChannelId) -> Result<i64> {
let mut value: c_long = 0;
acheck!(snd_mixer_selem_get_playback_volume(self.handle, channel as i32, &mut value)).and_then(|_| Ok(value as i64))
}
/// returns volume in millibels.
pub fn get_playback_vol_db(&self, channel: SelemChannelId) -> Result<MilliBel> {
self.get_playback_volume(channel)
.and_then(|volume| self.ask_playback_vol_db(volume))
}
/// Asks alsa to convert playback volume to millibels.
pub fn ask_playback_vol_db(&self, volume: i64) -> Result<MilliBel> {
let mut decibel_value: c_long = 0;
acheck!(snd_mixer_selem_ask_playback_vol_dB(self.handle, volume as c_long, &mut decibel_value))
.map(|_| MilliBel(decibel_value as i64))
}
pub fn get_capture_volume(&self, channel: SelemChannelId) -> Result<i64> {
let mut value: c_long = 0;
acheck!(snd_mixer_selem_get_capture_volume(self.handle, channel as i32, &mut value)).map(|_| value as i64)
}
/// returns volume in millibels.
pub fn get_capture_vol_db(&self, channel: SelemChannelId) -> Result<MilliBel> {
self.get_capture_volume(channel)
.and_then(|volume| self.ask_capture_vol_db(volume))
}
/// Asks alsa to convert capture volume to millibels
pub fn ask_capture_vol_db(&self, volume: i64) -> Result<MilliBel> {
let mut decibel_value: c_long = 0;
acheck!(snd_mixer_selem_ask_capture_vol_dB (self.handle, volume as c_long, &mut decibel_value))
.map(|_| MilliBel(decibel_value as i64))
}
pub fn set_playback_volume(&self, channel: SelemChannelId, value: i64) -> Result<()> {
acheck!(snd_mixer_selem_set_playback_volume(self.handle, channel as i32, value as c_long)).map(|_| ())
}
pub fn set_playback_volume_range(&self, min: i64, max: i64) -> Result<()> {
acheck!(snd_mixer_selem_set_playback_volume_range(self.handle, min as c_long, max as c_long)).map(|_| ())
}
pub fn set_playback_volume_all(&self, value: i64) -> Result<()> {
acheck!(snd_mixer_selem_set_playback_volume_all(self.handle, value as c_long)).map(|_| ())
}
pub fn set_playback_db(&self, channel: SelemChannelId, value: MilliBel, dir: Round) -> Result<()> {
acheck!(snd_mixer_selem_set_playback_dB(self.handle, channel as i32, *value as c_long, dir as c_int)).map(|_| ())
}
pub fn set_capture_db(&self, channel: SelemChannelId, value: MilliBel, dir: Round) -> Result<()> {
acheck!(snd_mixer_selem_set_capture_dB(self.handle, channel as i32, *value as c_long, dir as c_int)).map(|_| ())
}
pub fn set_playback_db_all(&self, value: MilliBel, dir: Round) -> Result<()> {
acheck!(snd_mixer_selem_set_playback_dB_all(self.handle, *value as c_long, dir as c_int)).map(|_| ())
}
pub fn set_capture_db_all(&self, value: MilliBel, dir: Round) -> Result<()> {
acheck!(snd_mixer_selem_set_capture_dB_all(self.handle, *value as c_long, dir as c_int)).map(|_| ())
}
pub fn set_capture_volume(&self, channel: SelemChannelId, value: i64) -> Result<()> {
acheck!(snd_mixer_selem_set_capture_volume(self.handle, channel as i32, value as c_long)).map(|_| ())
}
pub fn set_capture_volume_range(&self, min: i64, max: i64) -> Result<()> {
acheck!(snd_mixer_selem_set_capture_volume_range(self.handle, min as c_long, max as c_long)).map(|_| ())
}
pub fn set_playback_switch(&self, channel: SelemChannelId, value: i32) -> Result<()> {
acheck!(snd_mixer_selem_set_playback_switch(self.handle, channel as i32, value)).map(|_| ())
}
pub fn set_playback_switch_all(&self, value: i32) -> Result<()> {
acheck!(snd_mixer_selem_set_playback_switch_all(self.handle, value)).map(|_| ())
}
pub fn set_capture_switch(&self, channel: SelemChannelId, value: i32) -> Result<()> {
acheck!(snd_mixer_selem_set_capture_switch(self.handle, channel as i32, value)).map(|_| ())
}
pub fn set_capture_switch_all(&self, value: i32) -> Result<()> {
acheck!(snd_mixer_selem_set_capture_switch_all(self.handle, value)).map(|_| ())
}
pub fn get_playback_switch(&self, channel: SelemChannelId) -> Result<i32> {
let mut value: i32 = 0;
acheck!(snd_mixer_selem_get_playback_switch(self.handle, channel as i32, &mut value)).map(|_| value)
}
pub fn get_capture_switch(&self, channel: SelemChannelId) -> Result<i32> {
let mut value: i32 = 0;
acheck!(snd_mixer_selem_get_capture_switch(self.handle, channel as i32, &mut value)).map(|_| value)
}
pub fn is_enumerated(&self) -> bool {
unsafe { alsa::snd_mixer_selem_is_enumerated(self.handle) == 1 }
}
pub fn is_enum_playback(&self) -> bool {
unsafe { alsa::snd_mixer_selem_is_enum_playback(self.handle) == 1 }
}
pub fn is_enum_capture(&self) -> bool {
unsafe { alsa::snd_mixer_selem_is_enum_capture(self.handle) == 1 }
}
pub fn get_enum_items(&self) -> Result<u32> {
acheck!(snd_mixer_selem_get_enum_items(self.handle)).map(|v| v as u32)
}
pub fn get_enum_item_name(&self, idx: u32) -> Result<String> {
let mut temp = [0 as ::libc::c_char; 128];
acheck!(snd_mixer_selem_get_enum_item_name(self.handle, idx, temp.len()-1, temp.as_mut_ptr()))
.and_then(|_| from_const("snd_mixer_selem_get_enum_item_name", temp.as_ptr()))
.map(|v| v.into())
}
/// Enumerates over valid Enum values
pub fn iter_enum(&self) -> Result<IterEnum> {
Ok(IterEnum(self, 0, self.get_enum_items()?))
}
pub fn get_enum_item(&self, channel: SelemChannelId) -> Result<u32> {
let mut temp = 0;
acheck!(snd_mixer_selem_get_enum_item(self.handle, channel as i32, &mut temp))
.map(|_| temp)
}
pub fn set_enum_item(&self, channel: SelemChannelId, idx: u32) -> Result<()> {
acheck!(snd_mixer_selem_set_enum_item(self.handle, channel as i32, idx))
.map(|_| ())
}
}
impl<'a> ops::Deref for Selem<'a> {
type Target = Elem<'a>;
/// returns the elem of this selem
fn deref(&self) -> &Elem<'a> {
&self.0
}
}
pub struct IterEnum<'a>(&'a Selem<'a>, u32, u32);
impl<'a> Iterator for IterEnum<'a> {
type Item = Result<String>;
fn next(&mut self) -> Option<Self::Item> {
if self.1 >= self.2 { None }
else { self.1 += 1; Some(self.0.get_enum_item_name(self.1-1)) }
}
}
alsa_enum!(
/// Wrapper for [SND_MIXER_SCHN_*](http://www.alsa-project.org/alsa-doc/alsa-lib/group___simple_mixer.html) constants
SelemChannelId, ALL_SELEM_CHANNEL_ID[11],
Unknown = SND_MIXER_SCHN_UNKNOWN,
FrontLeft = SND_MIXER_SCHN_FRONT_LEFT,
FrontRight = SND_MIXER_SCHN_FRONT_RIGHT,
RearLeft = SND_MIXER_SCHN_REAR_LEFT,
RearRight = SND_MIXER_SCHN_REAR_RIGHT,
FrontCenter = SND_MIXER_SCHN_FRONT_CENTER,
Woofer = SND_MIXER_SCHN_WOOFER,
SideLeft = SND_MIXER_SCHN_SIDE_LEFT,
SideRight = SND_MIXER_SCHN_SIDE_RIGHT,
RearCenter = SND_MIXER_SCHN_REAR_CENTER,
Last = SND_MIXER_SCHN_LAST,
);
impl SelemChannelId {
pub fn mono() -> SelemChannelId { SelemChannelId::FrontLeft }
}
impl fmt::Display for SelemChannelId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Selem::channel_name(*self).unwrap())
}
}
#[test]
fn print_mixer_of_cards() {
use super::card;
for card in card::Iter::new().map(|c| c.unwrap()) {
println!("Card #{}: {} ({})", card.get_index(), card.get_name().unwrap(), card.get_longname().unwrap());
let mixer = Mixer::new(&format!("hw:{}", card.get_index()), false).unwrap();
for selem in mixer.iter().filter_map(|e| Selem::new(e)) {
let sid = selem.get_id();
println!("\tMixer element {},{}:", sid.get_name().unwrap(), sid.get_index());
if selem.has_volume() {
print!("\t Volume limits: ");
if selem.has_capture_volume() {
let (vmin, vmax) = selem.get_capture_volume_range();
let (mbmin, mbmax) = selem.get_capture_db_range();
print!("Capture = {} - {}", vmin, vmax);
print!(" ({} dB - {} dB)", mbmin.to_db(), mbmax.to_db());
}
if selem.has_playback_volume() {
let (vmin, vmax) = selem.get_playback_volume_range();
let (mbmin, mbmax) = selem.get_playback_db_range();
print!("Playback = {} - {}", vmin, vmax);
print!(" ({} dB - {} dB)", mbmin.to_db(), mbmax.to_db());
}
println!();
}
if selem.is_enumerated() {
print!("\t Valid values: ");
for v in selem.iter_enum().unwrap() { print!("{}, ", v.unwrap()) };
print!("\n\t Current values: ");
for v in SelemChannelId::all().iter().filter_map(|&v| selem.get_enum_item(v).ok()) {
print!("{}, ", selem.get_enum_item_name(v).unwrap());
}
println!();
}
if selem.can_capture() {
print!("\t Capture channels: ");
for channel in SelemChannelId::all() {
if selem.has_capture_channel(*channel) { print!("{}, ", channel) };
}
println!();
print!("\t Capture volumes: ");
for channel in SelemChannelId::all() {
if selem.has_capture_channel(*channel) { print!("{}: {} ({} dB), ", channel,
match selem.get_capture_volume(*channel) {Ok(v) => format!("{}", v), Err(_) => "n/a".to_string()},
match selem.get_capture_vol_db(*channel) {Ok(v) => format!("{}", v.to_db()), Err(_) => "n/a".to_string()}
);}
}
println!();
}
if selem.can_playback() {
print!("\t Playback channels: ");
if selem.is_playback_mono() {
print!("Mono");
} else {
for channel in SelemChannelId::all() {
if selem.has_playback_channel(*channel) { print!("{}, ", channel) };
}
}
println!();
if selem.has_playback_volume() {
print!("\t Playback volumes: ");
for channel in SelemChannelId::all() {
if selem.has_playback_channel(*channel) { print!("{}: {} / {}dB, ",
channel,
match selem.get_playback_volume(*channel) {Ok(v) => format!("{}", v), Err(_) => "n/a".to_string()},
match selem.get_playback_vol_db(*channel) {Ok(v) => format!("{}", v.to_db()), Err(_) => "n/a".to_string()}
);}
}
println!();
}
}
}
}
}
#[test]
#[ignore]
fn get_and_set_playback_volume() {
let mixer = Mixer::new("hw:1", false).unwrap();
let selem = mixer.find_selem(&SelemId::new("Master", 0)).unwrap();
let (rmin, rmax) = selem.get_playback_volume_range();
let mut channel = SelemChannelId::mono();
for c in SelemChannelId::all().iter() {
if selem.has_playback_channel(*c) { channel = *c; break }
}
println!("Testing on {} with limits {}-{} on channel {}", selem.get_id().get_name().unwrap(), rmin, rmax, channel);
let old: i64 = selem.get_playback_volume(channel).unwrap();
let new: i64 = rmax / 2;
assert_ne!(new, old);
println!("Changing volume of {} from {} to {}", channel, old, new);
selem.set_playback_volume(channel, new).unwrap();
let mut result: i64 = selem.get_playback_volume(channel).unwrap();
assert_eq!(new, result);
// return volume to old value
selem.set_playback_volume(channel, old).unwrap();
result = selem.get_playback_volume(channel).unwrap();
assert_eq!(old, result);
}
#[test]
#[ignore]
fn get_and_set_capture_volume() {
let mixer = Mixer::new("hw:1", false).unwrap();
let selem = mixer.find_selem(&SelemId::new("Capture", 0)).unwrap();
let (rmin, rmax) = selem.get_capture_volume_range();
let mut channel = SelemChannelId::mono();
for c in SelemChannelId::all().iter() {
if selem.has_playback_channel(*c) { channel = *c; break }
}
println!("Testing on {} with limits {}-{} on channel {}", selem.get_id().get_name().unwrap(), rmin, rmax, channel);
let old: i64 = selem.get_capture_volume(channel).unwrap();
let new: i64 = rmax / 2;
assert_ne!(new, old);
println!("Changing volume of {} from {} to {}", channel, old, new);
selem.set_capture_volume(channel, new).unwrap();
let mut result: i64 = selem.get_capture_volume(channel).unwrap();
assert_eq!(new, result);
// return volume to old value
selem.set_capture_volume(channel, old).unwrap();
result = selem.get_capture_volume(channel).unwrap();
assert_eq!(old, result);
}
#[test]
fn print_sizeof() {
let selemid = unsafe { alsa::snd_mixer_selem_id_sizeof() } as usize;
assert!(selemid <= SELEM_ID_SIZE);
println!("Selem id: {}", selemid);
}

1151
alsa/src/pcm.rs Normal file

File diff suppressed because it is too large Load diff

68
alsa/src/poll.rs Normal file
View file

@ -0,0 +1,68 @@
//! Tiny poll ffi
//!
//! A tiny wrapper around libc's poll system call.
use libc;
use super::error::*;
use std::io;
pub use libc::pollfd;
bitflags! {
pub struct Flags: ::libc::c_short {
const IN = ::libc::POLLIN;
const PRI = ::libc::POLLPRI;
const OUT = ::libc::POLLOUT;
const ERR = ::libc::POLLERR;
const HUP = ::libc::POLLHUP;
const NVAL = ::libc::POLLNVAL;
}
}
pub trait Descriptors {
fn count(&self) -> usize;
fn fill(&self, _: &mut [pollfd]) -> Result<usize>;
fn revents(&self, _: &[pollfd]) -> Result<Flags>;
/// Wrapper around count and fill - returns an array of pollfds
fn get(&self) -> Result<Vec<pollfd>> {
let mut v = vec![pollfd { fd: 0, events: 0, revents: 0 }; self.count()];
if self.fill(&mut v)? != v.len() { Err(Error::unsupported("did not fill the poll descriptors array")) }
else { Ok(v) }
}
}
impl Descriptors for pollfd {
fn count(&self) -> usize { 1 }
fn fill(&self, a: &mut [pollfd]) -> Result<usize> { a[0] = *self; Ok(1) }
fn revents(&self, a: &[pollfd]) -> Result<Flags> { Ok(Flags::from_bits_truncate(a[0].revents)) }
}
/// Wrapper around the libc poll call.
pub fn poll(fds: &mut[pollfd], timeout: i32) -> Result<usize> {
let r = unsafe { libc::poll(fds.as_mut_ptr(), fds.len() as libc::nfds_t, timeout as libc::c_int) };
if r >= 0 { Ok(r as usize) } else {
from_code("poll", -io::Error::last_os_error().raw_os_error().unwrap()).map(|_| unreachable!())
}
}
/// Builds a pollfd array, polls it, and returns the poll descriptors which have non-zero revents.
pub fn poll_all<'a>(desc: &[&'a dyn Descriptors], timeout: i32) -> Result<Vec<(&'a dyn Descriptors, Flags)>> {
let mut pollfds: Vec<pollfd> = vec!();
let mut indices = vec!();
for v2 in desc.iter().map(|q| q.get()) {
let v = v2?;
indices.push(pollfds.len() .. pollfds.len()+v.len());
pollfds.extend(v);
};
poll(&mut pollfds, timeout)?;
let mut res = vec!();
for (i, r) in indices.into_iter().enumerate() {
let z = desc[i].revents(&pollfds[r])?;
if !z.is_empty() { res.push((desc[i], z)); }
}
Ok(res)
}

211
alsa/src/rawmidi.rs Normal file
View file

@ -0,0 +1,211 @@
//! MIDI devices I/O and enumeration
use libc::{c_int, c_uint, c_void, size_t, c_short, pollfd};
use super::ctl_int::{ctl_ptr, Ctl};
use super::{Direction, poll};
use super::error::*;
use crate::alsa;
use std::{ptr, io};
use std::ffi::{CStr, CString};
/// Iterator over [Rawmidi](http://www.alsa-project.org/alsa-doc/alsa-lib/group___raw_midi.html) devices and subdevices
pub struct Iter<'a> {
ctl: &'a Ctl,
device: c_int,
in_count: i32,
out_count: i32,
current: i32,
}
/// [snd_rawmidi_info_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___raw_midi.html) wrapper
pub struct Info(*mut alsa::snd_rawmidi_info_t);
impl Drop for Info {
fn drop(&mut self) { unsafe { alsa::snd_rawmidi_info_free(self.0) }; }
}
impl Info {
fn new() -> Result<Info> {
let mut p = ptr::null_mut();
acheck!(snd_rawmidi_info_malloc(&mut p)).map(|_| Info(p))
}
fn from_iter(c: &Ctl, device: i32, sub: i32, dir: Direction) -> Result<Info> {
let r = Info::new()?;
unsafe { alsa::snd_rawmidi_info_set_device(r.0, device as c_uint) };
let d = match dir {
Direction::Playback => alsa::SND_RAWMIDI_STREAM_OUTPUT,
Direction::Capture => alsa::SND_RAWMIDI_STREAM_INPUT,
};
unsafe { alsa::snd_rawmidi_info_set_stream(r.0, d) };
unsafe { alsa::snd_rawmidi_info_set_subdevice(r.0, sub as c_uint) };
acheck!(snd_ctl_rawmidi_info(ctl_ptr(c), r.0)).map(|_| r)
}
fn subdev_count(c: &Ctl, device: c_int) -> Result<(i32, i32)> {
let i = Info::from_iter(c, device, 0, Direction::Capture)?;
let o = Info::from_iter(c, device, 0, Direction::Playback)?;
Ok((unsafe { alsa::snd_rawmidi_info_get_subdevices_count(o.0) as i32 },
unsafe { alsa::snd_rawmidi_info_get_subdevices_count(i.0) as i32 }))
}
pub fn get_device(&self) -> i32 { unsafe { alsa::snd_rawmidi_info_get_device(self.0) as i32 }}
pub fn get_subdevice(&self) -> i32 { unsafe { alsa::snd_rawmidi_info_get_subdevice(self.0) as i32 }}
pub fn get_stream(&self) -> super::Direction {
if unsafe { alsa::snd_rawmidi_info_get_stream(self.0) } == alsa::SND_RAWMIDI_STREAM_OUTPUT { super::Direction::Playback }
else { super::Direction::Capture }
}
pub fn get_subdevice_name(&self) -> Result<String> {
let c = unsafe { alsa::snd_rawmidi_info_get_subdevice_name(self.0) };
from_const("snd_rawmidi_info_get_subdevice_name", c).map(|s| s.to_string())
}
pub fn get_id(&self) -> Result<String> {
let c = unsafe { alsa::snd_rawmidi_info_get_id(self.0) };
from_const("snd_rawmidi_info_get_id", c).map(|s| s.to_string())
}
}
/// [snd_rawmidi_info_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___raw_midi.html) wrapper
pub struct Status(*mut alsa::snd_rawmidi_status_t);
impl Status {
fn new() -> Result<Self> {
let mut p = ptr::null_mut();
acheck!(snd_rawmidi_status_malloc(&mut p)).map(|_| Status(p))
}
}
impl Status {
pub fn get_avail(&self) -> usize { unsafe { alsa::snd_rawmidi_status_get_avail(self.0 as *const _) } }
pub fn get_xruns(&self) -> usize { unsafe { alsa::snd_rawmidi_status_get_xruns(self.0 as *const _) } }
}
impl Drop for Status {
fn drop(&mut self) { unsafe { alsa::snd_rawmidi_status_free(self.0) }; }
}
impl<'a> Iter<'a> {
pub fn new(c: &'a Ctl) -> Iter<'a> { Iter { ctl: c, device: -1, in_count: 0, out_count: 0, current: 0 }}
}
impl<'a> Iterator for Iter<'a> {
type Item = Result<Info>;
fn next(&mut self) -> Option<Result<Info>> {
if self.current < self.in_count {
self.current += 1;
return Some(Info::from_iter(self.ctl, self.device, self.current-1, Direction::Capture));
}
if self.current - self.in_count < self.out_count {
self.current += 1;
return Some(Info::from_iter(self.ctl, self.device, self.current-1-self.in_count, Direction::Playback));
}
let r = acheck!(snd_ctl_rawmidi_next_device(ctl_ptr(self.ctl), &mut self.device));
match r {
Err(e) => return Some(Err(e)),
Ok(_) if self.device == -1 => return None,
_ => {},
}
self.current = 0;
match Info::subdev_count(self.ctl, self.device) {
Err(e) => Some(Err(e)),
Ok((oo, ii)) => {
self.in_count = ii;
self.out_count = oo;
self.next()
}
}
}
}
/// [snd_rawmidi_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___raw_midi.html) wrapper
pub struct Rawmidi(*mut alsa::snd_rawmidi_t);
unsafe impl Send for Rawmidi {}
impl Drop for Rawmidi {
fn drop(&mut self) { unsafe { alsa::snd_rawmidi_close(self.0) }; }
}
impl Rawmidi {
/// Wrapper around open that takes a &str instead of a &CStr
pub fn new(name: &str, dir: Direction, nonblock: bool) -> Result<Self> {
Self::open(&CString::new(name).unwrap(), dir, nonblock)
}
pub fn open(name: &CStr, dir: Direction, nonblock: bool) -> Result<Rawmidi> {
let mut h = ptr::null_mut();
let flags = if nonblock { 2 } else { 0 }; // FIXME: alsa::SND_RAWMIDI_NONBLOCK does not exist in alsa-sys
acheck!(snd_rawmidi_open(
if dir == Direction::Capture { &mut h } else { ptr::null_mut() },
if dir == Direction::Playback { &mut h } else { ptr::null_mut() },
name.as_ptr(), flags))
.map(|_| Rawmidi(h))
}
pub fn info(&self) -> Result<Info> {
Info::new().and_then(|i| acheck!(snd_rawmidi_info(self.0, i.0)).map(|_| i))
}
pub fn status(&self) -> Result<Status> {
Status::new().and_then(|i| acheck!(snd_rawmidi_status(self.0, i.0)).map(|_| i))
}
pub fn drop(&self) -> Result<()> { acheck!(snd_rawmidi_drop(self.0)).map(|_| ()) }
pub fn drain(&self) -> Result<()> { acheck!(snd_rawmidi_drain(self.0)).map(|_| ()) }
pub fn name(&self) -> Result<String> {
let c = unsafe { alsa::snd_rawmidi_name(self.0) };
from_const("snd_rawmidi_name", c).map(|s| s.to_string())
}
pub fn io(&self) -> IO { IO(self) }
}
impl poll::Descriptors for Rawmidi {
fn count(&self) -> usize {
unsafe { alsa::snd_rawmidi_poll_descriptors_count(self.0) as usize }
}
fn fill(&self, p: &mut [pollfd]) -> Result<usize> {
let z = unsafe { alsa::snd_rawmidi_poll_descriptors(self.0, p.as_mut_ptr(), p.len() as c_uint) };
from_code("snd_rawmidi_poll_descriptors", z).map(|_| z as usize)
}
fn revents(&self, p: &[pollfd]) -> Result<poll::Flags> {
let mut r = 0;
let z = unsafe { alsa::snd_rawmidi_poll_descriptors_revents(self.0, p.as_ptr() as *mut pollfd, p.len() as c_uint, &mut r) };
from_code("snd_rawmidi_poll_descriptors_revents", z).map(|_| poll::Flags::from_bits_truncate(r as c_short))
}
}
/// Implements `std::io::Read` and `std::io::Write` for `Rawmidi`
pub struct IO<'a>(&'a Rawmidi);
impl<'a> io::Read for IO<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let r = unsafe { alsa::snd_rawmidi_read((self.0).0, buf.as_mut_ptr() as *mut c_void, buf.len() as size_t) };
if r < 0 { Err(io::Error::from_raw_os_error(r as i32)) }
else { Ok(r as usize) }
}
}
impl<'a> io::Write for IO<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let r = unsafe { alsa::snd_rawmidi_write((self.0).0, buf.as_ptr() as *const c_void, buf.len() as size_t) };
if r < 0 { Err(io::Error::from_raw_os_error(r as i32)) }
else { Ok(r as usize) }
}
fn flush(&mut self) -> io::Result<()> { Ok(()) }
}
#[test]
fn print_rawmidis() {
for a in super::card::Iter::new().map(|a| a.unwrap()) {
for b in Iter::new(&Ctl::from_card(&a, false).unwrap()).map(|b| b.unwrap()) {
println!("Rawmidi {:?} (hw:{},{},{}) {} - {}", b.get_stream(), a.get_index(), b.get_device(), b.get_subdevice(),
a.get_name().unwrap(), b.get_subdevice_name().unwrap())
}
}
}

1567
alsa/src/seq.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,9 @@
[package]
name = "synth-example"
version = "0.1.0"
authors = ["David Henningsson <coding@diwic.se>"]
edition = "2018"
[dependencies]
dasp = { version = "0.11", features = ["signal"] }
alsa = { path = "..", version = "0.6" }

View file

@ -0,0 +1,311 @@
// A quickly made Hammond organ.
use std::{iter, error};
use alsa::{seq, pcm};
use std::ffi::CString;
use dasp::signal;
type Res<T> = Result<T, Box<dyn error::Error>>;
fn connect_midi_source_ports(s: &alsa::Seq, our_port: i32) -> Res<()> {
// Iterate over clients and clients' ports
let our_id = s.client_id()?;
let ci = seq::ClientIter::new(&s);
for client in ci {
if client.get_client() == our_id { continue; } // Skip ourselves
let pi = seq::PortIter::new(&s, client.get_client());
for port in pi {
let caps = port.get_capability();
// Check that it's a normal input port
if !caps.contains(seq::PortCap::READ) || !caps.contains(seq::PortCap::SUBS_READ) { continue; }
if !port.get_type().contains(seq::PortType::MIDI_GENERIC) { continue; }
// Connect source and dest ports
let subs = seq::PortSubscribe::empty()?;
subs.set_sender(seq::Addr { client: port.get_client(), port: port.get_port() });
subs.set_dest(seq::Addr { client: our_id, port: our_port });
println!("Reading from midi input {:?}", port);
s.subscribe_port(&subs)?;
}
}
Ok(())
}
fn open_midi_dev() -> Res<alsa::Seq> {
// Open the sequencer.
let s = alsa::Seq::open(None, Some(alsa::Direction::Capture), true)?;
let cstr = CString::new("rust_synth_example").unwrap();
s.set_client_name(&cstr)?;
// Create a destination port we can read from
let mut dinfo = seq::PortInfo::empty().unwrap();
dinfo.set_capability(seq::PortCap::WRITE | seq::PortCap::SUBS_WRITE);
dinfo.set_type(seq::PortType::MIDI_GENERIC | seq::PortType::APPLICATION);
dinfo.set_name(&cstr);
s.create_port(&dinfo).unwrap();
let dport = dinfo.get_port();
// source ports should ideally be configurable, but right now we're just reading them all.
connect_midi_source_ports(&s, dport)?;
Ok(s)
}
fn open_audio_dev() -> Res<(alsa::PCM, u32)> {
let args: Vec<_> = std::env::args().collect();
if args.len() < 2 {
println!("Usage: 'cargo run --release CARD_NAME SAMPLE_RATE BUF_SIZE'");
Err("No card name specified")?
}
let req_devname = format!("hw:{}", args[1]);
let req_samplerate = args.get(2).map(|x| x.parse()).unwrap_or(Ok(48000))?;
let req_bufsize = args.get(3).map(|x| x.parse()).unwrap_or(Ok(256))?; // A few ms latency by default, that should be nice
// Open the device
let p = alsa::PCM::new(&req_devname, alsa::Direction::Playback, false)?;
// Set hardware parameters
{
let hwp = pcm::HwParams::any(&p)?;
hwp.set_channels(2)?;
hwp.set_rate(req_samplerate, alsa::ValueOr::Nearest)?;
hwp.set_format(pcm::Format::s16())?;
hwp.set_access(pcm::Access::MMapInterleaved)?;
hwp.set_buffer_size(req_bufsize)?;
hwp.set_period_size(req_bufsize / 4, alsa::ValueOr::Nearest)?;
p.hw_params(&hwp)?;
}
// Set software parameters
let rate = {
let hwp = p.hw_params_current()?;
let swp = p.sw_params_current()?;
let (bufsize, periodsize) = (hwp.get_buffer_size()?, hwp.get_period_size()?);
swp.set_start_threshold(bufsize - periodsize)?;
swp.set_avail_min(periodsize)?;
p.sw_params(&swp)?;
println!("Opened audio output {:?} with parameters: {:?}, {:?}", req_devname, hwp, swp);
hwp.get_rate()?
};
Ok((p, rate))
}
// Sample format
type SF = i16;
type SigGen = signal::Sine<signal::ConstHz>;
// Standard Hammond drawbar.
const BAR_FREQS: [f64; 9] = [16., 5.+1./3., 8., 4., 2.+2./3., 2., 1.+3./5., 1.+1./3., 1.];
#[derive(Clone)]
struct Sig {
note: u8,
sig: SigGen,
targetvol: f64,
curvol: f64,
baridx: usize,
}
struct Synth {
sigs: Vec<Option<Sig>>,
sample_rate: signal::Rate,
stored_sample: Option<SF>,
bar_values: [f64; 9],
}
impl Synth {
fn add_note(&mut self, note: u8, vol: f64) {
let hz = 440. * 2_f64.powf((note as f64 - 69.)/12.);
for (baridx, barfreq) in BAR_FREQS.iter().enumerate() {
let idx = self.sigs.iter().position(|s| s.is_none());
let idx = if let Some(idx) = idx { idx } else {
println!("Voice overflow!"); return;
};
let hz = self.sample_rate.const_hz(hz * 8. / barfreq);
let s = Sig { sig: hz.sine(), note, targetvol: vol, curvol: 0., baridx };
self.sigs[idx] = Some(s);
}
}
fn remove_note(&mut self, note: u8) {
for i in self.sigs.iter_mut() {
if let &mut Some(ref mut i) = i {
if i.note == note { i.targetvol = 0. }
}
}
}
fn cc(&mut self, ctrl: u32, value: i32) {
let idx = match ctrl {
// Standard knobs on UMA25S, modify to your liking
1 => 0,
74 => 1,
71 => 2,
73 => 3,
75 => 4,
72 => 5,
91 => 6,
93 => 7,
10 => 8,
_ => return,
};
self.bar_values[idx] = f64::from(value) / 255.;
}
}
impl Iterator for Synth {
type Item = SF;
fn next(&mut self) -> Option<Self::Item> {
use dasp::{signal::Signal, Sample};
// Mono -> Stereo
if let Some(s) = self.stored_sample.take() { return Some(s) };
let mut z = 0f64;
for sig in &mut self.sigs {
let mut remove = false;
if let &mut Some(ref mut i) = sig {
let barvalue = self.bar_values[i.baridx];
if barvalue > 0.0 {
let s = i.sig.next();
z += s.mul_amp(i.curvol * barvalue);
}
// Quick and dirty volume envelope to avoid clicks.
if i.curvol != i.targetvol {
if i.targetvol == 0. {
i.curvol -= 0.002;
if i.curvol <= 0. { remove = true; }
} else {
i.curvol += 0.002;
if i.curvol >= i.targetvol { i.curvol = i.targetvol; }
}
}
}
if remove { *sig = None };
}
let z = z.min(0.999).max(-0.999);
let z: Option<SF> = Some(SF::from_sample(z));
self.stored_sample = z;
z
}
}
fn write_samples_direct(p: &alsa::PCM, mmap: &mut alsa::direct::pcm::MmapPlayback<SF>, synth: &mut Synth)
-> Res<bool> {
if mmap.avail() > 0 {
// Write samples to DMA area from iterator
mmap.write(synth);
}
use alsa::pcm::State;
match mmap.status().state() {
State::Running => { return Ok(false); }, // All fine
State::Prepared => { println!("Starting audio output stream"); p.start()? },
State::XRun => { println!("Underrun in audio output stream!"); p.prepare()? },
State::Suspended => { println!("Resuming audio output stream"); p.resume()? },
n @ _ => Err(format!("Unexpected pcm state {:?}", n))?,
}
Ok(true) // Call us again, please, there might be more data to write
}
fn write_samples_io(p: &alsa::PCM, io: &mut alsa::pcm::IO<SF>, synth: &mut Synth) -> Res<bool> {
let avail = match p.avail_update() {
Ok(n) => n,
Err(e) => {
println!("Recovering from {}", e);
p.recover(e.errno() as std::os::raw::c_int, true)?;
p.avail_update()?
}
} as usize;
if avail > 0 {
io.mmap(avail, |buf| {
for sample in buf.iter_mut() {
*sample = synth.next().unwrap()
};
buf.len() / 2
})?;
}
use alsa::pcm::State;
match p.state() {
State::Running => Ok(false), // All fine
State::Prepared => { println!("Starting audio output stream"); p.start()?; Ok(true) },
State::Suspended | State::XRun => Ok(true), // Recover from this in next round
n @ _ => Err(format!("Unexpected pcm state {:?}", n))?,
}
}
fn read_midi_event(input: &mut seq::Input, synth: &mut Synth) -> Res<bool> {
if input.event_input_pending(true)? == 0 { return Ok(false); }
let ev = input.event_input()?;
// println!("Received: {:?}", ev);
match ev.get_type() {
seq::EventType::Noteon => {
let data: seq::EvNote = ev.get_data().unwrap();
if data.velocity == 0 {
synth.remove_note(data.note);
} else {
synth.add_note(data.note, f64::from(data.velocity + 64) / 2048.);
}
},
seq::EventType::Noteoff => {
let data: seq::EvNote = ev.get_data().unwrap();
synth.remove_note(data.note);
},
seq::EventType::Controller => {
let data: seq::EvCtrl = ev.get_data().unwrap();
synth.cc(data.param, data.value);
}
_ => {},
}
Ok(true)
}
fn run() -> Res<()> {
let (audio_dev, rate) = open_audio_dev()?;
let midi_dev = open_midi_dev()?;
let mut midi_input = midi_dev.input();
// 256 Voices synth
let mut synth = Synth {
sigs: iter::repeat(None).take(256).collect(),
sample_rate: signal::rate(f64::from(rate)),
stored_sample: None,
bar_values: [1., 0.75, 1., 0.75, 0., 0., 0., 0., 0.75], // Some Gospel-ish default.
};
// Create an array of fds to poll.
use alsa::PollDescriptors;
let mut fds = audio_dev.get()?;
fds.append(&mut (&midi_dev, Some(alsa::Direction::Capture)).get()?);
// Let's use the fancy new "direct mode" for minimum overhead!
let mut mmap = audio_dev.direct_mmap_playback::<SF>();
// Direct mode unavailable, use alsa-lib's mmap emulation instead
let mut io = if mmap.is_err() {
Some(audio_dev.io_i16()?)
} else { None };
loop {
if let Ok(ref mut mmap) = mmap {
if write_samples_direct(&audio_dev, mmap, &mut synth)? { continue; }
} else if let Some(ref mut io) = io {
if write_samples_io(&audio_dev, io, &mut synth)? { continue; }
}
if read_midi_event(&mut midi_input, &mut synth)? { continue; }
// Nothing to do, let's sleep until woken up by the kernel.
alsa::poll::poll(&mut fds, 100)?;
}
}
fn main() {
if let Err(e) = run() { println!("Error: {}", e); }
}

59
j314.conf Normal file
View file

@ -0,0 +1,59 @@
[Left Tweeter]
r_shunt = 1
r_dc = 2
r_amp = 3
tau_coil = 4
tau_magnet = 5
tr_coil = 6
ramp_factor = 7
temp_limit = 100
[Right Tweeter]
r_shunt = 1
r_dc = 2
r_amp = 3
tau_coil = 4
tau_magnet = 5
tr_coil = 6
ramp_factor = 7
temp_limit = 100
[Left Woofer 1]
r_shunt = 1
r_dc = 2
r_amp = 3
tau_coil = 4
tau_magnet = 5
tr_coil = 6
ramp_factor = 7
temp_limit = 100
[Right Woofer 1]
r_shunt = 1
r_dc = 2
r_amp = 3
tau_coil = 4
tau_magnet = 5
tr_coil = 6
ramp_factor = 7
temp_limit = 100
[Left Woofer 2]
r_shunt = 1
r_dc = 2
r_amp = 3
tau_coil = 4
tau_magnet = 5
tr_coil = 6
ramp_factor = 7
temp_limit = 100
[Right Woofer 2]
r_shunt = 1
r_dc = 2
r_amp = 3
tau_coil = 4
tau_magnet = 5
tr_coil = 6
ramp_factor = 7
temp_limit = 100

128
src/helpers.rs Normal file
View file

@ -0,0 +1,128 @@
// SPDX-License-Identifier: MIT
// (C) 2022 The Asahi Linux Contributors
use configparser::ini::Ini;
use alsa::mixer::MilliBel;
/**
Failsafe: Limit speaker volume massively and bail.
TODO: enable TAS safe mode with IOCTL.
*/
pub fn fail() {
println!("A catastrophic error has occurred.");
std::process::exit(1);
}
pub fn open_card(card: &str) -> alsa::ctl::Ctl {
let ctldev: alsa::ctl::Ctl = match alsa::ctl::Ctl::new(card, false) {
Ok(ctldev) => ctldev,
Err(e) => {
println!("{}: Could not open sound card! Error: {}", card, e);
fail();
std::process::exit(1);
},
};
return ctldev;
}
/**
Wrapper around configparser::ini::Ini.getfloat()
to safely unwrap the Result<Option<f64>, E> returned by
it.
*/
pub fn parse_float(config: &Ini, section: &str, key: &str) -> f64 {
let _result: Option<f64> = match config.getfloat(section, key) {
Ok(result) => match result{
Some(inner) => {
let float: f64 = inner;
return float;
},
None => {
println!("{}: Failed to parse {}", section, key);
fail();
std::process::exit(1);
},
},
Err(e) => {
println!("{}: Invalid value for {}. Error: {}", section, key, e);
fail();
std::process::exit(1);
},
};
}
/**
Wrapper around alsa::ctl::ElemValue::new(). Lets us bail on errors and
pass in the Bytes type for V/ISENSE
*/
pub fn new_elemvalue(t: alsa::ctl::ElemType) -> alsa::ctl::ElemValue {
let val = match alsa::ctl::ElemValue::new(t) {
Ok(val) => val,
Err(_e) => {
println!("Could not open a handle to an element!");
fail();
std::process::exit(1);
},
};
return val;
}
/**
Wrapper for alsa::ctl::Ctl::elem_read().
*/
pub fn read_ev(card: &alsa::ctl::Ctl, ev: &mut alsa::ctl::ElemValue, name: &str) {
let _val = match card.elem_read(ev) { // alsa:Result<()>
Ok(val) => val,
Err(e) => {
println!("Could not read elem value {}. alsa-lib error: {:?}", name, e);
fail();
std::process::exit(1);
},
};
}
/**
Wrapper for alsa::ctl::Ctl::elem_write().
*/
pub fn write_ev(card: &alsa::ctl::Ctl, ev: &alsa::ctl::ElemValue, name: &str) {
let _val = match card.elem_write(ev) { // alsa:Result<()>
Ok(val) => val,
Err(e) => {
println!("Could not write elem value {}. alsa-lib error: {:?}", name, e);
fail();
std::process::exit(1);
},
};
}
pub fn int_to_db(card: &alsa::ctl::Ctl, id: &alsa::ctl::ElemId, val: i32) -> MilliBel {
let db = match card.convert_to_db(id, val.into()) {
Ok(inner) => inner,
Err(e) => {
println!("Could not convert val {} to dB! alsa-lib error: {:?}", val, e);
fail();
std::process::exit(1);
},
};
return db;
}
pub fn db_to_int(card: &alsa::ctl::Ctl, id: &alsa::ctl::ElemId, val: f32) -> i32 {
let mb: MilliBel = MilliBel((val * 100.0) as i64);
let new_int = match card.convert_from_db(id, mb, alsa::Round::Floor) {
Ok(inner) => inner as i32,
Err(e) => {
println!("Could not convert MilliBel {:?} to int! alsa-lib error: {:?}", val, e);
fail();
std::process::exit(1);
},
};
return new_int;
}

80
src/main.rs Normal file
View file

@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT
// (C) 2022 The Asahi Linux Contributors
/**
Handles speaker safety on Apple Silicon machines. This code is designed to
fail safe. The speaker should not be enabled until this daemon has successfully
initialised. If at any time we run into an unrecoverable error (we shouldn't),
we gracefully bail and use an IOCTL to shut off the speakers.
*/
use std::io;
use std::fs::read_to_string;
use configparser::ini::Ini;
mod types;
mod helpers;
use crate::types::SafetyMonitor;
static ASAHI_DEVICE: &str = "hw:0";
// Will eventually be /etc/speakersafetyd/ or similar
static CONFIG_DIR: &str = "./";
static SUPPORTED: [&str; 2] = [
"j314",
"j316",
];
fn get_machine() -> String {
let _compat: io::Result<String> = match read_to_string("/proc/device-tree/compatible") {
Ok(compat) => {
return compat[6..10].to_string();
},
Err(e) => {
println!("Could not read devicetree compatible: {}", e);
std::process::exit(1);
}
};
}
fn get_drivers(config: &Ini) -> Vec<String> {
let drivers = config.sections();
return drivers;
}
fn main() {
let model: String = get_machine();
let mut cfg: Ini = Ini::new_cs();
let mut speakers: Vec<types::Speaker> = Vec::new();
let card: alsa::ctl::Ctl = helpers::open_card(&ASAHI_DEVICE);
if SUPPORTED.contains(&model.as_str()) {
cfg.load(CONFIG_DIR.to_owned() + &model + ".conf").unwrap();
} else {
println!("Unsupported machine {}", model);
std::process::exit(1);
}
let list_drivers = get_drivers(&cfg);
for i in list_drivers {
let new_speaker: types::Speaker = types::SafetyMonitor::new(&i, &cfg, &card);
speakers.push(new_speaker);
}
// Temporary to check that everything works. Threaded eventually if necessary.
for mut i in speakers {
i.run(&card);
}
}

250
src/types.rs Normal file
View file

@ -0,0 +1,250 @@
// SPDX-License-Identifier: MIT
// (C) 2022 The Asahi Linux Contributors
use std::ffi::{CString, CStr};
use half::f16;
use configparser::ini::Ini;
use alsa::ctl::Ctl;
use crate::helpers;
/**
Struct with fields necessary for manipulating an ALSA elem.
The val field is created using a wrapper so that we can handle
any errors. This is also necessary so that we can create one of type
Bytes for the V/ISENSE elems.
*/
struct Elem {
elem_name: String,
id: alsa::ctl::ElemId,
val: alsa::ctl::ElemValue,
}
trait ALSAElem {
fn new(name: String, card: &Ctl, t: alsa::ctl::ElemType) -> Self;
}
impl ALSAElem for Elem {
fn new(name: String, card: &Ctl, t: alsa::ctl::ElemType) -> Elem {
// CString::new() cannot borrow a String. We want name for the elem
// for error identification though, so it can't consume name directly.
let borrow: String = name.clone();
let mut new_elem: Elem = { Elem {
elem_name: name,
id: alsa::ctl::ElemId::new(alsa::ctl::ElemIface::Mixer),
val: helpers::new_elemvalue(t),
}};
let cname: CString = CString::new(borrow).unwrap();
let cstr: &CStr = cname.as_c_str();
new_elem.id.set_name(cstr);
new_elem.val.set_id(&new_elem.id);
helpers::read_ev(card, &mut new_elem.val, &new_elem.elem_name);
return new_elem;
}
}
/**
Mixer struct representing the controls associated with a given
Speaker. Populated with the important ALSA controls at runtime.
level: mixer volume control
vsense: VSENSE as reported by the driver (V, readonly)
isense: ISENSE as reported by the driver (A, readonly)
*/
struct Mixer {
drv: String,
level: Elem,
vsense: Elem,
isense: Elem,
}
trait ALSACtl {
fn new(name: &str, card: &Ctl) -> Self;
fn get_vsense(&mut self, card: &Ctl) -> f16;
fn get_isense(&mut self, card: &Ctl) -> f16;
fn get_lvl(&mut self, card: &Ctl) -> f32;
fn set_lvl(&mut self, card: &Ctl, lvl: f32);
}
impl ALSACtl for Mixer {
// TODO: wire up real V/ISENSE elems (pending driver support)
fn new(name: &str, card: &Ctl) -> Mixer {
let new_mixer: Mixer = { Mixer {
drv: name.to_owned(),
level: ALSAElem::new(name.to_owned() + " Speaker Volume", card,
alsa::ctl::ElemType::Integer),
vsense: ALSAElem::new(name.to_owned() + " VSENSE Switch", card,
alsa::ctl::ElemType::Boolean),
isense: ALSAElem::new(name.to_owned() + " ISENSE Switch", card,
alsa::ctl::ElemType::Boolean),
}};
return new_mixer;
}
/**
MOCK IMPLEMENTATIONS
V/ISENSE are 16-bit floats sent in a 32-bit TDM slot by the codec.
This is expressed by the driver as a byte array, with rightmost 16
bits as padding.
TODO: Condense into a single function and pass in a borrowed Elem
*/
fn get_vsense(&mut self, card: &Ctl) -> f16 {
helpers::read_ev(card, &mut self.vsense.val, &self.vsense.elem_name);
let val: &[u8] = match self.vsense.val.get_bytes() {
Some(inner) => inner,
None => {
println!("Could not read VSENSE from {}", self.drv);
helpers::fail();
std::process::exit(1);
}
};
let vs = f16::from_ne_bytes([val[0], val[1]]);
return vs;
}
fn get_isense(&mut self, card: &Ctl) -> f16 {
helpers::read_ev(card, &mut self.isense.val, &self.isense.elem_name);
let val: &[u8] = match self.vsense.val.get_bytes() {
Some(inner) => inner,
None => {
println!("Could not read ISENSE from {}", self.drv);
helpers::fail();
std::process::exit(1);
}
};
let is = f16::from_ne_bytes([val[0], val[1]]);
return is;
}
fn get_lvl(&mut self, card: &Ctl) -> f32 {
helpers::read_ev(card, &mut self.level.val, &self.level.elem_name);
let val: i32 = match self.level.val.get_integer(0) {
Some(inner) => inner,
None => {
println!("Could not read level from {}", self.drv);
helpers::fail();
std::process::exit(1);
},
};
let db: f32 = helpers::int_to_db(card, &self.level.id, val).to_db();
return db;
}
fn set_lvl(&mut self, card: &Ctl, lvl: f32) {
let new_val: i32 = helpers::db_to_int(card, &self.level.id, lvl);
match self.level.val.set_integer(0, new_val) {
Some(_) => {},
None => {
println!("Could not set level for {}", self.drv);
helpers::fail();
std::process::exit(1);
},
};
helpers::write_ev(card, &self.level.val, &self.level.elem_name);
}
}
/**
Struct representing a driver. Parameters are parsed out of a config
file, which is loaded at runtime based on the machine's DT compatible
string.
name: driver name as it appears in ALSA
alsa_iface: Mixer struct with handles to the driver's control elements
r_dc: dc resistance of the voice coil (ohms)
tau_coil: voice coil ramp time constant (seconds)
tau_magnet: magnet ramp time constant (seconds)
tr_coil: thermal resistance of voice coil (*C/W)
temp_limit: absolute max temp of the voice coil (*C)
Borrows the handle to the control interface to do calculations.
*/
pub struct Speaker {
name: String,
alsa_iface: Mixer,
tau_coil: f64,
tr_coil: f64,
temp_limit: f64,
}
pub trait SafetyMonitor {
fn new(driver_name: &str, config: &Ini, card: &Ctl) -> Self;
fn run(&mut self, card: &Ctl);
}
impl SafetyMonitor for Speaker {
fn new(driver_name: &str, config: &Ini, card: &Ctl) -> Speaker {
let new_speaker: Speaker = { Speaker {
name: driver_name.to_string(),
alsa_iface: ALSACtl::new(&driver_name, card),
tau_coil: helpers::parse_float(config, driver_name, "tau_coil"),
tr_coil: helpers::parse_float(config, driver_name, "tr_coil"),
temp_limit: helpers::parse_float(config, driver_name, "temp_limit"),
}};
return new_speaker;
}
// I'm not sure on the maths here for determining when to start dropping the volume.
fn run(&mut self, card: &Ctl) {
//let v: f16 = self.alsa_iface.get_vsense(card);
//let i: f16 = self.alsa_iface.get_isense(card);
let lvl: f32 = self.alsa_iface.get_lvl(card);
// Technically, this is the temp ~tau_coil seconds in the future
//let temp: f64 = ((v * i).to_f64()) * self.tr_coil;
// if temp < self.temp_limit && lvl < 0f32 {
// println!("Voice coil for {} below temp limit, ramping back up.", self.name);
//
// // For every degree below temp_limit, raise level by 0.5 dB
// let new_lvl: f32 = lvl + ((self.temp_limit - temp) as f32 * 0.5);
// self.alsa_iface.set_lvl(card, new_lvl);
// }
//
// if temp > self.temp_limit {
// println!("Voice coil at {}*C in {} on {}! Dropping volume!", temp, self.tau_coil, self.name);
//
// // For every degree above temp_limit, drop the level by 1.5 dB
// let new_lvl: f32 = lvl - ((temp - self.temp_limit) as f32 * 1.5);
// self.alsa_iface.set_lvl(card, new_lvl);
// }
// TEMPORARY PROOF THAT THIS WORKS!
println!("Volume on {} is currently {} dB. Setting to -18 dB.", self.name, lvl);
let new_lvl: f32 = -18.0;
self.alsa_iface.set_lvl(card, new_lvl);
println!("Volume on {} is now {} dB", self.name, self.alsa_iface.get_lvl(card));
}
}