Translated with www.DeepL.com/Translator (free version) from Japanese. Original Article https://zenn.dev/tomoyanonymous/articles/c4e9dc8e26f78a2093e8
https://www.electro-smith.com/daisy It's a microcontroller board that can be developed with Arduino IDE based on STM32 and various other microcontroller boards such as DSP libraries and Eurolac modules.
Cycling'74 Max has a feature that allows you to create low-level signal processing code for use with Cycling'74 Max. The name of the patch is .gendsp
instead of .maxpat
. As a bonus, there is a function to export to C++, so you can use it for vst signal processing and so on. But I'm a little worried about its future because I've been neglecting it in the development of Cycling recently.
https://cycling74.com/tutorials/gen~-for-beginners-part-1-a-place-to-start
https://github.com/electro-smith/DaisyWiki/wiki/1a.-Getting-Started-(Arduino-Edition)
https://www.arduino.cc/en/Main/software ...
You can also use brew cask arduino
.
https://github.com/stm32duino/wiki/wiki/Getting-Started
↑The part described in the Extra Step above, which is necessary for uploading by DFU
https://www.st.com/en/development-tools/stm32cubeprog.html
If you enter your email address in the "Open" section, a DL link will be sent to you. (In case of Mac, there is .app, but it opens without moving to /Application).
In addition, Java is necessary. It's hard to use Java. You can install Java by brew install openjdk
.
When you open it, you can see the actual programmer application uploaded to Applications.
Sketch->Include Library->Manage Library->Run the Library Manager, search for and install DaisyDuino.
- Tool->Board and select
Generic STM32H7 Series
(if you don't have it, you don't have an STM32duino installed) - Tool->Board-> part number and select
Daisy Seed
. - Tool-> USB Support(if available) and CDC
(generic 'Serial' supersede U(S)ART)
. - In Tool-> USB Speed, use
Low/Full Speed
for now - In Tools->Upload Method,
STM32CubeProgrammer (DFU)
One thing that is different from a normal Arduino board is that you don't have to select the USB port in tools->port (or you won't see it as an option). If you don't have it, it will still be uploaded.
Also, Daisy has two buttons on the board of the seed, RESET
and BOOT
, pressing BOOT and releasing RESET puts the board into write mode and stops the sound, allowing you to write your sketch. If you forget, you'll get an upload error.
First, create the appropriate .maxpat
and .gendsp
files in the appropriate folder. This time I pulled the filter code from gendsp examples.
The filter's frequency and Q settings are input, which is a bit difficult to use, so I made a patch that replaces them with params respectively.
After loading this gendsp, create a [gen~ filter.gendsp]
object at maxpat and send an exportcode filter.cpp
message. The first time, you will be asked for a folder to export to.
Create a .ino file with an appropriate name in an appropriate folder. Insert the code exported by gendsp into this folder.
The structure of the folder is shown in the screenshot below, except for filter.cpp and filter.h. There are some library files in the folder named gen_dsp, which I put together in a single hierarchy.
The code generated by this gen automatically conflicts with some parts of the standard library of daisy and stm. First I tried to solve this by modifying preprocessor definition, but I couldn't find a way to solve it, so I had to comment out the code.
Lines 148-157 of genlib_ops.h
.
#ifndef WIN32 //comment out here
// inline t_sample exp2(t_sample v) { return pow(2., v); }
// inline t_sample trunc(t_sample v) {
// t_sample epsilon = (v<0.0) * -2 * 1E-9 + 1E-9;
// copy to long so it gets truncated (probably cheaper than floor())
// long val = v + epsilon;
// return val;
// }
#endif // WIN32
Also lines 621~627 of genlib_ops.h
.
Depending on the your processing in gendsp, dbtoa
or mstosamps
above and below this function may also conflict. Please check for errors and try to fix them.
// inline t_sample ftom(t_sample in, t_sample tuning=440.) {
// return t_sample(69. + 17.31234050465299 * log(safediv(in, tuning)));
// }
// inline t_sample mtof(t_sample in, t_sample tuning=440.) {
// return t_sample(tuning * exp(.057762265 * (in - 69.0)));
// }
Lines 153 to 157 of genlib.cpp
// NEED THIS FOR WINDOWS: Comment out here
// void *operator new(size_t size) { return sysmem_newptr(size); }
// void *operator new[](size_t size) { return sysmem_newptr(size); }
// void operator delete(void *p) throw() { sysmem_freeptr(p); }
// void operator delete[](void *p) throw() { sysmem_freeptr(p); }
Now you're ready to go.
I have a Daisy Pod, so I looked for some examples for it that I could use, and this time I used the following SimpleOscillator.ino file as a base.
Originally, the waveform type can be changed by the encoder, the octave can be changed by the button, and the frequency canbe changed by the knob 1. So I changed it so that knob 1 changes the filter frequency and knob 2 changes the resonance.
#include "DaisyDuino.h"
#include "json.h"
#include "json_builder.h"
#include "genlib.h"
#include "filter.h"
#define NUM_WAVEFORMS 4
DaisyHardware hw;
Oscillator osc;
CommonState* filter_instance;
uint8_t waveforms[NUM_WAVEFORMS] = {
Oscillator::WAVE_SIN,
Oscillator::WAVE_TRI,
Oscillator::WAVE_POLYBLEP_SAW,
Oscillator::WAVE_POLYBLEP_SQUARE,
};
static float freq=1000;
float filterfreq = 1000;
float filterreson = 0.2;
const size_t blocksize = 512;
float** mybuffer;
float sig;
static int waveform, octave;
static void AudioCallback(float **in, float **out, size_t size)
{
hw.DebounceControls();
waveform += hw.encoder.Increment();
waveform = (waveform % NUM_WAVEFORMS + NUM_WAVEFORMS ) % NUM_WAVEFORMS;
osc.SetWaveform(waveforms[waveform]);
if(hw.buttons[1].RisingEdge())
octave++;
if(hw.buttons[0].RisingEdge())
octave--;
octave = DSY_CLAMP(octave, 0, 4);
// convert MIDI to frequency and multiply by octave size
filterfreq = analogRead(PIN_POD_POT_1) / 1023.f;
freq = mtof(1000 * 127 + (octave * 12));
filter::setparameter(filter_instance,0,filterfreq*20000,nullptr);
filterreson = analogRead(PIN_POD_POT_2) / 1023.f;
filter::setparameter(filter_instance,1,filterreson,nullptr);
osc.SetFreq(1000);
// Audio Loop
for(size_t i = 0; i < size; i ++)
{
// Process
sig = osc.Process();
out[0][i] = sig;
out[1][i] = sig;
}
float** out0 =&out[0];
filter::perform(filter_instance,out0,1,mybuffer,1,size);
for(size_t i = 0; i < size; i ++)
{
// Process
out[0][i] = mybuffer[0][i]*1;
}
}
void InitSynth(float samplerate)
{
osc.Init(samplerate);
osc.SetAmp(0.1);
waveform = 0;
octave = 0;
freq=1000;
}
void setup()
{
float samplerate, callback_rate;
hw = DAISY.init(DAISY_POD, AUDIO_SR_48K);
// hw.SetAudioBlockSize(blocksize);
mybuffer = new float*[1];
mybuffer[0] = new float[blocksize];
samplerate = DAISY.get_samplerate();
auto blocksize = samplerate/DAISY.get_callbackrate();
filter_instance = (CommonState*)filter::create(samplerate,blocksize);
hw.leds[0].Set(false,false,false);
hw.leds[1].Set(false,false,false);
InitSynth(samplerate);
DAISY.begin(AudioCallback);
}
void loop()
{
}
#include "json.h"
#include "json_builder.h"
#include "genlib.h"
#include "filter.h"
I put this after DaisyDuino.h. I'm not sure how and when cpp files are compiled and linked, but it seems the dependencies are compiled automatically..
//in global
CommonState* filter_instance;
// in setup()
samplerate = DAISY.get_samplerate();
auto blocksize = samplerate/DAISY.get_callbackrate();
filter_instance = (CommonState*)filter::create(samplerate,blocksize);
For some reason Daisy can't get the blocksize directly, but can only receive it by converting it to callbackrate, so I'm starting up an instance of the filter again with the undo process. So, I'm going to bite it and start up the instance again, and the namespace of filter::create
should be the same as the name of gendsp.
filter::setparameter(filter_instance,1,filterreson,nullptr);
The second argument specifies the index of the parameters to be set in gendsp when multiple parameters are created.
You can use getparametername(filter_instance,index)
to get the name of the param, but usually it is easier to directly refer to the definition in the .cpp file.
The daisy audio callback comes with static void AudioCallback(float **in, float **out, size_t size)
.
filter::perform(filter_instance,out0,1,mybuffer,1,size);
The arguments are, a pointer to the instance, a pointer to the input (float**), the number of input channels, a pointer to the output (float**), the number of output channels, and the buffer size. The buffer size can be specified directly by the size
argument of callback.
I thought I could use the same pointers for in and out and do destructive rewriting, but I couldn't, so I prepared a single buffer.
And then, compile and upload it. Finish!
- I'd like to see some more cpp-like code because the API of cpp made with gen~ is mostly C.
- Why does the headphone out seem to be in mono? I couldn't help it, so I plugged my headphones directly into the lineout (be careful with the volume).
- Maybe if all the procedure could be automated with node for max or something, you can upload automatically from Max if you want to.
- But for the time being, it's going to be easier to do it on your own this way until the daisy's get that infrastructure in place.