├── .gitignore ├── src ├── direct.rs ├── config.rs ├── card.rs ├── poll.rs ├── io.rs ├── device_name.rs ├── lib.rs ├── chmap.rs ├── hctl.rs ├── direct │ ├── ffi.rs │ └── pcm.rs ├── error.rs ├── rawmidi.rs ├── ctl_int.rs └── mixer.rs ├── synth-example ├── Cargo.toml └── src │ └── main.rs ├── .github └── workflows │ └── rust.yml ├── Cargo.toml ├── examples ├── ctl_list.rs ├── pcm_record.rs ├── trigger_hstamp.rs ├── midi_enumerate.rs └── pcm_enumerate.rs ├── LICENSE-MIT ├── README.md └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | .idea/ 5 | -------------------------------------------------------------------------------- /src/direct.rs: -------------------------------------------------------------------------------- 1 | //! Functions that bypass alsa-lib and talk directly to the kernel. 2 | 3 | pub mod pcm; 4 | 5 | mod ffi; 6 | -------------------------------------------------------------------------------- /synth-example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "synth-example" 3 | version = "0.1.0" 4 | authors = ["David Henningsson "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | dasp = { version = "0.11", features = ["signal"] } 9 | alsa = { path = "..", version = "0.10" } 10 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | # - name: Load loopback driver 20 | # run: sudo modprobe snd-aloop 21 | - name: Install dependencies 1 22 | run: sudo apt-get update 23 | - name: Install dependencies 2 24 | run: sudo apt-get install --no-install-recommends -y libasound2-dev 25 | - name: Build 26 | run: cargo check --verbose --all 27 | - name: Build no-std 28 | run: cargo check --no-default-features --verbose --all 29 | # - name: Run tests 30 | # run: cargo test --verbose --all 31 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alsa" 3 | version = "0.10.0" 4 | authors = ["David Henningsson "] 5 | 6 | description = "Thin but safe wrappers for ALSA (Linux sound API)" 7 | repository = "https://github.com/diwic/alsa-rs" 8 | documentation = "http://docs.rs/alsa" 9 | keywords = ["ALSA", "audio", "sound"] 10 | license = "Apache-2.0/MIT" 11 | categories = ["multimedia::audio", "api-bindings"] 12 | readme = "README.md" 13 | edition = "2021" 14 | include = ["README.md", "LICENSE-*", "Cargo.toml", "src/"] 15 | 16 | [dependencies] 17 | libc = "0.2" 18 | alsa-sys = "0.3.1" 19 | bitflags = "2.4.0" 20 | cfg-if = "1.0" 21 | 22 | [dev-dependencies] 23 | anyhow = "1.0" 24 | 25 | [badges] 26 | is-it-maintained-issue-resolution = { repository = "diwic/alsa-rs" } 27 | is-it-maintained-open-issues = { repository = "diwic/alsa-rs" } 28 | 29 | [features] 30 | default = ["std"] 31 | std = [] 32 | -------------------------------------------------------------------------------- /examples/ctl_list.rs: -------------------------------------------------------------------------------- 1 | //! Example that enumerates controls for a device. 2 | 3 | use alsa::Card; 4 | use alsa::card::Iter; 5 | use alsa::ctl::Ctl; 6 | use alsa::Error; 7 | 8 | fn list_controls_for_card(card: &Card) -> Result<(), Error>{ 9 | // Get a Ctl for the card 10 | let ctl_id = format!("hw:{}", card.get_index()); 11 | let ctl = Ctl::new(&ctl_id, false)?; 12 | 13 | println!("card {}", ctl_id); 14 | 15 | // Query the elements 16 | let elems = ctl.elem_list()?; 17 | for list_index in 0..elems.get_used() { 18 | let numeric_id = elems.get_numid(list_index)?; 19 | let name = elems.get_name(list_index)?; 20 | println!(" {}: {}", numeric_id, name); 21 | } 22 | 23 | Ok(()) 24 | } 25 | 26 | fn main() { 27 | let cards = Iter::new(); 28 | cards.for_each(|card| if let Ok(c) = card { list_controls_for_card(&c).unwrap_or_default() }); 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015-2021 David Henningsson, and other contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | //! Configuration file API 2 | //! 3 | //! For now, just contains functions regarding the global configuration 4 | //! stored as a cache inside alsa-lib. Calling `update_free_global` might help 5 | //! against valgrind reporting memory leaks. 6 | use crate::{alsa}; 7 | use super::error::*; 8 | use super::Output; 9 | use core::ptr; 10 | 11 | pub fn update() -> Result { 12 | acheck!(snd_config_update()).map(|x| x != 0) 13 | } 14 | 15 | pub fn update_free_global() -> Result<()> { 16 | acheck!(snd_config_update_free_global()).map(|_| ()) 17 | } 18 | 19 | /// [snd_config_t](https://alsa-project.org/alsa-doc/alsa-lib/group___config.html) wrapper 20 | pub struct Config(*mut alsa::snd_config_t); 21 | 22 | impl Drop for Config { 23 | fn drop(&mut self) { unsafe { alsa::snd_config_unref(self.0) }; } 24 | } 25 | 26 | pub fn update_ref() -> Result { 27 | let mut top = ptr::null_mut(); 28 | acheck!(snd_config_update_ref(&mut top)).map(|_| Config(top)) 29 | } 30 | 31 | impl Config { 32 | pub fn save(&self, o: &mut Output) -> Result<()> { 33 | acheck!(snd_config_save(self.0, super::io::output_handle(o))).map(|_| ()) 34 | } 35 | } 36 | 37 | #[test] 38 | fn config_save() { 39 | extern crate std; 40 | let c = update_ref().unwrap(); 41 | let mut outp = Output::buffer_open().unwrap(); 42 | c.save(&mut outp).unwrap(); 43 | std::println!("== Config save ==\n{}", outp); 44 | } 45 | -------------------------------------------------------------------------------- /examples/pcm_record.rs: -------------------------------------------------------------------------------- 1 | //! Example that continously reads data and displays its RMS volume. 2 | 3 | use alsa::pcm::*; 4 | use alsa::{Direction, ValueOr, Error}; 5 | 6 | fn start_capture(device: &str) -> Result { 7 | let pcm = PCM::new(device, Direction::Capture, false)?; 8 | { 9 | // For this example, we assume 44100Hz, one channel, 16 bit audio. 10 | let hwp = HwParams::any(&pcm)?; 11 | hwp.set_channels(1)?; 12 | hwp.set_rate(44100, ValueOr::Nearest)?; 13 | hwp.set_format(Format::s16())?; 14 | hwp.set_access(Access::RWInterleaved)?; 15 | pcm.hw_params(&hwp)?; 16 | } 17 | pcm.start()?; 18 | Ok(pcm) 19 | } 20 | 21 | // Calculates RMS (root mean square) as a way to determine volume 22 | fn rms(buf: &[i16]) -> f64 { 23 | if buf.len() == 0 { return 0f64; } 24 | let mut sum = 0f64; 25 | for &x in buf { 26 | sum += (x as f64) * (x as f64); 27 | } 28 | let r = (sum / (buf.len() as f64)).sqrt(); 29 | // Convert value to decibels 30 | 20.0 * (r / (i16::MAX as f64)).log10() 31 | } 32 | 33 | 34 | fn read_loop(pcm: &PCM) -> Result<(), Error> { 35 | let io = pcm.io_i16()?; 36 | let mut buf = [0i16; 8192]; 37 | loop { 38 | // Block while waiting for 8192 samples to be read from the device. 39 | assert_eq!(io.readi(&mut buf)?, buf.len()); 40 | let r = rms(&buf); 41 | println!("RMS: {:.1} dB", r); 42 | } 43 | } 44 | 45 | fn main() { 46 | // The "default" device is usually directed to the sound server process, 47 | // e g PulseAudio or PipeWire. 48 | let capture = start_capture("default").unwrap(); 49 | read_loop(&capture).unwrap(); 50 | } -------------------------------------------------------------------------------- /src/card.rs: -------------------------------------------------------------------------------- 1 | //! Sound card enumeration 2 | use libc::{c_int, c_char}; 3 | use super::error::*; 4 | use crate::alsa; 5 | use core::ffi::CStr; 6 | use ::alloc::string::String; 7 | 8 | /// An ALSA sound card, uniquely identified by its index. 9 | #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] 10 | pub struct Card(c_int); 11 | 12 | /// Iterate over existing sound cards. 13 | pub struct Iter(c_int); 14 | 15 | impl Iter { 16 | pub fn new() -> Iter { Iter(-1) } 17 | } 18 | 19 | impl Iterator for Iter { 20 | type Item = Result; 21 | 22 | fn next(&mut self) -> Option> { 23 | match acheck!(snd_card_next(&mut self.0)) { 24 | Ok(_) if self.0 == -1 => None, 25 | Ok(_) => Some(Ok(Card(self.0))), 26 | Err(e) => Some(Err(e)), 27 | } 28 | } 29 | } 30 | 31 | impl Card { 32 | pub fn new(index: c_int) -> Card { Card(index) } 33 | pub fn from_str(s: &CStr) -> Result { 34 | acheck!(snd_card_get_index(s.as_ptr())).map(Card) 35 | } 36 | pub fn get_name(&self) -> Result { 37 | let mut c: *mut c_char = ::core::ptr::null_mut(); 38 | acheck!(snd_card_get_name(self.0, &mut c)) 39 | .and_then(|_| from_alloc("snd_card_get_name", c)) 40 | } 41 | pub fn get_longname(&self) -> Result { 42 | let mut c: *mut c_char = ::core::ptr::null_mut(); 43 | acheck!(snd_card_get_longname(self.0, &mut c)) 44 | .and_then(|_| from_alloc("snd_card_get_longname", c)) 45 | } 46 | 47 | pub fn get_index(&self) -> c_int { self.0 } 48 | } 49 | 50 | #[test] 51 | fn print_cards() { 52 | extern crate std; 53 | for a in Iter::new().map(|a| a.unwrap()) { 54 | std::println!("Card #{}: {} ({})", a.get_index(), a.get_name().unwrap(), a.get_longname().unwrap()) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/trigger_hstamp.rs: -------------------------------------------------------------------------------- 1 | use alsa::{ 2 | pcm::{Access, Format, HwParams}, 3 | Direction, ValueOr, PCM, 4 | }; 5 | 6 | use anyhow::Result; // Sorry for the extra dep :D 7 | 8 | fn main() -> Result<()> { 9 | // Also tried specifying the hw card name, but it doesn't matter 10 | let pcm = PCM::new("default", Direction::Capture, false)?; 11 | 12 | { 13 | let hwp = HwParams::any(&pcm)?; 14 | hwp.set_channels(2)?; 15 | hwp.set_rate(16000, ValueOr::Nearest)?; 16 | hwp.set_format(Format::s32())?; 17 | hwp.set_access(Access::RWInterleaved)?; 18 | pcm.hw_params(&hwp)?; 19 | } 20 | 21 | { 22 | let swp = pcm.sw_params_current()?; 23 | swp.set_tstamp_mode(true)?; 24 | swp.set_tstamp_type(alsa::pcm::TstampType::Monotonic)?; // Tried with every kind of timestamp type, it doesn't matter 25 | pcm.sw_params(&swp)?; 26 | } 27 | 28 | let mut buffer = [0i32; 8000]; 29 | 30 | pcm.start()?; 31 | 32 | let pcm_io = pcm.io_i32()?; 33 | 34 | loop { 35 | // Debug: check if timestamps are enabled (it returns true as expected) 36 | let params = pcm.sw_params_current()?; 37 | println!("timestamps enabled: {}", params.get_tstamp_mode()?); 38 | 39 | let read_bytes = pcm_io.readi(&mut buffer)?; 40 | assert!(read_bytes > 0); 41 | 42 | // I've also tried to construct the status object just once outside the loop but nothing changes 43 | let status = pcm.status()?; 44 | 45 | dbg!(&status); 46 | 47 | // The following "htstamp" functions all wrongly return a timespec struct with 0 seconds and 0 nanoseconds 48 | // when using Rust >=1.88 (even tried on Nightly to check if it worked on there) 49 | let audio_htstamp = status.get_audio_htstamp(); 50 | println!("{} {}", audio_htstamp.tv_sec, audio_htstamp.tv_nsec); 51 | 52 | let htstamp = status.get_htstamp(); 53 | println!("{} {}", htstamp.tv_sec, htstamp.tv_nsec); 54 | 55 | let trigger_htstamp = status.get_trigger_htstamp(); 56 | println!("{} {}", trigger_htstamp.tv_sec, trigger_htstamp.tv_nsec); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ALSA bindings for Rust 2 | ======================= 3 | 4 | Thin but safe wrappers for [ALSA](https://alsa-project.org), the most 5 | common API for accessing audio devices on Linux. 6 | 7 | [![crates.io](https://img.shields.io/crates/v/alsa.svg)](https://crates.io/crates/alsa) 8 | [![API documentation](https://docs.rs/alsa/badge.svg)](https://docs.rs/alsa) 9 | [![license](https://img.shields.io/crates/l/alsa.svg)](https://crates.io/crates/alsa) 10 | 11 | The ALSA API is rather big, so everything is not covered yet, but expect the following to work: 12 | 13 | * Audio Playback (example in `pcm` module docs) 14 | 15 | * Audio Recording 16 | 17 | * Mixer controls 18 | 19 | * HCtl API (jack detection example in `hctl` module docs) 20 | 21 | * Raw midi 22 | 23 | * Midi sequencer (most of it) 24 | 25 | * Ctl API 26 | 27 | * Device name hints (example in `device_name` module docs) 28 | 29 | * Enumerations of all of the above 30 | 31 | * Poll and/or wait for all of the above 32 | 33 | The following is not yet implemented (mostly because nobody asked for them) : 34 | 35 | * Separate timer API (snd_timer_*) 36 | 37 | * Config API (snd_config_*) 38 | 39 | * Plug-in API 40 | 41 | Quickstart guide / API design: 42 | 43 | * Most functions map 1-to-1 to alsa-lib functions, e g, `ctl::CardInfo::get_id()` is a wrapper around 44 | `snd_ctl_card_info_get_id` and the [alsa-lib documentation](https://www.alsa-project.org/alsa-doc/alsa-lib/) 45 | can be consulted for additional information. 46 | 47 | * Structs are RAII and closed/freed on drop, e g, when a `PCM` struct is dropped, `snd_pcm_close` is called. 48 | 49 | * To read and write buffers, call the `io_*` methods. It will return a separate struct from which you can 50 | read or write, and which can also be used for mmap (if supported by the driver). 51 | 52 | * Error handling - most alsa-lib functions can return errors, so the return value from these is a `Result`. 53 | 54 | * Enumeration of cards, devices etc is done through structs implementing `Iterator`. 55 | 56 | * Many structs implement `poll::Descriptors`, to combine with your favorite async framework. 57 | Or just use `wait` if you don't need non-blocking functionality. 58 | 59 | Notes: 60 | 61 | * 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. 62 | 63 | -------------------------------------------------------------------------------- /examples/midi_enumerate.rs: -------------------------------------------------------------------------------- 1 | //! Example that enumerates hardware and PCM devices. 2 | 3 | use alsa::Card; 4 | use alsa::card::Iter as CardIter; 5 | use alsa::ctl::{Ctl}; 6 | use alsa::{Error}; 7 | use alsa::rawmidi::Iter as MidiIter; 8 | 9 | use alsa::seq::{ClientIter, Seq, PortIter, PortSubscribeIter, QuerySubsType, Addr}; 10 | 11 | pub fn list_rawmidi_for_card(card: &Card) -> Result<(), Error> { 12 | // Get a Ctl for the card 13 | let ctl_id = format!("hw:{}", card.get_index()); 14 | let ctl = Ctl::new(&ctl_id, false)?; 15 | 16 | // Read card id and name 17 | let cardinfo = ctl.card_info()?; 18 | let card_id = cardinfo.get_id()?; 19 | let card_name = cardinfo.get_name()?; 20 | let subdevices:Vec<_> = MidiIter::new(&ctl).filter_map(|d| d.ok()).collect(); 21 | if !subdevices.is_empty() { 22 | println!("card_id: {:?} card_name {:?} ", card_id, card_name); 23 | for info in &subdevices { 24 | println!("subdevice: {:?} {:?} {:?}", info.get_subdevice(), info.get_subdevice_name().unwrap(), info.get_stream()); 25 | } 26 | println!(); 27 | 28 | } 29 | Ok(()) 30 | } 31 | 32 | pub fn list_rawmidi_devices() { 33 | let cards = CardIter::new(); 34 | cards.for_each(|card| if let Ok(c) = card { list_rawmidi_for_card(&c).unwrap_or_default() }); 35 | } 36 | 37 | pub fn list_sequencer_port() { 38 | let seq = Seq::open(None, None, false).unwrap(); 39 | println!("Seq client {:?}", seq.client_id().unwrap()); 40 | println!(); 41 | 42 | seq.set_client_name(&c"ALSA_RS_EXAMPLE").unwrap(); 43 | for client in ClientIter::new(&seq) { 44 | println!("Client {:?} {:?}", client.get_client(), client.get_name().unwrap()); 45 | for port in PortIter::new(&seq, client.get_client()) { 46 | println!(" Port {:?} {:?}", port.get_port(), port.get_name().unwrap()); 47 | for sub in PortSubscribeIter::new(&seq, Addr { client: client.get_client(), port: port.get_port() } , QuerySubsType::READ) { 48 | println!(" READ {:?}", sub.get_dest()); 49 | } 50 | for sub in PortSubscribeIter::new(&seq, Addr { client: client.get_client(), port: port.get_port() } , QuerySubsType::WRITE) { 51 | println!(" WRITE {:?}", sub.get_dest()); 52 | } 53 | } 54 | println!(); 55 | } 56 | } 57 | 58 | fn main() { 59 | println!("\n--- Raw MIDI devices ---"); 60 | list_rawmidi_devices(); 61 | println!("\n--- MIDI Sequencer Clients, Ports and Subscribers ---"); 62 | list_sequencer_port(); 63 | } -------------------------------------------------------------------------------- /examples/pcm_enumerate.rs: -------------------------------------------------------------------------------- 1 | //! Example that enumerates hardware and PCM devices. 2 | 3 | use alsa::Card; 4 | use alsa::card::Iter; 5 | use alsa::device_name::HintIter; 6 | use alsa::ctl::{Ctl, DeviceIter}; 7 | use alsa::{Direction, Error}; 8 | 9 | // Each card can have multiple devices and subdevices, list them all 10 | fn list_devices_for_card(card: &Card, direction: Direction) -> Result<(), Error>{ 11 | // Get a Ctl for the card 12 | let ctl_id = format!("hw:{}", card.get_index()); 13 | let ctl = Ctl::new(&ctl_id, false)?; 14 | 15 | // Read card id and name 16 | let cardinfo = ctl.card_info()?; 17 | let card_id = cardinfo.get_id()?; 18 | let card_name = cardinfo.get_name()?; 19 | for device in DeviceIter::new(&ctl) { 20 | // Read info from Ctl 21 | let pcm_info = ctl.pcm_info(device as u32, 0, direction)?; 22 | 23 | // Read PCM name 24 | let pcm_name = pcm_info.get_name()?.to_string(); 25 | 26 | println!("card: {:<2} id: {:<10} device: {:<2} card name: '{}' PCM name: '{}'", card.get_index(), card_id, device, card_name, pcm_name); 27 | 28 | // Loop through subdevices and get their names 29 | let subdevs = pcm_info.get_subdevices_count(); 30 | for subdev in 0..subdevs { 31 | // Get subdevice name 32 | let pcm_info = ctl.pcm_info(device as u32, subdev, direction)?; 33 | let subdev_name = pcm_info.get_subdevice_name()?; 34 | 35 | println!(" subdevice: {:<2} name: '{}'", subdev, subdev_name); 36 | } 37 | } 38 | 39 | Ok(()) 40 | } 41 | 42 | pub fn list_hw_devices(direction: Direction) { 43 | let cards = Iter::new(); 44 | cards.for_each(|card| if let Ok(c) = card { list_devices_for_card(&c, direction).unwrap_or_default() }); 45 | } 46 | 47 | pub fn list_pcm_devices(direction: Direction) { 48 | let hints = HintIter::new_str(None, "pcm").unwrap(); 49 | for hint in hints { 50 | // When Direction is None it means that both the PCM supports both playback and capture 51 | if hint.name.is_some() && hint.desc.is_some() && (hint.direction.is_none() || hint.direction.map(|dir| dir == direction).unwrap_or_default()) { 52 | println!("name: {:<35} desc: {:?}", hint.name.unwrap(), hint.desc.unwrap()); 53 | } 54 | } 55 | } 56 | 57 | fn main() { 58 | println!("\n--- Hardware playback devices ---"); 59 | list_hw_devices(Direction::Playback); 60 | println!("\n--- Hardware capture devices ---"); 61 | list_hw_devices(Direction::Capture); 62 | 63 | println!("\n--- PCM playback devices ---"); 64 | list_pcm_devices(Direction::Playback); 65 | println!("\n--- PCM capture devices ---"); 66 | list_pcm_devices(Direction::Capture); 67 | } -------------------------------------------------------------------------------- /src/poll.rs: -------------------------------------------------------------------------------- 1 | //! Tiny poll ffi 2 | //! 3 | //! A tiny wrapper around libc's poll system call. 4 | 5 | use libc; 6 | use super::error::*; 7 | pub use libc::pollfd; 8 | use ::alloc::vec; 9 | use ::alloc::vec::Vec; 10 | 11 | bitflags! { 12 | #[repr(transparent)] 13 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 14 | pub struct Flags: ::libc::c_short { 15 | const IN = ::libc::POLLIN; 16 | const PRI = ::libc::POLLPRI; 17 | const OUT = ::libc::POLLOUT; 18 | const ERR = ::libc::POLLERR; 19 | const HUP = ::libc::POLLHUP; 20 | const NVAL = ::libc::POLLNVAL; 21 | } 22 | } 23 | 24 | pub trait Descriptors { 25 | fn count(&self) -> usize; 26 | fn fill(&self, _: &mut [pollfd]) -> Result; 27 | fn revents(&self, _: &[pollfd]) -> Result; 28 | 29 | /// Wrapper around count and fill - returns an array of pollfds 30 | fn get(&self) -> Result> { 31 | let mut v = vec![pollfd { fd: 0, events: 0, revents: 0 }; self.count()]; 32 | if self.fill(&mut v)? != v.len() { Err(Error::unsupported("did not fill the poll descriptors array")) } 33 | else { Ok(v) } 34 | } 35 | } 36 | 37 | impl Descriptors for pollfd { 38 | fn count(&self) -> usize { 1 } 39 | fn fill(&self, a: &mut [pollfd]) -> Result { a[0] = *self; Ok(1) } 40 | fn revents(&self, a: &[pollfd]) -> Result { Ok(Flags::from_bits_truncate(a[0].revents)) } 41 | } 42 | 43 | /// Wrapper around the libc poll call. 44 | pub fn poll(fds: &mut[pollfd], timeout: i32) -> Result { 45 | let r = unsafe { libc::poll(fds.as_mut_ptr(), fds.len() as libc::nfds_t, timeout as libc::c_int) }; 46 | if r >= 0 { Ok(r as usize) } else { 47 | #[cfg(feature = "std")] 48 | { from_code( 49 | "poll", 50 | -std::io::Error::last_os_error().raw_os_error().unwrap_or_default()).map(|_| unreachable!()) 51 | } 52 | #[cfg(not(feature = "std"))] 53 | { from_code("poll", -unsafe {super::error::errno}).map(|_| unreachable!()) } 54 | 55 | } 56 | } 57 | 58 | /// Builds a pollfd array, polls it, and returns the poll descriptors which have non-zero revents. 59 | pub fn poll_all<'a>(desc: &[&'a dyn Descriptors], timeout: i32) -> Result> { 60 | 61 | let mut pollfds: Vec = vec!(); 62 | let mut indices = vec!(); 63 | for v2 in desc.iter().map(|q| q.get()) { 64 | let v = v2?; 65 | indices.push(pollfds.len() .. pollfds.len()+v.len()); 66 | pollfds.extend(v); 67 | }; 68 | 69 | poll(&mut pollfds, timeout)?; 70 | 71 | let mut res = vec!(); 72 | for (i, r) in indices.into_iter().enumerate() { 73 | let z = desc[i].revents(&pollfds[r])?; 74 | if !z.is_empty() { res.push((desc[i], z)); } 75 | } 76 | Ok(res) 77 | } 78 | -------------------------------------------------------------------------------- /src/io.rs: -------------------------------------------------------------------------------- 1 | use crate::alsa; 2 | use super::error::*; 3 | use core::{slice, ptr, fmt}; 4 | use core::cell::RefCell; 5 | use ::alloc::rc::Rc; 6 | use libc::{c_char, c_int}; 7 | 8 | /// [snd_output_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___output.html) wrapper 9 | pub struct Output(*mut alsa::snd_output_t); 10 | 11 | unsafe impl Send for Output {} 12 | 13 | #[cfg(feature = "std")] 14 | std::thread_local! { 15 | static ERROR_OUTPUT: RefCell>>> = RefCell::new(None); 16 | } 17 | 18 | impl Drop for Output { 19 | fn drop(&mut self) { unsafe { alsa::snd_output_close(self.0) }; } 20 | } 21 | 22 | impl Output { 23 | 24 | pub fn buffer_open() -> Result { 25 | let mut q = ptr::null_mut(); 26 | acheck!(snd_output_buffer_open(&mut q)).map(|_| Output(q)) 27 | } 28 | 29 | pub fn buffer_string T>(&self, f: F) -> T { 30 | let b = unsafe { 31 | let mut q = ptr::null_mut(); 32 | let s = alsa::snd_output_buffer_string(self.0, &mut q); 33 | if s == 0 { &[] } else { slice::from_raw_parts(q as *const u8, s as usize) } 34 | }; 35 | f(b) 36 | } 37 | 38 | 39 | /// Installs a thread local error handler. 40 | /// 41 | /// Sometimes alsa-lib writes to stderr, but if you prefer, you can write it here instead. 42 | /// Should you wish to empty the buffer; just call local_error_handler again and drop the old instance. 43 | /// 44 | /// This is not available in `no-std` environments, because we use thread_local variables. 45 | #[cfg(feature = "std")] 46 | pub fn local_error_handler() -> Result>> { 47 | let output = Output::buffer_open()?; 48 | let r = Rc::new(RefCell::new(output)); 49 | ERROR_OUTPUT.with_borrow_mut(|e| *e = Some(r.clone())); 50 | unsafe { alsa::snd_lib_error_set_local(Some(our_error_handler)); } 51 | Ok(r) 52 | } 53 | } 54 | 55 | impl fmt::Debug for Output { 56 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 57 | write!(f, "Output(")?; 58 | fmt::Display::fmt(self, f)?; 59 | write!(f, ")") 60 | /* self.buffer_string(|b| f.write_str(try!(str::from_utf8(b).map_err(|_| fmt::Error)))) */ 61 | } 62 | } 63 | 64 | impl fmt::Display for Output { 65 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 66 | self.buffer_string(|b| { 67 | let s = ::alloc::string::String::from_utf8_lossy(b); 68 | f.write_str(&*s) 69 | }) 70 | } 71 | } 72 | 73 | pub fn output_handle(o: &Output) -> *mut alsa::snd_output_t { o.0 } 74 | 75 | #[cfg(feature = "std")] 76 | unsafe extern "C" fn our_error_handler(file: *const c_char, 77 | line: c_int, 78 | func: *const c_char, 79 | err: c_int, 80 | fmt: *const c_char, 81 | arg: *mut alsa::__va_list_tag, 82 | ) { 83 | ERROR_OUTPUT.with_borrow(|e| { 84 | let b = e.as_ref().expect("ERROR_OUTPUT not set").borrow_mut(); 85 | alsa::snd_output_puts(b.0, func); 86 | alsa::snd_output_puts(b.0, c": ".as_ptr()); 87 | alsa::snd_output_vprintf(b.0, fmt, arg); 88 | alsa::snd_output_putc(b.0, '\n' as i32); 89 | }) 90 | } 91 | -------------------------------------------------------------------------------- /src/device_name.rs: -------------------------------------------------------------------------------- 1 | //! Enumerate devices in the alsa library configuration 2 | //! 3 | //! # Example 4 | //! Print all devices found in various categories. 5 | //! 6 | //! ``` 7 | //! use ::alloc::ffi::CString; 8 | //! use alsa::device_name::HintIter; 9 | //! 10 | //! for t in &["pcm", "ctl", "rawmidi", "timer", "seq", "hwdep"] { 11 | //! println!("{} devices:", t); 12 | //! let i = HintIter::new(None, &*CString::new(*t).unwrap()).unwrap(); 13 | //! for a in i { println!(" {:?}", a) } 14 | //! } 15 | //! ``` 16 | 17 | use core::ptr; 18 | use libc::{c_void, c_int}; 19 | use crate::alsa; 20 | use super::{Card, Direction}; 21 | use super::error::*; 22 | use core::ffi::CStr; 23 | use ::alloc::ffi::CString; 24 | use ::alloc::string::String; 25 | 26 | /// [snd_device_name_hint](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper 27 | pub struct HintIter(*mut *mut c_void, isize); 28 | 29 | impl Drop for HintIter { 30 | fn drop(&mut self) { unsafe { alsa::snd_device_name_free_hint(self.0); }} 31 | } 32 | 33 | impl HintIter { 34 | /// typical interfaces are: "pcm", "ctl", "rawmidi", "timer", "seq" and "hwdep". 35 | pub fn new(card: Option<&Card>, iface: &CStr) -> Result { 36 | let mut p = ptr::null_mut(); 37 | let cnr = card.map(|c| c.get_index()).unwrap_or(-1) as c_int; 38 | acheck!(snd_device_name_hint(cnr, iface.as_ptr(), &mut p)) 39 | .map(|_| HintIter(p, 0)) 40 | } 41 | 42 | /// A constructor variant that takes the interface as a Rust string slice. 43 | pub fn new_str(card: Option<&Card>, iface: &str) -> Result { 44 | HintIter::new(card, &CString::new(iface).unwrap()) 45 | } 46 | } 47 | 48 | impl Iterator for HintIter { 49 | type Item = Hint; 50 | fn next(&mut self) -> Option { 51 | if self.0.is_null() { return None; } 52 | let p = unsafe { *self.0.offset(self.1) }; 53 | if p.is_null() { return None; } 54 | self.1 += 1; 55 | Some(Hint::new(p)) 56 | } 57 | } 58 | 59 | 60 | /// [snd_device_name_get_hint](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper 61 | #[derive(Debug, Clone)] 62 | pub struct Hint { 63 | pub name: Option, 64 | pub desc: Option, 65 | pub direction: Option, 66 | } 67 | 68 | impl Hint { 69 | fn get_str(p: *const c_void, name: &str) -> Option { 70 | let name = CString::new(name).unwrap(); 71 | let c = unsafe { alsa::snd_device_name_get_hint(p, name.as_ptr()) }; 72 | from_alloc("snd_device_name_get_hint", c).ok() 73 | } 74 | 75 | fn new(p: *const c_void) -> Hint { 76 | let d = Hint::get_str(p, "IOID").and_then(|x| match &*x { 77 | "Input" => Some(Direction::Capture), 78 | "Output" => Some(Direction::Playback), 79 | _ => None, 80 | }); 81 | Hint { name: Hint::get_str(p, "NAME"), desc: Hint::get_str(p, "DESC"), direction: d } 82 | } 83 | } 84 | 85 | #[test] 86 | fn print_hints() { 87 | extern crate std; 88 | for t in &["pcm", "ctl", "rawmidi", "timer", "seq", "hwdep"] { 89 | std::println!("{} devices:", t); 90 | let i = HintIter::new(None, &*CString::new(*t).unwrap()).unwrap(); 91 | for a in i { std::println!(" {:?}", a) } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Thin but safe wrappers for [ALSA](https://alsa-project.org). 2 | //! 3 | //! [GitHub repo](https://github.com/diwic/alsa-rs) 4 | //! 5 | //! [Crates.io](https://crates.io/crates/alsa) 6 | //! 7 | //! This ALSA API wrapper/binding is WIP - the ALSA API is huge, and new 8 | //! functions and structs might be added as requested. 9 | //! 10 | //! Most functions map 1-to-1 to alsa-lib functions, e g, `ctl::CardInfo::get_id()` is a wrapper around 11 | //! `snd_ctl_card_info_get_id` and the [alsa-lib documentation](https://www.alsa-project.org/alsa-doc/alsa-lib/) 12 | //! can be consulted for additional information. 13 | //! 14 | //! Enjoy! 15 | 16 | #![allow(clippy::all)] 17 | #![warn(clippy::correctness, clippy::suspicious, clippy::perf)] 18 | #![no_std] 19 | 20 | #[cfg(feature = "std")] 21 | extern crate std; 22 | 23 | extern crate alsa_sys as alsa; 24 | extern crate alloc; 25 | extern crate libc; 26 | #[macro_use] 27 | extern crate bitflags; 28 | 29 | macro_rules! alsa_enum { 30 | ($(#[$attr:meta])+ $name:ident, $static_name:ident [$count:expr], $( $a:ident = $b:ident),* ,) => 31 | { 32 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 33 | $(#[$attr])* 34 | pub enum $name { 35 | $( 36 | $a = alsa::$b as isize, 37 | )* 38 | } 39 | 40 | static $static_name: [$name; $count] = 41 | [ $( $name::$a, )* ]; 42 | 43 | impl $name { 44 | /// Returns a slice of all possible values; useful for iteration 45 | pub fn all() -> &'static [$name] { &$static_name[..] } 46 | 47 | #[allow(dead_code)] 48 | fn from_c_int(c: ::libc::c_int, s: &'static str) -> Result<$name> { 49 | Self::all().iter().find(|&&x| c == x as ::libc::c_int).map(|&x| x) 50 | .ok_or_else(|| Error::unsupported(s)) 51 | } 52 | 53 | #[allow(dead_code)] 54 | fn to_c_int(&self) -> ::libc::c_int { 55 | return *self as ::libc::c_int; 56 | } 57 | } 58 | 59 | } 60 | } 61 | 62 | /// Replaces constants ending with PLAYBACK/CAPTURE as well as 63 | /// INPUT/OUTPUT 64 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] 65 | pub enum Direction { 66 | Playback, 67 | Capture 68 | } 69 | impl Direction { 70 | #[inline] 71 | pub fn input() -> Direction { Direction::Capture } 72 | #[inline] 73 | pub fn output() -> Direction { Direction::Playback } 74 | } 75 | 76 | /// Used to restrict hw parameters. In case the submitted 77 | /// value is unavailable, in which direction should one search 78 | /// for available values? 79 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] 80 | pub enum ValueOr { 81 | /// The value set is the submitted value, or less 82 | Less = -1, 83 | /// The value set is the submitted value, or the nearest 84 | Nearest = 0, 85 | /// The value set is the submitted value, or greater 86 | Greater = 1, 87 | } 88 | 89 | /// Rounding mode (used in some mixer related calls) 90 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] 91 | pub enum Round { 92 | /// Round down (towards negative infinity) 93 | Floor = 0, 94 | /// Round up (towards positive infinity) 95 | Ceil = 1, 96 | } 97 | 98 | mod error; 99 | pub use crate::error::{Error, Result}; 100 | 101 | pub mod card; 102 | pub use crate::card::Card as Card; 103 | 104 | mod ctl_int; 105 | pub mod ctl { 106 | //! Control device API 107 | pub use super::ctl_int::{Ctl, CardInfo, DeviceIter, ElemIface, ElemId, ElemList, ElemType, ElemValue, ElemInfo}; 108 | } 109 | 110 | pub use crate::ctl::Ctl as Ctl; 111 | 112 | pub mod hctl; 113 | pub use crate::hctl::HCtl as HCtl; 114 | 115 | pub mod pcm; 116 | pub use crate::pcm::PCM as PCM; 117 | 118 | pub mod config; 119 | 120 | pub mod rawmidi; 121 | pub use crate::rawmidi::Rawmidi as Rawmidi; 122 | 123 | pub mod device_name; 124 | 125 | pub mod poll; 126 | pub use crate::poll::Descriptors as PollDescriptors; 127 | 128 | pub mod mixer; 129 | pub use crate::mixer::Mixer as Mixer; 130 | 131 | pub mod seq; 132 | pub use crate::seq::Seq as Seq; 133 | 134 | mod io; 135 | pub use crate::io::Output; 136 | 137 | // Reexported inside PCM module 138 | mod chmap; 139 | 140 | pub mod direct; 141 | -------------------------------------------------------------------------------- /src/chmap.rs: -------------------------------------------------------------------------------- 1 | use crate::alsa; 2 | use core::{fmt, mem, slice}; 3 | use super::error::*; 4 | use ::alloc::vec::Vec; 5 | use ::alloc::vec; 6 | 7 | alsa_enum!( 8 | /// [SND_CHMAP_TYPE_xxx](http://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html) constants 9 | ChmapType, ALL_CHMAP_TYPES[4], 10 | 11 | None = SND_CHMAP_TYPE_NONE, 12 | Fixed = SND_CHMAP_TYPE_FIXED, 13 | Var = SND_CHMAP_TYPE_VAR, 14 | Paired = SND_CHMAP_TYPE_PAIRED, 15 | ); 16 | 17 | alsa_enum!( 18 | /// [SND_CHMAP_xxx](http://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html) constants 19 | ChmapPosition, ALL_CHMAP_POSITIONS[37], 20 | 21 | Unknown = SND_CHMAP_UNKNOWN, 22 | NA = SND_CHMAP_NA, 23 | Mono = SND_CHMAP_MONO, 24 | FL = SND_CHMAP_FL, 25 | FR = SND_CHMAP_FR, 26 | RL = SND_CHMAP_RL, 27 | RR = SND_CHMAP_RR, 28 | FC = SND_CHMAP_FC, 29 | LFE = SND_CHMAP_LFE, 30 | SL = SND_CHMAP_SL, 31 | SR = SND_CHMAP_SR, 32 | RC = SND_CHMAP_RC, 33 | FLC = SND_CHMAP_FLC, 34 | FRC = SND_CHMAP_FRC, 35 | RLC = SND_CHMAP_RLC, 36 | RRC = SND_CHMAP_RRC, 37 | FLW = SND_CHMAP_FLW, 38 | FRW = SND_CHMAP_FRW, 39 | FLH = SND_CHMAP_FLH, 40 | FCH = SND_CHMAP_FCH, 41 | FRH = SND_CHMAP_FRH, 42 | TC = SND_CHMAP_TC, 43 | TFL = SND_CHMAP_TFL, 44 | TFR = SND_CHMAP_TFR, 45 | TFC = SND_CHMAP_TFC, 46 | TRL = SND_CHMAP_TRL, 47 | TRR = SND_CHMAP_TRR, 48 | TRC = SND_CHMAP_TRC, 49 | TFLC = SND_CHMAP_TFLC, 50 | TFRC = SND_CHMAP_TFRC, 51 | TSL = SND_CHMAP_TSL, 52 | TSR = SND_CHMAP_TSR, 53 | LLFE = SND_CHMAP_LLFE, 54 | RLFE = SND_CHMAP_RLFE, 55 | BC = SND_CHMAP_BC, 56 | BLC = SND_CHMAP_BLC, 57 | BRC = SND_CHMAP_BRC, 58 | ); 59 | 60 | impl fmt::Display for ChmapPosition { 61 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 62 | let s = unsafe { alsa::snd_pcm_chmap_long_name(*self as libc::c_uint) }; 63 | let s = from_const("snd_pcm_chmap_long_name", s)?; 64 | write!(f, "{}", s) 65 | } 66 | } 67 | 68 | 69 | /// [snd_pcm_chmap_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html) wrapper 70 | pub struct Chmap(*mut alsa::snd_pcm_chmap_t, bool); 71 | 72 | impl Drop for Chmap { 73 | fn drop(&mut self) { if self.1 { unsafe { libc::free(self.0 as *mut libc::c_void) }}} 74 | } 75 | 76 | impl Chmap { 77 | fn set_channels(&mut self, c: libc::c_uint) { unsafe { (*self.0) .channels = c }} 78 | fn as_slice_mut(&mut self) -> &mut [libc::c_uint] { 79 | unsafe { slice::from_raw_parts_mut((*self.0).pos.as_mut_ptr(), (*self.0).channels as usize) } 80 | } 81 | fn as_slice(&self) -> &[libc::c_uint] { 82 | unsafe { slice::from_raw_parts((*self.0).pos.as_ptr(), (*self.0).channels as usize) } 83 | } 84 | } 85 | 86 | impl fmt::Display for Chmap { 87 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 88 | let mut buf: Vec = vec![0; 512]; 89 | acheck!(snd_pcm_chmap_print(self.0, buf.len() as libc::size_t, buf.as_mut_ptr()))?; 90 | let s = from_const("snd_pcm_chmap_print", buf.as_mut_ptr())?; 91 | write!(f, "{}", s) 92 | } 93 | } 94 | 95 | impl<'a> From<&'a [ChmapPosition]> for Chmap { 96 | fn from(a: &'a [ChmapPosition]) -> Chmap { 97 | let p = unsafe { libc::malloc((mem::size_of::() + mem::size_of::() * a.len()) as libc::size_t) }; 98 | if p.is_null() { panic!("Out of memory") } 99 | let mut r = Chmap(p as *mut alsa::snd_pcm_chmap_t, true); 100 | r.set_channels(a.len() as libc::c_uint); 101 | for (i,v) in r.as_slice_mut().iter_mut().enumerate() { *v = a[i] as libc::c_uint } 102 | r 103 | } 104 | } 105 | 106 | impl<'a> From<&'a Chmap> for Vec { 107 | fn from(a: &'a Chmap) -> Vec { 108 | a.as_slice().iter().map(|&v| ChmapPosition::from_c_int(v as libc::c_int, "").unwrap()).collect() 109 | } 110 | } 111 | 112 | pub fn chmap_new(a: *mut alsa::snd_pcm_chmap_t) -> Chmap { Chmap(a, true) } 113 | pub fn chmap_handle(a: &Chmap) -> *mut alsa::snd_pcm_chmap_t { a.0 } 114 | 115 | 116 | /// 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) 117 | pub struct ChmapsQuery(*mut *mut alsa::snd_pcm_chmap_query_t, isize); 118 | 119 | impl Drop for ChmapsQuery { 120 | fn drop(&mut self) { unsafe { alsa::snd_pcm_free_chmaps(self.0) }} 121 | } 122 | 123 | pub fn chmaps_query_new(a: *mut *mut alsa::snd_pcm_chmap_query_t) -> ChmapsQuery { ChmapsQuery(a, 0) } 124 | 125 | impl Iterator for ChmapsQuery { 126 | type Item = (ChmapType, Chmap); 127 | fn next(&mut self) -> Option { 128 | if self.0.is_null() { return None; } 129 | let p = unsafe { *self.0.offset(self.1) }; 130 | if p.is_null() { return None; } 131 | self.1 += 1; 132 | let t = ChmapType::from_c_int(unsafe { (*p).type_ } as libc::c_int, "snd_pcm_query_chmaps").unwrap(); 133 | let m = Chmap(unsafe { &mut (*p).map }, false); 134 | Some((t, m)) 135 | } 136 | } 137 | 138 | 139 | #[test] 140 | fn chmap_for_first_pcm() { 141 | extern crate std; 142 | use super::*; 143 | use ::alloc::ffi::CString; 144 | use crate::device_name::HintIter; 145 | 146 | use crate::Output; 147 | 148 | let output = Output::local_error_handler().unwrap(); 149 | 150 | let i = HintIter::new(None, &*CString::new("pcm").unwrap()).unwrap(); 151 | for p in i.map(|n| n.name.unwrap()) { 152 | std::println!("Chmaps for {:?}:", p); 153 | match PCM::open(&CString::new(p).unwrap(), Direction::Playback, false) { 154 | Ok(a) => for c in a.query_chmaps() { 155 | std::println!(" {:?}, {}", c.0, c.1); 156 | }, 157 | Err(a) => std::println!(" {}", a) // It's okay to have entries in the name hint array that can't be opened 158 | } 159 | } 160 | 161 | output.borrow_mut().buffer_string(|buf| { 162 | let str = CString::new(buf).unwrap(); 163 | std::println!("Errors:\n{}", str.to_str().unwrap()); 164 | }); 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/hctl.rs: -------------------------------------------------------------------------------- 1 | //! HCtl API - for mixer control and jack detection 2 | //! 3 | //! # Example 4 | //! Print all jacks and their status 5 | //! 6 | //! ``` 7 | //! for a in ::alsa::card::Iter::new().map(|x| x.unwrap()) { 8 | //! use ::alloc::ffi::CString; 9 | //! use alsa::hctl::HCtl; 10 | //! let h = HCtl::open(&CString::new(format!("hw:{}", a.get_index())).unwrap(), false).unwrap(); 11 | //! h.load().unwrap(); 12 | //! for b in h.elem_iter() { 13 | //! use alsa::ctl::ElemIface; 14 | //! let id = b.get_id().unwrap(); 15 | //! if id.get_interface() != ElemIface::Card { continue; } 16 | //! let name = id.get_name().unwrap(); 17 | //! if !name.ends_with(" Jack") { continue; } 18 | //! if name.ends_with(" Phantom Jack") { 19 | //! println!("{} is always present", &name[..name.len()-13]) 20 | //! } 21 | //! else { println!("{} is {}", &name[..name.len()-5], 22 | //! if b.read().unwrap().get_boolean(0).unwrap() { "plugged in" } else { "unplugged" }) 23 | //! } 24 | //! } 25 | //! } 26 | //! ``` 27 | 28 | #![allow(dead_code)] 29 | /* There is a "field is never read" warning for the ElemIter struct. We still need to hold on 30 | to the HCtl pointer somehow; to guarantee that the HCtl does not go out of scope while we use 31 | ElemIter, which would make the snd_hctl_elem_t pointer invalid. Hence the decision to allow dead code here. 32 | I suppose there is a better solution for this but I'm not sure how. 33 | */ 34 | 35 | 36 | use crate::{alsa, Card}; 37 | use core::ffi::CStr; 38 | use ::alloc::ffi::CString; 39 | use super::error::*; 40 | use core::ptr; 41 | use super::{ctl_int, poll}; 42 | use libc::{c_short, c_uint, c_int, pollfd}; 43 | 44 | 45 | /// [snd_hctl_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___h_control.html) wrapper 46 | pub struct HCtl(*mut alsa::snd_hctl_t); 47 | 48 | unsafe impl Send for HCtl {} 49 | 50 | impl Drop for HCtl { 51 | fn drop(&mut self) { unsafe { alsa::snd_hctl_close(self.0) }; } 52 | } 53 | 54 | impl HCtl { 55 | /// Wrapper around open that takes a &str instead of a &CStr 56 | pub fn new(c: &str, nonblock: bool) -> Result { 57 | Self::open(&CString::new(c).unwrap(), nonblock) 58 | } 59 | 60 | /// Open does not support async mode (it's not very Rustic anyway) 61 | /// Note: You probably want to call `load` afterwards. 62 | pub fn open(c: &CStr, nonblock: bool) -> Result { 63 | let mut r = ptr::null_mut(); 64 | let flags = if nonblock { 1 } else { 0 }; // FIXME: alsa::SND_CTL_NONBLOCK does not exist in alsa-sys 65 | acheck!(snd_hctl_open(&mut r, c.as_ptr(), flags)) 66 | .map(|_| HCtl(r)) 67 | } 68 | 69 | /// Wrapper around open. You probably want to call `load` afterwards. 70 | pub fn from_card(c: &Card, nonblock: bool) -> Result { 71 | let s = ::alloc::format!("hw:{}", c.get_index()); 72 | HCtl::new(&s, nonblock) 73 | } 74 | 75 | pub fn load(&self) -> Result<()> { acheck!(snd_hctl_load(self.0)).map(|_| ()) } 76 | 77 | pub fn elem_iter(&self) -> ElemIter { ElemIter(self, ptr::null_mut()) } 78 | 79 | pub fn find_elem(&self, id: &ctl_int::ElemId) -> Option { 80 | let p = unsafe { alsa::snd_hctl_find_elem(self.0, ctl_int::elem_id_ptr(id)) }; 81 | if p.is_null() { None } else { Some(Elem(self, p)) } 82 | } 83 | 84 | pub fn handle_events(&self) -> Result { 85 | acheck!(snd_hctl_handle_events(self.0)).map(|x| x as u32) 86 | } 87 | 88 | pub fn wait(&self, timeout_ms: Option) -> Result { 89 | acheck!(snd_hctl_wait(self.0, timeout_ms.map(|x| x as c_int).unwrap_or(-1))).map(|i| i == 1) } 90 | } 91 | 92 | impl poll::Descriptors for HCtl { 93 | fn count(&self) -> usize { 94 | unsafe { alsa::snd_hctl_poll_descriptors_count(self.0) as usize } 95 | } 96 | fn fill(&self, p: &mut [pollfd]) -> Result { 97 | let z = unsafe { alsa::snd_hctl_poll_descriptors(self.0, p.as_mut_ptr(), p.len() as c_uint) }; 98 | from_code("snd_hctl_poll_descriptors", z).map(|_| z as usize) 99 | } 100 | fn revents(&self, p: &[pollfd]) -> Result { 101 | let mut r = 0; 102 | let z = unsafe { alsa::snd_hctl_poll_descriptors_revents(self.0, p.as_ptr() as *mut pollfd, p.len() as c_uint, &mut r) }; 103 | from_code("snd_hctl_poll_descriptors_revents", z).map(|_| poll::Flags::from_bits_truncate(r as c_short)) 104 | } 105 | } 106 | 107 | /// Iterates over elements for a `HCtl` 108 | pub struct ElemIter<'a>(&'a HCtl, *mut alsa::snd_hctl_elem_t); 109 | 110 | impl<'a> Iterator for ElemIter<'a> { 111 | type Item = Elem<'a>; 112 | fn next(&mut self) -> Option> { 113 | self.1 = if self.1.is_null() { unsafe { alsa::snd_hctl_first_elem((self.0).0) }} 114 | else { unsafe { alsa::snd_hctl_elem_next(self.1) }}; 115 | if self.1.is_null() { None } 116 | else { Some(Elem(self.0, self.1)) } 117 | } 118 | } 119 | 120 | 121 | /// [snd_hctl_elem_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___h_control.html) wrapper 122 | pub struct Elem<'a>(&'a HCtl, *mut alsa::snd_hctl_elem_t); 123 | 124 | impl<'a> Elem<'a> { 125 | pub fn get_id(&self) -> Result { 126 | let v = ctl_int::elem_id_new()?; 127 | unsafe { alsa::snd_hctl_elem_get_id(self.1, ctl_int::elem_id_ptr(&v)) }; 128 | Ok(v) 129 | } 130 | pub fn info(&self) -> Result { 131 | let v = ctl_int::elem_info_new()?; 132 | acheck!(snd_hctl_elem_info(self.1, ctl_int::elem_info_ptr(&v))).map(|_| v) 133 | } 134 | pub fn read(&self) -> Result { 135 | let i = self.info()?; 136 | let v = ctl_int::elem_value_new(i.get_type(), i.get_count())?; 137 | acheck!(snd_hctl_elem_read(self.1, ctl_int::elem_value_ptr(&v))).map(|_| v) 138 | } 139 | 140 | pub fn write(&self, v: &ctl_int::ElemValue) -> Result { 141 | acheck!(snd_hctl_elem_write(self.1, ctl_int::elem_value_ptr(v))).map(|e| e > 0) 142 | } 143 | } 144 | 145 | #[test] 146 | fn print_hctls() { 147 | extern crate std; 148 | for a in super::card::Iter::new().map(|x| x.unwrap()) { 149 | use ::alloc::ffi::CString; 150 | let h = HCtl::open(&CString::new(::alloc::format!("hw:{}", a.get_index())).unwrap(), false).unwrap(); 151 | h.load().unwrap(); 152 | std::println!("Card {}:", a.get_name().unwrap()); 153 | for b in h.elem_iter() { 154 | std::println!(" {:?} - {:?}", b.get_id().unwrap(), b.read().unwrap()); 155 | } 156 | } 157 | } 158 | 159 | #[test] 160 | fn print_jacks() { 161 | extern crate std; 162 | for a in super::card::Iter::new().map(|x| x.unwrap()) { 163 | use ::alloc::ffi::CString; 164 | let h = HCtl::open(&CString::new(::alloc::format!("hw:{}", a.get_index())).unwrap(), false).unwrap(); 165 | h.load().unwrap(); 166 | for b in h.elem_iter() { 167 | let id = b.get_id().unwrap(); 168 | if id.get_interface() != super::ctl_int::ElemIface::Card { continue; } 169 | let name = id.get_name().unwrap(); 170 | if !name.ends_with(" Jack") { continue; } 171 | if name.ends_with(" Phantom Jack") { 172 | std::println!("{} is always present", &name[..name.len()-13]) 173 | } 174 | else { std::println!("{} is {}", &name[..name.len()-5], 175 | if b.read().unwrap().get_boolean(0).unwrap() { "plugged in" } else { "unplugged" }) 176 | } 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/direct/ffi.rs: -------------------------------------------------------------------------------- 1 | //! Some definitions from the kernel headers 2 | 3 | #![allow(non_camel_case_types)] 4 | 5 | use cfg_if::cfg_if; 6 | 7 | // const SNDRV_PCM_MMAP_OFFSET_DATA: c_uint = 0x00000000; 8 | pub const SNDRV_PCM_MMAP_OFFSET_STATUS: libc::c_uint = 0x80000000; 9 | pub const SNDRV_PCM_MMAP_OFFSET_CONTROL: libc::c_uint = 0x81000000; 10 | 11 | pub const SNDRV_PCM_SYNC_PTR_HWSYNC: libc::c_uint = 1; 12 | pub const SNDRV_PCM_SYNC_PTR_APPL: libc::c_uint = 2; 13 | pub const SNDRV_PCM_SYNC_PTR_AVAIL_MIN: libc::c_uint = 4; 14 | 15 | // #[repr(C)] 16 | #[allow(non_camel_case_types)] 17 | pub type snd_pcm_state_t = libc::c_int; 18 | 19 | // #[repr(C)] 20 | #[allow(non_camel_case_types)] 21 | pub type snd_pcm_uframes_t = libc::c_ulong; 22 | 23 | // I think?! Not sure how this will work with X32 ABI?! 24 | #[allow(non_camel_case_types)] 25 | pub type __kernel_off_t = libc::c_long; 26 | 27 | #[repr(C)] 28 | #[derive(Copy, Clone)] 29 | pub struct snd_pcm_mmap_status { 30 | pub state: snd_pcm_state_t, /* RO: state - SNDRV_PCM_STATE_XXXX */ 31 | pub pad1: libc::c_int, /* Needed for 64 bit alignment */ 32 | pub hw_ptr: snd_pcm_uframes_t, /* RO: hw ptr (0...boundary-1) */ 33 | pub tstamp: libc::timespec, /* Timestamp */ 34 | pub suspended_state: snd_pcm_state_t, /* RO: suspended stream state */ 35 | pub audio_tstamp: libc::timespec, /* from sample counter or wall clock */ 36 | } 37 | 38 | #[repr(C)] 39 | #[derive(Debug, Copy, Clone)] 40 | pub struct snd_pcm_mmap_control { 41 | pub appl_ptr: snd_pcm_uframes_t, /* RW: appl ptr (0...boundary-1) */ 42 | pub avail_min: snd_pcm_uframes_t, /* RW: min available frames for wakeup */ 43 | } 44 | 45 | #[repr(C)] 46 | #[derive(Debug)] 47 | pub struct snd_pcm_channel_info { 48 | pub channel: libc::c_uint, 49 | pub offset: __kernel_off_t, /* mmap offset */ 50 | pub first: libc::c_uint, /* offset to first sample in bits */ 51 | pub step: libc::c_uint, /* samples distance in bits */ 52 | } 53 | 54 | #[repr(C)] 55 | #[derive(Copy, Clone)] 56 | pub union snd_pcm_mmap_status_r { 57 | pub status: snd_pcm_mmap_status, 58 | pub reserved: [libc::c_uchar; 64], 59 | } 60 | 61 | #[repr(C)] 62 | #[derive(Copy, Clone)] 63 | pub union snd_pcm_mmap_control_r { 64 | pub control: snd_pcm_mmap_control, 65 | pub reserved: [libc::c_uchar; 64], 66 | } 67 | 68 | #[repr(C)] 69 | #[derive(Copy, Clone)] 70 | pub struct snd_pcm_sync_ptr { 71 | pub flags: libc::c_uint, 72 | pub s: snd_pcm_mmap_status_r, 73 | pub c: snd_pcm_mmap_control_r, 74 | } 75 | 76 | cfg_if! { 77 | if #[cfg(any(target_os = "linux", target_os = "android"))] { 78 | // See 79 | 80 | cfg_if! { 81 | if #[cfg(any(target_os = "android", target_env = "musl"))] { 82 | pub(super) type ioctl_num_type = libc::c_int; 83 | } else { 84 | pub(super) type ioctl_num_type = libc::c_ulong; 85 | } 86 | } 87 | 88 | // The READ dir is consistent across arches 89 | pub(super) const READ: ioctl_num_type = 2; 90 | 91 | // But WRITE is not, as well as having a different number of bits for the SIZEBITS 92 | cfg_if!{ 93 | if #[cfg(any( 94 | target_arch = "mips", 95 | target_arch = "mips32r6", 96 | target_arch = "mips64", 97 | target_arch = "mips64r6", 98 | target_arch = "powerpc", 99 | target_arch = "powerpc64", 100 | target_arch = "sparc64" 101 | ))] { 102 | pub(super) const WRITE: ioctl_num_type = 4; 103 | const SIZEBITS: ioctl_num_type = 13; 104 | } else { 105 | pub(super) const WRITE: ioctl_num_type = 1; 106 | const SIZEBITS: ioctl_num_type = 14; 107 | } 108 | } 109 | 110 | const NRSHIFT: ioctl_num_type = 0; 111 | const NRBITS: ioctl_num_type = 8; 112 | const TYPEBITS: ioctl_num_type = 8; 113 | const TYPESHIFT: ioctl_num_type = NRSHIFT + NRBITS; 114 | const SIZESHIFT: ioctl_num_type = TYPESHIFT + TYPEBITS; 115 | const DIRSHIFT: ioctl_num_type = SIZESHIFT + SIZEBITS; 116 | 117 | /// Replication of the [`nix::ioc!`](https://github.com/nix-rust/nix/blob/197f55b3ccbce3273bf6ce119d1a8541b5df5d66/src/sys/ioctl/linux.rs#L78-L96) 118 | pub(super) const fn make_request( 119 | dir: ioctl_num_type, 120 | typ: u8, 121 | nr: u8, 122 | size: usize, 123 | ) -> ioctl_num_type { 124 | dir << DIRSHIFT 125 | | (typ as ioctl_num_type) << TYPESHIFT 126 | | (nr as ioctl_num_type) << NRSHIFT 127 | | (size as ioctl_num_type) << SIZESHIFT 128 | } 129 | } else if #[cfg(any( 130 | target_os = "dragonfly", 131 | target_os = "freebsd", 132 | target_os = "netbsd", 133 | target_os = "openbsd", 134 | target_os = "solaris", 135 | target_os = "illumos" 136 | ))] { 137 | // See 138 | 139 | cfg_if! { 140 | if #[cfg(not(any(target_os = "illumos", target_os = "solaris")))] { 141 | pub(super) type ioctl_num_type = libc::c_ulong; 142 | } else { 143 | pub(super) type ioctl_num_type = libc::c_int; 144 | } 145 | } 146 | 147 | #[allow(overflowing_literals)] 148 | pub(super) const READ: ioctl_num_type = 0x4000_0000; 149 | #[allow(overflowing_literals)] 150 | pub(super) const WRITE: ioctl_num_type = 0x8000_0000; 151 | 152 | const IOCPARM_MASK: ioctl_num_type = 0x1fff; 153 | 154 | /// Replication of [`nix::ioc!`](https://github.com/nix-rust/nix/blob/197f55b3ccbce3273bf6ce119d1a8541b5df5d66/src/sys/ioctl/bsd.rs#L31-L42) 155 | pub(super) const fn make_request( 156 | dir: ioctl_num_type, 157 | typ: u8, 158 | nr: u8, 159 | size: usize, 160 | ) -> ioctl_num_type { 161 | dir | ((size as ioctl_num_type) & IOCPARM_MASK) << 16 162 | | (typ as ioctl_num_type) << 8 163 | | nr as ioctl_num_type 164 | } 165 | } else { 166 | compile_error!("unknown target platform"); 167 | } 168 | } 169 | 170 | pub(crate) unsafe fn sndrv_pcm_ioctl_channel_info( 171 | fd: libc::c_int, 172 | data: *mut snd_pcm_channel_info, 173 | ) -> Result<(), crate::Error> { 174 | const REQUEST: ioctl_num_type = make_request( 175 | READ, 176 | b'A', 177 | 0x32, 178 | core::mem::size_of::(), 179 | ); 180 | 181 | unsafe { 182 | if libc::ioctl(fd, REQUEST, data) == -1 { 183 | Err(crate::Error::last("SNDRV_PCM_IOCTL_CHANNEL_INFO")) 184 | } else { 185 | Ok(()) 186 | } 187 | } 188 | } 189 | 190 | pub(crate) unsafe fn sndrv_pcm_ioctl_sync_ptr( 191 | fd: libc::c_int, 192 | data: *mut snd_pcm_sync_ptr, 193 | ) -> Result<(), crate::Error> { 194 | const REQUEST: ioctl_num_type = make_request( 195 | READ | WRITE, 196 | b'A', 197 | 0x23, 198 | core::mem::size_of::(), 199 | ); 200 | 201 | unsafe { 202 | if libc::ioctl(fd, REQUEST, data) == -1 { 203 | Err(crate::Error::last("SNDRV_PCM_IOCTL_SYNC_PTR")) 204 | } else { 205 | Ok(()) 206 | } 207 | } 208 | } 209 | 210 | pub fn pagesize() -> usize { 211 | unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize } 212 | } 213 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | #![macro_use] 2 | 3 | use libc::{c_char, c_int, c_void, free}; 4 | use core::error::Error as StdError; 5 | use core::ffi::CStr; 6 | use core::{fmt, str}; 7 | use ::alloc::string::{String, ToString}; 8 | 9 | /// ALSA error 10 | /// 11 | /// Most ALSA functions can return a negative error code. 12 | /// If so, then that error code is wrapped into this `Error` struct. 13 | /// An Error is also returned in case ALSA returns a string that 14 | /// cannot be translated into Rust's UTF-8 strings. 15 | #[derive(Clone, PartialEq, Copy)] 16 | pub struct Error(&'static str, c_int); 17 | 18 | pub type Result = ::core::result::Result; 19 | 20 | #[cfg(not(feature = "std"))] 21 | extern "C" { 22 | pub(crate) static errno: c_int; 23 | } 24 | 25 | macro_rules! acheck { 26 | ($f: ident ( $($x: expr),* ) ) => {{ 27 | let r = unsafe { alsa::$f( $($x),* ) }; 28 | if r < 0 { Err(Error::new(stringify!($f), -r as ::libc::c_int)) } 29 | else { Ok(r) } 30 | }} 31 | } 32 | 33 | pub fn from_const<'a>(func: &'static str, s: *const c_char) -> Result<&'a str> { 34 | if s.is_null() { 35 | return Err(invalid_str(func)); 36 | }; 37 | let cc = unsafe { CStr::from_ptr(s) }; 38 | str::from_utf8(cc.to_bytes()).map_err(|_| invalid_str(func)) 39 | } 40 | 41 | pub fn from_alloc(func: &'static str, s: *mut c_char) -> Result { 42 | if s.is_null() { 43 | return Err(invalid_str(func)); 44 | }; 45 | let c = unsafe { CStr::from_ptr(s) }; 46 | let ss = str::from_utf8(c.to_bytes()) 47 | .map_err(|_| { 48 | unsafe { 49 | free(s as *mut c_void); 50 | } 51 | invalid_str(func) 52 | })? 53 | .to_string(); 54 | unsafe { 55 | free(s as *mut c_void); 56 | } 57 | Ok(ss) 58 | } 59 | 60 | pub fn from_code(func: &'static str, r: c_int) -> Result { 61 | if r < 0 { 62 | Err(Error::new(func, r)) 63 | } else { 64 | Ok(r) 65 | } 66 | } 67 | 68 | impl Error { 69 | pub fn new(func: &'static str, res: c_int) -> Error { 70 | Self(func, res) 71 | } 72 | 73 | pub fn last(func: &'static str) -> Error { 74 | #[cfg(feature = "std")] { 75 | Self( 76 | func, 77 | std::io::Error::last_os_error().raw_os_error().unwrap_or_default(), 78 | ) 79 | } 80 | #[cfg(not(feature = "std"))] { 81 | Self( 82 | func, 83 | unsafe {errno}, 84 | ) 85 | } 86 | } 87 | 88 | pub fn unsupported(func: &'static str) -> Error { 89 | Error(func, libc::ENOTSUP) 90 | } 91 | 92 | /// The function which failed. 93 | pub fn func(&self) -> &'static str { 94 | self.0 95 | } 96 | 97 | /// Underlying error 98 | /// 99 | /// Match this against the re-export of `nix::Error` in this crate, not against a specific version 100 | /// of the nix crate. The nix crate version might be updated with minor updates of this library. 101 | pub fn errno(&self) -> i32 { 102 | self.1 103 | } 104 | } 105 | 106 | pub fn invalid_str(func: &'static str) -> Error { 107 | Error(func, libc::EILSEQ) 108 | } 109 | 110 | impl StdError for Error { 111 | fn description(&self) -> &str { 112 | "ALSA error" 113 | } 114 | } 115 | 116 | impl fmt::Debug for Error { 117 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 118 | write!(f, "{}", self) 119 | } 120 | } 121 | 122 | impl fmt::Display for Error { 123 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 124 | write!( 125 | f, 126 | "ALSA function '{}' failed with error '{} ({})'", 127 | self.0, 128 | desc(self.1), 129 | self.1, 130 | ) 131 | } 132 | } 133 | 134 | /// See 135 | /// 136 | /// Note this doesn't include the total set of possible errno variants, but they 137 | /// can easily be added in the future for better error messages 138 | fn desc(err: i32) -> &'static str { 139 | match err { 140 | libc::EPERM => "Operation not permitted", 141 | libc::ENOENT => "No such file or directory", 142 | libc::ESRCH => "No such process", 143 | libc::EINTR => "Interrupted system call", 144 | libc::EIO => "I/O error", 145 | libc::ENXIO => "No such device or address", 146 | libc::E2BIG => "Argument list too long", 147 | libc::ENOEXEC => "Exec format error", 148 | libc::EBADF => "Bad file number", 149 | libc::ECHILD => "No child processes", 150 | libc::EAGAIN => "Try again", 151 | libc::ENOMEM => "Out of memory", 152 | libc::EACCES => "Permission denied", 153 | libc::EFAULT => "Bad address", 154 | libc::ENOTBLK => "Block device required", 155 | libc::EBUSY => "Device or resource busy", 156 | libc::EEXIST => "File exists", 157 | libc::EXDEV => "Cross-device link", 158 | libc::ENODEV => "No such device", 159 | libc::ENOTDIR => "Not a directory", 160 | libc::EISDIR => "Is a directory", 161 | libc::EINVAL => "Invalid argument", 162 | libc::ENFILE => "File table overflow", 163 | libc::EMFILE => "Too many open files", 164 | libc::ENOTTY => "Not a typewriter", 165 | libc::ETXTBSY => "Text file busy", 166 | libc::EFBIG => "File too large", 167 | libc::ENOSPC => "No space left on device", 168 | libc::ESPIPE => "Illegal seek", 169 | libc::EROFS => "Read-only file system", 170 | libc::EMLINK => "Too many links", 171 | libc::EPIPE => "Broken pipe", 172 | libc::EDOM => "Math argument out of domain of func", 173 | libc::ERANGE => "Math result not representable", 174 | libc::EDEADLK => "Resource deadlock would occur", 175 | libc::ENAMETOOLONG => "File name too long", 176 | libc::ENOLCK => "No record locks available", 177 | libc::ENOSYS => "Function not implemented", 178 | libc::ENOTEMPTY => "Directory not empty", 179 | libc::ELOOP => "Too many symbolic links encountered", 180 | libc::ENOMSG => "No message of desired type", 181 | libc::EIDRM => "Identifier removed", 182 | libc::EINPROGRESS => "Operation now in progress", 183 | libc::EALREADY => "Operation already in progress", 184 | libc::ENOTSOCK => "Socket operation on non-socket", 185 | libc::EDESTADDRREQ => "Destination address required", 186 | libc::EMSGSIZE => "Message too long", 187 | libc::EPROTOTYPE => "Protocol wrong type for socket", 188 | libc::ENOPROTOOPT => "Protocol not available", 189 | libc::EPROTONOSUPPORT => "Protocol not supported", 190 | libc::ESOCKTNOSUPPORT => "Socket type not supported", 191 | libc::EPFNOSUPPORT => "Protocol family not supported", 192 | libc::EAFNOSUPPORT => "Address family not supported by protocol", 193 | libc::EADDRINUSE => "Address already in use", 194 | libc::EADDRNOTAVAIL => "Cannot assign requested address", 195 | libc::ENETDOWN => "Network is down", 196 | libc::ENETUNREACH => "Network is unreachable", 197 | libc::ENETRESET => "Network dropped connection because of reset", 198 | libc::ECONNABORTED => "Software caused connection abort", 199 | libc::ECONNRESET => "Connection reset by peer", 200 | libc::ENOBUFS => "No buffer space available", 201 | libc::EISCONN => "Transport endpoint is already connected", 202 | libc::ENOTCONN => "Transport endpoint is not connected", 203 | libc::ESHUTDOWN => "Cannot send after transport endpoint shutdown", 204 | libc::ETOOMANYREFS => "Too many references: cannot splice", 205 | libc::ETIMEDOUT => "Connection timed out", 206 | libc::ECONNREFUSED => "Connection refused", 207 | libc::EHOSTDOWN => "Host is down", 208 | libc::EHOSTUNREACH => "No route to host", 209 | libc::ENOTSUP => "Operation not supported", 210 | _ => "Unknown errno", 211 | } 212 | } 213 | 214 | impl From for fmt::Error { 215 | fn from(_: Error) -> fmt::Error { 216 | fmt::Error 217 | } 218 | } 219 | 220 | #[test] 221 | fn broken_pcm_name() { 222 | use ::alloc::ffi::CString; 223 | let e = crate::PCM::open( 224 | &*CString::new("this_PCM_does_not_exist").unwrap(), 225 | crate::Direction::Playback, 226 | false, 227 | ) 228 | .err() 229 | .unwrap(); 230 | assert_eq!(e.func(), "snd_pcm_open"); 231 | assert_eq!(e.errno(), libc::ENOENT); 232 | } 233 | -------------------------------------------------------------------------------- /src/rawmidi.rs: -------------------------------------------------------------------------------- 1 | //! MIDI devices I/O and enumeration 2 | 3 | use libc::{c_int, c_uint, c_void, size_t, c_short, pollfd}; 4 | use super::ctl_int::{ctl_ptr, Ctl}; 5 | use super::{Direction, poll}; 6 | use super::error::*; 7 | use crate::alsa; 8 | use ::alloc::ffi::CString; 9 | use ::alloc::string::{String, ToString}; 10 | use core::ptr; 11 | use core::ffi::CStr; 12 | 13 | /// Iterator over [Rawmidi](http://www.alsa-project.org/alsa-doc/alsa-lib/group___raw_midi.html) devices and subdevices 14 | pub struct Iter<'a> { 15 | ctl: &'a Ctl, 16 | device: c_int, 17 | in_count: i32, 18 | out_count: i32, 19 | current: i32, 20 | } 21 | 22 | /// [snd_rawmidi_info_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___raw_midi.html) wrapper 23 | pub struct Info(*mut alsa::snd_rawmidi_info_t); 24 | 25 | impl Drop for Info { 26 | fn drop(&mut self) { unsafe { alsa::snd_rawmidi_info_free(self.0) }; } 27 | } 28 | 29 | impl Info { 30 | fn new() -> Result { 31 | let mut p = ptr::null_mut(); 32 | acheck!(snd_rawmidi_info_malloc(&mut p)).map(|_| Info(p)) 33 | } 34 | 35 | fn from_iter(c: &Ctl, device: i32, sub: i32, dir: Direction) -> Result { 36 | let r = Info::new()?; 37 | unsafe { alsa::snd_rawmidi_info_set_device(r.0, device as c_uint) }; 38 | let d = match dir { 39 | Direction::Playback => alsa::SND_RAWMIDI_STREAM_OUTPUT, 40 | Direction::Capture => alsa::SND_RAWMIDI_STREAM_INPUT, 41 | }; 42 | unsafe { alsa::snd_rawmidi_info_set_stream(r.0, d) }; 43 | unsafe { alsa::snd_rawmidi_info_set_subdevice(r.0, sub as c_uint) }; 44 | acheck!(snd_ctl_rawmidi_info(ctl_ptr(c), r.0)).map(|_| r) 45 | } 46 | 47 | fn subdev_count(c: &Ctl, device: c_int) -> Result<(i32, i32)> { 48 | let i = Info::from_iter(c, device, 0, Direction::Capture)?; 49 | let o = Info::from_iter(c, device, 0, Direction::Playback)?; 50 | Ok((unsafe { alsa::snd_rawmidi_info_get_subdevices_count(o.0) as i32 }, 51 | unsafe { alsa::snd_rawmidi_info_get_subdevices_count(i.0) as i32 })) 52 | } 53 | 54 | pub fn get_device(&self) -> i32 { unsafe { alsa::snd_rawmidi_info_get_device(self.0) as i32 }} 55 | pub fn get_subdevice(&self) -> i32 { unsafe { alsa::snd_rawmidi_info_get_subdevice(self.0) as i32 }} 56 | pub fn get_stream(&self) -> super::Direction { 57 | if unsafe { alsa::snd_rawmidi_info_get_stream(self.0) } == alsa::SND_RAWMIDI_STREAM_OUTPUT { super::Direction::Playback } 58 | else { super::Direction::Capture } 59 | } 60 | 61 | pub fn get_subdevice_name(&self) -> Result { 62 | let c = unsafe { alsa::snd_rawmidi_info_get_subdevice_name(self.0) }; 63 | from_const("snd_rawmidi_info_get_subdevice_name", c).map(|s| s.to_string()) 64 | } 65 | pub fn get_id(&self) -> Result { 66 | let c = unsafe { alsa::snd_rawmidi_info_get_id(self.0) }; 67 | from_const("snd_rawmidi_info_get_id", c).map(|s| s.to_string()) 68 | } 69 | } 70 | 71 | /// [snd_rawmidi_info_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___raw_midi.html) wrapper 72 | pub struct Status(*mut alsa::snd_rawmidi_status_t); 73 | 74 | impl Status { 75 | fn new() -> Result { 76 | let mut p = ptr::null_mut(); 77 | acheck!(snd_rawmidi_status_malloc(&mut p)).map(|_| Status(p)) 78 | } 79 | } 80 | 81 | impl Status { 82 | pub fn get_avail(&self) -> usize { unsafe { alsa::snd_rawmidi_status_get_avail(self.0 as *const _) } } 83 | pub fn get_xruns(&self) -> usize { unsafe { alsa::snd_rawmidi_status_get_xruns(self.0 as *const _) } } 84 | } 85 | 86 | impl Drop for Status { 87 | fn drop(&mut self) { unsafe { alsa::snd_rawmidi_status_free(self.0) }; } 88 | } 89 | 90 | 91 | impl<'a> Iter<'a> { 92 | pub fn new(c: &'a Ctl) -> Iter<'a> { Iter { ctl: c, device: -1, in_count: 0, out_count: 0, current: 0 }} 93 | } 94 | 95 | impl<'a> Iterator for Iter<'a> { 96 | type Item = Result; 97 | fn next(&mut self) -> Option> { 98 | if self.current < self.in_count { 99 | self.current += 1; 100 | return Some(Info::from_iter(self.ctl, self.device, self.current-1, Direction::Capture)); 101 | } 102 | if self.current - self.in_count < self.out_count { 103 | self.current += 1; 104 | return Some(Info::from_iter(self.ctl, self.device, self.current-1-self.in_count, Direction::Playback)); 105 | } 106 | 107 | let r = acheck!(snd_ctl_rawmidi_next_device(ctl_ptr(self.ctl), &mut self.device)); 108 | match r { 109 | Err(e) => return Some(Err(e)), 110 | Ok(_) if self.device == -1 => return None, 111 | _ => {}, 112 | } 113 | self.current = 0; 114 | match Info::subdev_count(self.ctl, self.device) { 115 | Err(e) => Some(Err(e)), 116 | Ok((oo, ii)) => { 117 | self.in_count = ii; 118 | self.out_count = oo; 119 | self.next() 120 | } 121 | } 122 | } 123 | } 124 | 125 | /// [snd_rawmidi_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___raw_midi.html) wrapper 126 | pub struct Rawmidi(*mut alsa::snd_rawmidi_t); 127 | 128 | unsafe impl Send for Rawmidi {} 129 | 130 | impl Drop for Rawmidi { 131 | fn drop(&mut self) { unsafe { alsa::snd_rawmidi_close(self.0) }; } 132 | } 133 | 134 | impl Rawmidi { 135 | 136 | /// Wrapper around open that takes a &str instead of a &CStr 137 | pub fn new(name: &str, dir: Direction, nonblock: bool) -> Result { 138 | Self::open(&CString::new(name).unwrap(), dir, nonblock) 139 | } 140 | 141 | pub fn open(name: &CStr, dir: Direction, nonblock: bool) -> Result { 142 | let mut h = ptr::null_mut(); 143 | let flags = if nonblock { 2 } else { 0 }; // FIXME: alsa::SND_RAWMIDI_NONBLOCK does not exist in alsa-sys 144 | acheck!(snd_rawmidi_open( 145 | if dir == Direction::Capture { &mut h } else { ptr::null_mut() }, 146 | if dir == Direction::Playback { &mut h } else { ptr::null_mut() }, 147 | name.as_ptr(), flags)) 148 | .map(|_| Rawmidi(h)) 149 | } 150 | 151 | pub fn info(&self) -> Result { 152 | Info::new().and_then(|i| acheck!(snd_rawmidi_info(self.0, i.0)).map(|_| i)) 153 | } 154 | 155 | pub fn status(&self) -> Result { 156 | Status::new().and_then(|i| acheck!(snd_rawmidi_status(self.0, i.0)).map(|_| i)) 157 | } 158 | 159 | pub fn drop(&self) -> Result<()> { acheck!(snd_rawmidi_drop(self.0)).map(|_| ()) } 160 | pub fn drain(&self) -> Result<()> { acheck!(snd_rawmidi_drain(self.0)).map(|_| ()) } 161 | pub fn name(&self) -> Result { 162 | let c = unsafe { alsa::snd_rawmidi_name(self.0) }; 163 | from_const("snd_rawmidi_name", c).map(|s| s.to_string()) 164 | } 165 | 166 | pub fn io(&self) -> IO { IO(self) } 167 | } 168 | 169 | impl poll::Descriptors for Rawmidi { 170 | fn count(&self) -> usize { 171 | unsafe { alsa::snd_rawmidi_poll_descriptors_count(self.0) as usize } 172 | } 173 | fn fill(&self, p: &mut [pollfd]) -> Result { 174 | let z = unsafe { alsa::snd_rawmidi_poll_descriptors(self.0, p.as_mut_ptr(), p.len() as c_uint) }; 175 | from_code("snd_rawmidi_poll_descriptors", z).map(|_| z as usize) 176 | } 177 | fn revents(&self, p: &[pollfd]) -> Result { 178 | let mut r = 0; 179 | let z = unsafe { alsa::snd_rawmidi_poll_descriptors_revents(self.0, p.as_ptr() as *mut pollfd, p.len() as c_uint, &mut r) }; 180 | from_code("snd_rawmidi_poll_descriptors_revents", z).map(|_| poll::Flags::from_bits_truncate(r as c_short)) 181 | } 182 | } 183 | 184 | /// Implements `std::io::Read` and `std::io::Write` for `Rawmidi` 185 | pub struct IO<'a>(&'a Rawmidi); 186 | 187 | #[cfg(feature = "std")] 188 | impl<'a> std::io::Read for IO<'a> { 189 | fn read(&mut self, buf: &mut [u8]) -> std::io::Result { 190 | let r = unsafe { alsa::snd_rawmidi_read((self.0).0, buf.as_mut_ptr() as *mut c_void, buf.len() as size_t) }; 191 | if r < 0 { Err(std::io::Error::from_raw_os_error(r as i32)) } 192 | else { Ok(r as usize) } 193 | } 194 | } 195 | 196 | #[cfg(feature = "std")] 197 | impl<'a> std::io::Write for IO<'a> { 198 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 199 | let r = unsafe { alsa::snd_rawmidi_write((self.0).0, buf.as_ptr() as *const c_void, buf.len() as size_t) }; 200 | if r < 0 { Err(std::io::Error::from_raw_os_error(r as i32)) } 201 | else { Ok(r as usize) } 202 | } 203 | fn flush(&mut self) -> std::io::Result<()> { Ok(()) } 204 | } 205 | 206 | 207 | #[test] 208 | fn print_rawmidis() { 209 | extern crate std; 210 | 211 | for a in super::card::Iter::new().map(|a| a.unwrap()) { 212 | for b in Iter::new(&Ctl::from_card(&a, false).unwrap()).map(|b| b.unwrap()) { 213 | std::println!("Rawmidi {:?} (hw:{},{},{}) {} - {}", b.get_stream(), a.get_index(), b.get_device(), b.get_subdevice(), 214 | a.get_name().unwrap(), b.get_subdevice_name().unwrap()) 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /synth-example/src/main.rs: -------------------------------------------------------------------------------- 1 | // A quickly made Hammond organ. 2 | 3 | use std::{iter, error}; 4 | use alsa::{seq, pcm}; 5 | use std::ffi::CString; 6 | use dasp::signal; 7 | 8 | type Res = Result>; 9 | 10 | fn connect_midi_source_ports(s: &alsa::Seq, our_port: i32) -> Res<()> { 11 | // Iterate over clients and clients' ports 12 | let our_id = s.client_id()?; 13 | let ci = seq::ClientIter::new(&s); 14 | for client in ci { 15 | if client.get_client() == our_id { continue; } // Skip ourselves 16 | let pi = seq::PortIter::new(&s, client.get_client()); 17 | for port in pi { 18 | let caps = port.get_capability(); 19 | 20 | // Check that it's a normal input port 21 | if !caps.contains(seq::PortCap::READ) || !caps.contains(seq::PortCap::SUBS_READ) { continue; } 22 | if !port.get_type().contains(seq::PortType::MIDI_GENERIC) { continue; } 23 | 24 | // Connect source and dest ports 25 | let subs = seq::PortSubscribe::empty()?; 26 | subs.set_sender(seq::Addr { client: port.get_client(), port: port.get_port() }); 27 | subs.set_dest(seq::Addr { client: our_id, port: our_port }); 28 | println!("Reading from midi input {:?}", port); 29 | s.subscribe_port(&subs)?; 30 | } 31 | } 32 | 33 | Ok(()) 34 | } 35 | 36 | fn open_midi_dev() -> Res { 37 | // Open the sequencer. 38 | let s = alsa::Seq::open(None, Some(alsa::Direction::Capture), true)?; 39 | let cstr = CString::new("rust_synth_example").unwrap(); 40 | s.set_client_name(&cstr)?; 41 | 42 | // Create a destination port we can read from 43 | let mut dinfo = seq::PortInfo::empty().unwrap(); 44 | dinfo.set_capability(seq::PortCap::WRITE | seq::PortCap::SUBS_WRITE); 45 | dinfo.set_type(seq::PortType::MIDI_GENERIC | seq::PortType::APPLICATION); 46 | dinfo.set_name(&cstr); 47 | s.create_port(&dinfo).unwrap(); 48 | let dport = dinfo.get_port(); 49 | 50 | // source ports should ideally be configurable, but right now we're just reading them all. 51 | connect_midi_source_ports(&s, dport)?; 52 | 53 | Ok(s) 54 | } 55 | 56 | fn open_audio_dev() -> Res<(alsa::PCM, u32)> { 57 | let args: Vec<_> = std::env::args().collect(); 58 | if args.len() < 2 { 59 | println!("Usage: 'cargo run --release CARD_NAME SAMPLE_RATE BUF_SIZE'"); 60 | Err("No card name specified")? 61 | } 62 | let req_devname = format!("hw:{}", args[1]); 63 | let req_samplerate = args.get(2).map(|x| x.parse()).unwrap_or(Ok(48000))?; 64 | let req_bufsize = args.get(3).map(|x| x.parse()).unwrap_or(Ok(256))?; // A few ms latency by default, that should be nice 65 | 66 | // Open the device 67 | let p = alsa::PCM::new(&req_devname, alsa::Direction::Playback, false)?; 68 | 69 | // Set hardware parameters 70 | { 71 | let hwp = pcm::HwParams::any(&p)?; 72 | hwp.set_channels(2)?; 73 | hwp.set_rate(req_samplerate, alsa::ValueOr::Nearest)?; 74 | hwp.set_format(pcm::Format::s16())?; 75 | hwp.set_access(pcm::Access::MMapInterleaved)?; 76 | hwp.set_buffer_size(req_bufsize)?; 77 | hwp.set_period_size(req_bufsize / 4, alsa::ValueOr::Nearest)?; 78 | p.hw_params(&hwp)?; 79 | } 80 | 81 | // Set software parameters 82 | let rate = { 83 | let hwp = p.hw_params_current()?; 84 | let swp = p.sw_params_current()?; 85 | let (bufsize, periodsize) = (hwp.get_buffer_size()?, hwp.get_period_size()?); 86 | swp.set_start_threshold(bufsize - periodsize)?; 87 | swp.set_avail_min(periodsize)?; 88 | p.sw_params(&swp)?; 89 | println!("Opened audio output {:?} with parameters: {:?}, {:?}", req_devname, hwp, swp); 90 | hwp.get_rate()? 91 | }; 92 | 93 | Ok((p, rate)) 94 | } 95 | 96 | // Sample format 97 | type SF = i16; 98 | 99 | type SigGen = signal::Sine; 100 | 101 | // Standard Hammond drawbar. 102 | const BAR_FREQS: [f64; 9] = [16., 5.+1./3., 8., 4., 2.+2./3., 2., 1.+3./5., 1.+1./3., 1.]; 103 | 104 | #[derive(Clone)] 105 | struct Sig { 106 | note: u8, 107 | sig: SigGen, 108 | targetvol: f64, 109 | curvol: f64, 110 | baridx: usize, 111 | } 112 | 113 | 114 | struct Synth { 115 | sigs: Vec>, 116 | sample_rate: signal::Rate, 117 | stored_sample: Option, 118 | bar_values: [f64; 9], 119 | } 120 | 121 | impl Synth { 122 | fn add_note(&mut self, note: u8, vol: f64) { 123 | let hz = 440. * 2_f64.powf((note as f64 - 69.)/12.); 124 | 125 | for (baridx, barfreq) in BAR_FREQS.iter().enumerate() { 126 | let idx = self.sigs.iter().position(|s| s.is_none()); 127 | let idx = if let Some(idx) = idx { idx } else { 128 | println!("Voice overflow!"); return; 129 | }; 130 | let hz = self.sample_rate.const_hz(hz * 8. / barfreq); 131 | let s = Sig { sig: hz.sine(), note, targetvol: vol, curvol: 0., baridx }; 132 | self.sigs[idx] = Some(s); 133 | } 134 | } 135 | fn remove_note(&mut self, note: u8) { 136 | for i in self.sigs.iter_mut() { 137 | if let &mut Some(ref mut i) = i { 138 | if i.note == note { i.targetvol = 0. } 139 | } 140 | } 141 | } 142 | fn cc(&mut self, ctrl: u32, value: i32) { 143 | let idx = match ctrl { 144 | // Standard knobs on UMA25S, modify to your liking 145 | 1 => 0, 146 | 74 => 1, 147 | 71 => 2, 148 | 73 => 3, 149 | 75 => 4, 150 | 72 => 5, 151 | 91 => 6, 152 | 93 => 7, 153 | 10 => 8, 154 | _ => return, 155 | }; 156 | self.bar_values[idx] = f64::from(value) / 255.; 157 | } 158 | } 159 | 160 | impl Iterator for Synth { 161 | type Item = SF; 162 | fn next(&mut self) -> Option { 163 | use dasp::{signal::Signal, Sample}; 164 | 165 | // Mono -> Stereo 166 | if let Some(s) = self.stored_sample.take() { return Some(s) }; 167 | 168 | let mut z = 0f64; 169 | for sig in &mut self.sigs { 170 | let mut remove = false; 171 | if let &mut Some(ref mut i) = sig { 172 | let barvalue = self.bar_values[i.baridx]; 173 | if barvalue > 0.0 { 174 | let s = i.sig.next(); 175 | z += s.mul_amp(i.curvol * barvalue); 176 | } 177 | 178 | // Quick and dirty volume envelope to avoid clicks. 179 | if i.curvol != i.targetvol { 180 | if i.targetvol == 0. { 181 | i.curvol -= 0.002; 182 | if i.curvol <= 0. { remove = true; } 183 | } else { 184 | i.curvol += 0.002; 185 | if i.curvol >= i.targetvol { i.curvol = i.targetvol; } 186 | } 187 | } 188 | } 189 | if remove { *sig = None }; 190 | } 191 | let z = z.min(0.999).max(-0.999); 192 | let z: Option = Some(SF::from_sample(z)); 193 | self.stored_sample = z; 194 | z 195 | } 196 | } 197 | 198 | fn write_samples_direct(p: &alsa::PCM, mmap: &mut alsa::direct::pcm::MmapPlayback, synth: &mut Synth) 199 | -> Res { 200 | 201 | if mmap.avail() > 0 { 202 | // Write samples to DMA area from iterator 203 | mmap.write(synth); 204 | } 205 | use alsa::pcm::State; 206 | match mmap.status().state() { 207 | State::Running => { return Ok(false); }, // All fine 208 | State::Prepared => { println!("Starting audio output stream"); p.start()? }, 209 | State::XRun => { println!("Underrun in audio output stream!"); p.prepare()? }, 210 | State::Suspended => { println!("Resuming audio output stream"); p.resume()? }, 211 | n @ _ => Err(format!("Unexpected pcm state {:?}", n))?, 212 | } 213 | Ok(true) // Call us again, please, there might be more data to write 214 | } 215 | 216 | fn write_samples_io(p: &alsa::PCM, io: &mut alsa::pcm::IO, synth: &mut Synth) -> Res { 217 | let avail = match p.avail_update() { 218 | Ok(n) => n, 219 | Err(e) => { 220 | println!("Recovering from {}", e); 221 | p.recover(e.errno() as std::os::raw::c_int, true)?; 222 | p.avail_update()? 223 | } 224 | } as usize; 225 | 226 | if avail > 0 { 227 | io.mmap(avail, |buf| { 228 | for sample in buf.iter_mut() { 229 | *sample = synth.next().unwrap() 230 | }; 231 | buf.len() / 2 232 | })?; 233 | } 234 | use alsa::pcm::State; 235 | match p.state() { 236 | State::Running => Ok(false), // All fine 237 | State::Prepared => { println!("Starting audio output stream"); p.start()?; Ok(true) }, 238 | State::Suspended | State::XRun => Ok(true), // Recover from this in next round 239 | n @ _ => Err(format!("Unexpected pcm state {:?}", n))?, 240 | } 241 | } 242 | 243 | fn read_midi_event(input: &mut seq::Input, synth: &mut Synth) -> Res { 244 | if input.event_input_pending(true)? == 0 { return Ok(false); } 245 | let ev = input.event_input()?; 246 | // println!("Received: {:?}", ev); 247 | match ev.get_type() { 248 | seq::EventType::Noteon => { 249 | let data: seq::EvNote = ev.get_data().unwrap(); 250 | if data.velocity == 0 { 251 | synth.remove_note(data.note); 252 | } else { 253 | synth.add_note(data.note, f64::from(data.velocity + 64) / 2048.); 254 | } 255 | }, 256 | seq::EventType::Noteoff => { 257 | let data: seq::EvNote = ev.get_data().unwrap(); 258 | synth.remove_note(data.note); 259 | }, 260 | seq::EventType::Controller => { 261 | let data: seq::EvCtrl = ev.get_data().unwrap(); 262 | synth.cc(data.param, data.value); 263 | } 264 | _ => {}, 265 | } 266 | Ok(true) 267 | } 268 | 269 | 270 | fn run() -> Res<()> { 271 | let (audio_dev, rate) = open_audio_dev()?; 272 | let midi_dev = open_midi_dev()?; 273 | 274 | let mut midi_input = midi_dev.input(); 275 | 276 | // 256 Voices synth 277 | let mut synth = Synth { 278 | sigs: iter::repeat(None).take(256).collect(), 279 | sample_rate: signal::rate(f64::from(rate)), 280 | stored_sample: None, 281 | bar_values: [1., 0.75, 1., 0.75, 0., 0., 0., 0., 0.75], // Some Gospel-ish default. 282 | }; 283 | 284 | // Create an array of fds to poll. 285 | use alsa::PollDescriptors; 286 | let mut fds = audio_dev.get()?; 287 | fds.append(&mut (&midi_dev, Some(alsa::Direction::Capture)).get()?); 288 | 289 | // Let's use the fancy new "direct mode" for minimum overhead! 290 | let mut mmap = audio_dev.direct_mmap_playback::(); 291 | 292 | // Direct mode unavailable, use alsa-lib's mmap emulation instead 293 | let mut io = if mmap.is_err() { 294 | Some(audio_dev.io_i16()?) 295 | } else { None }; 296 | 297 | loop { 298 | if let Ok(ref mut mmap) = mmap { 299 | if write_samples_direct(&audio_dev, mmap, &mut synth)? { continue; } 300 | } else if let Some(ref mut io) = io { 301 | if write_samples_io(&audio_dev, io, &mut synth)? { continue; } 302 | } 303 | if read_midi_event(&mut midi_input, &mut synth)? { continue; } 304 | // Nothing to do, let's sleep until woken up by the kernel. 305 | alsa::poll::poll(&mut fds, 100)?; 306 | } 307 | } 308 | 309 | fn main() { 310 | if let Err(e) = run() { println!("Error: {}", e); } 311 | } 312 | -------------------------------------------------------------------------------- /src/ctl_int.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::alsa; 3 | use super::pcm::Info; 4 | use core::ffi::CStr; 5 | use ::alloc::ffi::CString; 6 | use super::Direction; 7 | use super::error::*; 8 | use super::mixer::MilliBel; 9 | use super::Round; 10 | use core::{ptr, mem, fmt, cmp}; 11 | use crate::{Card, poll}; 12 | use core::cell::UnsafeCell; 13 | use libc::{c_uint, c_void, size_t, c_long, c_int, pollfd, c_short}; 14 | 15 | /// We prefer not to allocate for every ElemId, ElemInfo or ElemValue. 16 | /// But we don't know if these will increase in the future or on other platforms. 17 | /// Unfortunately, Rust does not support alloca, so hard-code the sizes for now. 18 | 19 | const ELEM_ID_SIZE: usize = 64; 20 | // const ELEM_VALUE_SIZE: usize = 1224; 21 | // const ELEM_INFO_SIZE: usize = 272; 22 | 23 | /// [snd_ctl_pcm_next_device](https://www.alsa-project.org/alsa-doc/alsa-lib/control_8c.html#accbb0be6e5ca7361ffec0ea304ed1b05) wrapper. 24 | /// Iterate over devices of a card. 25 | pub struct DeviceIter<'a>(&'a Ctl, c_int); 26 | 27 | impl<'a> DeviceIter<'a>{ 28 | pub fn new(ctl: &'a Ctl) -> DeviceIter<'a> { 29 | DeviceIter(ctl, -1) 30 | } 31 | } 32 | 33 | impl<'a> Iterator for DeviceIter<'a> { 34 | type Item = c_int; 35 | 36 | fn next(&mut self) -> Option { 37 | match acheck!(snd_ctl_pcm_next_device(self.0.0, &mut self.1)) { 38 | Ok(_) if self.1 == -1 => None, 39 | Ok(_) => Some(self.1), 40 | Err(_) => None, 41 | } 42 | } 43 | } 44 | 45 | /// [snd_ctl_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper 46 | pub struct Ctl(*mut alsa::snd_ctl_t); 47 | 48 | unsafe impl Send for Ctl {} 49 | 50 | impl Ctl { 51 | /// Wrapper around open that takes a &str instead of a &CStr 52 | pub fn new(c: &str, nonblock: bool) -> Result { 53 | Self::open(&CString::new(c).unwrap(), nonblock) 54 | } 55 | 56 | /// Open does not support async mode (it's not very Rustic anyway) 57 | pub fn open(c: &CStr, nonblock: bool) -> Result { 58 | let mut r = ptr::null_mut(); 59 | let flags = if nonblock { 1 } else { 0 }; // FIXME: alsa::SND_CTL_NONBLOCK does not exist in alsa-sys 60 | acheck!(snd_ctl_open(&mut r, c.as_ptr(), flags)).map(|_| Ctl(r)) 61 | } 62 | 63 | pub fn from_card(c: &Card, nonblock: bool) -> Result { 64 | let s = ::alloc::format!("hw:{}", c.get_index()); 65 | Ctl::open(&CString::new(s).unwrap(), nonblock) 66 | } 67 | 68 | pub fn card_info(&self) -> Result { CardInfo::new().and_then(|c| 69 | acheck!(snd_ctl_card_info(self.0, c.0)).map(|_| c)) } 70 | 71 | pub fn wait(&self, timeout_ms: Option) -> Result { 72 | acheck!(snd_ctl_wait(self.0, timeout_ms.map(|x| x as c_int).unwrap_or(-1))).map(|i| i == 1) } 73 | 74 | pub fn get_db_range(&self, id: &ElemId) -> Result<(MilliBel, MilliBel)> { 75 | let mut min: c_long = 0; 76 | let mut max: c_long = 0; 77 | acheck!(snd_ctl_get_dB_range(self.0, elem_id_ptr(id), &mut min, &mut max)) 78 | .map(|_| (MilliBel(min as i64), MilliBel(max as i64))) 79 | } 80 | 81 | pub fn convert_to_db(&self, id: &ElemId, volume: i64) -> Result { 82 | let mut m: c_long = 0; 83 | acheck!(snd_ctl_convert_to_dB(self.0, elem_id_ptr(id), volume as c_long, &mut m)) 84 | .map(|_| (MilliBel(m as i64))) 85 | } 86 | 87 | pub fn convert_from_db(&self, id: &ElemId, mb: MilliBel, dir: Round) -> Result { 88 | let mut m: c_long = 0; 89 | acheck!(snd_ctl_convert_from_dB(self.0, elem_id_ptr(id), mb.0 as c_long, &mut m, dir as c_int)) 90 | .map(|_| m as i64) 91 | } 92 | 93 | pub fn elem_read(&self, val: &mut ElemValue) -> Result<()> { 94 | acheck!(snd_ctl_elem_read(self.0, elem_value_ptr(val))).map(|_| ()) 95 | } 96 | 97 | pub fn elem_write(&self, val: &ElemValue) -> Result<()> { 98 | acheck!(snd_ctl_elem_write(self.0, elem_value_ptr(val))).map(|_| ()) 99 | } 100 | 101 | pub fn elem_lock(&self, id: &ElemId) -> Result { 102 | acheck!(snd_ctl_elem_lock(self.0, elem_id_ptr(id))) 103 | } 104 | 105 | pub fn elem_unlock(&self, id: &ElemId) -> Result { 106 | acheck!(snd_ctl_elem_unlock(self.0, elem_id_ptr(id))) 107 | } 108 | 109 | pub fn elem_list(&self) -> Result { 110 | // obtain the list of all the elements now that we know how many there are 111 | let list = elem_list_new(|list| { 112 | acheck!(snd_ctl_elem_list(self.0, list.0))?; 113 | Ok(list.get_count()) 114 | })?; 115 | acheck!(snd_ctl_elem_list(self.0, list.0))?; 116 | Ok(list) 117 | } 118 | 119 | /// Note: According to alsa-lib documentation, you're also supposed to have functionality for 120 | /// returning whether or not you are subscribed. This does not work in practice, so I'm not 121 | /// including that here. 122 | pub fn subscribe_events(&self, subscribe: bool) -> Result<()> { 123 | acheck!(snd_ctl_subscribe_events(self.0, if subscribe { 1 } else { 0 })).map(|_| ()) 124 | } 125 | 126 | pub fn read(&self) -> Result> { 127 | let e = event_new()?; 128 | acheck!(snd_ctl_read(self.0, e.0)).map(|r| if r == 1 { Some(e) } else { None }) 129 | } 130 | 131 | pub fn pcm_info(&self, device: u32, subdevice: u32, direction: Direction) -> Result { 132 | Info::new().and_then(|mut info| { 133 | info.set_device(device); 134 | info.set_subdevice(subdevice); 135 | info.set_stream(direction); 136 | acheck!(snd_ctl_pcm_info(self.0, info.0)).map(|_| info ) 137 | }) 138 | } 139 | } 140 | 141 | impl Drop for Ctl { 142 | fn drop(&mut self) { unsafe { alsa::snd_ctl_close(self.0) }; } 143 | } 144 | 145 | impl poll::Descriptors for Ctl { 146 | fn count(&self) -> usize { 147 | unsafe { alsa::snd_ctl_poll_descriptors_count(self.0) as usize } 148 | } 149 | fn fill(&self, p: &mut [pollfd]) -> Result { 150 | let z = unsafe { alsa::snd_ctl_poll_descriptors(self.0, p.as_mut_ptr(), p.len() as c_uint) }; 151 | from_code("snd_ctl_poll_descriptors", z).map(|_| z as usize) 152 | } 153 | fn revents(&self, p: &[pollfd]) -> Result { 154 | let mut r = 0; 155 | let z = unsafe { alsa::snd_ctl_poll_descriptors_revents(self.0, p.as_ptr() as *mut pollfd, p.len() as c_uint, &mut r) }; 156 | from_code("snd_ctl_poll_descriptors_revents", z).map(|_| poll::Flags::from_bits_truncate(r as c_short)) 157 | } 158 | } 159 | 160 | 161 | pub fn ctl_ptr(a: &Ctl) -> *mut alsa::snd_ctl_t { a.0 } 162 | 163 | /// [snd_ctl_card_info_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper 164 | pub struct CardInfo(*mut alsa::snd_ctl_card_info_t); 165 | 166 | impl Drop for CardInfo { 167 | fn drop(&mut self) { unsafe { alsa::snd_ctl_card_info_free(self.0) }} 168 | } 169 | 170 | impl CardInfo { 171 | fn new() -> Result { 172 | let mut p = ptr::null_mut(); 173 | acheck!(snd_ctl_card_info_malloc(&mut p)).map(|_| CardInfo(p)) 174 | } 175 | 176 | pub fn get_id(&self) -> Result<&str> { 177 | from_const("snd_ctl_card_info_get_id", unsafe { alsa::snd_ctl_card_info_get_id(self.0) })} 178 | pub fn get_driver(&self) -> Result<&str> { 179 | from_const("snd_ctl_card_info_get_driver", unsafe { alsa::snd_ctl_card_info_get_driver(self.0) })} 180 | pub fn get_components(&self) -> Result<&str> { 181 | from_const("snd_ctl_card_info_get_components", unsafe { alsa::snd_ctl_card_info_get_components(self.0) })} 182 | pub fn get_longname(&self) -> Result<&str> { 183 | from_const("snd_ctl_card_info_get_longname", unsafe { alsa::snd_ctl_card_info_get_longname(self.0) })} 184 | pub fn get_name(&self) -> Result<&str> { 185 | from_const("snd_ctl_card_info_get_name", unsafe { alsa::snd_ctl_card_info_get_name(self.0) })} 186 | pub fn get_mixername(&self) -> Result<&str> { 187 | from_const("snd_ctl_card_info_get_mixername", unsafe { alsa::snd_ctl_card_info_get_mixername(self.0) })} 188 | pub fn get_card(&self) -> Card { Card::new(unsafe { alsa::snd_ctl_card_info_get_card(self.0) })} 189 | } 190 | 191 | alsa_enum!( 192 | /// [SND_CTL_ELEM_IFACE_xxx](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) constants 193 | ElemIface, ALL_ELEMIFACE[7], 194 | 195 | Card = SND_CTL_ELEM_IFACE_CARD, 196 | Hwdep = SND_CTL_ELEM_IFACE_HWDEP, 197 | Mixer = SND_CTL_ELEM_IFACE_MIXER, 198 | PCM = SND_CTL_ELEM_IFACE_PCM, 199 | Rawmidi = SND_CTL_ELEM_IFACE_RAWMIDI, 200 | Timer = SND_CTL_ELEM_IFACE_TIMER, 201 | Sequencer = SND_CTL_ELEM_IFACE_SEQUENCER, 202 | ); 203 | 204 | alsa_enum!( 205 | /// [SND_CTL_ELEM_TYPE_xxx](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) constants 206 | ElemType, ALL_ELEMTYPE[7], 207 | 208 | None = SND_CTL_ELEM_TYPE_NONE, 209 | Boolean = SND_CTL_ELEM_TYPE_BOOLEAN, 210 | Integer = SND_CTL_ELEM_TYPE_INTEGER, 211 | Enumerated = SND_CTL_ELEM_TYPE_ENUMERATED, 212 | Bytes = SND_CTL_ELEM_TYPE_BYTES, 213 | IEC958 = SND_CTL_ELEM_TYPE_IEC958, 214 | Integer64 = SND_CTL_ELEM_TYPE_INTEGER64, 215 | ); 216 | 217 | /// [snd_ctl_elem_value_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper 218 | pub struct ElemValue { 219 | ptr: *mut alsa::snd_ctl_elem_value_t, 220 | etype: ElemType, 221 | count: u32, 222 | } 223 | 224 | impl Drop for ElemValue { 225 | fn drop(&mut self) { unsafe { alsa::snd_ctl_elem_value_free(self.ptr) }; } 226 | } 227 | 228 | pub fn elem_value_ptr(a: &ElemValue) -> *mut alsa::snd_ctl_elem_value_t { a.ptr } 229 | 230 | pub fn elem_value_new(t: ElemType, count: u32) -> Result { 231 | let mut p = ptr::null_mut(); 232 | acheck!(snd_ctl_elem_value_malloc(&mut p)) 233 | .map(|_| ElemValue { ptr: p, etype: t, count }) 234 | } 235 | 236 | impl ElemValue { 237 | 238 | pub fn set_id(&mut self, id: &ElemId) { 239 | unsafe { alsa::snd_ctl_elem_value_set_id(self.ptr, elem_id_ptr(id)) } 240 | } 241 | 242 | // Note: The get_bytes hands out a reference to inside the object. Therefore, we can't treat 243 | // the content as "cell"ed, but must take a "&mut self" (to make sure the reference 244 | // from get_bytes has been dropped when calling a set_* function). 245 | 246 | pub fn get_boolean(&self, idx: u32) -> Option { 247 | if self.etype != ElemType::Boolean || idx >= self.count { None } 248 | else { Some( unsafe { alsa::snd_ctl_elem_value_get_boolean(self.ptr, idx as c_uint) } != 0) } 249 | } 250 | 251 | pub fn set_boolean(&mut self, idx: u32, val: bool) -> Option<()> { 252 | if self.etype != ElemType::Boolean || idx >= self.count { None } 253 | else { unsafe { alsa::snd_ctl_elem_value_set_boolean(self.ptr, idx as c_uint, if val {1} else {0}) }; Some(()) } 254 | } 255 | 256 | pub fn get_integer(&self, idx: u32) -> Option { 257 | if self.etype != ElemType::Integer || idx >= self.count { None } 258 | else { Some( unsafe { alsa::snd_ctl_elem_value_get_integer(self.ptr, idx as c_uint) } as i32) } 259 | } 260 | 261 | pub fn set_integer(&mut self, idx: u32, val: i32) -> Option<()> { 262 | if self.etype != ElemType::Integer || idx >= self.count { None } 263 | else { unsafe { alsa::snd_ctl_elem_value_set_integer(self.ptr, idx as c_uint, val as c_long) }; Some(()) } 264 | } 265 | 266 | pub fn get_integer64(&self, idx: u32) -> Option { 267 | if self.etype != ElemType::Integer64 || idx >= self.count { None } 268 | else { Some( unsafe { alsa::snd_ctl_elem_value_get_integer64(self.ptr, idx as c_uint) } as i64) } 269 | } 270 | 271 | pub fn set_integer64(&mut self, idx: u32, val: i64) -> Option<()> { 272 | if self.etype != ElemType::Integer || idx >= self.count { None } 273 | else { unsafe { alsa::snd_ctl_elem_value_set_integer64(self.ptr, idx as c_uint, val) }; Some(()) } 274 | } 275 | 276 | pub fn get_enumerated(&self, idx: u32) -> Option { 277 | if self.etype != ElemType::Enumerated || idx >= self.count { None } 278 | else { Some( unsafe { alsa::snd_ctl_elem_value_get_enumerated(self.ptr, idx as c_uint) } as u32) } 279 | } 280 | 281 | pub fn set_enumerated(&mut self, idx: u32, val: u32) -> Option<()> { 282 | if self.etype != ElemType::Enumerated || idx >= self.count { None } 283 | else { unsafe { alsa::snd_ctl_elem_value_set_enumerated(self.ptr, idx as c_uint, val as c_uint) }; Some(()) } 284 | } 285 | 286 | pub fn get_byte(&self, idx: u32) -> Option { 287 | if self.etype != ElemType::Bytes || idx >= self.count { None } 288 | else { Some( unsafe { alsa::snd_ctl_elem_value_get_byte(self.ptr, idx as c_uint) } as u8) } 289 | } 290 | 291 | pub fn set_byte(&mut self, idx: u32, val: u8) -> Option<()> { 292 | if self.etype != ElemType::Bytes || idx >= self.count { None } 293 | else { unsafe { alsa::snd_ctl_elem_value_set_byte(self.ptr, idx as c_uint, val) }; Some(()) } 294 | } 295 | 296 | pub fn get_bytes(&self) -> Option<&[u8]> { 297 | if self.etype != ElemType::Bytes { None } 298 | else { Some( unsafe { ::core::slice::from_raw_parts( 299 | alsa::snd_ctl_elem_value_get_bytes(self.ptr) as *const u8, self.count as usize) } ) } 300 | } 301 | 302 | pub fn set_bytes(&mut self, val: &[u8]) -> Option<()> { 303 | if self.etype != ElemType::Bytes || val.len() != self.count as usize { None } 304 | 305 | // Note: the alsa-lib function definition is broken. First, the pointer is declared as mut even 306 | // though it's const, and second, there is a "value" missing between "elem" and "set_bytes". 307 | else { unsafe { alsa::snd_ctl_elem_set_bytes(self.ptr, val.as_ptr() as *mut c_void, val.len() as size_t) }; Some(()) } 308 | } 309 | 310 | /// Creates a new ElemValue. 311 | pub fn new(t: ElemType) -> Result { 312 | // See max length in include/uapi/sound/asound.h in linux kernel for these values 313 | let count = match t { 314 | ElemType::None => 1, 315 | ElemType::Boolean => 128, 316 | ElemType::Integer => 128, 317 | ElemType::Enumerated => 128, 318 | ElemType::Bytes => 512, 319 | ElemType::IEC958 => 1, 320 | ElemType::Integer64 => 64, 321 | }; 322 | // if count > maxcount { return Err(Error::new(Some("ElemValue::new - count too large".into()), 1)) } 323 | let ev = elem_value_new(t, count)?; 324 | unsafe { alsa::snd_ctl_elem_value_clear(elem_value_ptr(&ev)) }; 325 | Ok(ev) 326 | } 327 | 328 | } 329 | 330 | impl fmt::Debug for ElemValue { 331 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 332 | use self::ElemType::*; 333 | write!(f, "ElemValue({:?}", self.etype)?; 334 | for a in 0..self.count { match self.etype { 335 | Boolean => write!(f, ",{:?}", self.get_boolean(a).unwrap()), 336 | Integer => write!(f, ",{:?}", self.get_integer(a).unwrap()), 337 | Integer64 => write!(f, ",{:?}", self.get_integer64(a).unwrap()), 338 | Enumerated => write!(f, ",{:?}", self.get_enumerated(a).unwrap()), 339 | Bytes => write!(f, ",{:?}", self.get_byte(a).unwrap()), 340 | _ => Ok(()), 341 | }?}; 342 | write!(f, ")") 343 | } 344 | } 345 | 346 | /// [snd_ctl_elem_info_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper 347 | pub struct ElemInfo(*mut alsa::snd_ctl_elem_info_t); 348 | 349 | pub fn elem_info_ptr(a: &ElemInfo) -> *mut alsa::snd_ctl_elem_info_t { a.0 } 350 | 351 | impl Drop for ElemInfo { 352 | fn drop(&mut self) { unsafe { alsa::snd_ctl_elem_info_free(self.0) }; } 353 | } 354 | 355 | pub fn elem_info_new() -> Result { 356 | let mut p = ptr::null_mut(); 357 | acheck!(snd_ctl_elem_info_malloc(&mut p)).map(|_| ElemInfo(p)) 358 | } 359 | 360 | impl ElemInfo { 361 | pub fn get_type(&self) -> ElemType { ElemType::from_c_int( 362 | unsafe { alsa::snd_ctl_elem_info_get_type(self.0) } as c_int, "snd_ctl_elem_info_get_type").unwrap() } 363 | pub fn get_count(&self) -> u32 { unsafe { alsa::snd_ctl_elem_info_get_count(self.0) as u32 } } 364 | } 365 | 366 | // 367 | // Non-allocating version of ElemId 368 | // 369 | 370 | /// [snd_ctl_elem_id_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper 371 | pub struct ElemId(UnsafeCell<[u8; ELEM_ID_SIZE]>); 372 | 373 | pub fn elem_id_new() -> Result { 374 | assert!(unsafe { alsa::snd_ctl_elem_id_sizeof() } as usize <= ELEM_ID_SIZE); 375 | Ok(ElemId(UnsafeCell::new(unsafe { mem::zeroed() }))) 376 | } 377 | 378 | #[inline] 379 | 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 } 380 | 381 | unsafe impl Send for ElemId {} 382 | 383 | impl Clone for ElemId { 384 | fn clone(&self) -> Self { 385 | ElemId(UnsafeCell::new(unsafe { *self.0.get() })) 386 | } 387 | } 388 | 389 | // 390 | // Allocating version of ElemId 391 | // 392 | 393 | /* 394 | 395 | /// [snd_ctl_elem_id_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper 396 | pub struct ElemId(*mut alsa::snd_ctl_elem_id_t); 397 | 398 | impl Drop for ElemId { 399 | fn drop(&mut self) { unsafe { alsa::snd_ctl_elem_id_free(self.0) }; } 400 | } 401 | 402 | pub fn elem_id_new() -> Result { 403 | let mut p = ptr::null_mut(); 404 | acheck!(snd_ctl_elem_id_malloc(&mut p)).map(|_| ElemId(p)) 405 | } 406 | 407 | pub fn elem_id_ptr(a: &ElemId) -> *mut alsa::snd_ctl_elem_id_t { a.0 } 408 | 409 | */ 410 | 411 | impl ElemId { 412 | pub fn get_name(&self) -> Result<&str> { 413 | from_const("snd_hctl_elem_id_get_name", unsafe { alsa::snd_ctl_elem_id_get_name(elem_id_ptr(self)) })} 414 | pub fn get_device(&self) -> u32 { unsafe { alsa::snd_ctl_elem_id_get_device(elem_id_ptr(self)) as u32 }} 415 | pub fn get_subdevice(&self) -> u32 { unsafe { alsa::snd_ctl_elem_id_get_subdevice(elem_id_ptr(self)) as u32 }} 416 | pub fn get_numid(&self) -> u32 { unsafe { alsa::snd_ctl_elem_id_get_numid(elem_id_ptr(self)) as u32 }} 417 | pub fn get_index(&self) -> u32 { unsafe { alsa::snd_ctl_elem_id_get_index(elem_id_ptr(self)) as u32 }} 418 | pub fn get_interface(&self) -> ElemIface { ElemIface::from_c_int( 419 | unsafe { alsa::snd_ctl_elem_id_get_interface(elem_id_ptr(self)) } as c_int, "snd_ctl_elem_id_get_interface").unwrap() } 420 | 421 | pub fn set_device(&mut self, v: u32) { unsafe { alsa::snd_ctl_elem_id_set_device(elem_id_ptr(self), v) }} 422 | pub fn set_subdevice(&mut self, v: u32) { unsafe { alsa::snd_ctl_elem_id_set_subdevice(elem_id_ptr(self), v) }} 423 | pub fn set_numid(&mut self, v: u32) { unsafe { alsa::snd_ctl_elem_id_set_numid(elem_id_ptr(self), v) }} 424 | pub fn set_index(&mut self, v: u32) { unsafe { alsa::snd_ctl_elem_id_set_index(elem_id_ptr(self), v) }} 425 | pub fn set_interface(&mut self, v: ElemIface) { unsafe { alsa::snd_ctl_elem_id_set_interface(elem_id_ptr(self), v as u32) }} 426 | pub fn set_name(&mut self, v: &CStr) { unsafe { alsa::snd_ctl_elem_id_set_name(elem_id_ptr(self), v.as_ptr()) }} 427 | 428 | /// Creates a new ElemId. 429 | /// 430 | /// To ensure safety (i e make sure we never have an invalid interface enum), we need to supply it to the "new" function. 431 | pub fn new(iface: ElemIface) -> Self { 432 | let mut r = elem_id_new().unwrap(); 433 | r.set_interface(iface); 434 | r 435 | } 436 | } 437 | 438 | impl cmp::Eq for ElemId {} 439 | 440 | impl cmp::PartialEq for ElemId { 441 | fn eq(&self, a: &ElemId) -> bool { 442 | self.get_numid() == a.get_numid() && self.get_interface() == a.get_interface() && 443 | self.get_index() == a.get_index() && self.get_device() == a.get_device() && 444 | self.get_subdevice() == a.get_subdevice() && self.get_name().ok() == a.get_name().ok() 445 | } 446 | } 447 | 448 | impl fmt::Debug for ElemId { 449 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 450 | let index = self.get_index(); 451 | let device = self.get_device(); 452 | let subdevice = self.get_subdevice(); 453 | 454 | write!(f, "ElemId(#{}, {:?}, {:?}", self.get_numid(), self.get_interface(), self.get_name())?; 455 | if index > 0 { write!(f, ", index={}", index)? }; 456 | if device > 0 || subdevice > 0 { write!(f, ", device={}", device)? }; 457 | if subdevice > 0 { write!(f, ", subdevice={}", device)? }; 458 | write!(f, ")") 459 | } 460 | } 461 | 462 | /// [snd_ctl_elem_list_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper 463 | pub struct ElemList(*mut alsa::snd_ctl_elem_list_t); 464 | 465 | impl Drop for ElemList { 466 | fn drop(&mut self) { 467 | unsafe { alsa::snd_ctl_elem_list_free_space(self.0) }; 468 | unsafe { alsa::snd_ctl_elem_list_free(self.0) }; 469 | } 470 | } 471 | 472 | fn elem_list_new Result>(f: F) -> Result { 473 | let mut p = ptr::null_mut(); 474 | let list = acheck!(snd_ctl_elem_list_malloc(&mut p)).map(|_| ElemList(p))?; 475 | let count = f(&list)?; 476 | if count > 0 { 477 | acheck!(snd_ctl_elem_list_alloc_space(list.0, count))?; 478 | } 479 | Ok(list) 480 | } 481 | 482 | impl ElemList { 483 | #[inline] 484 | fn ensure_valid_index(&self, index: u32) -> Result<()> { 485 | if index >= self.get_used() { 486 | Err(Error::new("snd_ctl_elem_list_*", libc::EINVAL)) 487 | } else { 488 | Ok(()) 489 | } 490 | } 491 | 492 | pub(crate) fn get_count(&self) -> u32 { unsafe { alsa::snd_ctl_elem_list_get_count(self.0) } } 493 | pub fn get_used(&self) -> u32 { unsafe { alsa::snd_ctl_elem_list_get_used(self.0) } } 494 | pub fn get_id(&self, index: u32) -> Result { 495 | self.ensure_valid_index(index)?; 496 | let elem_id = elem_id_new()?; 497 | unsafe { alsa::snd_ctl_elem_list_get_id(self.0, index, elem_id_ptr(&elem_id)) }; 498 | Ok(elem_id) 499 | } 500 | pub fn get_numid(&self, index: u32) -> Result { self.ensure_valid_index(index)?; Ok(unsafe { alsa::snd_ctl_elem_list_get_numid(self.0, index) }) } 501 | pub fn get_interface(&self, index: u32) -> Result { 502 | self.ensure_valid_index(index)?; 503 | ElemIface::from_c_int(unsafe { alsa::snd_ctl_elem_list_get_interface(self.0, index) } as c_int, "snd_ctl_elem_list_get_interface") 504 | } 505 | pub fn get_device(&self, index: u32) -> Result { self.ensure_valid_index(index)?; Ok(unsafe { alsa::snd_ctl_elem_list_get_device(self.0, index) }) } 506 | pub fn get_subdevice(&self, index: u32) -> Result { self.ensure_valid_index(index)?; Ok(unsafe { alsa::snd_ctl_elem_list_get_subdevice(self.0, index) }) } 507 | pub fn get_name(&self, index: u32) -> Result<&str> { 508 | self.ensure_valid_index(index)?; 509 | from_const("snd_ctl_elem_list_get_name", unsafe { alsa::snd_ctl_elem_list_get_name(self.0, index) }) 510 | } 511 | pub fn get_index(&self, index: u32) -> Result { self.ensure_valid_index(index)?; Ok(unsafe { alsa::snd_ctl_elem_list_get_index(self.0, index) }) } 512 | } 513 | 514 | /// [snd_ctl_event_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) wrapper 515 | pub struct Event(*mut alsa::snd_ctl_event_t); 516 | 517 | impl Drop for Event { 518 | fn drop(&mut self) { unsafe { alsa::snd_ctl_event_free(self.0) }; } 519 | } 520 | 521 | pub fn event_new() -> Result { 522 | let mut p = ptr::null_mut(); 523 | acheck!(snd_ctl_event_malloc(&mut p)).map(|_| Event(p)) 524 | } 525 | 526 | impl Event { 527 | pub fn get_mask(&self) -> EventMask { EventMask(unsafe { alsa::snd_ctl_event_elem_get_mask(self.0) as u32 })} 528 | pub fn get_id(&self) -> ElemId { 529 | let r = elem_id_new().unwrap(); 530 | unsafe { alsa::snd_ctl_event_elem_get_id(self.0, elem_id_ptr(&r)) }; 531 | r 532 | } 533 | } 534 | 535 | 536 | /// [SND_CTL_EVENT_MASK_XXX](http://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html) bitmask 537 | #[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] 538 | pub struct EventMask(pub u32); 539 | 540 | impl EventMask { 541 | pub fn remove(&self) -> bool { return self.0 & 0xffffffff == 0xffffffff } 542 | pub fn value(&self) -> bool { return (!self.remove()) && (self.0 & (1 << 0) != 0); } 543 | pub fn info(&self) -> bool { return (!self.remove()) && (self.0 & (1 << 1) != 0); } 544 | pub fn add(&self) -> bool { return (!self.remove()) && (self.0 & (1 << 2) != 0); } 545 | pub fn tlv(&self) -> bool { return (!self.remove()) && (self.0 & (1 << 3) != 0); } 546 | } 547 | 548 | #[test] 549 | fn print_sizeof() { 550 | extern crate std; 551 | let elemid = unsafe { alsa::snd_ctl_elem_id_sizeof() } as usize; 552 | let elemvalue = unsafe { alsa::snd_ctl_elem_value_sizeof() } as usize; 553 | let eleminfo = unsafe { alsa::snd_ctl_elem_info_sizeof() } as usize; 554 | 555 | assert!(elemid <= ELEM_ID_SIZE); 556 | // assert!(elemvalue <= ELEM_VALUE_SIZE); 557 | // assert!(eleminfo <= ELEM_INFO_SIZE); 558 | 559 | std::println!("Elem id: {}, Elem value: {}, Elem info: {}", elemid, elemvalue, eleminfo); 560 | } 561 | -------------------------------------------------------------------------------- /src/direct/pcm.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | This module bypasses alsa-lib and directly read and write into memory mapped kernel memory. 3 | In case of the sample memory, this is in many cases the DMA buffers that is transferred to the sound card. 4 | 5 | The reasons for doing this are: 6 | 7 | * Minimum overhead where it matters most: let alsa-lib do the code heavy setup - 8 | then steal its file descriptor and deal with sample streaming from Rust. 9 | * RT-safety to the maximum extent possible. Creating/dropping any of these structs causes syscalls, 10 | but function calls on these are just read and write from memory. No syscalls, no memory allocations, 11 | not even loops (with the exception of `MmapPlayback::write` that loops over samples to write). 12 | * Possibility to allow Send + Sync for structs 13 | * It's a fun experiment and an interesting deep dive into how alsa-lib does things. 14 | 15 | Note: Not all sound card drivers support this direct method of communication; although almost all 16 | modern/common ones do. It only works with hardware devices though (such as "hw:xxx" device strings), 17 | don't expect it to work with, e g, the PulseAudio plugin or so. 18 | 19 | For an example of how to use this mode, look in the "synth-example" directory. 20 | */ 21 | 22 | use libc; 23 | use core::{mem, ptr, fmt, cmp}; 24 | use crate::error::{Error, Result}; 25 | use crate::{pcm, PollDescriptors, Direction}; 26 | use crate::pcm::Frames; 27 | use core::marker::PhantomData; 28 | 29 | use super::ffi::*; 30 | 31 | #[cfg(feature = "std")] 32 | type RawFd = std::os::unix::io::RawFd; 33 | 34 | #[cfg(not(feature = "std"))] 35 | type RawFd = core::ffi::c_int; 36 | 37 | /// Read PCM status via a simple kernel syscall, bypassing alsa-lib. 38 | /// 39 | /// If Status is not available on your architecture, this is the second best option. 40 | pub struct SyncPtrStatus(snd_pcm_mmap_status); 41 | 42 | impl SyncPtrStatus { 43 | /// Executes sync_ptr syscall. 44 | /// 45 | /// Unsafe because 46 | /// - setting appl_ptr and avail_min might make alsa-lib confused 47 | /// - no check that the fd is really a PCM 48 | pub unsafe fn sync_ptr(fd: RawFd, hwsync: bool, appl_ptr: Option, avail_min: Option) -> Result { 49 | let mut data = snd_pcm_sync_ptr { 50 | flags: (if hwsync { SNDRV_PCM_SYNC_PTR_HWSYNC } else { 0 }) + 51 | (if appl_ptr.is_some() { SNDRV_PCM_SYNC_PTR_APPL } else { 0 }) + 52 | (if avail_min.is_some() { SNDRV_PCM_SYNC_PTR_AVAIL_MIN } else { 0 }), 53 | c: snd_pcm_mmap_control_r { 54 | control: snd_pcm_mmap_control { 55 | appl_ptr: appl_ptr.unwrap_or(0) as snd_pcm_uframes_t, 56 | avail_min: avail_min.unwrap_or(0) as snd_pcm_uframes_t, 57 | } 58 | }, 59 | s: mem::zeroed() 60 | }; 61 | 62 | sndrv_pcm_ioctl_sync_ptr(fd, &mut data)?; 63 | 64 | let i = data.s.status.state; 65 | if (i >= (pcm::State::Open as snd_pcm_state_t)) && (i <= (pcm::State::Disconnected as snd_pcm_state_t)) { 66 | Ok(SyncPtrStatus(data.s.status)) 67 | } else { 68 | Err(Error::unsupported("SNDRV_PCM_IOCTL_SYNC_PTR returned broken state")) 69 | } 70 | } 71 | 72 | pub fn hw_ptr(&self) -> pcm::Frames { self.0.hw_ptr as pcm::Frames } 73 | pub fn state(&self) -> pcm::State { unsafe { mem::transmute(self.0.state as u8) } /* valid range checked in sync_ptr */ } 74 | pub fn htstamp(&self) -> libc::timespec { self.0.tstamp } 75 | } 76 | 77 | 78 | 79 | /// Read PCM status directly from memory, bypassing alsa-lib. 80 | /// 81 | /// This means that it's 82 | /// 1) less overhead for reading status (no syscall, no allocations, no virtual dispatch, just a read from memory) 83 | /// 2) Send + Sync, and 84 | /// 3) will only work for "hw" / "plughw" devices (not e g PulseAudio plugins), and not 85 | /// all of those are supported, although all common ones are (as of 2017, and a kernel from the same decade). 86 | /// Kernel supported archs are: x86, PowerPC, Alpha. Use "SyncPtrStatus" for other archs. 87 | /// 88 | /// The values are updated every now and then by the kernel. Many functions will force an update to happen, 89 | /// e g `PCM::avail()` and `PCM::delay()`. 90 | /// 91 | /// Note: Even if you close the original PCM device, ALSA will not actually close the device until all 92 | /// Status structs are dropped too. 93 | /// 94 | #[derive(Debug)] 95 | pub struct Status(DriverMemory); 96 | 97 | fn pcm_to_fd(p: &pcm::PCM) -> Result { 98 | let mut fds: [libc::pollfd; 1] = unsafe { mem::zeroed() }; 99 | let c = PollDescriptors::fill(p, &mut fds)?; 100 | if c != 1 { 101 | return Err(Error::unsupported("snd_pcm_poll_descriptors returned wrong number of fds")) 102 | } 103 | Ok(fds[0].fd) 104 | } 105 | 106 | impl Status { 107 | pub fn new(p: &pcm::PCM) -> Result { Status::from_fd(pcm_to_fd(p)?) } 108 | 109 | pub fn from_fd(fd: RawFd) -> Result { 110 | DriverMemory::new(fd, 1, SNDRV_PCM_MMAP_OFFSET_STATUS as libc::off_t, false).map(Status) 111 | } 112 | 113 | /// Current PCM state. 114 | pub fn state(&self) -> pcm::State { 115 | unsafe { 116 | let i = ptr::read_volatile(&(*self.0.ptr).state); 117 | assert!((i >= (pcm::State::Open as snd_pcm_state_t)) && (i <= (pcm::State::Disconnected as snd_pcm_state_t))); 118 | mem::transmute(i as u8) 119 | } 120 | } 121 | 122 | /// Number of frames hardware has read or written 123 | /// 124 | /// This number is updated every now and then by the kernel. 125 | /// Calling most functions on the PCM will update it, so will usually a period interrupt. 126 | /// No guarantees given. 127 | /// 128 | /// This value wraps at "boundary" (a large value you can read from SwParams). 129 | pub fn hw_ptr(&self) -> pcm::Frames { 130 | unsafe { 131 | ptr::read_volatile(&(*self.0.ptr).hw_ptr) as pcm::Frames 132 | } 133 | } 134 | 135 | /// Timestamp - fast version of alsa-lib's Status::get_htstamp 136 | /// 137 | /// Note: This just reads the actual value in memory. 138 | /// Unfortunately, the timespec is too big to be read atomically on most archs. 139 | /// Therefore, this function can potentially give bogus result at times, at least in theory...? 140 | pub fn htstamp(&self) -> libc::timespec { 141 | unsafe { 142 | ptr::read_volatile(&(*self.0.ptr).tstamp) 143 | } 144 | } 145 | 146 | /// Audio timestamp - fast version of alsa-lib's Status::get_audio_htstamp 147 | /// 148 | /// Note: This just reads the actual value in memory. 149 | /// Unfortunately, the timespec is too big to be read atomically on most archs. 150 | /// Therefore, this function can potentially give bogus result at times, at least in theory...? 151 | pub fn audio_htstamp(&self) -> libc::timespec { 152 | unsafe { 153 | ptr::read_volatile(&(*self.0.ptr).audio_tstamp) 154 | } 155 | } 156 | } 157 | 158 | /// Write PCM appl ptr directly, bypassing alsa-lib. 159 | /// 160 | /// Provides direct access to appl ptr and avail min, without the overhead of 161 | /// alsa-lib or a syscall. Caveats that apply to Status applies to this struct too. 162 | #[derive(Debug)] 163 | pub struct Control(DriverMemory); 164 | 165 | impl Control { 166 | pub fn new(p: &pcm::PCM) -> Result { Self::from_fd(pcm_to_fd(p)?) } 167 | 168 | pub fn from_fd(fd: RawFd) -> Result { 169 | DriverMemory::new(fd, 1, SNDRV_PCM_MMAP_OFFSET_CONTROL as libc::off_t, true).map(Control) 170 | } 171 | 172 | /// Read number of frames application has read or written 173 | /// 174 | /// This value wraps at "boundary" (a large value you can read from SwParams). 175 | pub fn appl_ptr(&self) -> pcm::Frames { 176 | unsafe { 177 | ptr::read_volatile(&(*self.0.ptr).appl_ptr) as pcm::Frames 178 | } 179 | } 180 | 181 | /// Set number of frames application has read or written 182 | /// 183 | /// When the kernel wakes up due to a period interrupt, this value will 184 | /// be checked by the kernel. An XRUN will happen in case the application 185 | /// has not read or written enough data. 186 | pub fn set_appl_ptr(&self, value: pcm::Frames) { 187 | unsafe { 188 | ptr::write_volatile(&mut (*self.0.ptr).appl_ptr, value as snd_pcm_uframes_t) 189 | } 190 | } 191 | 192 | /// Read minimum number of frames in buffer in order to wakeup process 193 | pub fn avail_min(&self) -> pcm::Frames { 194 | unsafe { 195 | ptr::read_volatile(&(*self.0.ptr).avail_min) as pcm::Frames 196 | } 197 | } 198 | 199 | /// Write minimum number of frames in buffer in order to wakeup process 200 | pub fn set_avail_min(&self, value: pcm::Frames) { 201 | unsafe { 202 | ptr::write_volatile(&mut (*self.0.ptr).avail_min, value as snd_pcm_uframes_t) 203 | } 204 | } 205 | } 206 | 207 | struct DriverMemory { 208 | ptr: *mut S, 209 | size: libc::size_t, 210 | } 211 | 212 | impl fmt::Debug for DriverMemory { 213 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "DriverMemory({:?})", self.ptr) } 214 | } 215 | 216 | impl DriverMemory { 217 | fn new(fd: RawFd, count: usize, offs: libc::off_t, writable: bool) -> Result { 218 | let mut total = count * mem::size_of::(); 219 | let ps = pagesize(); 220 | assert!(total > 0); 221 | if total % ps != 0 { total += ps - total % ps }; 222 | let flags = if writable { libc::PROT_WRITE | libc::PROT_READ } else { libc::PROT_READ }; 223 | let p = unsafe { libc::mmap(ptr::null_mut(), total, flags, libc::MAP_FILE | libc::MAP_SHARED, fd, offs) }; 224 | if p.is_null() || p == libc::MAP_FAILED { 225 | Err(Error::last("mmap (of driver memory)")) 226 | } else { 227 | Ok(DriverMemory { ptr: p as *mut S, size: total }) 228 | } 229 | } 230 | } 231 | 232 | unsafe impl Send for DriverMemory {} 233 | unsafe impl Sync for DriverMemory {} 234 | 235 | impl Drop for DriverMemory { 236 | fn drop(&mut self) { 237 | unsafe {{ libc::munmap(self.ptr as *mut libc::c_void, self.size); } } 238 | } 239 | } 240 | 241 | #[derive(Debug)] 242 | struct SampleData { 243 | mem: DriverMemory, 244 | frames: pcm::Frames, 245 | channels: u32, 246 | } 247 | 248 | impl SampleData { 249 | pub fn new(p: &pcm::PCM) -> Result { 250 | let params = p.hw_params_current()?; 251 | let bufsize = params.get_buffer_size()?; 252 | let channels = params.get_channels()?; 253 | if params.get_access()? != pcm::Access::MMapInterleaved { 254 | return Err(Error::unsupported("Not MMAP interleaved data")) 255 | } 256 | 257 | let fd = pcm_to_fd(p)?; 258 | let info = unsafe { 259 | let mut info: snd_pcm_channel_info = mem::zeroed(); 260 | sndrv_pcm_ioctl_channel_info(fd, &mut info)?; 261 | info 262 | }; 263 | // println!("{:?}", info); 264 | if (info.step != channels * mem::size_of::() as u32 * 8) || (info.first != 0) { 265 | return Err(Error::unsupported("MMAP data size mismatch")) 266 | } 267 | Ok(SampleData { 268 | mem: DriverMemory::new(fd, (bufsize as usize) * (channels as usize), info.offset as libc::off_t, true)?, 269 | frames: bufsize, 270 | channels, 271 | }) 272 | } 273 | } 274 | 275 | 276 | /// Dummy trait for better generics 277 | pub trait MmapDir: fmt::Debug { 278 | const DIR: Direction; 279 | fn avail(hwptr: Frames, applptr: Frames, buffersize: Frames, boundary: Frames) -> Frames; 280 | } 281 | 282 | /// Dummy struct for better generics 283 | #[derive(Copy, Clone, Debug)] 284 | pub struct Playback; 285 | 286 | impl MmapDir for Playback { 287 | const DIR: Direction = Direction::Playback; 288 | #[inline] 289 | fn avail(hwptr: Frames, applptr: Frames, buffersize: Frames, boundary: Frames) -> Frames { 290 | let r = hwptr.wrapping_add(buffersize).wrapping_sub(applptr); 291 | let r = if r < 0 { r.wrapping_add(boundary) } else { r }; 292 | if r as usize >= boundary as usize { r.wrapping_sub(boundary) } else { r } 293 | } 294 | } 295 | 296 | /// Dummy struct for better generics 297 | #[derive(Copy, Clone, Debug)] 298 | pub struct Capture; 299 | 300 | impl MmapDir for Capture { 301 | const DIR: Direction = Direction::Capture; 302 | #[inline] 303 | fn avail(hwptr: Frames, applptr: Frames, _buffersize: Frames, boundary: Frames) -> Frames { 304 | let r = hwptr.wrapping_sub(applptr); 305 | if r < 0 { r.wrapping_add(boundary) } else { r } 306 | } 307 | } 308 | 309 | pub type MmapPlayback = MmapIO; 310 | 311 | pub type MmapCapture = MmapIO; 312 | 313 | #[derive(Debug)] 314 | /// Struct containing direct I/O functions shared between playback and capture. 315 | pub struct MmapIO { 316 | data: SampleData, 317 | c: Control, 318 | ss: Status, 319 | bound: Frames, 320 | dir: PhantomData<*const D>, 321 | } 322 | 323 | #[derive(Debug, Clone, Copy)] 324 | /// A raw pointer to samples, and the amount of samples readable or writable. 325 | pub struct RawSamples { 326 | pub ptr: *mut S, 327 | pub frames: Frames, 328 | pub channels: u32, 329 | } 330 | 331 | impl RawSamples { 332 | #[inline] 333 | /// Returns `frames` * `channels`, i e the amount of samples (of type `S`) that can be read/written. 334 | pub fn samples(&self) -> isize { self.frames as isize * (self.channels as isize) } 335 | 336 | /// Writes samples from an iterator. 337 | /// 338 | /// Returns true if iterator was depleted, and the number of samples written. 339 | /// This is just raw read/write of memory. 340 | pub unsafe fn write_samples>(&self, i: &mut I) -> (bool, isize) { 341 | let mut z = 0; 342 | let max_samples = self.samples(); 343 | while z < max_samples { 344 | let b = if let Some(b) = i.next() { b } else { return (true, z) }; 345 | ptr::write_volatile(self.ptr.offset(z), b); 346 | z += 1; 347 | }; 348 | (false, z) 349 | } 350 | 351 | } 352 | 353 | impl MmapIO { 354 | fn new(p: &pcm::PCM) -> Result { 355 | if p.info()?.get_stream() != D::DIR { 356 | return Err(Error::unsupported("Wrong direction")); 357 | } 358 | let boundary = p.sw_params_current()?.get_boundary()?; 359 | Ok(MmapIO { 360 | data: SampleData::new(p)?, 361 | c: Control::new(p)?, 362 | ss: Status::new(p)?, 363 | bound: boundary, 364 | dir: PhantomData, 365 | }) 366 | } 367 | } 368 | 369 | pub (crate) fn new_mmap(p: &pcm::PCM) -> Result> { MmapIO::new(p) } 370 | 371 | impl MmapIO { 372 | /// Read current status 373 | pub fn status(&self) -> &Status { &self.ss } 374 | 375 | /// Read current number of frames committed by application 376 | /// 377 | /// This number wraps at 'boundary'. 378 | #[inline] 379 | pub fn appl_ptr(&self) -> Frames { self.c.appl_ptr() } 380 | 381 | /// Read current number of frames read / written by hardware 382 | /// 383 | /// This number wraps at 'boundary'. 384 | #[inline] 385 | pub fn hw_ptr(&self) -> Frames { self.ss.hw_ptr() } 386 | 387 | /// The number at which hw_ptr and appl_ptr wraps. 388 | #[inline] 389 | pub fn boundary(&self) -> Frames { self.bound } 390 | 391 | /// Total number of frames in hardware buffer 392 | #[inline] 393 | pub fn buffer_size(&self) -> Frames { self.data.frames } 394 | 395 | /// Number of channels in stream 396 | #[inline] 397 | pub fn channels(&self) -> u32 { self.data.channels } 398 | 399 | /// Notifies the kernel that frames have now been read / written by the application 400 | /// 401 | /// This will allow the kernel to write new data into this part of the buffer. 402 | pub fn commit(&self, v: Frames) { 403 | let mut z = self.appl_ptr() + v; 404 | if z + v >= self.boundary() { z -= self.boundary() }; 405 | self.c.set_appl_ptr(z) 406 | } 407 | 408 | /// Number of frames available to read / write. 409 | /// 410 | /// In case of an underrun, this value might be bigger than the buffer size. 411 | pub fn avail(&self) -> Frames { D::avail(self.hw_ptr(), self.appl_ptr(), self.buffer_size(), self.boundary()) } 412 | 413 | /// Returns raw pointers to data to read / write. 414 | /// 415 | /// Use this if you want to read/write data yourself (instead of using iterators). If you do, 416 | /// using `write_volatile` or `read_volatile` is recommended, since it's DMA memory and can 417 | /// change at any time. 418 | /// 419 | /// Since this is a ring buffer, there might be more data to read/write in the beginning 420 | /// of the buffer as well. If so this is returned as the second return value. 421 | pub fn data_ptr(&self) -> (RawSamples, Option>) { 422 | let (hwptr, applptr) = (self.hw_ptr(), self.appl_ptr()); 423 | let c = self.channels(); 424 | let bufsize = self.buffer_size(); 425 | 426 | // These formulas mostly mimic the behaviour of 427 | // snd_pcm_mmap_begin (in alsa-lib/src/pcm/pcm.c). 428 | let offs = applptr % bufsize; 429 | let mut a = D::avail(hwptr, applptr, bufsize, self.boundary()); 430 | a = cmp::min(a, bufsize); 431 | let b = bufsize - offs; 432 | let more_data = if b < a { 433 | let z = a - b; 434 | a = b; 435 | Some( RawSamples { ptr: self.data.mem.ptr, frames: z, channels: c }) 436 | } else { None }; 437 | 438 | let p = unsafe { self.data.mem.ptr.offset(offs as isize * self.data.channels as isize) }; 439 | (RawSamples { ptr: p, frames: a, channels: c }, more_data) 440 | } 441 | } 442 | 443 | impl MmapPlayback { 444 | /// Write samples to the kernel ringbuffer. 445 | pub fn write>(&mut self, i: &mut I) -> Frames { 446 | let (data, more_data) = self.data_ptr(); 447 | let (iter_end, samples) = unsafe { data.write_samples(i) }; 448 | let mut z = samples / data.channels as isize; 449 | if !iter_end { 450 | if let Some(data2) = more_data { 451 | let (_, samples2) = unsafe { data2.write_samples(i) }; 452 | z += samples2 / data2.channels as isize; 453 | } 454 | } 455 | let z = z as Frames; 456 | self.commit(z); 457 | z 458 | } 459 | } 460 | 461 | impl MmapCapture { 462 | /// Read samples from the kernel ringbuffer. 463 | /// 464 | /// When the iterator is dropped or depleted, the read samples will be committed, i e, 465 | /// the kernel can then write data to the location again. So do this ASAP. 466 | pub fn iter(&mut self) -> CaptureIter { 467 | let (data, more_data) = self.data_ptr(); 468 | CaptureIter { 469 | m: self, 470 | samples: data, 471 | p_offs: 0, 472 | read_samples: 0, 473 | next_p: more_data, 474 | } 475 | } 476 | } 477 | 478 | /// Iterator over captured samples 479 | pub struct CaptureIter<'a, S: 'static> { 480 | m: &'a MmapCapture, 481 | samples: RawSamples, 482 | p_offs: isize, 483 | read_samples: isize, 484 | next_p: Option>, 485 | } 486 | 487 | impl<'a, S: 'static + Copy> CaptureIter<'a, S> { 488 | fn handle_max(&mut self) { 489 | self.p_offs = 0; 490 | if let Some(p2) = self.next_p.take() { 491 | self.samples = p2; 492 | } else { 493 | self.m.commit((self.read_samples / self.samples.channels as isize) as Frames); 494 | self.read_samples = 0; 495 | self.samples.frames = 0; // Shortcut to "None" in case anyone calls us again 496 | } 497 | } 498 | } 499 | 500 | impl<'a, S: 'static + Copy> Iterator for CaptureIter<'a, S> { 501 | type Item = S; 502 | 503 | #[inline] 504 | fn next(&mut self) -> Option { 505 | if self.p_offs >= self.samples.samples() { 506 | self.handle_max(); 507 | if self.samples.frames <= 0 { return None; } 508 | } 509 | let s = unsafe { ptr::read_volatile(self.samples.ptr.offset(self.p_offs)) }; 510 | self.p_offs += 1; 511 | self.read_samples += 1; 512 | Some(s) 513 | } 514 | } 515 | 516 | impl<'a, S: 'static> Drop for CaptureIter<'a, S> { 517 | fn drop(&mut self) { 518 | self.m.commit((self.read_samples / self.m.data.channels as isize) as Frames); 519 | } 520 | } 521 | 522 | 523 | #[test] 524 | #[ignore] // Not everyone has a recording device on plughw:1. So let's ignore this test by default. 525 | fn record_from_plughw_rw() { 526 | extern crate std; 527 | use crate::pcm::*; 528 | use crate::{ValueOr, Direction}; 529 | use ::alloc::ffi::CString; 530 | let pcm = PCM::open(&*CString::new("plughw:1").unwrap(), Direction::Capture, false).unwrap(); 531 | let ss = self::Status::new(&pcm).unwrap(); 532 | let c = self::Control::new(&pcm).unwrap(); 533 | let hwp = HwParams::any(&pcm).unwrap(); 534 | hwp.set_channels(2).unwrap(); 535 | hwp.set_rate(44100, ValueOr::Nearest).unwrap(); 536 | hwp.set_format(Format::s16()).unwrap(); 537 | hwp.set_access(Access::RWInterleaved).unwrap(); 538 | pcm.hw_params(&hwp).unwrap(); 539 | 540 | { 541 | let swp = pcm.sw_params_current().unwrap(); 542 | swp.set_tstamp_mode(true).unwrap(); 543 | pcm.sw_params(&swp).unwrap(); 544 | } 545 | assert_eq!(ss.state(), State::Prepared); 546 | pcm.start().unwrap(); 547 | assert_eq!(c.appl_ptr(), 0); 548 | std::println!("{:?}, {:?}", ss, c); 549 | let mut buf = [0i16; 512*2]; 550 | assert_eq!(pcm.io_i16().unwrap().readi(&mut buf).unwrap(), 512); 551 | assert_eq!(c.appl_ptr(), 512); 552 | 553 | assert_eq!(ss.state(), State::Running); 554 | assert!(ss.hw_ptr() >= 512); 555 | let t2 = ss.htstamp(); 556 | assert!(t2.tv_sec > 0 || t2.tv_nsec > 0); 557 | } 558 | 559 | 560 | #[test] 561 | #[ignore] // Not everyone has a record device on plughw:1. So let's ignore this test by default. 562 | fn record_from_plughw_mmap() { 563 | extern crate std; 564 | use crate::pcm::*; 565 | use crate::{ValueOr, Direction}; 566 | use ::alloc::ffi::CString; 567 | use ::alloc::vec::Vec; 568 | use std::{thread, time, println}; 569 | 570 | let pcm = PCM::open(&*CString::new("plughw:1").unwrap(), Direction::Capture, false).unwrap(); 571 | let hwp = HwParams::any(&pcm).unwrap(); 572 | hwp.set_channels(2).unwrap(); 573 | hwp.set_rate(44100, ValueOr::Nearest).unwrap(); 574 | hwp.set_format(Format::s16()).unwrap(); 575 | hwp.set_access(Access::MMapInterleaved).unwrap(); 576 | pcm.hw_params(&hwp).unwrap(); 577 | 578 | let ss = unsafe { SyncPtrStatus::sync_ptr(pcm_to_fd(&pcm).unwrap(), false, None, None).unwrap() }; 579 | assert_eq!(ss.state(), State::Prepared); 580 | 581 | let mut m = pcm.direct_mmap_capture::().unwrap(); 582 | 583 | assert_eq!(m.status().state(), State::Prepared); 584 | assert_eq!(m.appl_ptr(), 0); 585 | assert_eq!(m.hw_ptr(), 0); 586 | 587 | 588 | println!("{:?}", m); 589 | 590 | let now = time::Instant::now(); 591 | pcm.start().unwrap(); 592 | while m.avail() < 256 { thread::sleep(time::Duration::from_millis(1)) }; 593 | assert!(now.elapsed() >= time::Duration::from_millis(256 * 1000 / 44100)); 594 | let (ptr1, md) = m.data_ptr(); 595 | assert_eq!(ptr1.channels, 2); 596 | assert!(ptr1.frames >= 256); 597 | assert!(md.is_none()); 598 | println!("Has {:?} frames at {:?} in {:?}", m.avail(), ptr1.ptr, now.elapsed()); 599 | let samples: Vec = m.iter().collect(); 600 | assert!(samples.len() >= ptr1.frames as usize * 2); 601 | println!("Collected {} samples", samples.len()); 602 | let (ptr2, _md) = m.data_ptr(); 603 | assert!(unsafe { ptr1.ptr.offset(256 * 2) } <= ptr2.ptr); 604 | } 605 | 606 | #[test] 607 | #[ignore] 608 | fn playback_to_plughw_mmap() { 609 | extern crate std; 610 | use crate::pcm::*; 611 | use crate::{ValueOr, Direction}; 612 | use ::alloc::ffi::CString; 613 | 614 | let pcm = PCM::open(&*CString::new("plughw:1").unwrap(), Direction::Playback, false).unwrap(); 615 | let hwp = HwParams::any(&pcm).unwrap(); 616 | hwp.set_channels(2).unwrap(); 617 | hwp.set_rate(44100, ValueOr::Nearest).unwrap(); 618 | hwp.set_format(Format::s16()).unwrap(); 619 | hwp.set_access(Access::MMapInterleaved).unwrap(); 620 | pcm.hw_params(&hwp).unwrap(); 621 | let mut m = pcm.direct_mmap_playback::().unwrap(); 622 | 623 | assert_eq!(m.status().state(), State::Prepared); 624 | assert_eq!(m.appl_ptr(), 0); 625 | assert_eq!(m.hw_ptr(), 0); 626 | 627 | std::println!("{:?}", m); 628 | let mut i = (0..(m.buffer_size() * 2)).map(|i| 629 | (((i / 2) as f32 * 2.0 * std::f32::consts::PI / 128.0).sin() * 8192.0) as i16); 630 | m.write(&mut i); 631 | assert_eq!(m.appl_ptr(), m.buffer_size()); 632 | 633 | pcm.start().unwrap(); 634 | pcm.drain().unwrap(); 635 | assert_eq!(m.appl_ptr(), m.buffer_size()); 636 | assert!(m.hw_ptr() >= m.buffer_size()); 637 | } 638 | -------------------------------------------------------------------------------- /src/mixer.rs: -------------------------------------------------------------------------------- 1 | //! Mixer API - Simple Mixer API for mixer control 2 | //! 3 | use core::ffi::CStr; 4 | use ::alloc::ffi::CString; 5 | use ::alloc::string::String; 6 | use core::{ptr, mem, fmt, ops}; 7 | use libc::{c_long, c_int, c_uint, c_short, pollfd}; 8 | use crate::poll; 9 | 10 | use crate::alsa; 11 | use super::Round; 12 | use super::error::*; 13 | 14 | const SELEM_ID_SIZE: usize = 64; 15 | 16 | /// wraps [snd_mixer_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___mixer.html) 17 | #[derive(Debug)] 18 | pub struct Mixer(*mut alsa::snd_mixer_t); 19 | 20 | unsafe impl Send for Mixer {} 21 | 22 | impl Mixer { 23 | /// Opens a mixer and attaches it to a card identified by its name (like hw:0) and loads the 24 | /// mixer after registering a Selem. 25 | pub fn new(name: &str, nonblock: bool) -> Result { 26 | let mut mixer = Mixer::open(nonblock)?; 27 | mixer.attach(&CString::new(name).unwrap())?; 28 | Selem::register(&mut mixer)?; 29 | mixer.load()?; 30 | Ok(mixer) 31 | } 32 | 33 | /// Creates a Selem by looking for a specific selem by name given a mixer (of a card) 34 | pub fn find_selem(&self, id: &SelemId) -> Option { 35 | let selem = unsafe { alsa::snd_mixer_find_selem(self.0, id.as_ptr()) }; 36 | 37 | if selem.is_null() { None } 38 | else { Some(Selem(Elem {handle: selem, _mixer: self})) } 39 | } 40 | 41 | pub fn open(nonblock: bool) -> Result { 42 | let mut r = ptr::null_mut(); 43 | let flags = if nonblock { 1 } else { 0 }; // FIXME: alsa::SND_CTL_NONBLOCK does not exist in alsa-sys 44 | acheck!(snd_mixer_open(&mut r, flags)).map(|_| Mixer(r)) 45 | } 46 | 47 | pub fn attach(&mut self, name: &CStr) -> Result<()> { 48 | acheck!(snd_mixer_attach(self.0, name.as_ptr())).map(|_| ()) 49 | } 50 | 51 | pub fn load(&mut self) -> Result<()> { 52 | acheck!(snd_mixer_load(self.0)).map(|_| ()) 53 | } 54 | 55 | pub fn iter(&self) -> Iter { 56 | Iter { 57 | last_handle: ptr::null_mut(), 58 | mixer: self 59 | } 60 | } 61 | 62 | pub fn handle_events(&self) -> Result { 63 | acheck!(snd_mixer_handle_events(self.0)).map(|x| x as u32) 64 | } 65 | 66 | pub fn wait(&self, timeout_ms: Option) -> Result<()> { 67 | acheck!(snd_mixer_wait(self.0, timeout_ms.map(|x| x as c_int).unwrap_or(-1))).map(|_| ()) } 68 | } 69 | 70 | /// Closes mixer and frees used resources 71 | impl Drop for Mixer { 72 | fn drop(&mut self) { 73 | unsafe { alsa::snd_mixer_close(self.0) }; 74 | } 75 | } 76 | 77 | 78 | impl poll::Descriptors for Mixer { 79 | fn count(&self) -> usize { 80 | unsafe { alsa::snd_mixer_poll_descriptors_count(self.0) as usize } 81 | } 82 | fn fill(&self, p: &mut [pollfd]) -> Result { 83 | let z = unsafe { alsa::snd_mixer_poll_descriptors(self.0, p.as_mut_ptr(), p.len() as c_uint) }; 84 | from_code("snd_mixer_poll_descriptors", z).map(|_| z as usize) 85 | } 86 | fn revents(&self, p: &[pollfd]) -> Result { 87 | let mut r = 0; 88 | let z = unsafe { alsa::snd_mixer_poll_descriptors_revents(self.0, p.as_ptr() as *mut pollfd, p.len() as c_uint, &mut r) }; 89 | from_code("snd_mixer_poll_descriptors_revents", z).map(|_| poll::Flags::from_bits_truncate(r as c_short)) 90 | } 91 | } 92 | 93 | 94 | /// Wrapper for a mB (millibel) value. 95 | /// 96 | /// Despite some ALSA functions named "dB", they actually take mB values instead. 97 | /// This is a wrapper type to help with those calculations. Its interior is the 98 | /// actual mB value. 99 | #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 100 | pub struct MilliBel(pub i64); 101 | 102 | impl MilliBel { 103 | pub fn to_db(self) -> f32 { (self.0 as f32) / 100.0 } 104 | pub fn from_db(db: f32) -> Self { MilliBel((db * 100.0) as i64) } 105 | } 106 | 107 | impl ops::Deref for MilliBel { 108 | type Target = i64; 109 | fn deref(&self) -> &i64 { &self.0 } 110 | } 111 | 112 | impl ops::Add for MilliBel { 113 | type Output = MilliBel; 114 | fn add(self, rhs: Self) -> Self { MilliBel(self.0 + rhs.0) } 115 | } 116 | 117 | impl ops::AddAssign for MilliBel { 118 | fn add_assign(&mut self, rhs: Self) { self.0 += rhs.0 } 119 | } 120 | 121 | impl ops::Sub for MilliBel { 122 | type Output = MilliBel; 123 | fn sub(self, rhs: Self) -> Self { MilliBel(self.0 - rhs.0) } 124 | } 125 | 126 | impl ops::SubAssign for MilliBel { 127 | fn sub_assign(&mut self, rhs: Self) { self.0 -= rhs.0 } 128 | } 129 | 130 | /// Wraps [snd_mixer_elem_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___mixer.html) 131 | #[derive(Copy, Clone, Debug)] 132 | pub struct Elem<'a>{ 133 | handle: *mut alsa::snd_mixer_elem_t, 134 | _mixer: &'a Mixer 135 | } 136 | 137 | /// Iterator for all elements of mixer 138 | #[derive(Copy, Clone)] 139 | pub struct Iter<'a>{ 140 | last_handle: *mut alsa::snd_mixer_elem_t, 141 | mixer: &'a Mixer 142 | } 143 | 144 | impl<'a> Iterator for Iter<'a> { 145 | type Item = Elem<'a>; 146 | 147 | fn next(&mut self) -> Option> { 148 | let elem = if self.last_handle.is_null() { 149 | unsafe { alsa::snd_mixer_first_elem(self.mixer.0) } 150 | } else { 151 | unsafe { alsa::snd_mixer_elem_next(self.last_handle) } 152 | }; 153 | 154 | if elem.is_null() { 155 | None 156 | } else { 157 | self.last_handle = elem; 158 | Some(Elem { handle: elem, _mixer: self.mixer}) 159 | } 160 | } 161 | 162 | } 163 | 164 | /// Wrapper for [snd_mixer_selem_id_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___simple_mixer.html) 165 | /// No allocation (uses fixed array) 166 | // #[derive(Copy, Clone, Debug)] 167 | pub struct SelemId([u8; SELEM_ID_SIZE]); 168 | 169 | impl SelemId { 170 | 171 | pub fn new(name: &str, index: u32) -> SelemId { 172 | let mut s = SelemId::empty(); 173 | s.set_name(&CString::new(name).unwrap()); 174 | s.set_index(index); 175 | s 176 | } 177 | 178 | /// Returns an empty (zeroed) SelemId. This id is not a usable id and need to be initialized 179 | /// like `SelemId::new()` does 180 | pub fn empty() -> SelemId { 181 | assert!(unsafe { alsa::snd_mixer_selem_id_sizeof() } as usize <= SELEM_ID_SIZE); 182 | // Create empty selem_id and fill from mixer 183 | SelemId(unsafe { mem::zeroed() }) 184 | } 185 | 186 | /// Convert SelemId into ``*mut snd_mixer_selem_id_t` that the alsa call needs. 187 | /// See [snd_mixer_selem_id_t](http://www.alsa-project.org/alsa-doc/alsa-lib/group___simple_mixer.html) 188 | #[inline] 189 | fn as_ptr(&self) -> *mut alsa::snd_mixer_selem_id_t { 190 | self.0.as_ptr() as *const _ as *mut alsa::snd_mixer_selem_id_t 191 | } 192 | 193 | pub fn get_name(&self) -> Result<&str> { 194 | let c = unsafe { alsa::snd_mixer_selem_id_get_name(self.as_ptr()) }; 195 | from_const("snd_mixer_selem_id_get_name", c) 196 | } 197 | 198 | pub fn get_index(&self) -> u32 { 199 | unsafe { alsa::snd_mixer_selem_id_get_index(self.as_ptr()) } 200 | } 201 | 202 | pub fn set_name(&mut self, name: &CStr) { 203 | unsafe { alsa::snd_mixer_selem_id_set_name(self.as_ptr(), name.as_ptr()) }; 204 | } 205 | 206 | pub fn set_index(&mut self, index: u32) { 207 | unsafe { alsa::snd_mixer_selem_id_set_index(self.as_ptr(), index) }; 208 | } 209 | 210 | } 211 | 212 | /// Wraps an Elem as a Selem 213 | // #[derive(Copy, Clone)] 214 | pub struct Selem<'a>(Elem<'a>); 215 | 216 | impl<'a> Selem<'a> { 217 | /// Creates a Selem by wrapping `elem`. 218 | pub fn new(elem: Elem<'a>) -> Option> { 219 | if unsafe { alsa::snd_mixer_elem_get_type(elem.handle) } == alsa::SND_MIXER_ELEM_SIMPLE 220 | { Some(Selem(elem)) } else { None } 221 | } 222 | 223 | /// TODO: This function might change to support regopt and to return the mixer class 224 | pub fn register(mixer: &mut Mixer) -> Result<()> { 225 | acheck!(snd_mixer_selem_register(mixer.0, ptr::null_mut(), ptr::null_mut())).map(|_| ()) 226 | } 227 | 228 | pub fn get_id(&self) -> SelemId { 229 | let id = SelemId::empty(); 230 | unsafe { alsa::snd_mixer_selem_get_id(self.handle, id.as_ptr()) }; 231 | id 232 | } 233 | 234 | pub fn has_capture_volume(&self) -> bool { 235 | unsafe { alsa::snd_mixer_selem_has_capture_volume(self.handle) > 0 } 236 | } 237 | 238 | pub fn has_capture_switch(&self) -> bool { 239 | unsafe { alsa::snd_mixer_selem_has_capture_switch(self.handle) > 0 } 240 | } 241 | 242 | pub fn has_playback_volume(&self) -> bool { 243 | unsafe { alsa::snd_mixer_selem_has_playback_volume(self.handle) > 0 } 244 | } 245 | 246 | pub fn has_playback_switch(&self) -> bool { 247 | unsafe { alsa::snd_mixer_selem_has_playback_switch(self.handle) > 0 } 248 | } 249 | 250 | pub fn can_capture(&self) -> bool { 251 | self.has_capture_volume() || self.has_capture_switch() 252 | } 253 | 254 | pub fn can_playback(&self) -> bool { 255 | self.has_playback_volume() || self.has_playback_switch() 256 | } 257 | 258 | pub fn has_volume(&self) -> bool { 259 | self.has_capture_volume() || self.has_playback_volume() 260 | } 261 | 262 | /// returns range for capture volume as (min, max) values 263 | pub fn get_capture_volume_range(&self) -> (i64, i64) { 264 | let mut min: c_long = 0; 265 | let mut max: c_long = 0; 266 | unsafe { alsa::snd_mixer_selem_get_capture_volume_range(self.handle, &mut min, &mut max) }; 267 | (min as i64, max as i64) 268 | } 269 | 270 | /// returns (min, max) values. 271 | pub fn get_capture_db_range(&self) -> (MilliBel, MilliBel) { 272 | let mut min: c_long = 0; 273 | let mut max: c_long = 0; 274 | unsafe { alsa::snd_mixer_selem_get_capture_dB_range(self.handle, &mut min, &mut max) }; 275 | (MilliBel(min as i64), MilliBel(max as i64)) 276 | } 277 | 278 | /// returns (min, max) values. 279 | pub fn get_playback_volume_range(&self) -> (i64, i64) { 280 | let mut min: c_long = 0; 281 | let mut max: c_long = 0; 282 | unsafe { alsa::snd_mixer_selem_get_playback_volume_range(self.handle, &mut min, &mut max) }; 283 | (min as i64, max as i64) 284 | } 285 | 286 | /// returns (min, max) values. 287 | pub fn get_playback_db_range(&self) -> (MilliBel, MilliBel) { 288 | let mut min: c_long = 0; 289 | let mut max: c_long = 0; 290 | unsafe { alsa::snd_mixer_selem_get_playback_dB_range(self.handle, &mut min, &mut max) }; 291 | (MilliBel(min as i64), MilliBel(max as i64)) 292 | } 293 | 294 | pub fn is_capture_mono(&self) -> bool { 295 | unsafe { alsa::snd_mixer_selem_is_capture_mono(self.handle) == 1 } 296 | } 297 | 298 | pub fn is_playback_mono(&self) -> bool { 299 | unsafe { alsa::snd_mixer_selem_is_playback_mono(self.handle) == 1 } 300 | } 301 | 302 | pub fn has_capture_channel(&self, channel: SelemChannelId) -> bool { 303 | unsafe { alsa::snd_mixer_selem_has_capture_channel(self.handle, channel as i32) > 0 } 304 | } 305 | 306 | pub fn has_playback_channel(&self, channel: SelemChannelId) -> bool { 307 | unsafe { alsa::snd_mixer_selem_has_playback_channel(self.handle, channel as i32) > 0 } 308 | } 309 | 310 | /// Gets name from snd_mixer_selem_channel_name 311 | pub fn channel_name(channel: SelemChannelId) -> Result<&'static str> { 312 | let c = unsafe { alsa::snd_mixer_selem_channel_name(channel as i32) }; 313 | from_const("snd_mixer_selem_channel_name", c) 314 | } 315 | 316 | pub fn get_playback_volume(&self, channel: SelemChannelId) -> Result { 317 | let mut value: c_long = 0; 318 | acheck!(snd_mixer_selem_get_playback_volume(self.handle, channel as i32, &mut value)).and_then(|_| Ok(value as i64)) 319 | } 320 | 321 | /// returns volume in millibels. 322 | pub fn get_playback_vol_db(&self, channel: SelemChannelId) -> Result { 323 | self.get_playback_volume(channel) 324 | .and_then(|volume| self.ask_playback_vol_db(volume)) 325 | } 326 | 327 | /// Asks alsa to convert playback volume to millibels. 328 | pub fn ask_playback_vol_db(&self, volume: i64) -> Result { 329 | let mut decibel_value: c_long = 0; 330 | acheck!(snd_mixer_selem_ask_playback_vol_dB(self.handle, volume as c_long, &mut decibel_value)) 331 | .map(|_| MilliBel(decibel_value as i64)) 332 | } 333 | 334 | // Asks alsa to convert millibels to playback volume. 335 | pub fn ask_playback_db_vol(&self, db: MilliBel, dir: Round) -> Result { 336 | let mut raw_volume: c_long = 0; 337 | acheck!(snd_mixer_selem_ask_playback_dB_vol(self.handle, db.0 as c_long, dir as c_int, &mut raw_volume)) 338 | .map(|_| raw_volume as i64) 339 | } 340 | 341 | pub fn get_capture_volume(&self, channel: SelemChannelId) -> Result { 342 | let mut value: c_long = 0; 343 | acheck!(snd_mixer_selem_get_capture_volume(self.handle, channel as i32, &mut value)).map(|_| value as i64) 344 | } 345 | 346 | /// returns volume in millibels. 347 | pub fn get_capture_vol_db(&self, channel: SelemChannelId) -> Result { 348 | self.get_capture_volume(channel) 349 | .and_then(|volume| self.ask_capture_vol_db(volume)) 350 | } 351 | 352 | /// Asks alsa to convert capture volume to millibels 353 | pub fn ask_capture_vol_db(&self, volume: i64) -> Result { 354 | let mut decibel_value: c_long = 0; 355 | acheck!(snd_mixer_selem_ask_capture_vol_dB (self.handle, volume as c_long, &mut decibel_value)) 356 | .map(|_| MilliBel(decibel_value as i64)) 357 | } 358 | 359 | // Asks alsa to convert millibels to capture volume. 360 | pub fn ask_capture_db_vol(&self, db: MilliBel, dir: Round) -> Result { 361 | let mut raw_volume: c_long = 0; 362 | acheck!(snd_mixer_selem_ask_capture_dB_vol(self.handle, db.0 as c_long, dir as c_int, &mut raw_volume)) 363 | .map(|_| raw_volume as i64) 364 | } 365 | 366 | pub fn set_playback_volume(&self, channel: SelemChannelId, value: i64) -> Result<()> { 367 | acheck!(snd_mixer_selem_set_playback_volume(self.handle, channel as i32, value as c_long)).map(|_| ()) 368 | } 369 | 370 | pub fn set_playback_volume_range(&self, min: i64, max: i64) -> Result<()> { 371 | acheck!(snd_mixer_selem_set_playback_volume_range(self.handle, min as c_long, max as c_long)).map(|_| ()) 372 | } 373 | 374 | pub fn set_playback_volume_all(&self, value: i64) -> Result<()> { 375 | acheck!(snd_mixer_selem_set_playback_volume_all(self.handle, value as c_long)).map(|_| ()) 376 | } 377 | 378 | pub fn set_playback_db(&self, channel: SelemChannelId, value: MilliBel, dir: Round) -> Result<()> { 379 | acheck!(snd_mixer_selem_set_playback_dB(self.handle, channel as i32, *value as c_long, dir as c_int)).map(|_| ()) 380 | } 381 | 382 | pub fn set_capture_db(&self, channel: SelemChannelId, value: MilliBel, dir: Round) -> Result<()> { 383 | acheck!(snd_mixer_selem_set_capture_dB(self.handle, channel as i32, *value as c_long, dir as c_int)).map(|_| ()) 384 | } 385 | 386 | pub fn set_playback_db_all(&self, value: MilliBel, dir: Round) -> Result<()> { 387 | acheck!(snd_mixer_selem_set_playback_dB_all(self.handle, *value as c_long, dir as c_int)).map(|_| ()) 388 | } 389 | 390 | pub fn set_capture_db_all(&self, value: MilliBel, dir: Round) -> Result<()> { 391 | acheck!(snd_mixer_selem_set_capture_dB_all(self.handle, *value as c_long, dir as c_int)).map(|_| ()) 392 | } 393 | 394 | pub fn set_capture_volume(&self, channel: SelemChannelId, value: i64) -> Result<()> { 395 | acheck!(snd_mixer_selem_set_capture_volume(self.handle, channel as i32, value as c_long)).map(|_| ()) 396 | } 397 | 398 | pub fn set_capture_volume_range(&self, min: i64, max: i64) -> Result<()> { 399 | acheck!(snd_mixer_selem_set_capture_volume_range(self.handle, min as c_long, max as c_long)).map(|_| ()) 400 | } 401 | 402 | pub fn set_capture_volume_all(&self, value: i64) -> Result<()> { 403 | acheck!(snd_mixer_selem_set_capture_volume_all(self.handle, value as c_long)).map(|_| ()) 404 | } 405 | 406 | pub fn set_playback_switch(&self, channel: SelemChannelId, value: i32) -> Result<()> { 407 | acheck!(snd_mixer_selem_set_playback_switch(self.handle, channel as i32, value)).map(|_| ()) 408 | } 409 | 410 | pub fn set_playback_switch_all(&self, value: i32) -> Result<()> { 411 | acheck!(snd_mixer_selem_set_playback_switch_all(self.handle, value)).map(|_| ()) 412 | } 413 | 414 | pub fn set_capture_switch(&self, channel: SelemChannelId, value: i32) -> Result<()> { 415 | acheck!(snd_mixer_selem_set_capture_switch(self.handle, channel as i32, value)).map(|_| ()) 416 | } 417 | 418 | pub fn set_capture_switch_all(&self, value: i32) -> Result<()> { 419 | acheck!(snd_mixer_selem_set_capture_switch_all(self.handle, value)).map(|_| ()) 420 | } 421 | 422 | pub fn get_playback_switch(&self, channel: SelemChannelId) -> Result { 423 | let mut value: i32 = 0; 424 | acheck!(snd_mixer_selem_get_playback_switch(self.handle, channel as i32, &mut value)).map(|_| value) 425 | } 426 | 427 | pub fn get_capture_switch(&self, channel: SelemChannelId) -> Result { 428 | let mut value: i32 = 0; 429 | acheck!(snd_mixer_selem_get_capture_switch(self.handle, channel as i32, &mut value)).map(|_| value) 430 | } 431 | 432 | pub fn is_enumerated(&self) -> bool { 433 | unsafe { alsa::snd_mixer_selem_is_enumerated(self.handle) == 1 } 434 | } 435 | 436 | pub fn is_enum_playback(&self) -> bool { 437 | unsafe { alsa::snd_mixer_selem_is_enum_playback(self.handle) == 1 } 438 | } 439 | 440 | pub fn is_enum_capture(&self) -> bool { 441 | unsafe { alsa::snd_mixer_selem_is_enum_capture(self.handle) == 1 } 442 | } 443 | 444 | pub fn get_enum_items(&self) -> Result { 445 | acheck!(snd_mixer_selem_get_enum_items(self.handle)).map(|v| v as u32) 446 | } 447 | 448 | pub fn get_enum_item_name(&self, idx: u32) -> Result { 449 | let mut temp = [0 as ::libc::c_char; 128]; 450 | acheck!(snd_mixer_selem_get_enum_item_name(self.handle, idx, temp.len()-1, temp.as_mut_ptr())) 451 | .and_then(|_| from_const("snd_mixer_selem_get_enum_item_name", temp.as_ptr())) 452 | .map(|v| v.into()) 453 | } 454 | 455 | /// Enumerates over valid Enum values 456 | pub fn iter_enum(&self) -> Result { 457 | Ok(IterEnum(self, 0, self.get_enum_items()?)) 458 | } 459 | 460 | pub fn get_enum_item(&self, channel: SelemChannelId) -> Result { 461 | let mut temp = 0; 462 | acheck!(snd_mixer_selem_get_enum_item(self.handle, channel as i32, &mut temp)) 463 | .map(|_| temp) 464 | } 465 | 466 | pub fn set_enum_item(&self, channel: SelemChannelId, idx: u32) -> Result<()> { 467 | acheck!(snd_mixer_selem_set_enum_item(self.handle, channel as i32, idx)) 468 | .map(|_| ()) 469 | } 470 | } 471 | 472 | impl<'a> ops::Deref for Selem<'a> { 473 | type Target = Elem<'a>; 474 | 475 | /// returns the elem of this selem 476 | fn deref(&self) -> &Elem<'a> { 477 | &self.0 478 | } 479 | } 480 | 481 | pub struct IterEnum<'a>(&'a Selem<'a>, u32, u32); 482 | 483 | impl<'a> Iterator for IterEnum<'a> { 484 | type Item = Result; 485 | fn next(&mut self) -> Option { 486 | if self.1 >= self.2 { None } 487 | else { self.1 += 1; Some(self.0.get_enum_item_name(self.1-1)) } 488 | } 489 | } 490 | 491 | alsa_enum!( 492 | /// Wrapper for [SND_MIXER_SCHN_*](http://www.alsa-project.org/alsa-doc/alsa-lib/group___simple_mixer.html) constants 493 | SelemChannelId, ALL_SELEM_CHANNEL_ID[11], 494 | 495 | Unknown = SND_MIXER_SCHN_UNKNOWN, 496 | FrontLeft = SND_MIXER_SCHN_FRONT_LEFT, 497 | FrontRight = SND_MIXER_SCHN_FRONT_RIGHT, 498 | RearLeft = SND_MIXER_SCHN_REAR_LEFT, 499 | RearRight = SND_MIXER_SCHN_REAR_RIGHT, 500 | FrontCenter = SND_MIXER_SCHN_FRONT_CENTER, 501 | Woofer = SND_MIXER_SCHN_WOOFER, 502 | SideLeft = SND_MIXER_SCHN_SIDE_LEFT, 503 | SideRight = SND_MIXER_SCHN_SIDE_RIGHT, 504 | RearCenter = SND_MIXER_SCHN_REAR_CENTER, 505 | Last = SND_MIXER_SCHN_LAST, 506 | ); 507 | 508 | impl SelemChannelId { 509 | pub fn mono() -> SelemChannelId { SelemChannelId::FrontLeft } 510 | } 511 | 512 | impl fmt::Display for SelemChannelId { 513 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 514 | write!(f, "{}", Selem::channel_name(*self).unwrap()) 515 | } 516 | } 517 | 518 | #[test] 519 | fn print_mixer_of_cards() { 520 | extern crate std; 521 | use std::{println, print}; 522 | use ::alloc::format; 523 | use ::alloc::string::ToString; 524 | use super::card; 525 | 526 | for card in card::Iter::new().map(|c| c.unwrap()) { 527 | println!("Card #{}: {} ({})", card.get_index(), card.get_name().unwrap(), card.get_longname().unwrap()); 528 | 529 | let mixer = Mixer::new(&format!("hw:{}", card.get_index()), false).unwrap(); 530 | for selem in mixer.iter().filter_map(|e| Selem::new(e)) { 531 | 532 | let sid = selem.get_id(); 533 | println!("\tMixer element {},{}:", sid.get_name().unwrap(), sid.get_index()); 534 | 535 | if selem.has_volume() { 536 | print!("\t Volume limits: "); 537 | if selem.has_capture_volume() { 538 | let (vmin, vmax) = selem.get_capture_volume_range(); 539 | let (mbmin, mbmax) = selem.get_capture_db_range(); 540 | print!("Capture = {} - {}", vmin, vmax); 541 | print!(" ({} dB - {} dB)", mbmin.to_db(), mbmax.to_db()); 542 | } 543 | if selem.has_playback_volume() { 544 | let (vmin, vmax) = selem.get_playback_volume_range(); 545 | let (mbmin, mbmax) = selem.get_playback_db_range(); 546 | print!("Playback = {} - {}", vmin, vmax); 547 | print!(" ({} dB - {} dB)", mbmin.to_db(), mbmax.to_db()); 548 | } 549 | println!(); 550 | } 551 | 552 | if selem.is_enumerated() { 553 | print!("\t Valid values: "); 554 | for v in selem.iter_enum().unwrap() { print!("{}, ", v.unwrap()) }; 555 | print!("\n\t Current values: "); 556 | for v in SelemChannelId::all().iter().filter_map(|&v| selem.get_enum_item(v).ok()) { 557 | print!("{}, ", selem.get_enum_item_name(v).unwrap()); 558 | } 559 | println!(); 560 | } 561 | 562 | if selem.can_capture() { 563 | print!("\t Capture channels: "); 564 | if selem.is_capture_mono() { 565 | print!("Mono"); 566 | } else { 567 | for channel in SelemChannelId::all() { 568 | if selem.has_capture_channel(*channel) { print!("{}, ", channel) }; 569 | } 570 | } 571 | println!(); 572 | print!("\t Capture volumes: "); 573 | for channel in SelemChannelId::all() { 574 | if selem.has_capture_channel(*channel) { print!("{}: {} ({} dB), ", channel, 575 | match selem.get_capture_volume(*channel) {Ok(v) => format!("{}", v), Err(_) => "n/a".to_string()}, 576 | match selem.get_capture_vol_db(*channel) {Ok(v) => format!("{}", v.to_db()), Err(_) => "n/a".to_string()} 577 | );} 578 | } 579 | println!(); 580 | } 581 | 582 | if selem.can_playback() { 583 | print!("\t Playback channels: "); 584 | if selem.is_playback_mono() { 585 | print!("Mono"); 586 | } else { 587 | for channel in SelemChannelId::all() { 588 | if selem.has_playback_channel(*channel) { print!("{}, ", channel) }; 589 | } 590 | } 591 | println!(); 592 | if selem.has_playback_volume() { 593 | print!("\t Playback volumes: "); 594 | for channel in SelemChannelId::all() { 595 | if selem.has_playback_channel(*channel) { print!("{}: {} / {}dB, ", 596 | channel, 597 | match selem.get_playback_volume(*channel) {Ok(v) => format!("{}", v), Err(_) => "n/a".to_string()}, 598 | match selem.get_playback_vol_db(*channel) {Ok(v) => format!("{}", v.to_db()), Err(_) => "n/a".to_string()} 599 | );} 600 | } 601 | println!(); 602 | } 603 | } 604 | } 605 | } 606 | } 607 | 608 | #[test] 609 | #[ignore] 610 | fn get_and_set_playback_volume() { 611 | extern crate std; 612 | 613 | let mixer = Mixer::new("hw:1", false).unwrap(); 614 | let selem = mixer.find_selem(&SelemId::new("Master", 0)).unwrap(); 615 | 616 | let (rmin, rmax) = selem.get_playback_volume_range(); 617 | let mut channel = SelemChannelId::mono(); 618 | for c in SelemChannelId::all().iter() { 619 | if selem.has_playback_channel(*c) { channel = *c; break } 620 | } 621 | std::println!("Testing on {} with limits {}-{} on channel {}", selem.get_id().get_name().unwrap(), rmin, rmax, channel); 622 | 623 | let old: i64 = selem.get_playback_volume(channel).unwrap(); 624 | let new: i64 = rmax / 2; 625 | assert_ne!(new, old); 626 | 627 | std::println!("Changing volume of {} from {} to {}", channel, old, new); 628 | selem.set_playback_volume(channel, new).unwrap(); 629 | let mut result: i64 = selem.get_playback_volume(channel).unwrap(); 630 | assert_eq!(new, result); 631 | 632 | // return volume to old value 633 | selem.set_playback_volume(channel, old).unwrap(); 634 | result = selem.get_playback_volume(channel).unwrap(); 635 | assert_eq!(old, result); 636 | } 637 | 638 | #[test] 639 | #[ignore] 640 | fn get_and_set_capture_volume() { 641 | extern crate std; 642 | 643 | let mixer = Mixer::new("hw:1", false).unwrap(); 644 | let selem = mixer.find_selem(&SelemId::new("Capture", 0)).unwrap(); 645 | 646 | let (rmin, rmax) = selem.get_capture_volume_range(); 647 | let mut channel = SelemChannelId::mono(); 648 | for c in SelemChannelId::all().iter() { 649 | if selem.has_playback_channel(*c) { channel = *c; break } 650 | } 651 | std::println!("Testing on {} with limits {}-{} on channel {}", selem.get_id().get_name().unwrap(), rmin, rmax, channel); 652 | 653 | let old: i64 = selem.get_capture_volume(channel).unwrap(); 654 | let new: i64 = rmax / 2; 655 | assert_ne!(new, old); 656 | 657 | std::println!("Changing volume of {} from {} to {}", channel, old, new); 658 | selem.set_capture_volume(channel, new).unwrap(); 659 | let mut result: i64 = selem.get_capture_volume(channel).unwrap(); 660 | assert_eq!(new, result); 661 | 662 | // return volume to old value 663 | selem.set_capture_volume(channel, old).unwrap(); 664 | result = selem.get_capture_volume(channel).unwrap(); 665 | assert_eq!(old, result); 666 | } 667 | 668 | 669 | #[test] 670 | fn print_sizeof() { 671 | extern crate std; 672 | let selemid = unsafe { alsa::snd_mixer_selem_id_sizeof() } as usize; 673 | 674 | assert!(selemid <= SELEM_ID_SIZE); 675 | std::println!("Selem id: {}", selemid); 676 | } 677 | --------------------------------------------------------------------------------