Tempo Tapper
1.0.0
A simple library to implement a tempo tapper
|
Welcome to the Tempo Tapper library documentation.
The Tempo Tapper library is a super simple and straightforward library aimed to easily implement a tempo tapper where needed. Its usage can range anywhere from a software implementation of a tempo tapper, ex. within music production software, all the way to hardware implementations, such as within a digital metronome.
The motiviation to create this library came from the necessity to easily synchronise DIY audio-visuals to the beat of a song during real time DJ performances. While beat-detection algorithms are available, they are often too complex to run reliably on cheap hardware. Numerous DJ softwares also support DMX synchronisation, however often at the expense of needing costly hardware. After numerous experiments to automate light synchronisation trough audio input, it just happend to turn out that the use of a tempo tapper has proven itself to be much more reliable than any of the previous more complex solutions. Initially, a prototype tempo tapper software would run on my computer and would emit signals in sync with the tempo via serial to an Arduino. The Arduino would then alter the lights with every received signal. Being satisfied with the reliability of this dumb and simple solution, I was quickly inspired to write a generic library that allowed me to implement the tempo tapper directly onto the Arduino. That way, lights can be synced even in situations where access to a PC is not available.
The Tempo Tapper library has been written with cross-platform compatibility in mind. The following platforms are currently supported:
Platform | Macro | Description |
---|---|---|
POSIX | TT_TARGET_PLATFORM_POSIX | POSIX, or mostly POSIX compliant, platforms (ex. Linux, MacOS) |
Arduino | TT_TARGET_PLATFORM_ARDUINO | Platforms that support the Arduino programming framework |
To use the library for your target platform, ensure that the corresponding target platform macro has been defined in the compiler flags.
The Tempo Tapper library has been written in a way where all platform specific code is isolated from the libraries external interface. Code that is platform dependant is defined in source files with the following naming:
tempo_tapper_<PLATFORM>.cxx
Examples:
Porting the Tempo Tapper libary is done trough the following steps:
1. In the library header, include/tempo_tapper.h, define (and include headers if necessary) the tt_time_t
typedef to an appropriate data type or data strict used by the target platform to store and perform arithmetics with clock time values. This data type should be able to store time in microseconds. For example, on POSIX platforms, the library uses the timeval struct provided by the sys/time.h header. On Arduino platforms, an unsigned long
is used, since that is the data type used by the micros() function, which returns the current time in microseconds, since the program has been started. Ensure that the typedef is guarded by a #ifdef TT_TARGET_PLATFORM_<PLATFORM>
directive.
2. Define the following platform specific time related functions for your target platform:
These functions should be defined in a file with the following name:
tempo_tapper_<PLATFORM>.cxx
Where <PLATFORM>
is the name of the target platform. Ensure all code within the file is guarded by a #ifdef TT_TARGET_PLATFORM_<PLATFORM>
directive.
In the following section we will disect the term_tt_posix.cxx example, which uses the tempo tapper library, along with ncruses, to implement a terminal based tempo tapper on POSIX compliant systems (ex. Linux, MacOS).
Before we get to the part where the code is explained, ensure the following dependencies have been installed:
To compile, execute the following command from the projects root directory:
Notice that we defined the TT_TARGET_PLATFORM_POSIX
macro in the compiler flags. Not doing so, would leave the compiler clueless about which target platform we are compiling the library for and would throw an error. For a list of supported platforms, along with their necessary macros, please refer to the Supported Platforms section.
The resulting executable will be located in examples/posix/. To execute it from the project root directory, run:
Now to the actual example code itself. At the top of the code we start out by including the necessary headers:
Most importantly, we include the Tempo Tapper library header, tempo_tapper.h. This header exposes all necessary strucs and functions to implement a tempo tapper.
Following the header inclusions we get to the main function of our program:
Here we first declare and allocate a tempo tapper struct instance using the tt_new() function, and test whether it has been successfully created. Should a failure arise during the memory allocation, we free the struct pointer, print an error message and terminate the programm with an EXIT_FAILURE
code.
Next up, we proceed to initialize ncurses:
This will allow us to directly read keyboard input without needing to provide a terminating newline, easily clear the terminal window and allow us to hide the cursor. At the end we declare a variable that will store our terminals keyboard input.
Following the code further down, we find two nested loops:
The outer do-while loop checks if the q
key has been pressed. Should that be the case, the loop is exited and the program terminates. At the top of the outer do-while loop we print the initial Use the enter key to tap a tempo. Press q to quit.
message which greets the user whenever the program is first started, or the tapper has been reset. At the bottom of the outer do-while loop we see the tempo tapper is reset trough the tt_reset() function, meaning if the loop still continues (input
is not q
) after the nested while loop has terminated, the tempo tapper is reset. This will make more sense soon, now that we get to explain the inner while loop.
At the top of this loop we retrieve the users keyboard input using the getchar() function. Due to the ncruses timeout(-1) call that we have made at the very start of the main function, getchar() does not expect a newline character, but will instead immidiately return any provided keyboard input.
We then proceed to clear the terminal with the clear() function and evaluate which key has been pressed. If the input is q
or r
, the current loop is abruptly exited, the input variable is then handled by the outer do-while loop which we have already discussed previously. If the input is a carriage return \r
or a newline character \n
, both of which are returned by pressing the enter key, we register it as a tap and call the tt_tap() function onto our tempo tapper struct instance. Should to keyboard input be none of the previously mentioned characters, then the program will print a Invalid input!
message.
Finally, at the end of the loop we print the detected tempo in BPM, using the tt_bpm() function, and the period time in milliseconds by diving the period time in microseconds, retrieved by tt_period_us(), by 1000. Then we print the Press r to reset, press q to quit.
message and refresh the terminal window to show all changes using the refresh() function.
If the user has pressed the q key, the inner and outer loops will terminate and we get to the very end of our program where we simply end the ncruses window, free our memory allocated by the tempo tapper struct, and lastly return 0:
The Tempo Tapper library also provides a C++ wrapper class to implement a tempo tapper. It can be included with the tempo_tapper_cpp.h header, stores a private tempo tapper struct instance, and provides member methods that simply wrap around the C interface.
Please refer to the C++ wrapper class reference and to the examples/posix/term_tt_posix_cpp.cxx example, where we implement the terminal based tempo tapper example, disucssed in the example section above, using the C++ wrapper.