editor:-- mode: fundamental; tab-width: 4; indent-tabs-mode: nil --
Version: 20090419-9
syntax:loosely follows restructured text, http://docutils.sourceforge.net/rst.html
Author: Kai Vehmanen

Ecasound Programmer's Guide

Contents

Preface

This document describes how Ecasound and the related libraries work, how to use them, how to extend and add features and other similar issues. Before reading this document, you should first take a look at other available documentation (especially Ecasound Users's Guide).

If not otherwise specified, all documentation refers to the latest Ecasound version.

Document history

Hmm, why doesn't this work...?

| 19.04.2009 - Minor updates to NetECI and ECA_CONTROL.
| 21.08.2005 - Typos fixed, removed duplicated section on audio
|              routing. Minor updates to various sections.
| 25.04.2005 - Minor updates.
| 28.03.2005 - Added section about "Source code markup", update the
|              "Design principles" section.
| 13.03.2005 - Converted from LaTeX to ascii.
| 23.10.2004 - Added section "EIAM commands" that covers adding
|              new EIAM commands.
| 18.11.2003 - Typo fixes. Updated documentation to reflect the new
|              naming convention (ecasound refers to the binary,
|              Ecasound refers to the whole package).
| 07.11.2002 - Added documentation for NetECI.
| 25.10.2002 - Added "Checklist for Audio Object Implementations".
| 17.10.2002 - Added a warning against direct use of libecasound
|              and libkvutils. Using ECI is from now on the
|              preferred way of using ecasound as a development
|              platform. Rewrote the "Versioning" section.
| 02.10.2002 - Added the "Protocols and Interfaces" chapter.
| 29.04.2002 - Added chapter about "Unit Tests".
| 28.04.2002 - Revised namespace policy (see chapter
|              on namespaces), replaced references to
|              obsolete ECA_DEBUG with a description
|              of the new ECA_LOGGER subsystem.
| 27.02.2002 - Rewrote the "Control flows" chapter according
|              to the structural changes made in 2.1dev8. Added
|              a "References" section.
| 31.01.2002 - Reorganization of document structure. New chapter
|              about "Library organization".
| 19.12.2001 - Added chapter about include hierarchies.
| 28.10.2001 - Lots of changes to the "Object maps" chapter.
| 21.10.2001 - Added this history section.

General programming guidelines

Design and programming principles

The following sections describe some of the key design principles that have been guiding Ecasound development.

Open and generic design

Over the years Ecasound's core design has been revised many times. The aim has been to keep the core flexible enough, so it can be easily adapted to new use cases.

Object-orientation

Ecasound is written in C++ (as specified in 1997 ANSI/ISO C++ standard). Common object-oriented design practices should be utilized. At same time overuse of object-orientation should be avoided. Object-orientation is a very effective design method, but not the only one. Sometimes other approaches work better.

Data hiding

This design principle deserves to be mentioned separately. Whenever possible, the actual data representation and implementation details should always be hidden. This allows to make local implementation changes without affecting other parts of the code base. It cannot be emphasized enough how important this goal is for large software projects like Ecasound.

Design by contract

When writing a new routine, in addition to the actual code, also routine's behaviour should be described as accurately as possible using preconditions and postconditions to describe the external side-effects (how it changes the object state, what is the relation between arguments and return values).

The preconditions should specify all requirements and assumptions related to routine's inputs. If the caller violates this specification, routine is not responsible for the error.

The postconditions should specify what statements hold true when routine has been executed. This information helps the caller to better understand how the routine works and to identify implementation bugs.

Ideally, these conditions prove that the routine works correctly. Writing a complete description of a routine can be difficult, but the benefits of this approach should be clear. When you call a well-defined routine, a) you know what parameter values it accepts, b) you know what it does and c) if errors occur, it's easier to pinpoint the faulty routine.

In practice describing routines is done by combining verbose comments and defining pre/postconditions. As C++ doesn't directly support pre/postconditions, the DEFINITION_BY_CONTRACT and DBC tools provided by libkvutils are used.

Routine side effects

