All of lore.kernel.org
 help / color / mirror / Atom feed
* ALSA C++ API updated for 1.0.16
@ 2008-02-25 18:54 Lasse Kärkkäinen
  2008-02-26 10:01 ` Clemens Ladisch
  0 siblings, 1 reply; 7+ messages in thread
From: Lasse Kärkkäinen @ 2008-02-25 18:54 UTC (permalink / raw)
  To: alsa-devel

[-- Attachment #1: Type: text/plain, Size: 462 bytes --]

Here's an updated version of the C++ wrapper that I posted earlier in
September 2007. I would like this to be included in the ALSA
distribution after some peer review.

This wrapper has been used in UltraStar NG (a game shipped by Gentoo and
others) since September, with no known issues other than that it is
incomplete (very PCM-centric).

As a reminder, the API closely follows the C API, and also allows mixing
direct C API calls with itself transparently.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: alsa.hpp --]
[-- Type: text/x-c++hdr; name="alsa.hpp", Size: 22631 bytes --]

#ifndef ALSA_HPP_INCLUDED
#define ALSA_HPP_INCLUDED

/**

@file alsa.hpp
@brief An experimental low-level C++ API to ALSA.
@version 0.5
@author Lasse Kärkkäinen <tronic>
@license GNU LGPL 2.1 or later

This is a header-only C++ wrapper for the ALSA library. This means that you do
not need to add any new binary files to your build and you will only need to
link with -lasound, as if you were using the C API directly. GCC will probably
optimize all of the wrapper overhead away in any case, leaving you with a safer
and easier API to ALSA, but leaving your binaries just as if you had used C.

The library is designed to be very closely related to the C API, so that you
can still see what is going on under the hood, and also so that porting existing
applications to it is trivial. The interoperatibility between C and C++ APIs is
is a major design goal. What this means is that you can freely mix C and C++ API
calls with no problems.


Usage example:

alsa::pcm alsa_pcm;  // Create a PCM object (defaults to playback and mode = 0)

The above creates an equivalent for the snd_pcm_t*, which is what you need to
play or record anything. Make sure that the alsa_pcm object stays alive as long
as you need it (and preferrably not longer) by putting it inside a suitable
class or even inside the main() function. The object cannot be copied, but you
can pass references to it to other objects or functions.

The alsa_pcm also automatically converts into snd_pcm_t* as required, so you can
use it as an argument for the C API functions.

Next you'll need to configure it:

unsigned int rate = 44100;
unsigned int period;

alsa::hw_config(alsa_pcm)  // Create a new config space
  .set(SND_PCM_ACCESS_RW_INTERLEAVED)
  .set(SND_PCM_FORMAT_S16_LE)
  .rate_near(rate)
  .channels(1)
  .period_size_first(period)  // Get the smallest available period size
  .commit();  // Apply the config to the PCM

alsa::hw_config(pcm) constructs a new config space, using the current settings
from the PCM, if available, or the "any" space, if not set yet. The any space
contains all available hardware configurations and you have to narrow it down
to exactly one option by setting some parameters. Trying to narrow it too much
(by asking an amount of channels that is not available, for example) causes a
failure.

In case of failure, an alsa::error is thrown. When this happens, the commit part
never gets executed and thus the result is not stored to alsa_pcm and the
failed operation will have no effect (even to the temporary hw_config object,
which gets destroyed when the exception flies). However, all the operations
already performed successfully remain in effect.

The rate_near functions behaves like the C API *_near functions
do: they take the preferred value as an argument and then they modify the
argument, returning the actual rate. For example, if your sound card only
supports 48000 Hz, rate will be set to that on that call, even if some later
part, such as setting the number of channels, fails.

In the example above, a temporary object of type alsa::hw_config was used, but
you can also create a variable of it, should you need to test something in
between, or if you want to call the C API functions directly (hw_config
converts automatically into hw_params, which converts into snd_hw_params_t*).

For this, you may use a block like this:

{
    alsa::hw_config hw(alsa_pcm);
    hw.set(SND_ACCESS_(SND_PCM_ACCESS_MMAP_INTERLEAVED);
    hw.set(SND_PCM_FORMAT_FLOAT_BE);
    if (!snd_pcm_hw_params_is_full_duplex(hw)) hw.channels(2);
    hw.commit();
}

(anyone got a better example?)


Software configuration works in the same way, using alsa::sw_config, just the
parameters to be adjusted are different.

When constructed, both sw_config and hw_config try to load config from the given
PCM. If that fails, sw_config throws, but hw_config still tries loading the any
space. Alternatively, you may supply a snd_pcm_hw/sw_params_t const* as a second
argument for the constructor to get a copy of the contents of that that instead.

The contents may be loaded (overwrites the old contents) with these functions:
  .any()            Get the "any" configuration (hw_config only)
  .current()        Get the currently active configuration from PCM
Once finished with the changes, you should call:
  .commit()         Store current config space to the PCM

For enum values SND_PCM_*, you may use the following functions:
  .get(var)         Get the current setting into the given variable
  .set(val)         Set the value requested (also supports masks)
  .enum_test(val)   The same as .set, except that it does not set anything
  .enum_first(var)  Set the first available option, store the selection in var
  .enum_last(var)   Set the last available option, store the selection in var

The parameter to manipulate is chosen based on the argument type. The enum_*
functions and masks are only available for hardware parameters, not for
sw_param.

For integer values (times, sizes, counts), these functions are available:
  .get_X(var)       Get the current setting into the given variable
  .X(val)           Set the value requested
For ranges, the following can also be used:
  .get_X_min(var)   Get the smallest available value into var
  .get_X_max(var)   Get the largest available value into var
  .X_min(var)       Remove all values smaller than var and store the new
                    smallest value into var.
  .X_max(var)       Remove all values larger than var, store new max in var.
  .X_minmax(v1, v2) Limit to [v1, v2], store new range in v1, v2.
  .X_near(var)      Set the closest available value and store it in var
  .X_first(var)     Set the first available option, store the selection in var
  .X_last(var)      Set the last available option, store the selection in var

For booleans, these functions are available:
  .get_X(var)    Get the current setting (var must be unsigned int or bool)
  .set_X(val)    Set the value (val can be anything that converts into bool)
  
Replace X with the name of the parameter that you want to set. Consult the ALSA
C library reference for available options. All functions that modify their
arguments require the same type as is used in the C API (often unsigned int or
snd_pcm_uframes_t). The only exception is with bool types, where both bool and
unsigned int are accepted.

For those ranged parameters that support the dir argument (see ALSA docs), the
default value is always 0 when writing and NULL (none) when reading. You may
supply the preferred dir value or variable as the second argument and then the
value will be used or the result will be stored in the variable supplied.

For example, the following calls are equivalent:
snd_pcm_hw_params_set_format(pcm, hw, SND_PCM_FORMAT_FLOAT_LE)
  <=> hw.set(SND_PCM_FORMAT_FLOAT_LE)
snd_pcm_hw_params_set_rate_resample(pcm, hw, 1) <=> hw.rate_resample(true)
snd_pcm_hw_params_set_channels_near(pcm, hw, &num) <=> hw.channels_near(num)
snd_pcm_hw_params_get_rate(hw, &rate, NULL) <=> hw.get_rate(rate)
snd_pcm_hw_params_get_rate(hw, &rate, &dir) <=> hw.get_rate(rate, dir)


... except for the fact that the C++ versions also check the return values and
throw alsa::error if anything goes wrong. alsa::error inherits from
std::runtime_error and thus eventually from std::exception, so you can catch
pretty much everything by catching that somewhere in your code:

try {
	// do everything here
} catch (std::exception& e) {
	std::cerr << "FATAL ERROR: " << e.what() << std::endl;
}

However, recent versions of glibc seem to be handling uncaught exceptions quite
nicely, so even without a try-catch you may get a nice printout in your console:

terminate called after throwing an instance of 'alsa::error'
  what():  ALSA snd_pcm_hw_params_set_channels failed: Invalid argument
Aborted

If you need to know the error code, you may call e.code() after catching
alsa::error& e.


When you call the C API functions directly, the ALSA_CHECKED macro may prove to
be useful. It is used internally by the library for testing errors and throwing
exceptions when calling the C functions. It will throw alsa::error with a
description of the error if the function returns a negative value.

The macro is well-behaving, as it only calls an internal helper function,
evaluating the arguments given exactly once.

Usage: ALSA_CHECKED(snd_pcm_whatever, (arg1, arg2, arg2));

Note: a comma between function name and arguments.

The return value can also still be used (will return only >= 0):


For MMAP transfers, another RAII wrapper, alsa::mmap, is provided.

Usage:

// First we need to call avail_update (storing the return value is optional)
snd_pcm_uframes_t count = ALSA_CHECKED(snd_pcm_avail_update, (pcm));
alsa::mmap mmap(pcm, 256) // Begin MMAP transfer, request 256 frames

// Process the audio (mmap.frames frames of it, accessible via mmap.areas,
// starting at offset mmap.offset) and then let mmap go out of scope, which will
// end the transfer. If you didn't process all available frames, set the number
// of frames processed to mmap.frames before the object goes out of scope.

As usual, the constructor will throw alsa::error in case of error.


In case you really want to get low-level, alsa::hw_params and alsa::sw_params
are offered. These only contain the corresponding snd_pcm_*_params_t, but they
allocate and free memory automatically and they can also properly copy the
struct contents when they get copied. Be aware that the structure contents are
not initialized during construction, so you have to initialize it yourself (just
like with the C API). They are used internally by hw_config and sw_config and
normally it should be better to use these instead of dealing directly with the
params.

**/

#include <alsa/asoundlib.h>
#include <stdexcept>
#include <string>

/**
* A macro that executes func with the given args and tests for errors.
* Examples of use:
*   ALSA_CHECKED(snd_pcm_recover, (pcm, e.code(), 0));
*   snd_pcm_uframes_t count = ALSA_CHECKED(snd_pcm_avail_update, (pcm));
* @param func the function name
* @param args arguments in parenthesis
* @return the return value of the function
* @throws alsa::error if the return value is smaller than zero.
**/
#define ALSA_CHECKED(func, args) alsa::internal::check(func args, #func)

namespace alsa {
	/** @short Exception class **/
	class error: public std::runtime_error {
		int err;
	  public:
		error(std::string const& function, int err): std::runtime_error("ALSA " + function + " failed: " + std::string(snd_strerror(err))), err(err) {}
		int code() const { return err; }
	};

	namespace internal {
		/** For internal use only: a function used by the macro ALSA_CHECKED **/
		template<typename T> T check(T ret, char const* funcname) {
			if (ret < 0) throw error(funcname, ret);
			return ret;
		}
		/**
		* @short FOR INTERNAL USE ONLY. A utility class similar to
		* boost::noncopyable, duplicated here in order to avoid
		* a dependency on the Boost library.
		**/
		class noncopyable {
		  protected:
			noncopyable() {}
			~noncopyable() {}
		  private:
			noncopyable(noncopyable const&);
			noncopyable const& operator=(noncopyable const&);
		};
	}

	/**
	* @short A minimal RAII wrapper for ALSA PCM.
	* Automatically converts into snd_pcm_t* as needed, so the ALSA C API
	* can be used directly with this.
	**/
	class pcm: internal::noncopyable {
		snd_pcm_t* handle;
	  public:
		pcm(char const* device = "default", snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK, int mode = 0) {
			ALSA_CHECKED(snd_pcm_open, (&handle, device, stream, mode));
		}
		~pcm() { snd_pcm_close(handle); }
		operator snd_pcm_t*() { return handle; }
		operator snd_pcm_t const*() const { return handle; }
	};

	// RAII wrapper for snd_pcm_hw/sw_params_t types.
	
#define ALSA_HPP_PARAMWRAPPER(type) \
	class type##_params {\
		snd_pcm_##type##_params_t* handle;\
		void init() { ALSA_CHECKED(snd_pcm_##type##_params_malloc, (&handle)); }\
	  public:\
		type##_params() { init(); }\
		~type##_params() { snd_pcm_##type##_params_free(handle); }\
		type##_params(type##_params const& orig) { init(); *this = orig; }\
		type##_params(snd_pcm_##type##_params_t const* orig) { init(); *this = orig; }\
		type##_params& operator=(snd_pcm_##type##_params_t const* params) {\
			if (handle != params) snd_pcm_##type##_params_copy(handle, params);\
			return *this;\
		}\
		operator snd_pcm_##type##_params_t*() { return handle; }\
		operator snd_pcm_##type##_params_t const*() const { return handle; }\
	};

	ALSA_HPP_PARAMWRAPPER(hw)
	ALSA_HPP_PARAMWRAPPER(sw)
#undef ALSA_HPP_PARAMWRAPPER

// Various helper macros used for generating code for hw_config and sw_config

#define ALSA_HPP_FUNC(name, suffix) ALSA_HPP_TEMPLATE(& name(), suffix, (pcm, params))

#define ALSA_HPP_VARGET(name, type) \
  ALSA_HPP_TEMPLATE(& get_##name(type& val), _get_##name, (params, &val))\
  ALSA_HPP_TEMPLATE(const& get_##name(type& val) const, _get_##name, (params, &val))

#define ALSA_HPP_VAR(name, type) ALSA_HPP_VARGET(name, type)\
  ALSA_HPP_TEMPLATE(& name(type val), _set_##name, (pcm, params, val))

#define ALSA_HPP_ENUMVARMINIMAL(name) \
  ALSA_HPP_TEMPLATE(& get(snd_pcm_##name##_t& name), _get_##name, (params, &name))\
  ALSA_HPP_TEMPLATE(const& get(snd_pcm_##name##_t& name) const, _get_##name, (params, &name))\
  ALSA_HPP_TEMPLATE(& set(snd_pcm_##name##_t name), _set_##name, (pcm, params, name))

#define ALSA_HPP_ENUMVAR(name) ALSA_HPP_ENUMVARMINIMAL(name)\
  ALSA_HPP_TEMPLATE(& enum_test(snd_pcm_##name##_t name), _test_##name, (pcm, params, name))\
  ALSA_HPP_TEMPLATE(& enum_first(snd_pcm_##name##_t& name), _set_##name##_first, (pcm, params, &name))\
  ALSA_HPP_TEMPLATE(& enum_last(snd_pcm_##name##_t& name), _set_##name##_last, (pcm, params, &name))\
  ALSA_HPP_TEMPLATE(& set(snd_pcm_##name##_mask_t* mask), _set_##name##_mask, (pcm, params, mask))
  
#define ALSA_HPP_BOOLVAR(name) \
  ALSA_HPP_CLASS& get_##name(bool& val) { unsigned int tmp; get_##name(tmp); val = tmp; return *this; }\
  /*ALSA_HPP_CLASS const& get_##name(bool& val) const { unsigned int tmp; get_##name(tmp); val = tmp; return *this; }*/\
  ALSA_HPP_TEMPLATE(& get_##name(unsigned int& val), _get_##name, (pcm, params, &val))\
  /*ALSA_HPP_TEMPLATE(const& get_##name(unsigned int& val) const, _get_##name, (pcm, params, &val))*/\
  ALSA_HPP_TEMPLATE(& name(bool val = true), _set_##name, (pcm, params, val))

#define ALSA_HPP_RANGEVAR(name, type) ALSA_HPP_VAR(name, type)\
  ALSA_HPP_TEMPLATE(& get_##name##_min(type& min), _get_##name##_min, (params, &min))\
  ALSA_HPP_TEMPLATE(const& get_##name##_min(type& min) const, _get_##name##_min, (params, &min))\
  ALSA_HPP_TEMPLATE(& get_##name##_max(type& max), _get_##name##_max, (params, &max))\
  ALSA_HPP_TEMPLATE(const& get_##name##_max(type& max) const, _get_##name##_max, (params, &max))\
  ALSA_HPP_TEMPLATE(& name##_min(type& min), _set_##name##_min, (pcm, params, &min))\
  ALSA_HPP_TEMPLATE(& name##_max(type& max), _set_##name##_max, (pcm, params, &max))\
  ALSA_HPP_TEMPLATE(& name##_minmax(type& min, type& max), _set_##name##_minmax, (pcm, params, &min, &max))\
  ALSA_HPP_TEMPLATE(& name##_near(type& val), _set_##name##_near, (pcm, params, &val))\
  ALSA_HPP_TEMPLATE(& name##_first(type& val), _set_##name##_first, (pcm, params, &val))\
  ALSA_HPP_TEMPLATE(& name##_last(type& val), _set_##name##_last, (pcm, params, &val))

#define ALSA_HPP_RANGEVARDIR(name, type) \
  ALSA_HPP_TEMPLATE(& get_##name(type& val), _get_##name, (params, &val, NULL))\
  ALSA_HPP_TEMPLATE(const& get_##name(type& val) const, _get_##name, (params, &val, NULL))\
  ALSA_HPP_TEMPLATE(& get_##name(type& val, int& dir), _get_##name, (params, &val, &dir))\
  ALSA_HPP_TEMPLATE(const& get_##name(type& val, int& dir) const, _get_##name, (params, &val, &dir))\
  ALSA_HPP_TEMPLATE(& get_##name##_min(type& min), _get_##name##_min, (params, &min, NULL))\
  ALSA_HPP_TEMPLATE(const& get_##name##_min(type& min) const, _get_##name##_min, (params, &min, NULL))\
  ALSA_HPP_TEMPLATE(& get_##name##_min(type& min, int& dir), _get_##name##_min, (params, &min, &dir))\
  ALSA_HPP_TEMPLATE(const& get_##name##_min(type& min, int& dir) const, _get_##name##_min, (params, &min, &dir))\
  ALSA_HPP_TEMPLATE(& get_##name##_max(type& max), _get_##name##_max, (params, &max, NULL))\
  ALSA_HPP_TEMPLATE(const& get_##name##_max(type& max) const, _get_##name##_max, (params, &max, NULL))\
  ALSA_HPP_TEMPLATE(& get_##name##_max(type& max, int& dir), _get_##name##_max, (params, &max, &dir))\
  ALSA_HPP_TEMPLATE(const& get_##name##_max(type& max, int& dir) const, _get_##name##_max, (params, &max, &dir))\
  ALSA_HPP_TEMPLATE(& name(type val, int dir = 0), _set_##name, (pcm, params, val, dir))\
  ALSA_HPP_TEMPLATE(& name##_min(type& min), _set_##name##_min, (pcm, params, &min, NULL))\
  ALSA_HPP_TEMPLATE(& name##_min(type& min, int& dir), _set_##name##_min, (pcm, params, &min, &dir))\
  ALSA_HPP_TEMPLATE(& name##_max(type& max), _set_##name##_max, (pcm, params, &max, NULL))\
  ALSA_HPP_TEMPLATE(& name##_max(type& max, int& dir), _set_##name##_max, (pcm, params, &max, &dir))\
  ALSA_HPP_TEMPLATE(& name##_minmax(type& min, type& max), _set_##name##_minmax, (pcm, params, &min, NULL, &max, NULL))\
  ALSA_HPP_TEMPLATE(& name##_minmax(type& min, int& mindir, type& max, int& maxdir), _set_##name##_minmax, (pcm, params, &min, &mindir, &max, &maxdir))\
  ALSA_HPP_TEMPLATE(& name##_near(type& val), _set_##name##_near, (pcm, params, &val, NULL))\
  ALSA_HPP_TEMPLATE(& name##_near(type& val, int& dir), _set_##name##_near, (pcm, params, &val, &dir))\
  ALSA_HPP_TEMPLATE(& name##_first(type& val), _set_##name##_first, (pcm, params, &val, NULL))\
  ALSA_HPP_TEMPLATE(& name##_first(type& val, int& dir), _set_##name##_first, (pcm, params, &val, &dir))\
  ALSA_HPP_TEMPLATE(& name##_last(type& val), _set_##name##_last, (pcm, params, &val, NULL))\
  ALSA_HPP_TEMPLATE(& name##_last(type& val, int& dir), _set_##name##_last, (pcm, params, &val, &dir))

	/** @short A helper object for modifying hw_params of a PCM. **/
	class hw_config: internal::noncopyable {
		snd_pcm_t* pcm;
		hw_params params;
	  public:
		/**
		* Construct a new config object, initialized with the current settings
		* of the PCM or with the "any" configuration space, if there are none.
		**/
		hw_config(snd_pcm_t* pcm): pcm(pcm) {
			try { current(); } catch (std::runtime_error&) { any(); }
		}
		/** Construct a new config object, initialized with a copy from given parameters **/
		hw_config(snd_pcm_t* pcm, snd_pcm_hw_params_t const* params): pcm(pcm), params(params) {}
		operator hw_params&() { return params; }
		operator hw_params const&() const { return params; }
#define ALSA_HPP_CLASS hw_config
#define ALSA_HPP_TEMPLATE(proto, suffix, params) ALSA_HPP_CLASS proto { ALSA_CHECKED(snd_pcm_hw_params##suffix, params); return *this; }
		// Load / store functions
		ALSA_HPP_FUNC(commit,)
		ALSA_HPP_FUNC(any, _any)
		ALSA_HPP_FUNC(current, _current)
		// Enum functions
		ALSA_HPP_ENUMVAR(access)
		ALSA_HPP_ENUMVAR(format)
		ALSA_HPP_ENUMVAR(subformat)
		// Bool functions
		ALSA_HPP_BOOLVAR(rate_resample)
		ALSA_HPP_BOOLVAR(export_buffer)
		// Range functions
		ALSA_HPP_RANGEVAR(channels, unsigned int)
		ALSA_HPP_RANGEVAR(buffer_size, snd_pcm_uframes_t)
		// Range functions with direction argument
		ALSA_HPP_RANGEVARDIR(rate, unsigned int)
		ALSA_HPP_RANGEVARDIR(period_time, unsigned int)
		ALSA_HPP_RANGEVARDIR(period_size, snd_pcm_uframes_t)
		ALSA_HPP_RANGEVARDIR(periods, unsigned int)
		ALSA_HPP_RANGEVARDIR(buffer_time, unsigned int)
#undef ALSA_HPP_TEMPLATE
#undef ALSA_HPP_CLASS
	};

	class sw_config: internal::noncopyable {
		snd_pcm_t* pcm;
		sw_params params;
	  public:
		sw_config(snd_pcm_t* pcm): pcm(pcm) { current(); }
		/** Construct a new config object, initialized with a copy from given parameters **/
		sw_config(snd_pcm_t* pcm, snd_pcm_sw_params_t const* params): pcm(pcm), params(params) {}
		operator sw_params&() { return params; }
		operator sw_params const&() const { return params; }
#define ALSA_HPP_CLASS sw_config
#define ALSA_HPP_TEMPLATE(proto, suffix, params) ALSA_HPP_CLASS proto { ALSA_CHECKED(snd_pcm_sw_params##suffix, params); return *this; }
		// Load / store functions
		ALSA_HPP_FUNC(commit,)
		ALSA_HPP_FUNC(current, _current)
		// Enum functions
		typedef snd_pcm_tstamp_t snd_pcm_tstamp_mode_t; // Workaround for inconsistent naming in asound
		ALSA_HPP_ENUMVARMINIMAL(tstamp_mode)
		// Simple variable functions
		ALSA_HPP_VAR(avail_min, snd_pcm_uframes_t)
		ALSA_HPP_VAR(start_threshold, snd_pcm_uframes_t)
		ALSA_HPP_VAR(stop_threshold, snd_pcm_uframes_t)
		ALSA_HPP_VAR(silence_threshold, snd_pcm_uframes_t)
		ALSA_HPP_VAR(silence_size, snd_pcm_uframes_t)
		ALSA_HPP_VAR(tstamp_mode, snd_pcm_tstamp_t)
		// Get-only variable
		ALSA_HPP_VARGET(boundary, snd_pcm_uframes_t)
#undef ALSA_HPP_TEMPLATE
#undef ALSA_HPP_CLASS
	};

#undef ALSA_HPP_FUNC
#undef ALSA_HPP_VAR
#undef ALSA_HPP_VARGET
#undef ALSA_HPP_ENUMVAR
#undef ALSA_HPP_ENUMVARMINIMAL
#undef ALSA_HPP_BOOLVAR
#undef ALSA_HPP_RANGEVAR
#undef ALSA_HPP_RANGEVARDIR

	/** @short A RAII wrapper for snd_pcm_mmap_begin/end block. **/
	class mmap {
		snd_pcm_t* pcm;
	  public:
		snd_pcm_channel_area_t const* areas;
		snd_pcm_uframes_t const offset; // Const for external API
		snd_pcm_uframes_t frames;
		/**
		* Initiate MMAP transfer. Returns a buffer via .areas member variable.
		* snd_pcm_avail_update must be called directly before constructing the
		* alsa::mmap object, otherwise snd_pcm_mmap_begin may return a wrong
		* count of available frames.
		* @param pcm PCM handle
		* @param fr number of frames to request (check .frames for actual count)
		**/
		mmap(snd_pcm_t* pcm, snd_pcm_uframes_t fr): pcm(pcm), areas(), offset(), frames(fr) {
			ALSA_CHECKED(snd_pcm_mmap_begin, (pcm, &areas, const_cast<snd_pcm_uframes_t*>(&offset), &frames));
		}
		/**
		* End MMAP transfer. If not all frames were used, the count of frames
		* used be set in .frames member variable before alsa::mmap destruction.
		**/
		~mmap() {
			// We just assume that this works (can't do anything sensible if it fails).
			snd_pcm_mmap_commit(pcm, offset, frames);
		}
	};

}

#endif


[-- Attachment #3: Type: text/plain, Size: 160 bytes --]

_______________________________________________
Alsa-devel mailing list
Alsa-devel@alsa-project.org
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: ALSA C++ API updated for 1.0.16
  2008-02-25 18:54 ALSA C++ API updated for 1.0.16 Lasse Kärkkäinen
@ 2008-02-26 10:01 ` Clemens Ladisch
  2008-02-26 14:49   ` Michael Gerdau
  2008-02-26 16:59   ` Lasse Kärkkäinen
  0 siblings, 2 replies; 7+ messages in thread
