Audacity - From Vision to Architecture

In this post, we further investigate the set of fundamental concepts and properties of Audacity, notably the container, component, connector, development and run-time views. What is most interesting to note, however, is that despite the age of the Audacity application, very little refactoring was finalized1.

The main architectural style

The software was first developed by Dominic Mazzoni, whose main intention was to create a platform where developers can build and debug audio processing algorithms2. However, given the popularity Audacity has gained over the years, the software has evolved into the powerful yet simple audio editor we know today.

As the documentation of Audacity is considerably cluttered, the main goal that the team strives to accomplish is for the software to be simple and lightweight3. This is achieved by using the so called discoverable interface principle, where a user can uncover all the functionalities by just using the software4. Given the numerous people contributing to this project, such a unifying design choice has been helping Audacity maintain its consistency over the years5.

To keep the style clean, simple and effective, the team built Audacity following a layered architecture6. The project is split into 3 core layers, each layer having a variable number of modules (bottom to top order):

  1. Platform-specific Implementation Layer: Contains sublayers which deal with cross-platform functionality.

  2. Low-level abstraction layer: This layer is made out of two modules: PortAudio7 and wxWidget8. Layers below this one are there only to provide different implementations of these two models for different devices.

  3. Higher-level abstraction layer: In this layer, higher-level abstraction libraries are created using the classes provided by the previous layer.

Figure: Division of the architecture into layers.

In practice, such an architectural design is not that easy to accomplish for a software like Audacity. The constant changes in design given by the open-source nature, makes it more prone to bad design decisions1. One example, which is mentioned by the developers working on it, is the tight coupling between some of their components which makes the integration of new features a difficult task.

With two refactoring attempts failing, Audacity remains a balanced mix between a well-structured and less-well structured code, with the developer’s goal to incrementally clean the code over time with the help of the community1.

Containers view

As per definition:

A container is an architectural abstraction which that has the perk of being deployed and evolved in parallel/separately. In other words, containers can be seen as different execution environments in which the system can run.1

Until 2008, on the Windows platform, Audacity was compiled as a single monolithic executable, with both the Audacity and wxWidget source codes being deployed using the same environment. This made the application function as a single container. Since 2008 however, the development team and community decided to make wxWidget into a separate Dynamic Linked Library (DLL). In essence, wxWidget is now loaded separately at run-time and can be dynamically used during execution1.

Components view

Audacity consists of several components which are not all developed in-house1. The wxWidgets and PortAudio are two third party libraries that represent an OS abstraction layer to allow Audacity code to be run on Windows, macOS and Linux operating systems. The other components presented below are built in-house.

Figure: Components diagram of Audacity

wxWidgets GUI: A C++ library that provides the cross-platform behavior of Audacity’s GUI. It allows the developers to contribute to the user interface of the application in a standardized manner8.

PortAudio: An audio library that allows Audactiy to play and record audio over several OS. It is the direct link between the AudioIO component and the audio card on the device7.

AudioIO: A built-in class that handles the connection between the audio processing computed on Audacity and the PortAudio library.

Blockfiles: Allows Audacity to divide audio files into editable chunks to allow for faster addition and deletion into audio recordings. It allows developers to write directly into a file on disk without the need to load audio files into the RAM, resulting in the creation of the .aup file extension.

ShuttleGui: A built-in layer that has been recently added between the wxWidgets and the application to transfer information between the two layers. It was added to Audacity to reduce duplicated code.

Built-in effects: A set of processing actions that can be performed on audio files.

Scripting plugin: Provides a scripting interface over a named pipe which allows the users to run commands using other scripting languages (such as Python).

Command module: Responsible for transferring commands between the GUI, the scripting plugin and the built-in effects through simple calls.

Supporting libraries: Audacity includes a wide collection of libraries such as the VAMP API. Most of these libraries rely on dynamically loaded modules and have no knowledge of the rest of the components. They make calls directly to the commands module.

All components, except the Command module, have knowledge only about one or two other components. The Command module is the main component in Audacity. It serves as the central connector between most components (AudioIO, ShuttleGUI, BlockFiles, Built-in effects). As shown in the diagram below, the ShuttleGUI depends on wxWidgets to provide GUI elements to the Audacity application. Similarly, AudioIO gets access to the audio card via the PortAudio. Another important relation between the modules is the one between the external scripting languages and the Scripting plugin. The latter provides a pipe linking the Command module to the external scripting language without peeking into the information transferred within itself.

Connectors view

The components described in the previous section work in unison to provide a fluid User Experience (UX). However, the architecture of Audacity was not designed up front 1, resulting in the nature of connections between the components being non-uniform.

The main connection for audio processing is done by function calls using the command module, that allows the built-in effects to read/write data using the blockfiles component and to playback or record audio using the PortAudio. Moreover, the Audacity application connects to the third party libraries, namely: PortAudio and wxWidgets using API calls. This connection allows the user to specify the actions that need to be taken through the GUI and allows Audacity to use the audio card on the device.

Development view

Audacity’s source code consists of several principle components that all start from the AudacityApp.cpp file which is the main class for Audacity9. This class initializes all visual/GUI components through the use of wxWidgets. These GUI components are what the users interact with.

All the wxWidgets GUI elements are encompassed in the ShuttleGUI.cpp class. This class is also responsible for transferring (shuttling) data to and from GUI elements (buttons, sliders, etc).

In general, the development view can be summarized into different layers.:

  • GUI Layer: This is where the user interacts with the application.
  • Data processing layer: Wherever required, Audacity defines an intermediate layer between user input (data) and how that data affects the state of the program (changing parameters in libraries for example). An example would be the ShuttleGUI or the AudioIO classes.
  • Program state layer: This is the layer where the state of the program is actually adjusted. Values denoting the state of the application are set here, and calls to library functionality tends to reside here.