A clear distinction should be made between routines that have side-effects (=methods, processors, modifiers; routines that change object's state) and const routines (=functions, observers).

To make monitoring side effects easier, all Ecasound classes should be const-correct. A object is const-correct if a function taking only a single argument that is a const reference to that object is not able, without explicit casting, to obtain a non-const reference to that same object (or a portion thereof) from within the function body.

Sanity checks

Sanity checks are done only to prevent crashes. All effects and operators should accept also "insane" parameters. For example, the amplifier effect accepts -100.0% as the gain value. This of course results in inverted sample data, which is a useful outcome. As Ecasound is supposed to be a tool for creative work and experimenting, the decisions on which parameters are useful for audio processing should not be made in source code.

Error handling

Two specific things worth mentioning: First, the standard UNIX-style error handling, where functions performing actions return an integer value, is not used in Ecasound. As described in the above section Routine side effects, all routines are either modifiers or observers, not both. So when using Ecasound APIs, you first perform an action (modifying function), and then afterwards check what happened (using an observer function).

Exceptions

C++ exceptions are used in Ecasound. Exception based error handling has its problems, but in some cases it is clearly the best option. Exceptions are most useful in situations where controlled error recovery is very difficult, and in situations where errors occurs only very rarely. This allows callers to avoid constantly checking returns values for functions that in normal use never fail. Another special case is handling critical errors that occur in class contructors.

Using exceptions for anything other than pure error handling is to be avoided at all cost. And when exceptions are used, their use must be specified in function prototypes. This is important, as clients need to know what exceptions can be thrown. C++ unfortunately doesn't require strict exception prototypes, so this issue requires extra care.

A list of specific cases where exceptions are used follows:

AUDIO_IO - open()
This method is used for initializing external connections (opening files or devices, loading shared libraries, opening IPC connections). It's impossible to know in advance what might happen. In many cases it is also useful to get more verbose information about the problem that caused open() to fail. Throwing an exception is an excellent way to achieve this.
ECA_CHAINSETUP - enable()
TBD
ECA_CHAINSETUP - load_from_file, save() and save_to_file
TBD
ECA_SESSION - constructor
TBD

Coding style, naming conventions and source code markup

This section describes some of the conventions used in Ecasound development. As a general rule, one should adapt to whatever style and conventions used in already existing code.

Variable and type naming

Variable names are all lower case and words are separated with underscores (int very_long_variable_name_with_underscores). Class data members are marked with "_rep" postfix. Data members which are pointers are marked with "_repp". Index-style short variable names (n, m, etc.) are only used in local scopes. Enum types have capitalized names (Some_enum).

Use of macro processing should be avoided, but when necessary, macro names should be capitalized.

Package specific

libecasound, ecasound, ecatools, libkvutils
Class names are all in upper case and words separated with underscores (class ECA_CONTROL_BASE). This a standard style in Eiffel programming.
libqtecasound, qtecasound, ecawave
Qt-style is used when naming classes (class QELevelMeter), otherwise same as above.

Private classes

Some classes are divided into public and private parts. This is done to make it easier to maintain binary-level compatibility between library versions, and to get rid of header file dependencies.

Private classes have a "_impl" postfix in their name. They are usually stored into separate files which also use the "_impl" notation.

For instance the ECA_ENGINE class (eca-engine.h) has a private class ECA_ENGINE_impl (eca-engine_impl.h). Access to ECA_ENGINE_impl is only allowed to ECA_ENGINE member functions. In addition, the private header file (eca-engine_impl.h) is only included from the ECA_ENGINE implementation file (eca-engine.cpp). This allows us to add new data members to ECA_ENGINE_impl without breaking the binary interface.

Unit tests

Unit tests are used for verifying that modules work as intended. A test for component, with a public interface defined in "prefix-component.h", should located in "prefix-component_test.h". The test itself should implement the ECA_TEST_CASE interface. In addition, generic test cases should be added to ECA_TEST_REPOSITORY - see "libecasound/eca-test-repository.cpp".

Source code markup

In addition to the Javadoc-style source code documentation (see 'Documentation style' section), inline commentary markup is used to document important code segments. Use of common markup notation is preferred (for example it is nice to be able to grep for a list of open items in certain part of the codebase):

  • Known bugs, unhandled cases, and missing features should be marked with "FIXME: description" comments.
  • Explanatory notes that help to understand the code should be marked with "NOTE: description".

Style updates

The general rule is to use consistant style within one source file (i.e. compilation unit). Updates to style issues are also done with the same granularity. The following global updates have been made so far to the sources:

eca-style-version: 1
The opening braces of multi-line functions should be put on a separate line, at column 1, instead of being on the same line with function signature. This change only applies to functions, in other words the K&R style is followed.
eca-style-version: 2
Extra parenthesis around "return" values should be removed ("return" is a keyword, not a function).
eca-style-version: 3
The module name prefix, for example "(eca-session) ", should be removed from ECA_LOG_MSG() statements. The module prefix is added automatically to the debug messages when debug level "module_names" is activated.

If a file has been updated according to these guidelines, the appropriate style version should be mentioned at the start of the file ("Attributes: eca-style-version: XXX").

Physical level organization

Ecasound libraries and applications are divided into distribution packages, directories and file groups.

Include hierarchies

Include statements that are not stricly necessary should be dropped! Not only do they cause unwanted dependencies, they also create more work for the compiler (Ecasound already takes painfully long to compile). Some rules to follow:

  • In header files, no extra header files should be defined. For instance in many cases it's enough to state that object SOME_TYPE is a class without need for the full implementation; so instead of "#include "sometype.h", use "class SOME_TYPE;".
  • For modules with separate implementation and header files, dependencies to other modules need not be stated in both.
  • Direct dependencies to outside modules must always be mentioned directly. It's easy to unknowingly include a required header file via some other header file. This should be avoided as it hides real dependencies.
  • When including headers for more special services, it's good to add a comment why this header file is needed.

Distribution packages

As an example, Ecasound and Ecawave are distributed as separate packages. This decision has been made because a) they are clearly independent, b) they have different external dependencies, and c) they address different target uses.