From: Clemens Ladisch @ 2008-02-26 10:01 UTC (permalink / raw)
  To: Lasse Kärkkäinen; +Cc: alsa-devel

Lasse Kärkkäinen wrote:
> Here's an updated version of the C++ wrapper that I posted earlier in
> September 2007. I would like this to be included in the ALSA
> distribution after some peer review.

Yes, an ALSA C++ wrapper is a good idea.

> error(std::string const& function, int err): std::runtime_error("ALSA " + function + " failed: " + std::string(snd_strerror(err))), err(err) {}

I'm not sure that including the function name and all parameters as they
appear in the source file in the error message is a good idea; I
wouldn't want to impose this policy on all applications using this
header.

> pcm(char const* device = "default", snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK, int mode = 0) {

This looks too much like a default constructor.  I think neither device
nor stream should have a default value.

> ALSA_HPP_CLASS& set_##name(...) { ...; return *this; }\

Is there a reason why you return the object itself?  Usually, returning
an object implies that that object is a _different_ object with the
setting applied, which would mean that the original object was _not_
changed, but that isn't true here.

> class hw_config: internal::noncopyable {

Why should this object not be copyable?

And why do you have two different objects (*w_params and *w_config) for
wrapping the hardware/software parameters?

> hw_config(snd_pcm_t* pcm): pcm(pcm) {
> 	try { current(); } catch (std::runtime_error&) { any(); }

I don't like using exceptions when there's nothing wrong.  Besides,
getting the current parameters may fail due to other errors (like device
unplugged).

I think it would be better if this object doesn't have a public
constructor, and you would be able to create one only through a pcm
object, like "hw_config c = pcm.any_hw_params();".

> ~mmap() {
> 	// We just assume that this works (can't do anything sensible if it fails).
> 	snd_pcm_mmap_commit(pcm, offset, frames);

This is the place where all the usual write errors must be checked.
This cannot be done in a destructor.  I think using RAII for the non-
error commit just isn't possible.


Regards,
Clemens
_______________________________________________
Alsa-devel mailing list
Alsa-devel@alsa-project.org
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: ALSA C++ API updated for 1.0.16
  2008-02-26 10:01 ` Clemens Ladisch
@ 2008-02-26 14:49   ` Michael Gerdau
  2008-02-26 16:59   ` Lasse Kärkkäinen
  1 sibling, 0 replies; 7+ messages in thread
From: Michael Gerdau @ 2008-02-26 14:49 UTC (permalink / raw)
  To: alsa-devel; +Cc: Clemens Ladisch, Lasse Kärkkäinen


[-- Attachment #1.1: Type: text/plain, Size: 810 bytes --]

> > ALSA_HPP_CLASS& set_##name(...) { ...; return *this; }\
> 
> Is there a reason why you return the object itself?  Usually, returning
> an object implies that that object is a _different_ object with the
> setting applied, which would mean that the original object was _not_
> changed, but that isn't true here.

I'm not an OO design guru, but at least IOC (IBM OpenClass; it used to
be a rather powerful multiplatform GUI framework - before they made it
AIX only) used this very paradigma all over the place.

You were supposed to write statements like:
  obj.method1().method2().method3().___.method_n();

At least that's what all their example code looked like.

Best,
Michael
-- 
 Michael Gerdau       email: mgerdau@tiscali.de
 GPG-keys available on request or at public keyserver

[-- Attachment #1.2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 194 bytes --]

[-- Attachment #2: Type: text/plain, Size: 160 bytes --]

_______________________________________________
Alsa-devel mailing list
Alsa-devel@alsa-project.org
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: ALSA C++ API updated for 1.0.16
  2008-02-26 10:01 ` Clemens Ladisch
  2008-02-26 14:49   ` Michael Gerdau
@ 2008-02-26 16:59   ` Lasse Kärkkäinen
  2008-02-27 11:50     ` Clemens Ladisch
  1 sibling, 1 reply; 7+ messages in thread
From: Lasse Kärkkäinen @ 2008-02-26 16:59 UTC (permalink / raw)
  To: Clemens Ladisch; +Cc: alsa-devel

Thank you for your input, which is quite valuable.

>> error(std::string const& function, int err): std::runtime_error("ALSA " + function + " failed: " + std::string(snd_strerror(err))), err(err) {}
> 
> I'm not sure that including the function name and all parameters as they
> appear in the source file in the error message is a good idea; I
> wouldn't want to impose this policy on all applications using this
> header.

It is just an exception thrown, the program may catch it and display
another error message. The original ALSA error code is also included
within it.

E.g.

try {
    // Some code that causes error
} catch (alsa::error& e) {
    std::cerr << "ALSA error: " << snd_strerror(e.code()) << std::endl;
}

Prints only the error, not the function that caused it, while

try {
    // ...
} catch (std::exception& e) {
    std::cerr << e.what() << std::endl;
}

would print the formatted message, containing the function name, too.

In practice the function name seems to be quite essential for debugging,
as EPIPE or some other of the standard error codes will tell little or
nothing.

For completeness, I think that it might be desirable to also store the
function name separately (like the code is stored), but can anyone see
any actual use for this?

>> pcm(char const* device = "default", snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK, int mode = 0) {
> 
> This looks too much like a default constructor.  I think neither device
> nor stream should have a default value.

Well, it is a default constructor. One design goal was to make this
wrapper as easy to use as possible, and choosing sensible defaults for
that is helpful.

If you are expecting alsa::pcm pcmobject; to produce an object that is
not constructed, we are talking of two-stage construction, which is
considered harmful by many C++ coders nowadays. A constructor should
always construct the object fully and throw an exception (and thus
prevent the object being created) if that is not possible. Design &
Evolution of C++, by Bjarne Stroustrup, and other books discuss this
matter thoroughly. (surprisingly enough, fstreams of the standard
library still allow two-stage construction)

Thus, I see the expected behavior of creating pcmobject that way as
being to actually construct a new PCM, with the default parameters
(where "default" and playback are the most common choices; and also far
better than people hardcoding their applications to use "plughw:0" or
whatever happens to work for them).

>> ALSA_HPP_CLASS& set_##name(...) { ...; return *this; }\
> 
> Is there a reason why you return the object itself?  Usually, returning
> an object implies that that object is a _different_ object with the
> setting applied, which would mean that the original object was _not_
> changed, but that isn't true here.

This is for function call chaining. It is a common idiom used by the
standard library and others. In this particular case, it is the very
thing that allows syntax like

alsa::hw_config(alsa_pcm)  // Temporary object
  .set(SND_PCM_ACCESS_RW_INTERLEAVED)
  .set(SND_PCM_FORMAT_S16_LE)
  .rate_near(rate)
  .channels(1)
  .period_size_first(period)
  .commit();  // The object is disposed here

Instead of

{
	alsa::hw_config config(alsa_pcm);
	config.set(SND_PCM_ACCESS_RW_INTERLEAVED);
	config.set(SND_PCM_FORMAT_S16_LE);
	config.rate_near(rate);
	config.channels(1);
	config.period_size_first(period);
	config.commit();
}

(braces are needed for limiting the config variable lifetime)

>> class hw_config: internal::noncopyable {
> 
> Why should this object not be copyable?

Because it contains the PCM pointer, whose copy semantics are somewhat
hairy. E.g. the following code would break silently:

alsa::hw_config f() {
	alsa::pcm pcm;
	return alsa::hw_config(pcm);
}

Please note that you can still make copies of alsa::hw_params.

> And why do you have two different objects (*w_params and *w_config) for
> wrapping the hardware/software parameters?

The alsa::hw_config wrapper is meant to be used only temporarily, for
easily setting the parameters. It differs from alsa::hw_params by also
tying a PCM to it.

>> hw_config(snd_pcm_t* pcm): pcm(pcm) {
>> 	try { current(); } catch (std::runtime_error&) { any(); }
> 
> I don't like using exceptions when there's nothing wrong.  Besides,
> getting the current parameters may fail due to other errors (like device
> unplugged).

It seems like a sensible fallback, anyway. The other option would be to
load any() settings always and have the user call current(), if he wants
those instead. Would you find this preferable?

Some settings need to be loaded by the constructor in order to ensure
that the structure is in a consistent state at all times.

> I think it would be better if this object doesn't have a public
> constructor, and you would be able to create one only through a pcm
> object, like "hw_config c = pcm.any_hw_params();".

That seems to require unwanted hackery (friend classes, helper objects,
etc).

Rather, if such selection is needed, I would add a second argument to
the constructor (with a suitable default value), allowing one to choose
between the two (e.g. bool useCurrent = false). However, just loading
the any settings in the constructor and loading the current settings on
top of them as required seems easier to use (theoretically slightly
slower due to the extra initialization if the current parameters are
preferred, but performance cannot be an issue in configuring a PCM).

>> ~mmap() {
>> 	// We just assume that this works (can't do anything sensible if it fails).
>> 	snd_pcm_mmap_commit(pcm, offset, frames);
> 
> This is the place where all the usual write errors must be checked.
> This cannot be done in a destructor.  I think using RAII for the non-
> error commit just isn't possible.

Ouch. This is a big problem.

I see three options

1. Drop the MMAP wrapper entirely
    * Easily leads to leaks when exceptions are thrown

2. Add a separate commit function
    * Use exceptions to signal errors from this function
    * Set pcm and areas to NULL after commit
    * Possibly throw exceptions if the object is accessed after this
      (either by operator overloading or by getter functions instead of
      public members)
    * Still commit in destructor if commit was not called

3. Throw in destructor only if std::uncaught_exception() returns false
    * Ignore all errors if stack unwinding is in progress
    * Weird semantics, not commonly used

Option 2 is hairy, as it leaves the object in unusable state after
commit is called, but it still seems highly preferable to option 1,
which would require great care from the user for avoiding resource leaks.

Option 3 is pretty much out of the question because programmers don't
expect destructors to ever throw anything.

What do you think?

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: ALSA C++ API updated for 1.0.16
  2008-02-26 16:59   ` Lasse Kärkkäinen
@ 2008-02-27 11:50     ` Clemens Ladisch
  2008-02-29  5:57       ` Aldrin Martoq
  0 siblings, 1 reply; 7+ messages in thread
From: Clemens Ladisch @ 2008-02-27 11:50 UTC (permalink / raw)
  To: Lasse Kärkkäinen; +Cc: alsa-devel

Lasse Kärkkäinen wrote:
> Thank you for your input, which is quite valuable.
>
> > > error(std::string const& function, int err): std::runtime_error("ALSA " + function + " failed: " + std::string(snd_strerror(err))), err(err) {}
> >
> > I'm not sure that including the function name and all parameters as they
> > appear in the source file in the error message is a good idea; I
> > wouldn't want to impose this policy on all applications using this
> > header.
>
> It is just an exception thrown, the program may catch it and display
> another error message. The original ALSA error code is also included
> within it.

I'm just objecting to the fact that the function name and ALSA's error
string are combined into a string that cannot easily be parsed later.

> In practice the function name seems to be quite essential for debugging,
> as EPIPE or some other of the standard error codes will tell little or
> nothing.

Agreed.

> For completeness, I think that it might be desirable to also store the
> function name separately (like the code is stored), but can anyone see
> any actual use for this?

I'd like to have only the snd_strerror() result as the message.  In this
case, the function name has to be stored separately if it is needed.

> > > pcm(char const* device = "default", snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK, int mode = 0) {
> >
> > This looks too much like a default constructor.  I think neither device
> > nor stream should have a default value.
>
> Well, it is a default constructor. One design goal was to make this
> wrapper as easy to use as possible, and choosing sensible defaults for
> that is helpful.

While playback will be used more often, I think the difference between
playback and capture is so big that we cannot make one of them the
default.

> (where "default" and playback are the most common choices; and also far
> better than people hardcoding their applications to use "plughw:0" or
> whatever happens to work for them).

In practice, most applications allow the user to specify the device
name, so the default value won't be used anyway.

> If you are expecting alsa::pcm pcmobject; to produce an object that is
> not constructed, ...

No, I'm not.

> > > ALSA_HPP_CLASS& set_##name(...) { ...; return *this; }\
> >
> > Is there a reason why you return the object itself?  Usually, returning
> > an object implies that that object is a _different_ object with the
> > setting applied, which would mean that the original object was _not_
> > changed, but that isn't true here.
>
> This is for function call chaining. It is a common idiom used by the
> standard library and others.

The STL streams use this to allow using the convert-to-bool operator to
check the error status of the stream (because most errors aren't
reported by exceptions).  That this also allows chaining is just a side
effect.

> > > hw_config(snd_pcm_t* pcm): pcm(pcm) {
> > > 	try { current(); } catch (std::runtime_error&) { any(); }
> >
> > I don't like using exceptions when there's nothing wrong.  Besides,
> > getting the current parameters may fail due to other errors (like device
> > unplugged).
>
> It seems like a sensible fallback, anyway. The other option would be to
> load any() settings always and have the user call current(), if he wants
> those instead. Would you find this preferable?

My preference would be that the user has to indicate whether he wants
to read the current setting or to begin choosing new settings.  It is
not possible for this constructor to guess what the user wants.

> Some settings need to be loaded by the constructor in order to ensure
> that the structure is in a consistent state at all times.

ALSA's snd_pcm_hw_params_t is in a consistent state even before initial
or current settings are loaded from a PCM device.  But then the
hw_config object isn't the direct wrapper for snd_pcm_hw_params_t ...

> > > ~mmap() {
> > > 	// We just assume that this works (can't do anything sensible if it fails).
> > > 	snd_pcm_mmap_commit(pcm, offset, frames);
> >
> > This is the place where all the usual write errors must be checked.
> > This cannot be done in a destructor.  I think using RAII for the non-
> > error commit just isn't possible.
>
> Ouch. This is a big problem.

This is very similar to database transaction that must be either
committed or rolled back.  The usual idiom seems to be a RAII class
whose destructor rolls back if a commit or rollback hasn't already
happended.

In our case, a rollback is equivalent to calling snd_pcm_mmap_commit
with frames==0.

Hmm, it should be possible to commit any number of frames as long as it
isn't more than originally requested.

> 2. Add a separate commit function
> ...
> Option 2 is hairy, as it leaves the object in unusable state after
> commit is called,

After committing, it is indeed not allowed to access the mmap buffer,
so this isn't too unexpected.

In practice, the commit will be the last action before the destructor.

BTW: The object should be called mmap_access or something like that.


As far as I can see, your header tries to do two things: wrapping the
ALSA API, and offering an easier-to-use API that is not as low-level
as the ALSA API itself.  I think these two should be put in separate
header files.

Anyway, we need more wrappers for all those data structures.  I'll see
what I can come up with.


Regards,
Clemens
_______________________________________________
Alsa-devel mailing list
Alsa-devel@alsa-project.org
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: ALSA C++ API updated for 1.0.16
  2008-02-27 11:50     ` Clemens Ladisch
@ 2008-02-29  5:57       ` Aldrin Martoq
  2008-03-01  0:56         ` Lasse Kärkkäinen
  0 siblings, 1 reply; 7+ messages in thread
From: Aldrin Martoq @ 2008-02-29  5:57 UTC (permalink / raw)
  To: Clemens Ladisch; +Cc: alsa-devel, Lasse Kärkkäinen

On Wed, Feb 27, 2008 at 8:50 AM, Clemens Ladisch <clemens@ladisch.de> wrote:
> Lasse Kärkkäinen wrote:
>  > > > error(std::string const& function, int err): std::runtime_error("ALSA " + function + " failed: " + std::string(snd_strerror(err))), err(err) {}
>  > > I'm not sure that including the function name and all parameters as they
>  > > appear in the source file in the error message is a good idea; I
>  > > wouldn't want to impose this policy on all applications using this
>  > > header.
>  > It is just an exception thrown, the program may catch it and display
>  > another error message. The original ALSA error code is also included
>  > within it.
>  I'm just objecting to the fact that the function name and ALSA's error
>  string are combined into a string that cannot easily be parsed later.

Many of those strings are not meant to be parsed, they are for human
review (developer/user).


>  > In practice the function name seems to be quite essential for debugging,
>  > as EPIPE or some other of the standard error codes will tell little or
>  > nothing.
>  Agreed.

I would prefer that the method from the C++ API should appear instead
of the internal C API function. The C++ developer is calling the
constructor of alsa::pcm, not snd_pcm_open().

>  > For completeness, I think that it might be desirable to also store the
>  > function name separately (like the code is stored), but can anyone see
>  > any actual use for this?
>  I'd like to have only the snd_strerror() result as the message.  In this
>  case, the function name has to be stored separately if it is needed.

I've found the snd_strerror() messages are quite unusable, at least
for the alsaseq api... they return something like 'file not found'
which seems more related to the internal implementation rather that an
actual alsa-related error...

>  > > > pcm(char const* device = "default", snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK, int mode = 0) {
>  > > This looks too much like a default constructor.  I think neither device
>  > > nor stream should have a default value.
>  > Well, it is a default constructor. One design goal was to make this
>  > wrapper as easy to use as possible, and choosing sensible defaults for
>  > that is helpful.
>  While playback will be used more often, I think the difference between
>  playback and capture is so big that we cannot make one of them the
>  default.

Me too.

[...]

>  > > > ALSA_HPP_CLASS& set_##name(...) { ...; return *this; }\
>  > > Is there a reason why you return the object itself?  Usually, returning
>  > > an object implies that that object is a _different_ object with the
>  > > setting applied, which would mean that the original object was _not_
>  > > changed, but that isn't true here.
>  > This is for function call chaining. It is a common idiom used by the
>  > standard library and others.
>  The STL streams use this to allow using the convert-to-bool operator to
>  check the error status of the stream (because most errors aren't
>  reported by exceptions).  That this also allows chaining is just a side
>  effect.

I found this feature quite useful.

>  > > > hw_config(snd_pcm_t* pcm): pcm(pcm) {
>  > > >   try { current(); } catch (std::runtime_error&) { any(); }
>  > > I don't like using exceptions when there's nothing wrong.  Besides,
>  > > getting the current parameters may fail due to other errors (like device
>  > > unplugged).
>  > It seems like a sensible fallback, anyway. The other option would be to
>  > load any() settings always and have the user call current(), if he wants
>  > those instead. Would you find this preferable?
>  My preference would be that the user has to indicate whether he wants
>  to read the current setting or to begin choosing new settings.  It is
>  not possible for this constructor to guess what the user wants.

>From a OO perspective, I would expect that a *_config object be
created from an existing pcm object. Something like:

alsa::pcm pcm;

pcm.get_config().set(FOO).set(BAR);


If there is something that doesn't apply to a pcm instance, the C++
API could provide an instance of a pcm_any object and call that
instead:

alsa::pcm_any.get_config().set(FOO).set(BAR);


[...]


HTH,


-- 
Aldrin Martoq

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: ALSA C++ API updated for 1.0.16
  2008-02-29  5:57       ` Aldrin Martoq
@ 2008-03-01  0:56         ` Lasse Kärkkäinen
  0 siblings, 0 replies; 7+ messages in thread
From: Lasse Kärkkäinen @ 2008-03-01  0:56 UTC (permalink / raw)
  To: Aldrin Martoq; +Cc: alsa-devel, Clemens Ladisch

[-- Attachment #1: Type: text/plain, Size: 6668 bytes --]

Clemens wrote:
> I'm just objecting to the fact that the function name and ALSA's error
> string are combined into a string that cannot easily be parsed later.

As Aldrin pointed out, it is not meant to be parsed. I have now provided
separate functions for accessing the function names directly.

> I'd like to have only the snd_strerror() result as the message.  In this
> case, the function name has to be stored separately if it is needed.

"alsa::pcm::pcm: snd_pcm_open failed: No such file or directory" is
useful for debugging, "No such file or directory" is not. Therefore, I
don't see why you want only snd_strerror in that message.

> While playback will be used more often, I think the difference between
> playback and capture is so big that we cannot make one of them the
> default.

I have removed the default arguments, as you requested.

>>>> hw_config(snd_pcm_t* pcm): pcm(pcm) {
>>>> 	try { current(); } catch (std::runtime_error&) { any(); }
>>> I don't like using exceptions when there's nothing wrong.  Besides,
>>> getting the current parameters may fail due to other errors (like device
>>> unplugged).
>> It seems like a sensible fallback, anyway. The other option would be to
>> load any() settings always and have the user call current(), if he wants
>> those instead. Would you find this preferable?
>
> My preference would be that the user has to indicate whether he wants
> to read the current setting or to begin choosing new settings.  It is
> not possible for this constructor to guess what the user wants.

I have looked at the issue again; the current system (load current if
possible, any otherwise) is to make it resemble the API that sw_config
provides (always loading the current settings). If the user specifically
wants current or any settings (and wants an exception if that fails), he
may call the any() and current() functions after first constructing the
object. If not, he will get the most sensible default behavior.

However, I changed to catch to only match alsa::error (not that it
matters, but it's clearer that way).

>> Some settings need to be loaded by the constructor in order to ensure
>> that the structure is in a consistent state at all times.
>
> ALSA's snd_pcm_hw_params_t is in a consistent state even before initial
> or current settings are loaded from a PCM device.  But then the
> hw_config object isn't the direct wrapper for snd_pcm_hw_params_t ...

The documentation says that an invalid snd_pcm_hw_params_t is allocated
(by snd_pcm_hw_params_malloc). I did not look into ALSA source code to
find out whether this means uninitialized memory or just a kind of
"none" configuration space that could still be used by the other
functions. In the latter case, removing the initialization from
hw_config constructor, and having the user explicitly call any() or
current(), would be a sensible choice.

In this case it would be nice if this behavior was also clearly
documented on the C API.

> In our case, a rollback is equivalent to calling snd_pcm_mmap_commit
> with frames==0.

Ah, didn't think of that. Fixed to work this way, safe commit function
provided.

> Hmm, it should be possible to commit any number of frames as long as it
> isn't more than originally requested.

One could try to commit more than the original number with the old
wrapper, but this is fixed now (among with the commit function).

> After committing, it is indeed not allowed to access the mmap buffer,
> so this isn't too unexpected.

Just to stay safe, I made it throw std::logic_error if any access is
attempted after commit.

> BTW: The object should be called mmap_access or something like that.

This differs from the naming convention used in other parts of the
wrapper (take a part of the C API function name). It seems pretty
obvious what an object called mmap does on a PCM, so I don't quite see
why you want another name for it.

> As far as I can see, your header tries to do two things: wrapping the
> ALSA API, and offering an easier-to-use API that is not as low-level
> as the ALSA API itself.  I think these two should be put in separate
> header files.

I don't see why they couldn't coexist in the same header. C++
programmers are used to some flexibility (things happening
automatically, function overloading, default arguments, "OOP" syntax),
so it seems normal to offer these in such a wrapper.

However, I don't intend alsa.hpp, as it is now, as an easy solution for
the user, but have instead written a separate audio library on top of
it, abstracting many of the features of ALSA and also supporting other
audio interfaces under the hood.

> Anyway, we need more wrappers for all those data structures.  I'll see
> what I can come up with.

True. The problem is that it is a lot of work, even with all those
macros. Without helper macros one would need thousands of lines just to
wrap all the functions.

Any help is appreciated, of course :)


Aldrin wrote:
> I would prefer that the method from the C++ API should appear instead
> of the internal C API function. The C++ developer is calling the
> constructor of alsa::pcm, not snd_pcm_open().

I have fixed this. Now it prints the calling function, the C API
function and the snd_strerror message. All can be requested as
individual strings to order having to parse the formatted message
returned by what().

> From a OO perspective, I would expect that a *_config object be
> created from an existing pcm object. Something like:
> 
> alsa::pcm pcm;
> 
> pcm.get_config().set(FOO).set(BAR);

This does not allow storing the generated configuration, which is a
requirement if we want to add some control logic to it instead of just
setting things blindly. (remember, hw_config is noncopyable because it
depends on a PCM, which is noncopyable)

> If there is something that doesn't apply to a pcm instance, the C++
> API could provide an instance of a pcm_any object and call that
> instead:
> 
> alsa::pcm_any.get_config().set(FOO).set(BAR);

Even the "any" configuration is always linked to a PCM:
int snd_pcm_hw_params_any(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);

A new version is attached.

Changelog for alsa.hpp 0.6:
- alsa::mmap completely rewritten
- alsa::error now has accessors for function names
- ALSA_CHECKED and alsa::error changed to also store the name of the
function using the macro
- alsa::pcm device and stream default values removed
- catching errors only, instead of any runtime_error
- added missing *_param operator=(*_param...)
- naming style changed: member variables now use m_ prefix to avoid
shadowing warnings from GCC -Wshadow
- other compile warnings eliminated as well (-Weffc++ etc)
- documentation updates


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: alsa.hpp --]
[-- Type: text/x-c++hdr; name="alsa.hpp", Size: 24361 bytes --]

#ifndef ALSA_HPP_INCLUDED
#define ALSA_HPP_INCLUDED

/**

@file alsa.hpp
@brief An experimental low-level C++ API to ALSA.
@version 0.6
@author Lasse Kärkkäinen <tronic>
@license GNU LGPL 2.1 or later

This is a header-only C++ wrapper for the ALSA library. This means that you do
not need to add any new binary files to your build and you will only need to
link with -lasound, as if you were using the C API directly. GCC will probably
optimize all of the wrapper overhead away in any case, leaving you with a safer
and easier API to ALSA, but leaving your binaries just as if you had used C.

The library is designed to be very closely related to the C API, so that you
can still see what is going on under the hood, and also so that porting existing
applications to it is trivial. The interoperatibility between C and C++ APIs is
a major design goal. What this means is that you can freely mix C and C++ API
calls with no problems.


Usage example:

alsa::pcm alsa_pcm("default", SND_PCM_STREAM_PLAYBACK);  // Create a PCM object

The above creates an equivalent for the snd_pcm_t*, which is what you need to
play or record anything. Make sure that the alsa_pcm object stays alive as long
as you need it (and preferrably not longer) by putting it inside a suitable
class or even inside the main() function. The object cannot be copied, but you
can pass references to it to other objects or functions.

The alsa_pcm also automatically converts into snd_pcm_t* as required, so you can
use it as an argument for the C API functions.

Next you'll need to configure it:

unsigned int rate = 44100;
unsigned int period;

alsa::hw_config(alsa_pcm)  // Create a new config space
  .set(SND_PCM_ACCESS_RW_INTERLEAVED)
  .set(SND_PCM_FORMAT_S16_LE)
  .rate_near(rate)
  .channels(1)
  .period_size_first(period)  // Get the smallest available period size
  .commit();  // Apply the config to the PCM

alsa::hw_config(pcm) constructs a new config space, using the current settings
from the PCM, if available, or the "any" space, if not set yet. The any space
contains all available hardware configurations and you have to narrow it down
to exactly one option by setting some parameters. Trying to narrow it too much
(by asking an amount of channels that is not available, for example) causes a
failure.

In case of failure, an alsa::error is thrown. When this happens, the commit part
never gets executed and thus the result is not stored to alsa_pcm and the
failed operation will have no effect (even to the temporary hw_config object,
which gets destroyed when the exception flies). However, all the operations
already performed successfully remain in effect.

The rate_near functions behaves like the C API *_near functions
do: they take the preferred value as an argument and then they modify the
argument, returning the actual rate. For example, if your sound card only
supports 48000 Hz, rate will be set to that on that call, even if some later
part, such as setting the number of channels, fails.

In the example above, a temporary object of type alsa::hw_config was used, but
you can also create a variable of it, should you need to test something in
between, or if you want to call the C API functions directly (hw_config
converts automatically into hw_params, which converts into snd_hw_params_t*).

For this, you may use a block like this:

{
    alsa::hw_config hw(alsa_pcm);
    hw.set(SND_ACCESS_(SND_PCM_ACCESS_MMAP_INTERLEAVED);
    hw.set(SND_PCM_FORMAT_FLOAT_BE);
    if (!snd_pcm_hw_params_is_full_duplex(hw)) hw.channels(2);
    hw.commit();
}

(anyone got a better example?)


Software configuration works in the same way, using alsa::sw_config, just the
parameters to be adjusted are different.

When constructed, both sw_config and hw_config try to load config from the given
PCM. If that fails, sw_config throws, but hw_config still tries loading the any
space. Alternatively, you may supply a snd_pcm_hw/sw_params_t const* as a second
argument for the constructor to get a copy of the contents of that that instead.

The contents may be loaded (overwrites the old contents) with these functions:
  .any()            Get the "any" configuration (hw_config only)
  .current()        Get the currently active configuration from PCM
Once finished with the changes, you should call:
  .commit()         Store current config space to the PCM

For enum values SND_PCM_*, you may use the following functions:
  .get(var)         Get the current setting into the given variable
  .set(val)         Set the value requested (also supports masks)
  .enum_test(val)   The same as .set, except that it does not set anything
  .enum_first(var)  Set the first available option, store the selection in var
  .enum_last(var)   Set the last available option, store the selection in var

The parameter to manipulate is chosen based on the argument type. The enum_*
functions and masks are only available for hardware parameters, not for
sw_param.

For integer values (times, sizes, counts), these functions are available:
  .get_X(var)       Get the current setting into the given variable
  .X(val)           Set the value requested
For ranges, the following can also be used:
  .get_X_min(var)   Get the smallest available value into var
  .get_X_max(var)   Get the largest available value into var
  .X_min(var)       Remove all values smaller than var and store the new
                    smallest value into var.
  .X_max(var)       Remove all values larger than var, store new max in var.
  .X_minmax(v1, v2) Limit to [v1, v2], store new range in v1, v2.
  .X_near(var)      Set the closest available value and store it in var
  .X_first(var)     Set the first available option, store the selection in var
  .X_last(var)      Set the last available option, store the selection in var

For booleans, these functions are available:
  .get_X(var)    Get the current setting (var must be unsigned int or bool)
  .set_X(val)    Set the value (val can be anything that converts into bool)
  
Replace X with the name of the parameter that you want to set. Consult the ALSA
C library reference for available options. All functions that modify their
arguments require the same type as is used in the C API (often unsigned int or
snd_pcm_uframes_t). The only exception is with bool types, where both bool and
unsigned int are accepted.

For those ranged parameters that support the dir argument (see ALSA docs), the
default value is always 0 when writing and NULL (none) when reading. You may
supply the preferred dir value or variable as the second argument and then the
value will be used or the result will be stored in the variable supplied.

For example, the following calls are equivalent:
snd_pcm_hw_params_set_format(pcm, hw, SND_PCM_FORMAT_FLOAT_LE)
  <=> hw.set(SND_PCM_FORMAT_FLOAT_LE)
snd_pcm_hw_params_set_rate_resample(pcm, hw, 1) <=> hw.rate_resample(true)
snd_pcm_hw_params_set_channels_near(pcm, hw, &num) <=> hw.channels_near(num)
snd_pcm_hw_params_get_rate(hw, &rate, NULL) <=> hw.get_rate(rate)
snd_pcm_hw_params_get_rate(hw, &rate, &dir) <=> hw.get_rate(rate, dir)


... except for the fact that the C++ versions also check the return values and
throw alsa::error if anything goes wrong. This inherts from std::runtime_error
and thus eventually from std::exception, so you can catch pretty much everything
by catching that somewhere in your code:

try {
	// do everything here
} catch (std::exception& e) {
	std::cerr << "FATAL ERROR: " << e.what() << std::endl;
}

If you need to know the error code, the ALSA C function name or the C++ function
within which the error originated, you may call e.code(), e.cfunc() or e.func()
after catching alsa::error& e. If you just want a developer-readable error
message that contains all this information, use e.what().

Please note that some programming errors (using the wrapper incorrectly) are
detected and handled within the C++ API. In this case std::logic_error or
something derived from it is thrown instead of the custom exceptions.

When you call the C API functions directly, the ALSA_CHECKED macro may prove to
be useful. It is used internally by the library for testing errors and throwing
exceptions when calling the C functions. It will throw alsa::error with a
description of the error if the function returns a negative value.

The macro is well-behaving, as it only calls an internal helper function,
evaluating the arguments given exactly once. The return value can also still be
used (will return only >= 0):

Usage: ALSA_CHECKED(snd_pcm_whatever, (arg1, arg2, arg2));

Note: a comma between function name and arguments.


MMAP transfers can be done with the alsa::mmap RAII wrapper.

Usage:

// First we need to call avail_update (storing the return value is optional)
snd_pcm_uframes_t count = ALSA_CHECKED(snd_pcm_avail_update, (pcm));
alsa::mmap mmap(pcm, 256) // Begin MMAP transfer, request 256 frames

// Process the audio (mmap.frames() frames of it, accessible via mmap.area(n),
// starting at offset mmap.offset())

mmap.commit();  // If you used less than mmap.frames() frames, commit(n) instead

// You may not use the mmap object after the commit. The transfer is terminated
// properly (by commiting 0 frames w/o error handling) if no commit is done
// before the object goes out of scope.


In case you really want to get low-level, alsa::hw_params and alsa::sw_params
are offered. These only contain the corresponding snd_pcm_*_params_t, but they
allocate and free memory automatically and they can also properly copy the
struct contents when they get copied. Be aware that the structure contents are
not initialized during construction, so you have to initialize it yourself (just
like with the C API). They are used internally by hw_config and sw_config and
normally it should be better to use these instead of dealing directly with the
params.

**/

#include <alsa/asoundlib.h>
#include <stdexcept>
#include <string>

/**
* A macro that executes func with the given args and tests for errors.
* Examples of use:
*   ALSA_CHECKED(snd_pcm_recover, (pcm, e.code(), 0));
*   snd_pcm_uframes_t count = ALSA_CHECKED(snd_pcm_avail_update, (pcm));
* @param func the function name
* @param args arguments in parenthesis
* @return the return value of the function
* @throws alsa::error if the return value is smaller than zero.
**/
#define ALSA_CHECKED(cfunc, args) alsa::internal::check(cfunc args, #cfunc, __PRETTY_FUNCTION__)

namespace alsa {
	/** @short Exception class **/
	class error: public std::runtime_error {
		int m_code;
		std::string m_cfunc;
		std::string m_func;
	  public:
		error(std::string const& cf, int errcode, std::string const& f):
		  std::runtime_error(f + ": " + cf + " failed: " + std::string(snd_strerror(errcode))),
		  m_code(errcode),
		  m_cfunc(cf),
		  m_func(f)
		{}
		~error() throw() {}
		/** Error code returned by the function **/
		int code() const { return m_code; }
		/** Get the C API function that returned the error code **/
		std::string const& cfunc() const { return m_cfunc; }
		/** Get the function that used ALSA_CHECKED **/
		std::string const& func() const { return m_func; }
	};
	
	namespace internal {
		/** For internal use only: a function used by the macro ALSA_CHECKED **/
		template<typename T> T check(T ret, char const* cfunc, std::string prettyfunc) {
			if (ret >= 0) return ret;
			// Remove return type, qualifiers and arguments from __PRETTY_FUNCTION__
			std::string::size_type end = prettyfunc.find('(');
			std::string::size_type begin = prettyfunc.find(' ');
			if (begin == std::string::npos || begin > end) begin = 0; else ++begin;
			for (std::string::size_type tmp; (tmp = prettyfunc.rfind(' ', end)) != std::string::npos && tmp > begin; end = tmp);
			throw error(cfunc, ret, prettyfunc.substr(begin, end - begin));
		}
	}

#define ALSA_HPP_NONCOPYABLE(c) c(c const&); c const& operator=(c const&);

	/**
	* @short A minimal RAII wrapper for ALSA PCM.
	* Automatically converts into snd_pcm_t* as needed, so the ALSA C API
	* can be used directly with this.
	**/
	class pcm { ALSA_HPP_NONCOPYABLE(pcm)
		snd_pcm_t* m_pcm;
	  public:
		pcm(char const* device, snd_pcm_stream_t stream, int mode = 0): m_pcm() {
			ALSA_CHECKED(snd_pcm_open, (&m_pcm, device, stream, mode));
		}
		~pcm() { snd_pcm_close(m_pcm); }
		operator snd_pcm_t*() { return m_pcm; }
		operator snd_pcm_t const*() const { return m_pcm; }
	};

	// RAII wrapper for snd_pcm_hw/sw_params_t types.
	
#define ALSA_HPP_PARAMWRAPPER(type) \
	class type##_params {\
		snd_pcm_##type##_params_t* m_handle;\
		void init() { ALSA_CHECKED(snd_pcm_##type##_params_malloc, (&m_handle)); }\
	  public:\
		type##_params(): m_handle() { init(); }\
		~type##_params() { snd_pcm_##type##_params_free(m_handle); }\
		type##_params(type##_params const& orig): m_handle() { init(); *this = orig; }\
		type##_params(snd_pcm_##type##_params_t const* orig): m_handle() { init(); *this = orig; }\
		type##_params& operator=(type##_params const& params) { *this = params.m_handle; return *this; }\
		type##_params& operator=(snd_pcm_##type##_params_t const* params) {\
			if (m_handle != params) snd_pcm_##type##_params_copy(m_handle, params);\
			return *this;\
		}\
		operator snd_pcm_##type##_params_t*() { return m_handle; }\
		operator snd_pcm_##type##_params_t const*() const { return m_handle; }\
	};

	ALSA_HPP_PARAMWRAPPER(hw)
	ALSA_HPP_PARAMWRAPPER(sw)
#undef ALSA_HPP_PARAMWRAPPER

// Various helper macros used for generating code for hw_config and sw_config

#define ALSA_HPP_FUNC(name, suffix) ALSA_HPP_TEMPLATE(& name(), suffix, (m_pcm, m_params))

#define ALSA_HPP_VARGET(name, type) \
  ALSA_HPP_TEMPLATE(& get_##name(type& val), _get_##name, (m_params, &val))\
  ALSA_HPP_TEMPLATE(const& get_##name(type& val) const, _get_##name, (m_params, &val))

#define ALSA_HPP_VAR(name, type) ALSA_HPP_VARGET(name, type)\
  ALSA_HPP_TEMPLATE(& name(type val), _set_##name, (m_pcm, m_params, val))

#define ALSA_HPP_ENUMVARMINIMAL(name) \
  ALSA_HPP_TEMPLATE(& get(snd_pcm_##name##_t& name_), _get_##name, (m_params, &name_))\
  ALSA_HPP_TEMPLATE(const& get(snd_pcm_##name##_t& name_) const, _get_##name, (m_params, &name_))\
  ALSA_HPP_TEMPLATE(& set(snd_pcm_##name##_t name_), _set_##name, (m_pcm, m_params, name_))

#define ALSA_HPP_ENUMVAR(name) ALSA_HPP_ENUMVARMINIMAL(name)\
  ALSA_HPP_TEMPLATE(& enum_test(snd_pcm_##name##_t name), _test_##name, (m_pcm, m_params, name))\
  ALSA_HPP_TEMPLATE(& enum_first(snd_pcm_##name##_t& name), _set_##name##_first, (m_pcm, m_params, &name))\
  ALSA_HPP_TEMPLATE(& enum_last(snd_pcm_##name##_t& name), _set_##name##_last, (m_pcm, m_params, &name))\
  ALSA_HPP_TEMPLATE(& set(snd_pcm_##name##_mask_t* mask), _set_##name##_mask, (m_pcm, m_params, mask))
  
#define ALSA_HPP_BOOLVAR(name) \
  ALSA_HPP_CLASS& get_##name(bool& val) { unsigned int tmp; get_##name(tmp); val = tmp; return *this; }\
  /*ALSA_HPP_CLASS const& get_##name(bool& val) const { unsigned int tmp; get_##name(tmp); val = tmp; return *this; }*/\
  ALSA_HPP_TEMPLATE(& get_##name(unsigned int& val), _get_##name, (m_pcm, m_params, &val))\
  /*ALSA_HPP_TEMPLATE(const& get_##name(unsigned int& val) const, _get_##name, (m_pcm, m_params, &val))*/\
  ALSA_HPP_TEMPLATE(& name(bool val = true), _set_##name, (m_pcm, m_params, val))

#define ALSA_HPP_RANGEVAR(name, type) ALSA_HPP_VAR(name, type)\
  ALSA_HPP_TEMPLATE(& get_##name##_min(type& min), _get_##name##_min, (m_params, &min))\
  ALSA_HPP_TEMPLATE(const& get_##name##_min(type& min) const, _get_##name##_min, (m_params, &min))\
  ALSA_HPP_TEMPLATE(& get_##name##_max(type& max), _get_##name##_max, (m_params, &max))\
  ALSA_HPP_TEMPLATE(const& get_##name##_max(type& max) const, _get_##name##_max, (m_params, &max))\
  ALSA_HPP_TEMPLATE(& name##_min(type& min), _set_##name##_min, (m_pcm, m_params, &min))\
  ALSA_HPP_TEMPLATE(& name##_max(type& max), _set_##name##_max, (m_pcm, m_params, &max))\
  ALSA_HPP_TEMPLATE(& name##_minmax(type& min, type& max), _set_##name##_minmax, (m_pcm, m_params, &min, &max))\
  ALSA_HPP_TEMPLATE(& name##_near(type& val), _set_##name##_near, (m_pcm, m_params, &val))\
  ALSA_HPP_TEMPLATE(& name##_first(type& val), _set_##name##_first, (m_pcm, m_params, &val))\
  ALSA_HPP_TEMPLATE(& name##_last(type& val), _set_##name##_last, (m_pcm, m_params, &val))

#define ALSA_HPP_RANGEVARDIR(name, type) \
  ALSA_HPP_TEMPLATE(& get_##name(type& val), _get_##name, (m_params, &val, NULL))\
  ALSA_HPP_TEMPLATE(const& get_##name(type& val) const, _get_##name, (m_params, &val, NULL))\
  ALSA_HPP_TEMPLATE(& get_##name(type& val, int& dir), _get_##name, (m_params, &val, &dir))\
  ALSA_HPP_TEMPLATE(const& get_##name(type& val, int& dir) const, _get_##name, (m_params, &val, &dir))\
  ALSA_HPP_TEMPLATE(& get_##name##_min(type& min), _get_##name##_min, (m_params, &min, NULL))\
  ALSA_HPP_TEMPLATE(const& get_##name##_min(type& min) const, _get_##name##_min, (m_params, &min, NULL))\
  ALSA_HPP_TEMPLATE(& get_##name##_min(type& min, int& dir), _get_##name##_min, (m_params, &min, &dir))\
  ALSA_HPP_TEMPLATE(const& get_##name##_min(type& min, int& dir) const, _get_##name##_min, (m_params, &min, &dir))\
  ALSA_HPP_TEMPLATE(& get_##name##_max(type& max), _get_##name##_max, (m_params, &max, NULL))\
  ALSA_HPP_TEMPLATE(const& get_##name##_max(type& max) const, _get_##name##_max, (m_params, &max, NULL))\
  ALSA_HPP_TEMPLATE(& get_##name##_max(type& max, int& dir), _get_##name##_max, (m_params, &max, &dir))\
  ALSA_HPP_TEMPLATE(const& get_##name##_max(type& max, int& dir) const, _get_##name##_max, (m_params, &max, &dir))\
  ALSA_HPP_TEMPLATE(& name(type val, int dir = 0), _set_##name, (m_pcm, m_params, val, dir))\
  ALSA_HPP_TEMPLATE(& name##_min(type& min), _set_##name##_min, (m_pcm, m_params, &min, NULL))\
  ALSA_HPP_TEMPLATE(& name##_min(type& min, int& dir), _set_##name##_min, (m_pcm, m_params, &min, &dir))\
  ALSA_HPP_TEMPLATE(& name##_max(type& max), _set_##name##_max, (m_pcm, m_params, &max, NULL))\
  ALSA_HPP_TEMPLATE(& name##_max(type& max, int& dir), _set_##name##_max, (m_pcm, m_params, &max, &dir))\
  ALSA_HPP_TEMPLATE(& name##_minmax(type& min, type& max), _set_##name##_minmax, (m_pcm, m_params, &min, NULL, &max, NULL))\
  ALSA_HPP_TEMPLATE(& name##_minmax(type& min, int& mindir, type& max, int& maxdir), _set_##name##_minmax, (m_pcm, m_params, &min, &mindir, &max, &maxdir))\
  ALSA_HPP_TEMPLATE(& name##_near(type& val), _set_##name##_near, (m_pcm, m_params, &val, NULL))\
  ALSA_HPP_TEMPLATE(& name##_near(type& val, int& dir), _set_##name##_near, (m_pcm, m_params, &val, &dir))\
  ALSA_HPP_TEMPLATE(& name##_first(type& val), _set_##name##_first, (m_pcm, m_params, &val, NULL))\
  ALSA_HPP_TEMPLATE(& name##_first(type& val, int& dir), _set_##name##_first, (m_pcm, m_params, &val, &dir))\
  ALSA_HPP_TEMPLATE(& name##_last(type& val), _set_##name##_last, (m_pcm, m_params, &val, NULL))\
  ALSA_HPP_TEMPLATE(& name##_last(type& val, int& dir), _set_##name##_last, (m_pcm, m_params, &val, &dir))

	/** @short A helper object for modifying hw_params of a PCM. **/
	class hw_config { ALSA_HPP_NONCOPYABLE(hw_config)
		snd_pcm_t* m_pcm;
		hw_params m_params;
	  public:
		/**
		* Construct a new config object, initialized with the current settings
		* of the PCM or with the "any" configuration space, if there are none.
		**/
		hw_config(snd_pcm_t* pcm): m_pcm(pcm), m_params() {
			try { current(); } catch (std::runtime_error&) { any(); }
		}
		/** Construct a new config object, initialized with a copy from given parameters **/
		hw_config(snd_pcm_t* pcm, snd_pcm_hw_params_t const* params): m_pcm(pcm), m_params(params) {}
		operator hw_params&() { return m_params; }
		operator hw_params const&() const { return m_params; }
#define ALSA_HPP_CLASS hw_config
#define ALSA_HPP_TEMPLATE(proto, suffix, params) ALSA_HPP_CLASS proto { ALSA_CHECKED(snd_pcm_hw_params##suffix, params); return *this; }
		// Load / store functions
		ALSA_HPP_FUNC(commit,)
		ALSA_HPP_FUNC(any, _any)
		ALSA_HPP_FUNC(current, _current)
		// Enum functions
		ALSA_HPP_ENUMVAR(access)
		ALSA_HPP_ENUMVAR(format)
		ALSA_HPP_ENUMVAR(subformat)
		// Bool functions
		ALSA_HPP_BOOLVAR(rate_resample)
		ALSA_HPP_BOOLVAR(export_buffer)
		// Range functions
		ALSA_HPP_RANGEVAR(channels, unsigned int)
		ALSA_HPP_RANGEVAR(buffer_size, snd_pcm_uframes_t)
		// Range functions with direction argument
		ALSA_HPP_RANGEVARDIR(rate, unsigned int)
		ALSA_HPP_RANGEVARDIR(period_time, unsigned int)
		ALSA_HPP_RANGEVARDIR(period_size, snd_pcm_uframes_t)
		ALSA_HPP_RANGEVARDIR(periods, unsigned int)
		ALSA_HPP_RANGEVARDIR(buffer_time, unsigned int)
#undef ALSA_HPP_TEMPLATE
#undef ALSA_HPP_CLASS
	};

	class sw_config { ALSA_HPP_NONCOPYABLE(sw_config)
		snd_pcm_t* m_pcm;
		sw_params m_params;
	  public:
		sw_config(snd_pcm_t* pcm): m_pcm(pcm), m_params() { current(); }
		/** Construct a new config object, initialized with a copy from given parameters **/
		sw_config(snd_pcm_t* pcm, snd_pcm_sw_params_t const* params): m_pcm(pcm), m_params(params) {}
		operator sw_params&() { return m_params; }
		operator sw_params const&() const { return m_params; }
#define ALSA_HPP_CLASS sw_config
#define ALSA_HPP_TEMPLATE(proto, suffix, params) ALSA_HPP_CLASS proto { ALSA_CHECKED(snd_pcm_sw_params##suffix, params); return *this; }
		// Load / store functions
		ALSA_HPP_FUNC(commit,)
		ALSA_HPP_FUNC(current, _current)
		// Enum functions
		typedef snd_pcm_tstamp_t snd_pcm_tstamp_mode_t; // Workaround for inconsistent naming in asound
		ALSA_HPP_ENUMVARMINIMAL(tstamp_mode)
		// Simple variable functions
		ALSA_HPP_VAR(avail_min, snd_pcm_uframes_t)
		ALSA_HPP_VAR(start_threshold, snd_pcm_uframes_t)
		ALSA_HPP_VAR(stop_threshold, snd_pcm_uframes_t)
		ALSA_HPP_VAR(silence_threshold, snd_pcm_uframes_t)
		ALSA_HPP_VAR(silence_size, snd_pcm_uframes_t)
		ALSA_HPP_VAR(tstamp_mode, snd_pcm_tstamp_t)
		// Get-only variable
		ALSA_HPP_VARGET(boundary, snd_pcm_uframes_t)
#undef ALSA_HPP_TEMPLATE
#undef ALSA_HPP_CLASS
	};

#undef ALSA_HPP_FUNC
#undef ALSA_HPP_VAR
#undef ALSA_HPP_VARGET
#undef ALSA_HPP_ENUMVAR
#undef ALSA_HPP_ENUMVARMINIMAL
#undef ALSA_HPP_BOOLVAR
#undef ALSA_HPP_RANGEVAR
#undef ALSA_HPP_RANGEVARDIR

	/** @short A RAII wrapper for snd_pcm_mmap_begin/end block. **/
	class mmap { ALSA_HPP_NONCOPYABLE(mmap)
		snd_pcm_t* m_pcm;
		snd_pcm_channel_area_t const* m_areas;
		snd_pcm_uframes_t m_offset;
		snd_pcm_uframes_t m_frames;
		void test() const {
			if (!m_pcm) throw std::logic_error("alsa::mmap accessed after commit");
		}
	  public:
		/** Access a snd_pcm_channel_area_t. &area(0) to access the array. **/
		snd_pcm_channel_area_t const& area(std::size_t num) { test(); return m_areas[num]; }
		/** Return the offset where the data is within the areas **/
		snd_pcm_uframes_t offset() const { test(); return m_offset; }
		/** Return the maximum number of frames that may be used **/
		snd_pcm_uframes_t frames() const { test(); return m_frames; }
		/**
		* Initiate MMAP transfer.
		* snd_pcm_avail_update must be called directly before constructing the
		* alsa::mmap object, otherwise snd_pcm_mmap_begin may return a wrong
		* count of available frames.
		* @param pcm PCM handle
		* @param req number of frames to request (check .frames() for actual count)
		**/
		mmap(snd_pcm_t* pcm, snd_pcm_uframes_t req): m_pcm(pcm), m_areas(), m_offset(), m_frames(req) {
			ALSA_CHECKED(snd_pcm_mmap_begin, (m_pcm, &m_areas, &m_offset, &m_frames));
		}
		/** End mmap transfer; no data will be commited. **/
		~mmap() {
			if (m_pcm) snd_pcm_mmap_commit(m_pcm, m_offset, 0);
		}
		/** Commit all frames **/
		void commit() {
			commit(frames());
		}
		/** Commit n frames **/
		void commit(snd_pcm_uframes_t n) {
			if (n > frames()) throw std::out_of_range("alsa::mmap::commit(n) with n > frames()");
			ALSA_CHECKED(snd_pcm_mmap_commit, (m_pcm, offset(), n));
			m_pcm = NULL;
			m_areas = NULL;
		}
	};

#undef ALSA_HPP_NONCOPYABLE

}

#endif


[-- Attachment #3: Type: text/plain, Size: 160 bytes --]

_______________________________________________
Alsa-devel mailing list
Alsa-devel@alsa-project.org
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2008-03-01  1:01 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-02-25 18:54 ALSA C++ API updated for 1.0.16 Lasse Kärkkäinen
2008-02-26 10:01 ` Clemens Ladisch
2008-02-26 14:49   ` Michael Gerdau
2008-02-26 16:59   ` Lasse Kärkkäinen
2008-02-27 11:50     ` Clemens Ladisch
2008-02-29  5:57       ` Aldrin Martoq
2008-03-01  0:56         ` Lasse Kärkkäinen

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.