[Index] [Examples] [FAQ] [Man pages] [User's guide] [Programmer's guide] [Source tree] [ecasound home page]

ecasound documentation - programmer's guide


Kai Vehmanen

10012000

Table of Contents

1: Preface

2: Guide-lines for design and programming

2.1: Open and generic design

2.2: Object-orientation

2.3: Data hiding

2.4: Design by contract

2.5: Routine side effects

2.6: Sanity checks

3: How ecasound works?

3.1: Common use-cases

3.1.1: Simple non-interactive processing
3.1.2: Multitrack mixing
3.1.3: Realtime effect processing
3.1.4: One-track recording
3.1.5: Multitrack recording
3.1.6: Recycling a signal through external devices

3.2: Signal flow

3.3: Control flow

3.3.1: Passive operation
3.3.2: Interactive operation

3.4: Class descriptions

3.4.1: Core
3.4.2: Data objects

4: Using ecasound from other programs

4.1: Console mode ecasound

4.2: Controller class interface

4.3: Ecasound classes as building blocks

5: Adding new features and components?

5.1: Effects and other chain operators



1: Preface

This documents describes how ecasound library works, how to use it, how to extend and add features to it and so on. Before reading this documentent, you should first look at other available documentation (especially ecasound users's guide.

Unlike most web pages, this document really is under construction.

2: Guide-lines for design and programming

2.1: Open and generic design

Over the years ecasound's core design has been revised many times. After rewriting some code sections hundreds of times, you start to appreciate genericity. :) Although specific use-cases are used for testing new ideas, they are just design aids.

2.2: Object-orientation

Ecasound is written in standard C++ (as specified in 1997 ANSI C++ standard). Because C++ language itself doesn't force you to follow OO-principles, I often use Eiffel language as a reference when designing classes and routines.

2.3: Data hiding

This OO-feature deserves to be mentioned separately. Whenever possible, I always try to hide the actual data representation. This helps to make local implementation changes. One thing I've especially tried to avoid is excessive use of pointer magic.

2.4: Design by contract

Design by contract means that when you write a new routine, in addition to the actual code, you also describe routine's behaviour as accurately as possible.

Routine must specify all requirements and assumptions. If the caller violates this specification, routine is not responsible for the error. This means that routine mustn't check argument validity. This must be done by the caller.

Routine should also specify, what conditions are true when returning to the caller. By doing this, routine ensures that it works correctly and calling routine knows what is happening.

Ideally, these conditions prove that the routine works correctly. 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 to them and c) if errors occur, it's easier to pinpoint the faulty routine. In practice this is done by using comments and pre/postconditions. As C++ doesn't directly support pre/postconditions, I've simulated them using the class DEFINITION_BY_CONTRACT from kvutils package and with normal assert() calls.

2.5: Routine side effects

I try to make a clear distinction between routines that have side-effects (=methods, processors, modifiers; routines that change object's state) and const routines (=functions, observers).

2.6: Sanity checks

Sanity checks are done only to prevent crashes. All effects and operators happily accept "insane" parameters. For instance you can give -100.0% to the amplifier effect. This of course results in inverted sample data. I think this a reasonable approach. After all, ecasound is supposed to be a tool for creative work and experimenting. It's not meant for e-commerce. ;)

3: How ecasound works?

3.1: Common use-cases

Here's some common cases how ecasound can be used.

3.1.1: Simple non-interactive processing

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

3.1.2: Multitrack mixing

Multiple inputs are mixed into one output.

3.1.3: Realtime effect processing

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

3.1.4: One-track recording

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

3.1.5: Multitrack recording

The most common situation is that there are two separate chains. First one consists of realtime input routed to a non-realtime output. This is the recording chain. The other one is the monitor chain and it consists of one or more non-realtime inputs routed to a realtime output. You could also route your realtime 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).

3.1.6: Recycling a signal through external devices

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

3.2: Signal flow

This is simple. A group of inputs is routed to a group of chains. Audio data is processed in the chains and afterwards routed to a group of outputs. Currently signals can't be redirected from one chain to another, but you can assing inputs and outputs to multiple chains.

3.3: Control flow

3.3.1: Passive operation

When ecasound is run in passive mode, the program flow is simple. A ECA_SESSION object is created with suitable parameters, it is passed to a ECA_PROCESSOR object and that is all. Once engine is started, it does the processing and exits.