Directories

It's convenient to organize larger sets of source code into separate directories. For instance libecasound and ecatools components of the Ecasound package are located in separate directories.

File groups

Although files are divided in directories and subdirectories, there's still a need to logically group a set of source files based on their use and role in the overall design. As the use of C++ namespaces is very limited in Ecasound (to avoid portability problems), filename prefixes are used for grouping files. Here's a short list of commonly used prefixes.

audioio*.{cpp,h}
Audio device and file input/output.
audiofx*.{cpp,h}
Audio effects and other DSP-related code.
audiogate*.{cpp,h}
Gate operators.
eca*.{cpp,h}
Core functionality.
midi*.{cpp,h}
MIDI input/output devices, handlers and controller code.
osc*.{cpp,h}
Oscillator and other controller sources.
qe*.{cpp,h}
Generic prefix for files utilizing both Qt and Ecasound libraries.
samplebuffer*.{cpp,h}
Routines and helper functions for processing audio data buffers.

You should note that these are just recommendations - there are no strict rules on how files should be named.

C++ std namespace

The preferred way to access C++ standard library functions is to use explicit namespace selectors ("std::string") in public headers files, and "using declarations" in the implementation parts ("using std::string"). It's also possible to import the whole std namespace ("using namespace std;") in the beginning of an implementation file (but never in headers!).

Documentation style

Javadoc-style class documentation is the preferred style. Class members can be documented either when they are declared (header files), or when they are defined. Especially when specifying complicated interfaces, it's better to put documentation in the definition files. This way the header files remain compact and serve better as a reference.

Here's a few general documentation guide lines:

Use of 3rd person
"Writes samples to memory." instead of "Write samples to memory."
Sentences start with a verb
"Writes samples to memory." instead of "Samples are written to memory."
Use "this" instead of "the"
"Get controllers connected to this effect." instead of "Get controllers connected to the effect.

Versioning

All Ecasound releases have a distinct version number. The version number syntax is x.y[.z][-extraT], where x and y are the major and minor numbers, and z is an optional revision number. To test major changes, separate -preX or -rcX versions can be distributed before the official release.

In addition, all Ecasound libraries have a separate interface version. The libtool-style version:revision:age versioning is used. See the libtool documentation for details.

