Tutorial Sketches - Explanations » History » Version 44
Version 43 (Robert Jack, 2016-05-16 04:07 PM) → Version 44/45 (Robert Jack, 2016-05-16 04:09 PM)
h1. Tutorial Sketches - Explanations
Find the tutorial sketches in Respository > Projects, or click this link: https://code.soundsoftware.ac.uk/projects/beaglert/repository/show/projects
Below is a list of the sample sketches, and what they do.
h3. The structure of a Bela project
A collection of Bela files are called a "project".
If you open a project folder in the above repository, you'll see that each of these Bela project contains two files: main.cpp and render.cpp (some projects have additional files, but every project has at least these two). The main.cpp file you don't really have to worry about; it contains helper functions and things that run command line arguments. Most work is done in the render.cpp file.
h3. The structure of a render.cpp file
A render.cpp file has three functions: @setup()@, @render()@ and @cleanup()@.
@setup()@ is an initialisation function which runs before audio rendering begins. It is called once when the project starts. Use it to prepare any memory or resources that will be needed in @render()@.
@render()@ is a function that is regularly called, over and over continuously, at the highest priority by the audio engine. It is used to process audio and sensor data. This function is called regularly by the system every time there is a new block of audio and/or sensor data to process.
@cleanup()@ is a function that is called when the program stops, to finish up any processes that might still be running.
Here we will briefly explain each function and the structure of the render.cpp document
h3. Before any functions
At the top of the file, include any libraries you might need.
Additionally, declare any global variables. In these tutorial sketches, all global variables are preceded by a @g@ so we always know which variables are global - @gSampleData@, for instance. It's not mandatory but is a really good way of keeping track of what's global and what isn't.
Sometimes it's necessary to access a variable from another file, such as main.cpp. In this case, precede this variable with the keyword @extern@.
h3. Function arguments
@setup()@, @render()@ and @cleanup()@ each take the same arguments. These are:
@0ext *context
void *userData
@
These arguments are pointers to data structures. The main one that's used is @context@, which is a pointer to a data structure containing lots of information you need.
Take a look at what's in the data structure here: https://code.soundsoftware.ac.uk/projects/beaglert/embedded/structBeagleRTContext.html
You can access any of these bits of information about current audio and sensor settings and pointers to data buffers that are contained in the data structure like this: @context->name_of_item@
For example, @context->audioChannels@ returns the number of audio channels. @context->audioSampleRate@ returns the audio sample rate. @context->audioIn[n]@ would give you the current input sample (assuming that your input is mono - if it's not you will have to account for multiple channels).
Note that @audioIn@, @audioOut@, @analogIn@, @analogOut@ are arrays (buffers).
h2. analogDigitalDemo
This sketch showcases many different ways to write and read digital pins, including generating clocks and creating binary counters.
The code as it is will not work properly, as the direction of the pins is not set. As an exercise, you will need to set the pin mode before writing or reading the digital pins. This is for advanced users only.
h2. audio_in_FFT
This sketch performs an FFT (Fast Fourier Transform) on incoming audio. It uses the NE10 library, included at the top of the file (line 11).
Read the documentation on the NE10 library here: http://projectne10.github.io/Ne10/doc/annotated.html
The variables @timeDomainIn@, @timeDomainOut@ and @frequencyDomain@ are variables of the struct @ne10_fft_cpx_float32_t@ (http://projectne10.github.io/Ne10/doc/structne10__fft__cpx__float32__t.html). These are declared at the top of the file (line 21), and memory is allocated for them in @setup()@ (line 41).
In @render()@ a @for@ loop performs the FFT is performed on each sample, and the resulting output is placed on each channel.
h2. basic
This sketch, better known as bleep, produces a sine tone.
The frequency of the sine tone is determined in a global variable, @gFrequency@ (line 12). This is produced by incrementing the phase of a sin function on every audio frame.
The important thing to notice is the nested for loop structure. You will see this in all Bela projects and in most digital audio applications. The first for loop cycles through the audio frames, the second through each of the audio channels (in this case left 0 and right 1). It is good to familiarise yourself with this structure as it is fundamental to producing sound with the system.
h2. basic_analog_input
This sketch produces a sine tone, the frequency and amplitude of which are affected by data received on the analog pins. Before looping through each audio frame, we declare a value for the frequency and amplitude of our sine tone (line 55); we adjust these values by taking in data from analog sensors (for example, a potentiometer).
The important thing to notice is that audio is sampled twice as often as analog data. The audio sampling rate is 44.1kHz (44100 frames per second) and the analog sampling rate is 22.05kHz (22050 frames per second). On line 62 you might notice that we are processing the analog data and updating frequency and amplitude only on every *second* audio sample, since the analog sampling rate is half that of the audio.
Note that the pin numbers are stored in the variables @gAnalogInputFrequency@ and @gAnalogInputAmplitude@. These are declared in the main.cpp file; if you look in that file you will see that they have the values of 0 and 1. Bear in mind that these are analog input pins which is a specific header!
h2. basic_analog_output
This sketch uses a sine wave to drive the brightness of a series of LEDs connected to the eight analog out pins. Again you can see the nested for loop structure but this time for the analog output channels rather than the audio.
Within the first for loop in render we cycle through each frame in the analog output matrix. At each frame we then cycle through the analog output channels with another for loop and set the output voltage according to the phase of a sine tone that acts as an LFO. The analog output pins can provide a voltage of ~4.092V.
The output on each pin is set with @analogWriteFrame@ within the for loop that cycles through the analog output channels. This needs to be provided with arguments as follows @analogWriteFrame(context, n, channel, out)@. Channel is where the you give the address of the analog output pin (in this case we cycle through each pin address in the for loop), out is the variable that holds the desired output (in this case set by the sine wave).
Notice that the phase of the brightness cycle for each led is different. This is achieved by updating a variable that stores a relative phase value -- this is advanced by pi/4 (1/8 of a full rotation) for each channel giving each of the eight LEDs a different phase.
h2. basic_blink
This sketch shows the simplest case of digital out. Connect an LED in series with a 470ohm resistor between P8_07 and ground. The led is blinked on and off by setting the digital pin @HIGH@ and @LOW@ every @interval@ seconds (set it in the @render()@ function).
Firstly the pin mode must be set to output mode: @pinModeFrame(context, 0, P8_07, OUTPUT);@ in the @setup()@ function. The output of the digital pins is set by the following code: @digitalWriteFrame(context, n, P8_07, status);@ where @status@ can be equal to either @HIGH@ or @LOW@. When set @HIGH@ the pin will give 3.3V, when set to @LOW@ 0V.
To A sample counter @count@ is implemented to keep track of elapsed time we have a sample counter @count@. time. When @count@ reaches a certain limit it switches state to either @HIGH@ or @LOW@ depending on its current state. In (in this case the limit is @context->digitalSampleRate*interval@ which allows us to write the desired interval in seconds, the value of which is stored in @interval@. @interval@) it switches state to either @HIGH@ or @LOW@ depending on its current state.
h2. basic_button
This sketch brings together digital out with digital in. The program will read a button and turn the LED on and off according to the state of the button.
- connect an LED in series with a 470ohm resistor between P8_07 and ground.
- connect a 1k resistor to P9_03 (+3.3V),
- connect the other end of the resistor to both a button and P8_08
- connect the other end of the button to ground.
You will notice that the LED will normally stay on and will turn off as long as the button is pressed. This is due to the fact that the LED is set to the same value read at input P8_08. When the button is not pressed, P8_08 is @HIGH@ and so P8_07 is set to @HIGH@ as well, so that the LED conducts and emits light. When the button is pressed, P8_08 goes @LOW@ and P8_07 is set to @LOW@, turning off the LED.
As an excise try and change the code so that the LED only turns on when the button is pressed?
h2. basic_network
This sketch allows you to send audio and sensor data over UDP to a DAW on the host. The host needs to run Udpioplugin which you can find here https://code.soundsoftware.ac.uk/projects/udpioplugin. Note that this sketch and the accompanying plugin are still in testing.
h2. basic_passthru
This sketch demonstrates the simplest possible case of using audio: it passes audio input straight to audio output.
Note the nested for loop structure. You will see this in all Bela projects. The first for loop cycles through the audio frames, the second through each of the audio channels (in this case left 0 and right 1).
We write samples to the audio output buffer like this: @context->audioOut[n * context->audioChannels + ch]@ where n is the current audio frame and ch is the current channel, both provided by the nested for loop structure.
We can access samples in the audio input buffer in a similar way, like this: @context->audioIn[n * context->audioChannels + ch]@.
So a simple audio pass through is achieved by setting output buffer equal to input buffer: @context->audioOut[n * context->audioChannels + ch] = context->audioIn[n * context->audioChannels + ch]@.
h2. cape_test
This sketch is for testing purposes only. Each analog input should be connected to each analog output 0-0, 1-1, etc. The board will then produce either a 880Hz pulse if the every channel is functioning or a 220Hz continuous tone if one of the channels is not working. A message will be printed to the terminal window that details the particular channel that is not working.
h2. d-box
These files are from the d-box hackable musical instrument. See https://code.soundsoftware.ac.uk/publications/128?project_id=beaglert for more information.
h2. filter_FIR
This sketch implements a finite impulse response filter.
h2. filter_IIR
This sketch implements a infinite impulse response filter.
h2. oscillator_bank
These files demonstrate an oscillator bank implemented in assembly code that is used as part of the d-box project.
h2. samples
This sketch shows how to playback audio samples from a buffer.
An audio file is loaded into a buffer @SampleData@ as @gSampleData@. This is accessed with a read pointer that is incremented at audio rate within the render function: @out += gSampleData.samples[gReadPtr++]@.
Note that the read pointer is stopped from incrementing past the length of the @gSampleData@. This is achieved by comparing the read pointer value against the sample length which we can access as follows: @if(gReadPtr >= gSampleData.sampleLen)@
The sample is triggered by keyboard input: (a) starts sample playback, (s) stops sample playback. The triggering is treated as a lower priority task to the audio. You can see this at the bottom of the render function: @Bela_scheduleAuxiliaryTask(gTriggerSamplesTask)@;
h2. tank_wars
This sketch uses the analog outputs to draw a tank wars game using vector graphics. Best viewed on an analog oscilloscope.
Find the tutorial sketches in Respository > Projects, or click this link: https://code.soundsoftware.ac.uk/projects/beaglert/repository/show/projects
Below is a list of the sample sketches, and what they do.
h3. The structure of a Bela project
A collection of Bela files are called a "project".
If you open a project folder in the above repository, you'll see that each of these Bela project contains two files: main.cpp and render.cpp (some projects have additional files, but every project has at least these two). The main.cpp file you don't really have to worry about; it contains helper functions and things that run command line arguments. Most work is done in the render.cpp file.
h3. The structure of a render.cpp file
A render.cpp file has three functions: @setup()@, @render()@ and @cleanup()@.
@setup()@ is an initialisation function which runs before audio rendering begins. It is called once when the project starts. Use it to prepare any memory or resources that will be needed in @render()@.
@render()@ is a function that is regularly called, over and over continuously, at the highest priority by the audio engine. It is used to process audio and sensor data. This function is called regularly by the system every time there is a new block of audio and/or sensor data to process.
@cleanup()@ is a function that is called when the program stops, to finish up any processes that might still be running.
Here we will briefly explain each function and the structure of the render.cpp document
h3. Before any functions
At the top of the file, include any libraries you might need.
Additionally, declare any global variables. In these tutorial sketches, all global variables are preceded by a @g@ so we always know which variables are global - @gSampleData@, for instance. It's not mandatory but is a really good way of keeping track of what's global and what isn't.
Sometimes it's necessary to access a variable from another file, such as main.cpp. In this case, precede this variable with the keyword @extern@.
h3. Function arguments
@setup()@, @render()@ and @cleanup()@ each take the same arguments. These are:
@0ext *context
void *userData
@
These arguments are pointers to data structures. The main one that's used is @context@, which is a pointer to a data structure containing lots of information you need.
Take a look at what's in the data structure here: https://code.soundsoftware.ac.uk/projects/beaglert/embedded/structBeagleRTContext.html
You can access any of these bits of information about current audio and sensor settings and pointers to data buffers that are contained in the data structure like this: @context->name_of_item@
For example, @context->audioChannels@ returns the number of audio channels. @context->audioSampleRate@ returns the audio sample rate. @context->audioIn[n]@ would give you the current input sample (assuming that your input is mono - if it's not you will have to account for multiple channels).
Note that @audioIn@, @audioOut@, @analogIn@, @analogOut@ are arrays (buffers).
h2. analogDigitalDemo
This sketch showcases many different ways to write and read digital pins, including generating clocks and creating binary counters.
The code as it is will not work properly, as the direction of the pins is not set. As an exercise, you will need to set the pin mode before writing or reading the digital pins. This is for advanced users only.
h2. audio_in_FFT
This sketch performs an FFT (Fast Fourier Transform) on incoming audio. It uses the NE10 library, included at the top of the file (line 11).
Read the documentation on the NE10 library here: http://projectne10.github.io/Ne10/doc/annotated.html
The variables @timeDomainIn@, @timeDomainOut@ and @frequencyDomain@ are variables of the struct @ne10_fft_cpx_float32_t@ (http://projectne10.github.io/Ne10/doc/structne10__fft__cpx__float32__t.html). These are declared at the top of the file (line 21), and memory is allocated for them in @setup()@ (line 41).
In @render()@ a @for@ loop performs the FFT is performed on each sample, and the resulting output is placed on each channel.
h2. basic
This sketch, better known as bleep, produces a sine tone.
The frequency of the sine tone is determined in a global variable, @gFrequency@ (line 12). This is produced by incrementing the phase of a sin function on every audio frame.
The important thing to notice is the nested for loop structure. You will see this in all Bela projects and in most digital audio applications. The first for loop cycles through the audio frames, the second through each of the audio channels (in this case left 0 and right 1). It is good to familiarise yourself with this structure as it is fundamental to producing sound with the system.
h2. basic_analog_input
This sketch produces a sine tone, the frequency and amplitude of which are affected by data received on the analog pins. Before looping through each audio frame, we declare a value for the frequency and amplitude of our sine tone (line 55); we adjust these values by taking in data from analog sensors (for example, a potentiometer).
The important thing to notice is that audio is sampled twice as often as analog data. The audio sampling rate is 44.1kHz (44100 frames per second) and the analog sampling rate is 22.05kHz (22050 frames per second). On line 62 you might notice that we are processing the analog data and updating frequency and amplitude only on every *second* audio sample, since the analog sampling rate is half that of the audio.
Note that the pin numbers are stored in the variables @gAnalogInputFrequency@ and @gAnalogInputAmplitude@. These are declared in the main.cpp file; if you look in that file you will see that they have the values of 0 and 1. Bear in mind that these are analog input pins which is a specific header!
h2. basic_analog_output
This sketch uses a sine wave to drive the brightness of a series of LEDs connected to the eight analog out pins. Again you can see the nested for loop structure but this time for the analog output channels rather than the audio.
Within the first for loop in render we cycle through each frame in the analog output matrix. At each frame we then cycle through the analog output channels with another for loop and set the output voltage according to the phase of a sine tone that acts as an LFO. The analog output pins can provide a voltage of ~4.092V.
The output on each pin is set with @analogWriteFrame@ within the for loop that cycles through the analog output channels. This needs to be provided with arguments as follows @analogWriteFrame(context, n, channel, out)@. Channel is where the you give the address of the analog output pin (in this case we cycle through each pin address in the for loop), out is the variable that holds the desired output (in this case set by the sine wave).
Notice that the phase of the brightness cycle for each led is different. This is achieved by updating a variable that stores a relative phase value -- this is advanced by pi/4 (1/8 of a full rotation) for each channel giving each of the eight LEDs a different phase.
h2. basic_blink
This sketch shows the simplest case of digital out. Connect an LED in series with a 470ohm resistor between P8_07 and ground. The led is blinked on and off by setting the digital pin @HIGH@ and @LOW@ every @interval@ seconds (set it in the @render()@ function).
Firstly the pin mode must be set to output mode: @pinModeFrame(context, 0, P8_07, OUTPUT);@ in the @setup()@ function. The output of the digital pins is set by the following code: @digitalWriteFrame(context, n, P8_07, status);@ where @status@ can be equal to either @HIGH@ or @LOW@. When set @HIGH@ the pin will give 3.3V, when set to @LOW@ 0V.
To A sample counter @count@ is implemented to keep track of elapsed time we have a sample counter @count@. time. When @count@ reaches a certain limit it switches state to either @HIGH@ or @LOW@ depending on its current state. In (in this case the limit is @context->digitalSampleRate*interval@ which allows us to write the desired interval in seconds, the value of which is stored in @interval@. @interval@) it switches state to either @HIGH@ or @LOW@ depending on its current state.
h2. basic_button
This sketch brings together digital out with digital in. The program will read a button and turn the LED on and off according to the state of the button.
- connect an LED in series with a 470ohm resistor between P8_07 and ground.
- connect a 1k resistor to P9_03 (+3.3V),
- connect the other end of the resistor to both a button and P8_08
- connect the other end of the button to ground.
You will notice that the LED will normally stay on and will turn off as long as the button is pressed. This is due to the fact that the LED is set to the same value read at input P8_08. When the button is not pressed, P8_08 is @HIGH@ and so P8_07 is set to @HIGH@ as well, so that the LED conducts and emits light. When the button is pressed, P8_08 goes @LOW@ and P8_07 is set to @LOW@, turning off the LED.
As an excise try and change the code so that the LED only turns on when the button is pressed?
h2. basic_network
This sketch allows you to send audio and sensor data over UDP to a DAW on the host. The host needs to run Udpioplugin which you can find here https://code.soundsoftware.ac.uk/projects/udpioplugin. Note that this sketch and the accompanying plugin are still in testing.
h2. basic_passthru
This sketch demonstrates the simplest possible case of using audio: it passes audio input straight to audio output.
Note the nested for loop structure. You will see this in all Bela projects. The first for loop cycles through the audio frames, the second through each of the audio channels (in this case left 0 and right 1).
We write samples to the audio output buffer like this: @context->audioOut[n * context->audioChannels + ch]@ where n is the current audio frame and ch is the current channel, both provided by the nested for loop structure.
We can access samples in the audio input buffer in a similar way, like this: @context->audioIn[n * context->audioChannels + ch]@.
So a simple audio pass through is achieved by setting output buffer equal to input buffer: @context->audioOut[n * context->audioChannels + ch] = context->audioIn[n * context->audioChannels + ch]@.
h2. cape_test
This sketch is for testing purposes only. Each analog input should be connected to each analog output 0-0, 1-1, etc. The board will then produce either a 880Hz pulse if the every channel is functioning or a 220Hz continuous tone if one of the channels is not working. A message will be printed to the terminal window that details the particular channel that is not working.
h2. d-box
These files are from the d-box hackable musical instrument. See https://code.soundsoftware.ac.uk/publications/128?project_id=beaglert for more information.
h2. filter_FIR
This sketch implements a finite impulse response filter.
h2. filter_IIR
This sketch implements a infinite impulse response filter.
h2. oscillator_bank
These files demonstrate an oscillator bank implemented in assembly code that is used as part of the d-box project.
h2. samples
This sketch shows how to playback audio samples from a buffer.
An audio file is loaded into a buffer @SampleData@ as @gSampleData@. This is accessed with a read pointer that is incremented at audio rate within the render function: @out += gSampleData.samples[gReadPtr++]@.
Note that the read pointer is stopped from incrementing past the length of the @gSampleData@. This is achieved by comparing the read pointer value against the sample length which we can access as follows: @if(gReadPtr >= gSampleData.sampleLen)@
The sample is triggered by keyboard input: (a) starts sample playback, (s) stops sample playback. The triggering is treated as a lower priority task to the audio. You can see this at the bottom of the render function: @Bela_scheduleAuxiliaryTask(gTriggerSamplesTask)@;
h2. tank_wars
This sketch uses the analog outputs to draw a tank wars game using vector graphics. Best viewed on an analog oscilloscope.