Difference between revisions of "Demiurge Sound Processing Engine"
(15 intermediate revisions by the same user not shown) | |||
Line 19: | Line 19: | ||
DSPE runs at a "Sample Rate" which is set at start, and a timer is set up to activate DSPE at that periodicity. DSPE will start the graph activity by calling the <code>read_fn()</code> on each so called "SINK", which is a hardware destination of a signal/value. These are currently Analog Outputs, LEDs and Digital Output. This also means that blocks that don't connect to a graph that has a SINK will NOT be executed. This can be utilized for slow processes, independent of the very timing critical sample loop (driven by the hardware timer interrupt). | DSPE runs at a "Sample Rate" which is set at start, and a timer is set up to activate DSPE at that periodicity. DSPE will start the graph activity by calling the <code>read_fn()</code> on each so called "SINK", which is a hardware destination of a signal/value. These are currently Analog Outputs, LEDs and Digital Output. This also means that blocks that don't connect to a graph that has a SINK will NOT be executed. This can be utilized for slow processes, independent of the very timing critical sample loop (driven by the hardware timer interrupt). | ||
− | See [[Standard Blocks]] for detailed information about each standard block. | + | See [[Demiurge/Standard Blocks|Standard Blocks]] for detailed information about each standard block. |
− | See the [[Block Implementation Guide]] to understand how to create new blocks. | + | See the [[Demiurge/Block Implementation Guide|Block Implementation Guide]] to understand how to create new blocks. |
+ | |||
+ | == Engine Operation == | ||
+ | The DSPE has a startup/config phase where everything is set-up, initialize the hardware, configure the engine for that hardware and wire up the specific function of the module. And once the <code>demiurge_start()</code> is called the timer is initialized (with the <code>demiurge_samplerate</code> periodicity) and on the timer interrupt the following happens; | ||
+ | |||
+ | void demiurge_tick() { | ||
+ | demiurge_current_time+= micros_per_tick; | ||
+ | // We are setting the outputs at the start of a cycle, to ensure that the interval is identical from cycle to cycle. | ||
+ | update_dac(); | ||
+ | update_leds(); | ||
+ | update_gates(); | ||
+ | read_gates(); | ||
+ | read_adc(); | ||
+ | |||
+ | for (int i = 0; i < DEMIURGE_MAX_SINKS; i++) { | ||
+ | signal_t *sink = sinks[i]; | ||
+ | if (sink != NULL) { | ||
+ | sink->read_fn(sink, demiurge_current_time); // ignore return value | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | As noted as a comment, the first thing that happens is that the Digital-to-Analog Converters are updated, to ensure that they happen at exactly the correct interval and not jitter in time, which is something we can hear. | ||
+ | |||
+ | The <code>sink->read_fn()</code> is what triggers the entire (and relevant part of) graph will be updated. It is a "PULL" architecture, which ensures minimum compute cycles and values available in order. | ||
+ | |||
+ | All this code executes in the Timer ISR (Interrupt Service Routine), and it is important that the implementor of a Eurorack Module ensure that the total graph will complete before a new timer interrupt is coming. If not possible, try to lower the <code>demiurge_samplerate</code> to a working value. | ||
+ | |||
+ | == Getting Started == | ||
+ | === Prerequisites === | ||
+ | # Demiurge 1, Rev F or later | ||
+ | # The Flashbang from Awesome Audio Apparatus, or ST-Link2, or compatible flashing device. | ||
+ | # STM32CubeIDE - Download from ST Microelectronics | ||
+ | |||
+ | === Step-by-step === | ||
+ | # Download the [https://stm32world.com/packs/demiurge/demiurge1.ioc demiurge-1.ioc] file that defines the configuration needed for the hardware. | ||
+ | # Start STM32CubeIDE | ||
+ | # "File" -> "New" -> "New STM32 Project from an Existing STM32CubeMX Configuration File (.ioc)" | ||
+ | # Select the downloaded demiurge-1.ioc file, and give the project a name. Click Finish. | ||
+ | # In the STM32CubeMX editor (should be opened automatically, otherwise double-click the .ioc file in your project), go to "Pinout & Configuration" -> "Software Packs" -> "Manage Software packs" (may take a while to open up. | ||
+ | # Click on "From Url...", "New" and enter https://stm32world.com/packs/AwesomeAudioApparatus.pidx, then "Check" which should succeed, followed by "OK". | ||
+ | # Then "OK" again to close that window, then it will update/refresh all the packs from the net. | ||
+ | # "Close" the "Embedded Software Manager". | ||
+ | # Go to "Pinout & Configuration" -> "Software Packs" -> "Select Components" | ||
+ | # You should find "AwesomeAudioApparatus.Demiurge", near the top of the list. | ||
+ | # Click "Install". | ||
+ | # Then click on the ">" to the left to expand the group, if not already expanded. | ||
+ | # 3 components will be shown. Click on each of the check boxes, and you need to select "SDK" for the "Device Application". | ||
+ | # Click "OK". | ||
+ | # "AwesomeAudioApparatus.Demiurge.x.y.z" should now show up under "Software Packs" in the left column in STM32CubeMX. | ||
+ | # Click on "AwesomeAudioApparatus.Demiurge.x.y.z", and you get checkboxes for 3 "Modes". Select all of them. | ||
+ | # You now have configuration parameters at the bottom of that column. Configure according to your intent. | ||
+ | # Save the .ioc file, and you will probably be asked if you want to generate the code. Answer "Yes". If you don't get asked, then right click on the .ioc file in the Project Explorer and select "Generate Code". | ||
+ | |||
+ | At this stage you have a working project, but it will not do anything. | ||
+ | |||
+ | You are expected to add <code>demiurge_tick()</code> to the "TIM3 interrupt handler" (<code>TIM3_IRQHandler()</code>) in the <code>Core/Src/stm32f4xx_it.c</code> file. | ||
+ | |||
+ | <code><pre> | ||
+ | void TIM3_IRQHandler(void) | ||
+ | { | ||
+ | /* USER CODE BEGIN TIM3_IRQn 0 */ | ||
+ | /* USER CODE END TIM3_IRQn 0 */ | ||
+ | HAL_TIM_IRQHandler(&htim3); | ||
+ | /* USER CODE BEGIN TIM3_IRQn 1 */ | ||
+ | demiurge_tick(); | ||
+ | /* USER CODE END TIM3_IRQn 1 */ | ||
+ | } | ||
+ | </pre></code> | ||
+ | |||
+ | This will also require a <code>#include "demiurge-spi.h"</code> at the top of the same file; | ||
+ | |||
+ | <code><pre> | ||
+ | |||
+ | /* Includes ------------------------------------------------------------------*/ | ||
+ | #include "main.h" | ||
+ | #include "stm32f4xx_it.h" | ||
+ | /* Private includes ----------------------------------------------------------*/ | ||
+ | /* USER CODE BEGIN Includes */ | ||
+ | #include "demiurge-spi.h" // <----- THIS!!!! | ||
+ | /* USER CODE END Includes */ | ||
+ | </pre></code> | ||
+ | |||
+ | |||
+ | The module functionality itself is expected to be created in <code>User/App/user.c</code> and a stub has been generated. See the [https://github.com/AwesomeAudioApparatus/demiurge-examples Examples] for inspiration and guidance. | ||
== Example == | == Example == |
Latest revision as of 12:52, 9 November 2022
The Demiurge Sound Processing Engine is a sound processing platform. It is designed as processing blocks that are wired together once (typically at boot) and then handles the update cycle (no buffering) automatically and without jitter.
Mental Model
The mental model for the Demiurge Sound Processing Engine (DSPE) is that of blocks of computations that are wired together in the directed acyclical graph (DAG) where the wires represents audio, CV or gate/trig voltage levels.
Here is an example of what that could look like, a Voltage Controlled Amplifier
Blocks
There are a large set of standard blocks and more are developed over time and your contribution is welcome.
Each block will call the read_fn()
on each block connected to its inputs, passing the block instance and "current time" as arguments. Returned is the output value as a float.
DSPE runs at a "Sample Rate" which is set at start, and a timer is set up to activate DSPE at that periodicity. DSPE will start the graph activity by calling the read_fn()
on each so called "SINK", which is a hardware destination of a signal/value. These are currently Analog Outputs, LEDs and Digital Output. This also means that blocks that don't connect to a graph that has a SINK will NOT be executed. This can be utilized for slow processes, independent of the very timing critical sample loop (driven by the hardware timer interrupt).
See Standard Blocks for detailed information about each standard block.
See the Block Implementation Guide to understand how to create new blocks.
Engine Operation
The DSPE has a startup/config phase where everything is set-up, initialize the hardware, configure the engine for that hardware and wire up the specific function of the module. And once the demiurge_start()
is called the timer is initialized (with the demiurge_samplerate
periodicity) and on the timer interrupt the following happens;
void demiurge_tick() { demiurge_current_time+= micros_per_tick; // We are setting the outputs at the start of a cycle, to ensure that the interval is identical from cycle to cycle. update_dac(); update_leds(); update_gates(); read_gates(); read_adc(); for (int i = 0; i < DEMIURGE_MAX_SINKS; i++) { signal_t *sink = sinks[i]; if (sink != NULL) { sink->read_fn(sink, demiurge_current_time); // ignore return value } } }
As noted as a comment, the first thing that happens is that the Digital-to-Analog Converters are updated, to ensure that they happen at exactly the correct interval and not jitter in time, which is something we can hear.
The sink->read_fn()
is what triggers the entire (and relevant part of) graph will be updated. It is a "PULL" architecture, which ensures minimum compute cycles and values available in order.
All this code executes in the Timer ISR (Interrupt Service Routine), and it is important that the implementor of a Eurorack Module ensure that the total graph will complete before a new timer interrupt is coming. If not possible, try to lower the demiurge_samplerate
to a working value.
Getting Started
Prerequisites
- Demiurge 1, Rev F or later
- The Flashbang from Awesome Audio Apparatus, or ST-Link2, or compatible flashing device.
- STM32CubeIDE - Download from ST Microelectronics
Step-by-step
- Download the demiurge-1.ioc file that defines the configuration needed for the hardware.
- Start STM32CubeIDE
- "File" -> "New" -> "New STM32 Project from an Existing STM32CubeMX Configuration File (.ioc)"
- Select the downloaded demiurge-1.ioc file, and give the project a name. Click Finish.
- In the STM32CubeMX editor (should be opened automatically, otherwise double-click the .ioc file in your project), go to "Pinout & Configuration" -> "Software Packs" -> "Manage Software packs" (may take a while to open up.
- Click on "From Url...", "New" and enter https://stm32world.com/packs/AwesomeAudioApparatus.pidx, then "Check" which should succeed, followed by "OK".
- Then "OK" again to close that window, then it will update/refresh all the packs from the net.
- "Close" the "Embedded Software Manager".
- Go to "Pinout & Configuration" -> "Software Packs" -> "Select Components"
- You should find "AwesomeAudioApparatus.Demiurge", near the top of the list.
- Click "Install".
- Then click on the ">" to the left to expand the group, if not already expanded.
- 3 components will be shown. Click on each of the check boxes, and you need to select "SDK" for the "Device Application".
- Click "OK".
- "AwesomeAudioApparatus.Demiurge.x.y.z" should now show up under "Software Packs" in the left column in STM32CubeMX.
- Click on "AwesomeAudioApparatus.Demiurge.x.y.z", and you get checkboxes for 3 "Modes". Select all of them.
- You now have configuration parameters at the bottom of that column. Configure according to your intent.
- Save the .ioc file, and you will probably be asked if you want to generate the code. Answer "Yes". If you don't get asked, then right click on the .ioc file in the Project Explorer and select "Generate Code".
At this stage you have a working project, but it will not do anything.
You are expected to add demiurge_tick()
to the "TIM3 interrupt handler" (TIM3_IRQHandler()
) in the Core/Src/stm32f4xx_it.c
file.
void TIM3_IRQHandler(void)
{
/* USER CODE BEGIN TIM3_IRQn 0 */
/* USER CODE END TIM3_IRQn 0 */
HAL_TIM_IRQHandler(&htim3);
/* USER CODE BEGIN TIM3_IRQn 1 */
demiurge_tick();
/* USER CODE END TIM3_IRQn 1 */
}
This will also require a #include "demiurge-spi.h"
at the top of the same file;
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f4xx_it.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "demiurge-spi.h" // <----- THIS!!!!
/* USER CODE END Includes */
The module functionality itself is expected to be created in User/App/user.c
and a stub has been generated. See the Examples for inspiration and guidance.
Example
In the "Mental Model" section above, we saw a complete VCA. To code that, we need to declare each of the Inputs, Outputs,ControlPairs and Scale as follows
#include "demiurge.h" #include "demiurge-board.h" static audio_inport_t input1; // Declaration of Audio Input Ports static audio_inport_t input2; static control_pair_t pair1; // Declaration of CV+Potentiometer Input pairs static control_pair_t pair2; static audio_outport_t out1; // Declaration of Audio Output Ports static audio_outport_t out2; static scale_t scale1; static scale_t scale2; /* * Dual VCA. */ void vca_prepare() { demiurge_samplerate = 48000; // 48000 samples/second demiurge_set_inport_audio(1); // set the first jack as Audio input. demiurge_set_inport_cv(2); // set the second jack as Control Voltage input demiurge_set_inport_audio(3); // set the third jack as Audio input. demiurge_set_inport_cv(4); // set the fourth jack as Control Voltage input demiurge_set_potentiometer(2, 0.0f, 10.0f); // Potentiometers to a 0-10 range demiurge_set_potentiometer(4, 0.0f, 10.0f); demiurge_set_outport_audio(1); // Set the outputs to Audio output. demiurge_set_outport_audio(2); } void vca_setup() { control_pair_init(&pair1, 2); // CV+Pot at the second position from the top of Demiurge control_pair_init(&pair2, 4); // CV+Pot at the fourth position from the top of Demiurge audio_inport_init(&input1, 1); // Audio In on first input from the top audio_inport_init(&input2, 3); // Audio In on third input from the top audio_outport_init(&out1, 1); // Audio Out on left output channel audio_outport_init(&out2, 2); // Audio Out on right output channel scale_init(&scale1); scale_configure(&scale1, &input1.me, &pair1.me); scale1.scale = 0.1f; // 0-10 control in --> 0-1.0 change of input signal. scale_init(&scale2); scale_configure(&scale2, &input2.me, &pair2.me); scale2.scale = 0.1f; // Connect scale output to out1 audio_outport_configure_input(&out1, &scale1.me); // Connect scale output to out2 audio_outport_configure_input(&out2, &scale2.me); } void vca_loop() { }
The `setup()` and `loop()` functions are also custom, just to create a
familiarity from the Arduino environment. In reality, this is defined in
our `main.c` file, after starting the DSPE and part of the creatioin of
a Demiurge project for the STM32CubeMX and optionally the STM32CubeIDE.
vca_prepare(); // Prepare the board for the functionality demiurge_driver_init(); // Initialize the hardware driver demiurge_init(); // Initialize the Demiurge platform vca_setup(); // Set up the functionality demiurge_start(); // Start the audio processing while(1) { vca_loop(); // Give time for LEDs and Buttons }
More Examples
All examples are in the Examples Repository and showcases many common Eurorack modules, in the most basic form.
Mute
A Dual channel Mute.
VCA
A Dual channel VCA.
VCO
Dual channel Voltage Controlled Oscillator.
Envelope Generator
A standard Envelope Generator (Attack, Decay, Sustain, Release)
LFO
Dual channel Low Frequency Oscillator.