One important thing to note is that the library interface version numbers are tied to the source-code level interfaces, not the binary interfaces. Because binary interfaces are not explicitly versioned, applications should always statically link against the Ecasound libraries. Also, any private source-code interfaces, ie. header files with a "_impl.h" postfix, are not part of the versioned public interface. Applications should not rely on these interfaces!

All changes in the public interfaces are documented in library specific ChangeLog files. These files are usually located in the top-level source directory of the versioned library.

One thing to note is that Ecasound's versioning practises have changed quite a few times during the project's history. The rules described above only apply to Ecasound 2.2.0 and newer releases.

How Ecasound works?

Example use cases

Here's a few common use cases how Ecasound can be used.

Simple non-interactive processing

One input is processed and then written to one output. This includes effect processing, normal sample playback, format conversions, etc.

Multitrack mixing

Multiple inputs are mixed into one output.

Real-Time effect processing

There's at least one real-time input and one real-time output. Signal is sampled from the real-time input, processed and written to the real-time output.

One-track recording

One real-time input is processed and written to one or more outputs.

Multitrack recording

The most common situation is that there are two separate chains. First one consists of real-time input routed to a non-real-time output. This is the recording chain. The other one is the monitor chain and it consists of one or more non-real-time inputs routed to a real-time output. You could also route your real-time input to the monitoring chain, but this is not recommended because of severe timing problems. To synchronize these two separate chains, Ecasound uses a special multitrack mode (which should be enabled automatically).

Recycling a signal through external devices

Just like multirack recording. The only difference is that real-time input and output are externally connected.

Audio signal routing

Basic audio flow inside an Ecasound chainsetup is as follows: Audio data is routed from input audio objects to a group of chains. In the chains audio data is processed using chain operators. After processing data is routed to output objects.

Using internal loop devices, it's also possible to route signals from one chain to another. Looping causes extra latency of one engine cycle.

Routing of signals is based on the ability to assign inputs and outputs to multiple chains. Assigning an input object to multiple chains divides the audio signal generating multiple copies of the original input data. Similarly with an output object, data from multiple chains is mixed together to one output object.

Control flow

Batch operation

When Ecasound is run in batch mode, the program flow is simple. To store the session data, a ECA_SESSION object is first created. The created object is then passed as an argument for ECA_CONTROL class constructor.

All required configuration of inputs, outputs and chain operators is done using the services provided by ECA_CONTROL. Once a valid chainsetup is ready for processing, batch operation is initiated by issuing ECA_CONTROL::run(). This function will block until processing is finished.

Interactive operation

Interactive operation is similar to batch operation. The important difference is that processing is started with ECA_CONTROL::start(). Unlike run(), start() does not block the calling thread. This makes it possible to continue using the ECA_CONTROL interface while engine is running in the background.

Two important concepts to understand when working with ECA_CONTROL are the selected and connected chainsetups. ECA_CONTROL allows working with multiple chainsetups, but only one of them can be edited at a time, and similarly only one at a time can be connected to the processing engine. For instance if you add a new input object with add_audio_input(), it is added to the selected chainsetup. Similarly when you issue start(), the connected chainsetup is started.

Library organization

The primary source for class documentation is header files. A browsable version of header documentation is at http://nosignal.fi/ecasound/Documentation Anyway, let's look at the some central classes.

Interfaces for external use

The following classes of libecasound are designed as primary interfaces for external use. The approach is based on the Facade (GoF185) design pattern. The primary goals are concentrating functionality, and maintaining a higher level of interface stability.

ECA_CONTROL - eca-control.h

ECA_CONTROL represents the whole public interface offered by libecasound. The primary purpose of ECA_CONTROL is to offer a consistent, straightforward interface for controlling Ecasound. The interface is also designed to be more stable than other parts of the library.

On important part of ECA_CONTROL is the functionality for interpreting EOS (Ecasound Option Syntax) and EAIM (Ecasound Interactive Mode) commands.

ECA_CONTROL_MAIN (eca-control-main.h) is an abstract base class that defines a subset of core ECA_CONTROL functionality. This interface is implemented by e.g. ECA_CONTROL (the default implementation) and ECA_CONTROL_MT (a thread-safe variant that allows simultaneous use from multiple threads).