Another way to do passive processing is to create a ECA_CONTROLLER object and use it to to access and modify the ECA_SESSION object before passing it to ECA_PROCESSOR.

3.3.2: Interactive operation

In interactive mode, everything is done using the interface provided by ECA_CONTROLLER. This is when things get complex:

ECA_SESSION object can contain many ECA_CHAINSETUP objects, but only one of them can be active. On the other hand it is possible that there are no chainsetups. If this is the case, about the only thing you can do is to add a new chainsetup.

When some chainsetup is activated, it can be edited using the interface provided by ECA_CONTROLLER. Before actual processing can start, the chainsetup must first be connected. Only valid chainsetups (at least one input-output pair connected to the same chain) can be connected.

ECA_CHAINSETUP can be...

not selected
- can't be accessed from ECA_PROCESSOR

selected, invalid
- can be edited (files and devices are not opened)

selected, valid
- can be connected (files and devices are not opened)

connected
- ready for processing (files and devices are opened before connecting)

ECA_PROCESSOR status is one of...

not_ready
- ECA_SESSION object is not ready for processing or ECA_PROCESSOR hasn't been created

running
- processing

stopped
- processing hasn't been started or it has been stopped before completion

finished
- processing has been completed

3.4: Class descriptions

The primary source for class documentation is header files. A browsable version of header documentation is at www.wakkanet.fi/~kaiv/ecasound/Documentation/kdoc_pages.html. Anyway, let's look at the some central classes.

3.4.1: Core

ECA_PROCESSOR

ECA_PROCESSOR is the actual processing engine. It is initialized with a pointer to a ECA_SESSION object, which has all information needed at runtime. Processing is started with the exec() member function and after that, ECA_PROCESSOR runs on its own. If the interactive mode is enabled in ECA_SESSION, ECA_PROCESSOR can be controlled using the ECA_CONTROLLER class. It offers a safe way to control ecasound. Another way to communicate with ECA_PROCESSOR is to access the ECA_SESSION object directly.

ECA_SESSION

ECA_SESSION represents the data used by ecasound. A session contains all ECA_CHAINSETUP objects and general runtime settings (iactive-mode, debug-level, etc). Only one ECA_CHAINSETUP can be active at a time. To make it easier to control how threads access ECA_SESSION, only ECA_PROCESSOR and ECA_CONTROLLER classes have direct access to ECA_SESSION data and functions. Other classes can only use const members of ECA_SESSION.

ECA_CONTROLLER

ECA_CONTROLLER represents the whole public interface offered by ecasound library. It also has a simple command interpreter (interactive-mode) that can used for controlling ecasound.

3.4.2: Data objects

SAMPLEBUFFER

Basic unit for representing sample data. The data type used to represent a single sample, valid value range, channel count, global sampling rate and system endianess are all specified in "samplebuffer.h".

DEBUG

Virtual interface class for the debugging subsystem. Ecasound engine sends all debug messages to this class. The actual implementation of this class can be done in many ways. For example in the console mode version of ecasound, TEXTDEBUG class is used to implement the DEBUG interface. It sends all messages that have a suitable debug level to the standard output stream. On the other hand, in qtecasound DEBUG is implemented using a Qt widget.

4: Using ecasound from other programs

4.1: Console mode ecasound

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. And in some situations, performance might be an issue.

4.2: Controller class interface

By linking your program to libecasound, you can use the ECA_CONTROLLER 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. All ecasound interface programs use this class. Here's a few lines of code:


--cut--
ECA_SESSION esession;
ECA_CONTROLLER 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 do as above, but use ECA_PROCESSOR directly to do the actual processing. This way the processing is done without additional threads. Here's a short sample:


--cut--
ECA_SESSION esession;
ECA_CONTROLLER ctrl (&esession);
ctrl.new_chainsetup("default");
[... other setup routines ]
ECA_PROCESSOR p (&esession);
p.exec(); // blocks until processing is finished
--cut--

4.3: Ecasound classes as building blocks

And of course, you can also use individual ecasound classes directly. This means more control, but it also means more works. 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--

5: Adding new features and components?

5.1: Effects and other chain operators

Write a new class that inherits from CHAIN_OPERATOR or any of its successors. Implement all 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/src/eca-chainop-map.cpp, register_default_objects(). Now the new effect can be used just like any other ecasound effect (parameters control, effect presets, etc).