Audio Framework

Overview

The Audio Framework (AF) is a collection of tools and libraries that allow efficient development of audio applications on GAP9 platform. It consists in a collection of tools to create graphs from unitary blocks (components). A high-level input graph file can be designed by one of two front ends:

  • CMCore: a Python library that helps generate and manage component descriptions and graphs, as well as the backend used as an interface between the graph instantiation and the rest of the toolchain.

  • Graph Designer (GD): A GUI frontend on top of CMCore.

The rest of the Audio Framework consists of the following tools:

  • Audio Mapping Tool (AMT): Host tool that takes input graph in .graph (JSON) format and converts it to a fully resolved graph that is fed to Runtime Generator (RTG).

  • Runtime Generator (RTG): Host tool that takes fully resolved graph produced by AMT and produces set of source and configuration files that are to be build for GAP9 target.

  • Toolchain (TC): A short name for AMT followed by RTG.

  • Runtime Library (RNT): Target API that enables user applications to load and run graphs produced by the toolchain.

Graph Designer is the main tool used to design audio graphs, filters and other components using a GUI. To facilitate testing and automation, graphs can also be created written in Python or in C, through the API provided as component library.

The graph process flow of the toolchain is as follows:

  • Users can create and tune a graph using the Graph Designer (to use a GUI) or a Genepy script (through Python API)

  • The graph is then modified, its contents verified completed from component templates by the Mapping Tool

  • The Runtime Generator then compiles that graph into compilable C code, managed by the toolchain.

The audio libraries contain premade components that can be used as building blocks to speed design process of audio graphs, as well as a collection of tools to test and secure those blocks.

Development status

Tool

Status

CMCore

Usable, feature complete

Graph Designer

Usable, incomplete

Audio Mapping Tool

Usable, incomplete

Runtime Generator

Usable, incomplete

Runtime library

WIP

Contents

Examples

The examples folder include a number of examples showing how to use the framework. The file_in_out example (examples/applications/file_in_out/) is a basic hello world example for audio, showing how to load construct a simple graph and loading/saving audio from file.

Below is a walktrough of the basic workflow of the framework.

1. Generate a graph

Creating a graph is done by assembling components (a graph’s lowest level unit) into a larger audio topology. This can be done using a CMCore script or (in time) the Graph Designer. Here is an example of a script to create a graph using CMCore:

"""
This example describes how to create a CMCore graph and dump it as a ".graph"
file.

In this example, the following audio topology is created:

    file_source -> FFT -> IFFT -> file_sink
"""
import os
import pathlib
import cmcore

# This only makes sure the script is ran from the current directory
os.chdir(pathlib.Path(__file__).parent)

# First, lets initialize an empty graph:
graph = cmcore.Node("my_graph")

# Let's build a very simple graph: file_source -> FFT -> IFFT -> file_sink

# Let's the inputs to this graph
graph.add_node("file_src")
# This node is a file source, and a template can be associated to it.
# `graph.nodes` is a list. To easily find that node, we can use the method
# `get_node()` associated to the node name to get the Node object
# We can now associate a template to that node through the `set_template()`
# method:
# The `overload` argument fetches the content of the .comp and loads it into the node
graph.get_node("file_src").set_template("file_source/file_source.comp", overload=True)

# If required, `add_inputs()` and `add_outputs()` methods can be used to add
# multiple initialized inputs and outputs in batch to a node, like this:
# input_L = cmcore.Input("input_L")
# input_R = cmcore.Input("input_R")
# graph.get_node("mixer_0").add_inputs(input_L, input_R)
# graph.get_node("mixer_0").add_output("output_0")
# In this topology, nodes only have 1 input and 1 output.

# The `add_nodes()` method can be used to add nodes in batch. Doing as follows
# adds 2 empty subnodes to the graph, called "fft" and "ifft".
nodes = []
for node in ["fft", "ifft"]:
    nodes.append(cmcore.Node(node))
graph.add_nodes(*nodes)

# Setting the template to a node adds all the appropriate properties of the
# component to the node.
# fft
graph.get_node("fft").set_template("fft/fft.comp", overload=True)
# ifft is an FFT with a specifically set parameter
graph.get_node("ifft").set_template("fft/fft.comp", overload=True)
# Set FFT component parameter "inverse" value to 1, effectively making it an iFFT
graph.get_node("ifft").parameters["inverse"].set_value(1)

# It is also possible to initialize a node object and to then add it to the
# graph, as so:
sink = cmcore.Node("file_sink")
sink.set_template("file_sink/file_sink.comp", overload=True)
# The node called `sink` has been created and initialized through its template,
# we can add it to the graph:
graph.add_node(sink)

# All nodes have been created, we can bind them. In the `bind()` method:
# * the 1st argument is the name of the node, and is optional.
# * the 2nd is the node objet which we want to bind as preceding node
# * the 3rd is the name of the output of the node we want to bind to
# * the 4th is the node object which we want to bind as a following node
# * the 5th is the name of the input of the node we want to bind to
# If no name is given, the bindings will be named by order of creation
# file_src to fft
graph.bind("binding_file_src", "file_src", "push", "fft", "process")
# fft to ifft
graph.bind("binding_fft_ifft", "fft", "push", "ifft", "process")
# ifft to file_sink
graph.bind("binding_ifft_sink", "ifft", "push", "file_sink", "process")

# Finally, let's export the graph file. The argument is the output directory,
# the exported file will always be called `<graph_name>.graph`.
output_dir = "output"
graph.dump(output_dir)

Such a code will create a graph, recognized by its .graph extension. Each of its fundamental bricks is a component template, located in one of the component libraries. The templates in this library are provided in the GWT component library.

Other examples of CMCore usage can be found in cmcore/examples.

2. Pass the graph to the audio toolchain

gap-audio-toolchain <my_example_graph>.graph
Note

Currently, only the mapping tool will run to completion.

Glossary

GD

Graph Designer

AF

Audio Framework

AMT

Audio Mapping Tool

RTG

Runtime Generator

TC

Toolchain (AMT + RTG)

RNT

Runtime library

Audio library

  • framework The component-based framework, with components written for the host computer and the target,

  • common A collection of various tools:

    • graph parameter generators

    • graph content checkers

    • test helpers and managers

    • conversion utilities: fixed and floating point tools, decibel converters, etc.

  • components The collection of components made available in the GWT audio library.

  • internal A collection of internal scripts and utilities to manage this repository.