It is recommended that applications use the subset defined by ECA_CONTROL_MAIN.

ECA_CONTROL_INTERFACE - eca-control-interface.h

C++ implementation of the Ecasound Control Interface (ECI) API. See section "Ecasound Control Interface" for more information.

Core classes

This section introduces the core classes, which define the central data types and are responsible for the main program logic.

AUDIO_IO_PROXY_SERVER - audioio-proxy-server.h

Implements a audio input/output subsystem that adds a second layer of buffering between the main processing engine and non-real-time audio input and output objects.

Double buffering is needed to guarantee a real-time constrained data stream even when dealing with non-real-time objects like disk files.

CHAIN - eca-chain.h

Class representing one abstract audio signal chain. CHAIN objects consist of chain operators, controllers and their state information.

ECA_CHAINSETUP - eca-chainsetup.h

ECA_CHAINSETUP is the central class for storing user-visible objects. All inputs, output, chain operator and controller objects are attached to some ECA_CHAINSETUP object.

ECA_ENGINE - eca-engine.h

ECA_ENGINE is the actual processing engine. It is initialized with a pointer to a ECA_CHAINSETUP object, which has all information needed at runtime. In other words ECA_ENGINE is used to execute the chainsetup. You could say ECA_ENGINE renders the final product according to instruction given in ECA_CHAINSETUP.

Processing is started with the exec() member function and after that, ECA_ENGINE runs on its own. If 'batch_mode' is selected (parameter to exec()), one started ECA_ENGINE will run until a 'finished' condition is met and then exit automatically. Finished means that we have read all available data from input sources. Of course if some input has infinite length (soundcards for example), processing will never finish. To get around this limitation, it's possible to set the processing length (see ECA_CONTROL_OBJECTS::set_chainsetup_processing_length_in_seconds()).

If batch mode is not enabled, engine will just perform the init phase and starts waiting for further instructions. These instructions can be send to the engine using the ECA_ENGINE::command() member function.

ECA_ENGINE has the following states:

not_ready
ECA_SESSION object is not ready for processing or ECA_ENGINE hasn't been created
running
processing
stopped
processing hasn't been started or it has been stopped before completion
finished
processing has been completed
error
an error has occured during prosessing

ECA_SESSION - eca-session.h

ECA_SESSION is an abtraction used to represents a group of chainsetups. At any time, only one chainsetup object at a time can be active (connected). For modification, one chainsetup can be set as 'selected'. This means that all configuration operations are targeted to the selected chainsetup.

The only public access to ECA_SESSION objects is through ECA_CONTROL objects.

MIDI_SERVER - midi-server.h

Engine that handles all MIDI input and output.

SAMPLEBUFFER - samplebuffer.h

Basic unit for representing blocks of sample data. The data type used to represent single samples, valid value ranges, channel count and system endianess are all specified in "samplebuffer.h" and "sample_specs.h".

Feature and capability interface classes

Many libecasound classes have similar attribute sets and capabilities. To make use of these shared features, most common features have their own virtual base classes. All objects that have a particular feature, inherit the same virtual base class. This makes object grouping and management easier and less error prone.

DYNAMIC_PARAMETERS<T> - dynamic-parameters.h

Implemented by all classes that provide a set of generic parameters of type T. Parameter can be observed and modified, and they usually are identified by a unique name and a more verbose description. Number of parameters can vary dynamically. Other objects can access these parameters without detailed knowledge of the object itself.

ECA_AUDIO_FORMAT - eca-audio-format.h

Implemented by all classes that an audio format attribute set that can be observed and modified.

ECA_AUDIO_POSITION - eca-audio-position.h

Implemented by all classes that need to maintain current audio position and length.

ECA_SAMPLERATE_AWARE - eca-samplerate-aware.h

Implemented by all classes that need knowledge of current sampling rate.

MIDI_CLIENT - midi-client.h

Implemented b all classes that require a connection to an instance of MIDI_SERVER.

Object interfaces

