mirror of
https://github.com/ivabus/speakersafetyd
synced 2024-11-22 16:25:06 +03:00
alsa: remove local version of crate
the bindings we rely on were merged in v0.7.1, so we no longer need to carry the crate downstream. Signed-off-by: James Calligeros <jcalligeros99@gmail.com>
This commit is contained in:
parent
2e853a1851
commit
391cf6231f
28 changed files with 4 additions and 12981 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -4,7 +4,9 @@ version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alsa"
|
name = "alsa"
|
||||||
version = "0.7.0"
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2562ad8dcf0f789f65c6fdaad8a8a9708ed6b488e649da28c01656ad66b8b47"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alsa-sys",
|
"alsa-sys",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
|
|
@ -7,7 +7,7 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
half = "^2.1.0"
|
half = "^2.1.0"
|
||||||
alsa = { path = "./alsa" }
|
alsa = "^0.7.1"
|
||||||
configparser = { version = "^3.0.2", features=["indexmap"] }
|
configparser = { version = "^3.0.2", features=["indexmap"] }
|
||||||
clap = { version = "4.1.6", features=["derive"] }
|
clap = { version = "4.1.6", features=["derive"] }
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
|
|
|
@ -7,8 +7,6 @@ or enabling speaker output on your machine. An announcement will be made when sp
|
||||||
support is ready for use.
|
support is ready for use.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
* We currently rely on a local version of the `alsa` crate, as a release has not yet been
|
|
||||||
pushed to crates.io with the required bindings.
|
|
||||||
* A patched eleven secret herbs and spices kernel
|
* A patched eleven secret herbs and spices kernel
|
||||||
|
|
||||||
## Todo list
|
## Todo list
|
||||||
|
@ -25,7 +23,3 @@ support is ready for use.
|
||||||
- [ ] Daemonise correctly
|
- [ ] Daemonise correctly
|
||||||
- [ ] Kernel driver interlock
|
- [ ] Kernel driver interlock
|
||||||
- [ ] Packaging/distro-agnosticism
|
- [ ] Packaging/distro-agnosticism
|
||||||
|
|
||||||
## Sundry
|
|
||||||
The `alsa` crate is Copyright (c) 2015-2021 David Henningsson, and other
|
|
||||||
contributors. Redistributed under the MIT license.
|
|
||||||
|
|
58
alsa/Cargo.lock
generated
58
alsa/Cargo.lock
generated
|
@ -1,58 +0,0 @@
|
||||||
# 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"
|
|
|
@ -1,24 +0,0 @@
|
||||||
[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" }
|
|
|
@ -1,177 +0,0 @@
|
||||||
|
|
||||||
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
|
|
|
@ -1,21 +0,0 @@
|
||||||
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.
|
|
|
@ -1,62 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
//! 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();
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
//! 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())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,148 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,455 +0,0 @@
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
//! 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) }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
//! 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
|
@ -1,79 +0,0 @@
|
||||||
|
|
||||||
// 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 }
|
|
||||||
}
|
|
|
@ -1,630 +0,0 @@
|
||||||
/*!
|
|
||||||
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());
|
|
||||||
}
|
|
|
@ -1,100 +0,0 @@
|
||||||
#![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
162
alsa/src/hctl.rs
|
@ -1,162 +0,0 @@
|
||||||
//! 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" })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
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
140
alsa/src/lib.rs
|
@ -1,140 +0,0 @@
|
||||||
//! 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;
|
|
||||||
}
|
|
|
@ -1,639 +0,0 @@
|
||||||
//! 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
1151
alsa/src/pcm.rs
File diff suppressed because it is too large
Load diff
|
@ -1,68 +0,0 @@
|
||||||
//! 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)
|
|
||||||
}
|
|
|
@ -1,211 +0,0 @@
|
||||||
//! 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
1567
alsa/src/seq.rs
File diff suppressed because it is too large
Load diff
|
@ -1,9 +0,0 @@
|
||||||
[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" }
|
|
|
@ -1,311 +0,0 @@
|
||||||
// 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); }
|
|
||||||
}
|
|
Loading…
Reference in a new issue