The upcoming section will talk about some of Audacity’s major features and how they are connected in the source code.

Major features of development view

Audio recording

From the user’s perspective, the capturing of audio is performed through the usage of a simple GUI element. However, beneath the GUI layer, the complexity and interconnectedness of Audacity becomes more apparent. Audio-related functionality is directly managed via the PortAudio library. PortAudio is employed through the use of an API call, a responsive function which copies data to and from the sound buffers. Audacity directly handles the data copying by defining an AudioIO class that serves as the interface between the application and the PortAudio library. The AudioIO class additionally defines how the audio data is handled when playing back, and how audio is recorded6.

Figure: Development view diagram of its major components

Storing audio

An interesting challenge all audio processing softwares face is the handling of insertions/deletions within a track. To handle this problem, Audacity divides an audio file into many BlockFiles. Rather than storing the audio clip in the Random Access Memory (RAM), Audacity directly fetches blocks from storage whenever required1. Any insertion or deletion requests are hence handled using operations on these blocks.

Scripting

Audacity offers a scripting feature which enables the user to write scripts in Python to interact with Audacity in a programmatic way10. Through the scripting feature, the user can invoke commands that corresponds to a user clicking GUI elements and setting values in Audacity. The scripting feature simply consists of a pipe that allows the user to write commands to execute Audacity functionality in a textual format. The user defined scripts are then read and any Audacity-specific commands are simply executed. Scripting and other modules/plugins have a dedicated Manager that handles such interactions.

Run time view

The application starts in the AudacityApp.cpp file, which contains the main application. In this file, the initialization steps are taken with two functions, namely OnInit() and InitPart2()9.

  • OnInit(): Initializes settings (preferences) languages and other preferences. Any ‘pre-initialization’ steps are handled here11.
  • InitPart2(): Location of code where the functional components are initialized. This would include the GUI components and the other core components mentioned in the Component View section. Initializing GUI elements implies displaying the visual component properly and also adding event handlers for handling user input12.

Moreover, audio processing functionalities are handled using dedicated threads. During the initialization process, a thread is created for the ‘AudioIO’ component. The job of this thread is to receive data from the lower level PortAudio library and perform operations on them such as storing and displaying. The PortAudio also starts a thread which is responsible for direct interaction with the audio device.

Finally, user interaction is handled using EventHandlers. Whenever an user interacts with a GUI component, the correct Handler() function is called to achieve the desired result. For simple interactions, the handling is often done directly in the Handler() function. More involved interactions, such as audio recording have dedicated classes (components) to them which may run on their own thread.

Key Quality Attributes

The following section will look at and discuss how the architects of Audacity realized the key quality attributes. The main key quality attribute which Audacity focuses on is a lightweight experience. In order to fulfill this requirement, Audacity makes use of the discoverable user interface principle5.

To allow for greater portability, Audacity ensures that the software is accessible to artists on multiple platforms. Audacity is written in a mixture of C and C++ and built using CMake, an open-source system available on common operating systems. Additionally, Audacity makes use of wxWidgets and PortAudio, both open-source provide cross-platform behavior which allows the software to be more easily deployed13.

To fulfill the customization quality attribute, Audacity allows users to modify their own settings and experience. The software can arrange for over hundreds of unique combinations, tailored to one’s personal preferences14.

The modularity quality attribute is achieved by making use of several plug-in systems, which notably offer macro functionalities or additional audio processing functions15. Scripting, also, plays an important role in the modularity of Audacity by allowing the use of Python scripting to build custom connected UI elements (such as a new toolbar), or to automate track region selection and customized editing.

To allow anyone to contribute to the software whilst maintaining quality, Audacity uses a robust Feature Request Pipeline with a comprehensive set of code standards16. Despite the lack of refactoring, an incremental split is being created between the GUI and the audio processing functions, effectively accomplishing what LibAudacity and Mezzo aimed to achieve.

Conclusion

Audacity’s architecture and design principles have changed a lot since its inception, and a low amount of refactoring has been completed. While the system achieves its all of key quality attributes, remnants of old architectural patterns, unused libraries and toxic code still remain, leaving Audacity to incur a sizeable technical debt. The following essay will take an in-depth look at this technical debt.

References


  1. https://www.aosabook.org/en/audacity.html ↩︎

  2. https://www.audacityteam.org/about/credits/ ↩︎

  3. https://wiki.audacityteam.org/wiki/For_Developers ↩︎

  4. https://whatis.techtarget.com/definition/discoverability-in-UX-design ↩︎

  5. https://wiki.audacityteam.org/wiki/GUI_Design_Guidelines ↩︎

  6. https://wiki.audacityteam.org/wiki/ArchitecturalDesign ↩︎

  7. http://www.portaudio.com/ ↩︎

  8. https://www.wxwidgets.org/ ↩︎

  9. https://doxy.audacityteam.org/class_audacity_app.html ↩︎

  10. https://manual.audacityteam.org/man/scripting.html ↩︎

  11. https://doxy.audacityteam.org/class_audacity_app.html#a285f156873feac61aa4297d166e0f2c1 ↩︎

  12. https://doxy.audacityteam.org/class_audacity_app.html#a3a18ab8bf1cc96632ad1b996d77cfdd7 ↩︎

  13. https://wiki.audacityteam.org/wiki/AudacityLibraries ↩︎

  14. https://manual.audacityteam.org/man/audacity_setup_and_configuration.html ↩︎

  15. https://manual.audacityteam.org/man/macros.html ↩︎

  16. https://wiki.audacityteam.org/wiki/The_Feature_Requests_Pipeline ↩︎