Object interfaces define the behaviour for common objects used by libecasound. The core classes rarely operate on specific object types, but instead use object interfaces (abstract interfaces). Object interfaces are usually abstract C++ classes (instances of these classes cannot be created as some of functions don't yet have a concreate implementation, ie. they pure virtual functions).

AUDIO_IO - audioio.h

Virtual base class for all audio I/O objects. Different types of audio objects include files, audio devices, sound producing program modules, audio server clients, and so on.

More specialized interface classesa are AUDIO_IO_DEVICE (for real-time audio objects) and AUDIO_IO_BUFFERED (for POSIX-style buffered i/o). There's also a special AUDIO_IO_MANAGER interface for managing multiple audio objects of same type inside one chainsetup.

CHAIN_OPERATOR - eca-chainop.h

Virtual base class for chain operators.

CONTROLLER_SOURCE - ctrl-source.h

Virtual base class for all controller sources.

MIDI_IO - midiio.h

Virtual base for objects capable of reading and writing raw MIDI data.

MIDI_HANDLER - midi-server.h

Virtual base class for objects capable of receiving and processing MIDI data.

Object interface implementations - plugins

Majority of the classes in libecasound fall to this category. They implement the behaviour of some object interface type. As other parts of the library only use the object interfaces, these implementation classes are fairly isolated. Changes made inside object implementation have no effect to other parts of the library. Similarly new object interface implementations can be added without modifying the core classes.

Utility classes

TBD

eca-logger.h - ECA_LOGGER

Singleton class that provides an interface to Ecasound's logging subsystem. Libecasound sends all log messages to this interface. The actual logger implementation can be done in many ways. For example in the console mode user-interface of Ecasound, TEXTDEBUG class implements the ECA_LOGGER_INTERFACE class interface. It sends all messages that have a suitable debug level to the console's standard output. On the other hand, in qtecasound, ECA_LOGGER_INTERFACE is implemented using a Qt widget.

New ECA_LOGGER_INTERFACE implementations can be registered at runtime with the ECA_LOGGER::attach_logger() member function (declared in eca-logger.h).

Object maps

Object maps are central repositories for commonly used objects. Their main purpose is to add flexibility to handling different object types - especially to handling dynamic addition and removal of whole object types. They provide the following services:

  • listing all object types in any of the available categories (for instance, list all effect types)
  • creating new object instances based on keyword strings (for instance, returns an mp3 object if "foo.mp3" is given as keyword)
  • adding new object types (object map item is identified by tuple of "keyword, regex expression, object type")
  • removing object types
  • reverse mapping objects back to keyword strings

In Ecasound, all access to object maps goes throuh the library-wide ECA_OBJECT_FACTORY class, which provides a set of static functions to access the object maps.

This system may sound a bit complex, but in practise it is quite simple and makes a lot of things more easier. For instance, when adding new object types to the library, you only have to add one function call which registers the new object; no need to modify any other part of the library. It also makes it possible to add new types at runtime, including dynamically loaded plugins.

One special use-case is where an application linked against libecasound adds its own custom object types on startup. All parts of libecasound can use the custom objects, although they are not part of library itself.

All objects defined in libecasound are registered in the file eca-static-object-maps.cpp.

eca-object.h - ECA_OBJECT

A virtual base class that represents an Ecasound object. All objects handled by the object factory must inherit this class.

eca-object-factory.h - ECA_OBJECT_FACTORY

The public interface to Ecasound's object maps. All its functions are static.

Adding new features and components to Ecasound?

Things to remember when writing new C++ classes

TBD

Copy constructor and assignment operator

Always take a moment to check your copy constructor and the assign operation (=operation()). Basicly you have three alternatives:

  • Trust the automatically created default definitons. If you don't have any pointers as data members, this isn't necessarily a bad choice at all. At least the compiler remembers to copy all members!
  • If you have pointers to objects as class data members, you should write definitions for both the copy-constructor and the assign operation.
  • If you are lazy, just declare the two functions as null functions, and put them in "private:"_ access scope. At least this way nobody will use the functions by accident!

Audio objects

To implement a new audio object type, you must first select which top-level class to derive from. Usually this is either AUDIO_IO (the top-level class), AUDIO_IO_BUFFERED (a more low level interface) or AUDIO_IO_DEVICE (real-time devices).

The second step is to implement the various virtual functions declared in the parent classes. These functions can be divided into four categories: 1) attributes (describes the object and its capabilities), 2) configuration (routines used for setting up the object), 3) main functionality (open, close, input, output, etc) and 4) runtime information (status info).

Adding the new object to Ecasound is much like adding a new effect (see the next section). Basicly you just add it to the makefiles and then register it to the appropriate object map (see below).

Checklist for Audio Object Implementations

  1. Check that read_buffer() and write_buffer() change the internal position with either set_position_in_samples() or change_position_in_samples() functions of ECA_AUDIO_POSITION. Also, when writing a new file, extend_position() should also be called. All this is done automatically if using read_samples() and write_samples() from AUDIO_IO_BUFFERED.
  2. If implementing a proxy object, separately consider all public functions of audioio-proxy.h (whether to reimplement or use as they are).
  3. Check that open() and close() call AUDIO_IO::open() and AUDIO_IO::close(), and in the right order.
  4. If the object supports seeking, seek_position() must be implemented.
  5. Check that the set_parameter() and get_parameter() methods work correctly when they are used for saving and restoring audio object state (for example cs-edit EIAM command).

Effects and other chain operators

Write a new class that inherits from CHAIN_OPERATOR or any of its successors. Implement the necessary routines (init, set/get_parameter, process and a default constructor) and add your source files to libecasound's makefiles. Then all that's left to do is to add your effect to libecasound/eca-static-object-maps.cpp, register_default_objects(). Now the new effect can be used just like any other Ecasound effect (parameters control, effect presets, etc).

Another way to add effects to Ecasound is to write them as LADSPA plugins. The API is well documented and there's plenty of example code available. See http://www.ladspa.org for more information.

Differences between audio objects and chain operators

Design-wise, audio objects and effects (chain operators) aren't that far away from each other. Many audio apps don't separate these concepts at all (for instance most UG based synthesizers). In Ecasound though, there are some differences:

Input/output:

  • audio objects can be opened for reading writing or read&write
  • ... effects are not modal (i.e. modeless)
  • audio objects read from, or write to, a buffer
  • ... effects get a buffer which they operate on (in-place processing)

Audio format:

  • audio objects have a distinct audio format (sample rate, bits, channels)
  • .... effects should be capable of accepting audio data in any format (this is usually easy as Ecasound converts all input data to its internal format)

Control:

  • audio objects can be opened, closed, prepared, started and stopped
  • ... effects have a much simpler running state: either uninitialized or ready-for-processing

Position:

  • audio objects have length and position attributes
  • ... effects just process buffers and don't need to care about position information

A good example of the similarity between the two types are LADSPA oscillator plugins. Although they are effects, you can easily use them as audio sources (inputs) by specifying:

"ecasound -i null -o /dev/dsp -el:sine_fcac,440,1"

LADSPA plugins

Ecasound supports LADSPA-effect plugins (Linux Audio Developer's Simple Plugin API). See LAD mailing list web site for more info about LADSPA. Other useful sites are LADSPA home page and LADSPA documentation.

EIAM commands

Adding a new interactive mode commands requires changes to the following files:

  • libecasound/eca-iamode-parser_impl.h: Unique id for the new command has to be added to enum Commands.
  • libecasound/eca-iamode-parser.cpp: The new command must be added to the appropriate register_commands_*() function.
  • libecasound/eca-iamode-parser.cpp: The new command must be added to the appropriate action_requires_*() sets.
  • libecasound/eca-control.cpp: The actual implementation of the new command.
  • Documentation/ecasound-iam_manpage.yo: Documentation must be added.

Application development using the Ecasound framework

Console mode ecasound - [all languages]

This is the easiest way to take advantage of Ecasound features in your own programs. You can fork ecasound, pipe commands to ecasound's interactive mode or you can create chainsetup (.ecs) files and load them to ecasound. You'll be able to do practically anything. The only real problem is getting information from ecasound. You'll have to parse ecasound's ascii output if you want to do this. To make this a bit easier, Ecasound offers the wellformed output mode and dump-* commands. These can be used to easily parse configuration and status information.

Ecasound Control Interface - [C++, C, Python, Perl, ...]

Idea behind Ecasound Control Interface (ECI) is to take a subset of functionality provided by libecasound, write a simple API for it, and port it to various languages. At the moment, at least C++, C and Python implementations of the ECI API are available and part of the main Ecasound distribution. ECI is heavily based on the Ecasound Interactive Mode (EIAM), and the services it provides.

Specific tasks ECI is aimed at:

    1. automating (scripting in its traditional sense)
    1. frontends (generic / specialized)
    1. sound services to other apps

NetECI - [various]

NetECI is a network version of the ECI API. When Ecasound is started in server mode (see ecasound(1) for the related options), it listens for incoming NetECI commands on a TCP socket. Client applications can connect to this socket and use the connection to control and observe the active session. Multiple clients can connect to the same session.

The protocol is identical to one used in ECI (see section "Ecasound Interactive Mode - Well-Formed Output Mode" below). Clients write EIAM commands to the socket, followed by a CRLF pair. The server will reply using the well-formed output mode syntax.

See implementation of ecamonitor (part of ecatools), for a working example.

Libecasound's ECA_CONTROL class - [C++]

Note! Direct use of libecasound and libkvutils is not recommended anymore! Please use the Ecasound Control Interface (ECI) instead.

By linking your program to libecasound, you can use the ECA_CONTROL class for controlling Ecasound. This is a large interface class that offers routines for controlling all Ecasound features. It's easy to use while still powerful. Best examples are the utils in ecatools directory (most of them are just a couple screenfuls of code). Also, qtecasound and ecawave heavily use ECA_CONTROL. Here's a few lines of example code:

| --cut--
| ECA_SESSION esession;
| ECA_CONTROL ctrl (&esession);
| ctrl.new_chainsetup("default");
| [... other setup routines ]
| ctrl.start(); // starts processing in another thread (doesn't block)
| --cut--

If you don't want to use threads, you can run the setup in batch mode:

| --cut--
| ECA_SESSION esession;
| ECA_CONTROL ctrl (&esession);
| ctrl.add_chainsetup("default");
| [... other setup routines ]
| ctrl.run(); // blocks until processing is finished
| --cut--

Ecasound classes as building blocks - [C++]

Note! Direct use of libecasound and libkvutils is not recommended anymore! Please use the Ecasound Control Interface (ECI) instead.

You can also use individual Ecasound classes directly. This means more control, but it also means more work. Here's another short sample:

--cut--
- create a SAMPLE_BUFFER object for storing the samples
- read samples with an audio I/O object - for example WAVEFILE
- process sample data with some effect class - for example EFFECT_LOWPASS
- maybe change the filter frequency with EFFECT_LOWPASS::set_parameter(1, new_value)
- write samples with an audio I/O object - OSSDEVICE, WAVEFILE, etc.
--cut--

Protocols and Interfaces

Ecasound Interactive Mode - Well-Formed Output Mode

By issuing the EIAM command "int-output-mode-wellformed", Ecasound will start printing all messages using the following format:

| <message> = <loglevel><sp><msgsize>(<genmsg> | <returnmsg>)
|
| <loglevel> = <integer>      ; loglevel number
| <msgsize = <integer>        ; size of content in octets
| <genmsg> = <contentblock>   ; generic log message
| <returnmsg> = <sp><returntype><contentblock>
|                             ; EIAM return value message
| <contentblock> = <crlf><content><crlf><crlf>
|                             ; actual content of the message
| <returntype> = "i" | "li" | "f" | "s" | "S" | "e"
|                             ; type of the return value (see ECI/EIAM docs)
| <content> = *<octet>        ; zero or more octets of message content
|
| <sp> = 0x20                 ; space
| <octet> = 0x00-0xff         ; 8bits of data
| <crlf> = <cr><lf>           ; new line
| <cr> = 0x0d                 ; carriage return
| <lf> = 0x0a                 ; line feed
| <integer> = +<digit>        ; one or more digits
| <digit> = 0x30-0x39         ; digits 0-9