[{"data":1,"prerenderedAt":1234},["ShallowReactive",2],{"posts:\u002Fposts\u002F2024-10-01-digital-synth-factibility":3},{"id":4,"title":5,"body":6,"categories":1219,"date":1221,"description":1222,"extension":1223,"image":53,"lang":1222,"meta":1224,"navigation":175,"path":1225,"seo":1226,"stem":1227,"tags":1228,"titleInPicture":1232,"__hash__":1233},"posts\u002Fposts\u002F2024-10-01-digital-synth-factibility.md","Finding options for a Digital Synth Workshop",{"type":7,"value":8,"toc":1206},"minimark",[9,13,39,50,55,59,62,65,69,72,75,78,81,84,89,103,132,141,145,148,152,158,422,426,435,438,441,1127,1131,1134,1154,1158,1161,1176,1181,1185,1188,1192,1195,1199,1202],[10,11,12],"p",{},"I want to design a workshop where people create their first digital synthesizer (I want to callit digisynth). This turned out to be challenging because two aspects of it are challenging, and they interrelate",[14,15,16,25,36],"ul",{},[17,18,19,20],"li",{},"Creating a synthesizer that is interesting enough from the perspective of the user. Also, when demoing it through a video (e.g. Instagram) for it to look compelling enough.\n",[14,21,22],{},[17,23,24],{},"What would make the synth compelling? It's similarity to known synths on a simpler factor? Unexpected interactivity?",[17,26,27,28],{},"Cheap enough, using somewhat familiar, or easy-to-acquire hardware\n",[14,29,30,33],{},[17,31,32],{},"In Chile, it's exceedingly difficult to get most electronic parts; also, one never knows whether a part will no longer be imported.",[17,34,35],{},"Arduinos are easy to find. Also some parts are replaceable, e.g. headphone jacks or 8-ohm speakers.",[17,37,38],{},"An easy process of getting it to work, to reduce the risk of frustrating results.",[10,40,41,42,49],{},"I tend to over-complicate things: the most obvious answer is to use ",[43,44,48],"a",{"href":45,"rel":46},"https:\u002F\u002Fsensorium.github.io\u002FMozzi\u002Fgallery\u002F",[47],"nofollow","Mozzi"," and build one of the shown examples. However, I wanted to dig deeper and find ways to attain greater sound resolution or an obvious way to synthesize sound sample by sample, which I love doing.",[51,52],"img",{"src":53,"alt":54},"\u002Fimages\u002F2024\u002F10\u002F20240930 ESP32 with I2S DAC 2.jpg","ESP32 with I2S DAC",[56,57,48],"h2",{"id":58},"mozzi",[10,60,61],{},"Yes, Mozzi is the answer because all the needed materials are widely available; I could even do this workshop online and I can be almost sure that everyone will be able to purchase an arduino UNO, or pro mini, or another. Also, Mozzi is awesome and fun.",[10,63,64],{},"A downside of Mozzi is that it utilizes quite advanced type-casting abstractions which could take too long to explain. This downside can be overcome by teaching enough recipes and offering a few tools that one could use even without deeply understanding what's going on. Since it's so easy to set up, plenty of time is left to delve into this. A Mozzi-based workshop would be based on an explanation of the examples in theory, plus concrete to tweak these examples.",[56,66,68],{"id":67},"i2s","I2S",[10,70,71],{},"I currently have scarce resources. On top of that, it takes about a month for something bought online to reach me in Chile. I have about three I2S DACs in my toolbox, and getting one of these to work would be sufficient. I2S offers greater sound quality and an easier setup than Mozzi's Hi-Fi mode (nothing can beat Mozzi's one-pin setup, though). This is because all the required hardware is typically present on the breakout boards, so one doesn't need to find precisely valued resistors or capacitors, etc.",[10,73,74],{},"Another advantage of I2S is that we can use a more powerful yet still widely available MCU, such as the ESP32, ESP8266, Arduino Uno R4, etc.",[10,76,77],{},"The downside with I2S is that it introduces an additional compatibility variable. While Mozzi works on many devices, getting it to work on certain MCUs with specific I2S DACs is a different challenge. Even though I2S is standardized and well-documented, setting up an Arduino sketch to work with it is no longer a simple task. For example, Mozzi will not easily work on an ESP32 with a DAC without delving into the details of how timer interrupts are handled and how audio is sent to the DAC.",[10,79,80],{},"I already have in my possession an ESP32, which is also a widely available chip on many boards and price ranges. The ESP32 presents quite few quirks of it's own, which are nonetheless reasonable to deal with.",[10,82,83],{},"Here are some of the solutions that I could get working easily:",[85,86,88],"h3",{"id":87},"stk-over-esp32-with-i2c-dac","STK over ESP32 with I2C DAC",[10,90,91,92,97,98,102],{},"Phil Schatzmann did an incredible job of making these libraries. He integrated some c-written audio synthesis kit, which I think was originally intended for desktop computers, into an Arduino library that works with the ESP32. Although it's not the functionality I am looking for, he implemented ways to stream the audio through Bluetooth and other protocols including Serial. I haven't been able yet to delve deeper into the capacity of the library, but it seems to be quite a comprehensive. More can be read ",[43,93,96],{"href":94,"rel":95},"https:\u002F\u002Fwww.pschatzmann.ch\u002Fhome\u002F2020\u002F09\u002F24\u002Fthe-synthesis-toolkit-skt-library-for-the-arduino-esp32\u002F",[47],"here"," and the library can be found ",[43,99,96],{"href":100,"rel":101},"https:\u002F\u002Fgithub.com\u002Fpschatzmann\u002FArduino-STK",[47],".",[10,104,105,106,111,112,116,117,120,121,126,127],{},"That's not all: this STK library became part of a much bigger library; the ",[43,107,110],{"href":108,"rel":109},"https:\u002F\u002Fgithub.com\u002Fpschatzmann\u002Farduino-audio-tools",[47],"Arduino Audio Tools",". The library harmonizes several audio libraries, including Mozzi, into one huge library. It allows one to create a ",[113,114,115],"em",{},"Stream"," abstraction from these ",[113,118,119],{},"virtual audio signals"," and consequently allows the program to make pretty much any combination of software and hardwares (",[43,122,125],{"href":123,"rel":124},"https:\u002F\u002Fgithub.com\u002Fpschatzmann\u002Farduino-audio-tools\u002Fwiki\u002FRunning-an-Audio-Sketch-on-the-Desktop",[47],"even desktop?!","), provided that one configures them correctly. The resulting syntax is quite elegant - I'd like to delve a bit deeper into the usage of this library to see if the elegance withstands more complex synthesis patches. If I create something with this library, I should remind myself posting it to his ",[43,128,131],{"href":129,"rel":130},"https:\u002F\u002Fgithub.com\u002Fpschatzmann\u002Farduino-audio-tools\u002Fdiscussions\u002Fcategories\u002Fshow-and-tell",[47],"show-and-tell",[10,133,134,135,140],{},"With Adafruit ESP32 feather, it's a bit tricky to know what digital pins are sent to which pins on the board. ",[43,136,139],{"href":137,"rel":138},"https:\u002F\u002Flearn.adafruit.com\u002Fassets\u002F111179",[47],"This picture"," was useful. Here is how I connected it, as to match the chosen sketches' pinouts. Please don't remark about how ruffed-up my ESP32 is.",[51,142],{"src":143,"alt":144},"\u002Fimages\u002F2024\u002F10\u002F20240930 ESP32 with I2S DAC.jpg","ESP32 with I2S DAC arduino pins",[10,146,147],{},"Here two setups that I could get working easily, using ESP32 and UDA1334 breakout board by Adafruit:",[85,149,151],{"id":150},"using-audiotools-entirely","Using audiotools entirely",[10,153,154,155,102],{},"This one has a very elegant syntax, and all the dirty work of writing blocks to I2C is hidden from view. This type of sketch seems that would be most useful for a workshop that focuses on the synthesis aspect of ",[113,156,157],{},"digisynth",[159,160,165],"pre",{"className":161,"code":162,"language":163,"meta":164,"style":164},"language-arduino shiki shiki-themes github-dark","\n\u002F**\n * @file streams-generator-i2s.ino\n * @author Phil Schatzmann\n * @brief see https:\u002F\u002Fgithub.com\u002Fpschatzmann\u002Farduino-audio-tools\u002Fblob\u002Fmain\u002Fexamples\u002Fexamples-stream\u002Fstreams-generator-i2s\u002FREADME.md \n * @copyright GPLv3\n *\u002F\n\n#include \"AudioTools.h\"\n\nAudioInfo info(44100, 2, 16);\nSineWaveGenerator\u003Cint16_t> sineWave(32000);     \u002F\u002F subclass of SoundGenerator with max amplitude of 32000\nGeneratedSoundStream\u003Cint16_t> sound(sineWave);  \u002F\u002F Stream generated from sine wave\nI2SStream out;\nStreamCopy copier(out, sound);  \u002F\u002F copies sound into i2s\n\n\u002F\u002F Arduino Setup\nvoid setup(void) {\n  \u002F\u002F Open Serial\n\n  \u002F\u002F start I2S\n  Serial.println(\"starting I2S...\");\n  auto config = out.defaultConfig(TX_MODE);\n  config.copyFrom(info);\n  config.pin_bck = 27;\n  config.pin_ws = 26;\n  config.pin_data = 25;\n  \u002F\u002Fconfig.buffer_count = 8;\n  \u002F\u002Fconfig.buffer_size = 1024;\n  out.begin(config);\n\n  \u002F\u002F Setup sine wave\n  sineWave.begin(info, N_B4);\n  Serial.println(\"started...\");\n}\n\n\u002F\u002F Arduino loop - copy sound to out\nvoid loop() {\n  copier.copy();\n  if (random(1000) > 995) {\n    sineWave.setFrequency(random(2000)+ 20);\n  }\n}\n\n\n","arduino","",[166,167,168,177,183,189,195,201,207,213,218,224,229,235,241,247,253,259,264,270,276,282,287,293,299,305,311,317,323,329,335,341,347,352,358,364,370,376,381,387,393,399,405,411,417],"code",{"__ignoreMap":164},[169,170,173],"span",{"class":171,"line":172},"line",1,[169,174,176],{"emptyLinePlaceholder":175},true,"\n",[169,178,180],{"class":171,"line":179},2,[169,181,182],{},"\u002F**\n",[169,184,186],{"class":171,"line":185},3,[169,187,188],{}," * @file streams-generator-i2s.ino\n",[169,190,192],{"class":171,"line":191},4,[169,193,194],{}," * @author Phil Schatzmann\n",[169,196,198],{"class":171,"line":197},5,[169,199,200],{}," * @brief see https:\u002F\u002Fgithub.com\u002Fpschatzmann\u002Farduino-audio-tools\u002Fblob\u002Fmain\u002Fexamples\u002Fexamples-stream\u002Fstreams-generator-i2s\u002FREADME.md \n",[169,202,204],{"class":171,"line":203},6,[169,205,206],{}," * @copyright GPLv3\n",[169,208,210],{"class":171,"line":209},7,[169,211,212],{}," *\u002F\n",[169,214,216],{"class":171,"line":215},8,[169,217,176],{"emptyLinePlaceholder":175},[169,219,221],{"class":171,"line":220},9,[169,222,223],{},"#include \"AudioTools.h\"\n",[169,225,227],{"class":171,"line":226},10,[169,228,176],{"emptyLinePlaceholder":175},[169,230,232],{"class":171,"line":231},11,[169,233,234],{},"AudioInfo info(44100, 2, 16);\n",[169,236,238],{"class":171,"line":237},12,[169,239,240],{},"SineWaveGenerator\u003Cint16_t> sineWave(32000);     \u002F\u002F subclass of SoundGenerator with max amplitude of 32000\n",[169,242,244],{"class":171,"line":243},13,[169,245,246],{},"GeneratedSoundStream\u003Cint16_t> sound(sineWave);  \u002F\u002F Stream generated from sine wave\n",[169,248,250],{"class":171,"line":249},14,[169,251,252],{},"I2SStream out;\n",[169,254,256],{"class":171,"line":255},15,[169,257,258],{},"StreamCopy copier(out, sound);  \u002F\u002F copies sound into i2s\n",[169,260,262],{"class":171,"line":261},16,[169,263,176],{"emptyLinePlaceholder":175},[169,265,267],{"class":171,"line":266},17,[169,268,269],{},"\u002F\u002F Arduino Setup\n",[169,271,273],{"class":171,"line":272},18,[169,274,275],{},"void setup(void) {\n",[169,277,279],{"class":171,"line":278},19,[169,280,281],{},"  \u002F\u002F Open Serial\n",[169,283,285],{"class":171,"line":284},20,[169,286,176],{"emptyLinePlaceholder":175},[169,288,290],{"class":171,"line":289},21,[169,291,292],{},"  \u002F\u002F start I2S\n",[169,294,296],{"class":171,"line":295},22,[169,297,298],{},"  Serial.println(\"starting I2S...\");\n",[169,300,302],{"class":171,"line":301},23,[169,303,304],{},"  auto config = out.defaultConfig(TX_MODE);\n",[169,306,308],{"class":171,"line":307},24,[169,309,310],{},"  config.copyFrom(info);\n",[169,312,314],{"class":171,"line":313},25,[169,315,316],{},"  config.pin_bck = 27;\n",[169,318,320],{"class":171,"line":319},26,[169,321,322],{},"  config.pin_ws = 26;\n",[169,324,326],{"class":171,"line":325},27,[169,327,328],{},"  config.pin_data = 25;\n",[169,330,332],{"class":171,"line":331},28,[169,333,334],{},"  \u002F\u002Fconfig.buffer_count = 8;\n",[169,336,338],{"class":171,"line":337},29,[169,339,340],{},"  \u002F\u002Fconfig.buffer_size = 1024;\n",[169,342,344],{"class":171,"line":343},30,[169,345,346],{},"  out.begin(config);\n",[169,348,350],{"class":171,"line":349},31,[169,351,176],{"emptyLinePlaceholder":175},[169,353,355],{"class":171,"line":354},32,[169,356,357],{},"  \u002F\u002F Setup sine wave\n",[169,359,361],{"class":171,"line":360},33,[169,362,363],{},"  sineWave.begin(info, N_B4);\n",[169,365,367],{"class":171,"line":366},34,[169,368,369],{},"  Serial.println(\"started...\");\n",[169,371,373],{"class":171,"line":372},35,[169,374,375],{},"}\n",[169,377,379],{"class":171,"line":378},36,[169,380,176],{"emptyLinePlaceholder":175},[169,382,384],{"class":171,"line":383},37,[169,385,386],{},"\u002F\u002F Arduino loop - copy sound to out\n",[169,388,390],{"class":171,"line":389},38,[169,391,392],{},"void loop() {\n",[169,394,396],{"class":171,"line":395},39,[169,397,398],{},"  copier.copy();\n",[169,400,402],{"class":171,"line":401},40,[169,403,404],{},"  if (random(1000) > 995) {\n",[169,406,408],{"class":171,"line":407},41,[169,409,410],{},"    sineWave.setFrequency(random(2000)+ 20);\n",[169,412,414],{"class":171,"line":413},42,[169,415,416],{},"  }\n",[169,418,420],{"class":171,"line":419},43,[169,421,375],{},[85,423,425],{"id":424},"using-esp32-stk-library-custom-write-routine","Using ESP32-STK library + custom write routine",[10,427,428,429,434],{},"The custom I2S write routine is very much based on ",[43,430,433],{"href":431,"rel":432},"https:\u002F\u002Fblog.infrasonicaudio.com\u002Freal-time-audio-synthesis-on-esp-32",[47],"this post by Infrasonic Audio",". I like it because it shows the process of block-writing quite transparently, and the author was kind to write quite a good explanation of the principles beside providing the code. What I love about the sample-by-sample approach, is that the DSP aspect is quite transparent, opposed to other APIs that intend to simulate the logic of analog modular synthesis. In this way of writing, the underlying math of sound is very tangible.",[10,436,437],{},"This approach could work for a beginner-level workshop, but the student has to be open to making lots of mistakes. Some of those mistakes are happy accidents too.",[10,439,440],{},"Some aspects of sound synthesis can be quite frustrating, though; such as performance matters and consequently fixed-point math; buffer underruns, etc. I think this approach is most suitable for an MCU that features floating point calculation hardware; so that users can work with the math they are familiar with.",[159,442,444],{"className":161,"code":443,"language":163,"meta":164,"style":164},"\u002F**\n * Sourced from \n * https:\u002F\u002Fblog.infrasonicaudio.com\u002Freal-time-audio-synthesis-on-esp-32\n*\u002F\n\u002F\u002F Includes\n#include \"driver\u002Fi2s.h\"                       \u002F\u002F Library of I2S routines, comes with ESP32 standard install\nstatic const i2s_port_t i2s_num = I2S_NUM_0;  \u002F\u002F i2s port number\n\n#define SAMPLE_RATE (44100)\n\n#define DMA_BUF_LEN (32)\n#define DMA_NUM_BUF (2)\n#define TWOPI (6.28318531f)\n#define PHASE_INC (TWOPI * WAVE_FREQ_HZ \u002F SAMPLE_RATE)\n\u002F\u002F STK instrument lib\n#include \"StkAll.h\"\n\nusing namespace stk;\n\nClarinet clarinet(440);\nVoicer voicer;\nint group = 0;\nfloat amplitude = 64;\n\nStkFrames output(20, 1);\nint note = 22; \u002F\u002F starting midi note\n\n\u002F\u002F Output buffer (2ch interleaved)\nstatic uint16_t out_buf[DMA_BUF_LEN * 2];\n\n\u002F\u002F I2S configuration structures\n\nstatic const i2s_config_t i2s_config = {\n  .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),\n  .sample_rate = 44100,\n  \u002F\u002F.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,\u002F\u002F also works on UDA1334\n  .bits_per_sample = I2S_BITS_PER_SAMPLE_24BIT,\n  \u002F\u002F.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,\u002F\u002F also works on UDA1334\n  .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,\n  \u002F\u002F.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),\n  .communication_format = I2S_COMM_FORMAT_STAND_MSB,\n  .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,  \u002F\u002F high interrupt priority\n  .dma_buf_count = DMA_NUM_BUF,\n  .dma_buf_len = DMA_BUF_LEN,\n  .use_apll = 0,\n  .tx_desc_auto_clear = true,\n  .fixed_mclk = -1\n};\n\n\nstatic const i2s_pin_config_t pin_config = {\n  .bck_io_num = 27,                 \u002F\u002F The bit clock connectiom, goes to pin 27 of ESP32\n  .ws_io_num = 26,                  \u002F\u002F Word select, also known as word select or left right clock\n  .data_out_num = 25,               \u002F\u002F Data out from the ESP32, connect to DIN on 38357A\n  .data_in_num = I2S_PIN_NO_CHANGE  \u002F\u002F we are not interested in I2S data into the ESP32\n};\n\n\u002F\u002F synth\n\nfloat noteToFrequency(uint8_t x) {\n  int a = 440;  \u002F\u002Ffrequency of A (coomon value is 440Hz)\n  return (a \u002F 32) * pow(2, ((x - 9) \u002F 12));\n}\n\n\nvoid setup() {\n  i2s_driver_install(i2s_num, &i2s_config, 0, NULL);  \u002F\u002F ESP32 will allocated resources to run I2S\n  i2s_set_pin(i2s_num, &pin_config);\n  \u002F\u002F Highest possible priority for realtime audio task\n  xTaskCreate(audio_task, \"audio\", 1024, NULL, configMAX_PRIORITIES - 1, NULL);\n\n  Serial.begin(115200);\n\n  Stk::setSampleRate(44100.0);\n  voicer.addInstrument(&clarinet, group);\n  \n}\n\nvoid loop() {\n  \u002F\u002Finput.tick(output);\n  note += rand() % 10 - 5;\n  \u002F\u002Ffloat frequency = noteToFrequency(note);\n  unsigned long metro = floor(millis() \u002F 310);\n  if(metro % 2 == 0) {\n    voicer.noteOff(note, amplitude, group);\n  } else {\n    voicer.noteOn(note, amplitude, group);\n  }\n}\n\n\n\u002F\u002F Fill the output buffer and write to I2S DMA\nstatic void write_buffer() {\n  float samp = 0.0f;\n  size_t bytes_written;\n\n  \u002F\u002Fi2s_write(i2s_num, output, output.size(), &bytes_written, portMAX_DELAY);\n\n  for (int i = 0; i \u003C DMA_BUF_LEN; i++) {\n    samp = voicer.tick();\n    \u002F\u002F Scale to 8-bit integer range\n    \u002F\u002F samp *= 255.0f;\n    \u002F\u002F or??\n    samp *= 1024.0f;\n    \u002F\u002F Shift to MSB of 16-bit int for internal DAC\n    out_buf[i * 2] = out_buf[i * 2 + 1] = (uint16_t)samp \u003C\u003C 4;\n    \u002F\u002Fout_buf[i * 2] = out_buf[i * 2 + 1] = (uint16_t)samp \u003C\u003C 8;\n  }\n  \u002F\u002F Write with max delay. We want to push buffers as fast as we\n  \u002F\u002F can into DMA memory. If DMA memory isn't transmitted yet this\n  \u002F\u002F will yield the task until the interrupt fires when DMA buffer has\n  \u002F\u002F space again. If we aren't keeping up with the real-time deadline,\n  \u002F\u002F audio will glitch and the task will completely consume the CPU,\n  \u002F\u002F not allowing any task switching interrupts to be processed.\n  i2s_write(i2s_num, out_buf, sizeof(out_buf), &bytes_written, portMAX_DELAY);\n\n\n  \u002F\u002F You could put a taskYIELD() here to ensure other tasks always have a chance to run.\n  \u002F\u002F taskYIELD();\n}\n\nstatic void audio_task(void *userData) {\n  while (1) {\n    write_buffer();\n  }\n}\n",[166,445,446,450,455,460,465,470,478,483,487,492,496,501,506,511,516,521,526,530,535,539,544,549,554,559,563,568,573,577,582,587,591,596,600,605,610,615,620,625,630,635,640,645,650,655,661,667,673,679,685,690,695,701,707,713,719,725,730,735,741,746,752,758,764,769,774,779,785,791,797,803,809,814,820,825,831,837,843,848,853,858,864,870,876,882,888,894,900,906,911,916,921,926,932,938,944,950,955,961,966,972,978,984,990,996,1002,1008,1014,1020,1025,1031,1037,1043,1049,1055,1061,1067,1072,1077,1083,1089,1094,1099,1105,1111,1117,1122],{"__ignoreMap":164},[169,447,448],{"class":171,"line":172},[169,449,182],{},[169,451,452],{"class":171,"line":179},[169,453,454],{}," * Sourced from \n",[169,456,457],{"class":171,"line":185},[169,458,459],{}," * https:\u002F\u002Fblog.infrasonicaudio.com\u002Freal-time-audio-synthesis-on-esp-32\n",[169,461,462],{"class":171,"line":191},[169,463,464],{},"*\u002F\n",[169,466,467],{"class":171,"line":197},[169,468,469],{},"\u002F\u002F Includes\n",[169,471,472,475],{"class":171,"line":203},[169,473,474],{},"#include \"driver\u002Fi2s.h\"",[169,476,477],{},"                       \u002F\u002F Library of I2S routines, comes with ESP32 standard install\n",[169,479,480],{"class":171,"line":209},[169,481,482],{},"static const i2s_port_t i2s_num = I2S_NUM_0;  \u002F\u002F i2s port number\n",[169,484,485],{"class":171,"line":215},[169,486,176],{"emptyLinePlaceholder":175},[169,488,489],{"class":171,"line":220},[169,490,491],{},"#define SAMPLE_RATE (44100)\n",[169,493,494],{"class":171,"line":226},[169,495,176],{"emptyLinePlaceholder":175},[169,497,498],{"class":171,"line":231},[169,499,500],{},"#define DMA_BUF_LEN (32)\n",[169,502,503],{"class":171,"line":237},[169,504,505],{},"#define DMA_NUM_BUF (2)\n",[169,507,508],{"class":171,"line":243},[169,509,510],{},"#define TWOPI (6.28318531f)\n",[169,512,513],{"class":171,"line":249},[169,514,515],{},"#define PHASE_INC (TWOPI * WAVE_FREQ_HZ \u002F SAMPLE_RATE)\n",[169,517,518],{"class":171,"line":255},[169,519,520],{},"\u002F\u002F STK instrument lib\n",[169,522,523],{"class":171,"line":261},[169,524,525],{},"#include \"StkAll.h\"\n",[169,527,528],{"class":171,"line":266},[169,529,176],{"emptyLinePlaceholder":175},[169,531,532],{"class":171,"line":272},[169,533,534],{},"using namespace stk;\n",[169,536,537],{"class":171,"line":278},[169,538,176],{"emptyLinePlaceholder":175},[169,540,541],{"class":171,"line":284},[169,542,543],{},"Clarinet clarinet(440);\n",[169,545,546],{"class":171,"line":289},[169,547,548],{},"Voicer voicer;\n",[169,550,551],{"class":171,"line":295},[169,552,553],{},"int group = 0;\n",[169,555,556],{"class":171,"line":301},[169,557,558],{},"float amplitude = 64;\n",[169,560,561],{"class":171,"line":307},[169,562,176],{"emptyLinePlaceholder":175},[169,564,565],{"class":171,"line":313},[169,566,567],{},"StkFrames output(20, 1);\n",[169,569,570],{"class":171,"line":319},[169,571,572],{},"int note = 22; \u002F\u002F starting midi note\n",[169,574,575],{"class":171,"line":325},[169,576,176],{"emptyLinePlaceholder":175},[169,578,579],{"class":171,"line":331},[169,580,581],{},"\u002F\u002F Output buffer (2ch interleaved)\n",[169,583,584],{"class":171,"line":337},[169,585,586],{},"static uint16_t out_buf[DMA_BUF_LEN * 2];\n",[169,588,589],{"class":171,"line":343},[169,590,176],{"emptyLinePlaceholder":175},[169,592,593],{"class":171,"line":349},[169,594,595],{},"\u002F\u002F I2S configuration structures\n",[169,597,598],{"class":171,"line":354},[169,599,176],{"emptyLinePlaceholder":175},[169,601,602],{"class":171,"line":360},[169,603,604],{},"static const i2s_config_t i2s_config = {\n",[169,606,607],{"class":171,"line":366},[169,608,609],{},"  .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),\n",[169,611,612],{"class":171,"line":372},[169,613,614],{},"  .sample_rate = 44100,\n",[169,616,617],{"class":171,"line":378},[169,618,619],{},"  \u002F\u002F.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,\u002F\u002F also works on UDA1334\n",[169,621,622],{"class":171,"line":383},[169,623,624],{},"  .bits_per_sample = I2S_BITS_PER_SAMPLE_24BIT,\n",[169,626,627],{"class":171,"line":389},[169,628,629],{},"  \u002F\u002F.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,\u002F\u002F also works on UDA1334\n",[169,631,632],{"class":171,"line":395},[169,633,634],{},"  .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,\n",[169,636,637],{"class":171,"line":401},[169,638,639],{},"  \u002F\u002F.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),\n",[169,641,642],{"class":171,"line":407},[169,643,644],{},"  .communication_format = I2S_COMM_FORMAT_STAND_MSB,\n",[169,646,647],{"class":171,"line":413},[169,648,649],{},"  .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,  \u002F\u002F high interrupt priority\n",[169,651,652],{"class":171,"line":419},[169,653,654],{},"  .dma_buf_count = DMA_NUM_BUF,\n",[169,656,658],{"class":171,"line":657},44,[169,659,660],{},"  .dma_buf_len = DMA_BUF_LEN,\n",[169,662,664],{"class":171,"line":663},45,[169,665,666],{},"  .use_apll = 0,\n",[169,668,670],{"class":171,"line":669},46,[169,671,672],{},"  .tx_desc_auto_clear = true,\n",[169,674,676],{"class":171,"line":675},47,[169,677,678],{},"  .fixed_mclk = -1\n",[169,680,682],{"class":171,"line":681},48,[169,683,684],{},"};\n",[169,686,688],{"class":171,"line":687},49,[169,689,176],{"emptyLinePlaceholder":175},[169,691,693],{"class":171,"line":692},50,[169,694,176],{"emptyLinePlaceholder":175},[169,696,698],{"class":171,"line":697},51,[169,699,700],{},"static const i2s_pin_config_t pin_config = {\n",[169,702,704],{"class":171,"line":703},52,[169,705,706],{},"  .bck_io_num = 27,                 \u002F\u002F The bit clock connectiom, goes to pin 27 of ESP32\n",[169,708,710],{"class":171,"line":709},53,[169,711,712],{},"  .ws_io_num = 26,                  \u002F\u002F Word select, also known as word select or left right clock\n",[169,714,716],{"class":171,"line":715},54,[169,717,718],{},"  .data_out_num = 25,               \u002F\u002F Data out from the ESP32, connect to DIN on 38357A\n",[169,720,722],{"class":171,"line":721},55,[169,723,724],{},"  .data_in_num = I2S_PIN_NO_CHANGE  \u002F\u002F we are not interested in I2S data into the ESP32\n",[169,726,728],{"class":171,"line":727},56,[169,729,684],{},[169,731,733],{"class":171,"line":732},57,[169,734,176],{"emptyLinePlaceholder":175},[169,736,738],{"class":171,"line":737},58,[169,739,740],{},"\u002F\u002F synth\n",[169,742,744],{"class":171,"line":743},59,[169,745,176],{"emptyLinePlaceholder":175},[169,747,749],{"class":171,"line":748},60,[169,750,751],{},"float noteToFrequency(uint8_t x) {\n",[169,753,755],{"class":171,"line":754},61,[169,756,757],{},"  int a = 440;  \u002F\u002Ffrequency of A (coomon value is 440Hz)\n",[169,759,761],{"class":171,"line":760},62,[169,762,763],{},"  return (a \u002F 32) * pow(2, ((x - 9) \u002F 12));\n",[169,765,767],{"class":171,"line":766},63,[169,768,375],{},[169,770,772],{"class":171,"line":771},64,[169,773,176],{"emptyLinePlaceholder":175},[169,775,777],{"class":171,"line":776},65,[169,778,176],{"emptyLinePlaceholder":175},[169,780,782],{"class":171,"line":781},66,[169,783,784],{},"void setup() {\n",[169,786,788],{"class":171,"line":787},67,[169,789,790],{},"  i2s_driver_install(i2s_num, &i2s_config, 0, NULL);  \u002F\u002F ESP32 will allocated resources to run I2S\n",[169,792,794],{"class":171,"line":793},68,[169,795,796],{},"  i2s_set_pin(i2s_num, &pin_config);\n",[169,798,800],{"class":171,"line":799},69,[169,801,802],{},"  \u002F\u002F Highest possible priority for realtime audio task\n",[169,804,806],{"class":171,"line":805},70,[169,807,808],{},"  xTaskCreate(audio_task, \"audio\", 1024, NULL, configMAX_PRIORITIES - 1, NULL);\n",[169,810,812],{"class":171,"line":811},71,[169,813,176],{"emptyLinePlaceholder":175},[169,815,817],{"class":171,"line":816},72,[169,818,819],{},"  Serial.begin(115200);\n",[169,821,823],{"class":171,"line":822},73,[169,824,176],{"emptyLinePlaceholder":175},[169,826,828],{"class":171,"line":827},74,[169,829,830],{},"  Stk::setSampleRate(44100.0);\n",[169,832,834],{"class":171,"line":833},75,[169,835,836],{},"  voicer.addInstrument(&clarinet, group);\n",[169,838,840],{"class":171,"line":839},76,[169,841,842],{},"  \n",[169,844,846],{"class":171,"line":845},77,[169,847,375],{},[169,849,851],{"class":171,"line":850},78,[169,852,176],{"emptyLinePlaceholder":175},[169,854,856],{"class":171,"line":855},79,[169,857,392],{},[169,859,861],{"class":171,"line":860},80,[169,862,863],{},"  \u002F\u002Finput.tick(output);\n",[169,865,867],{"class":171,"line":866},81,[169,868,869],{},"  note += rand() % 10 - 5;\n",[169,871,873],{"class":171,"line":872},82,[169,874,875],{},"  \u002F\u002Ffloat frequency = noteToFrequency(note);\n",[169,877,879],{"class":171,"line":878},83,[169,880,881],{},"  unsigned long metro = floor(millis() \u002F 310);\n",[169,883,885],{"class":171,"line":884},84,[169,886,887],{},"  if(metro % 2 == 0) {\n",[169,889,891],{"class":171,"line":890},85,[169,892,893],{},"    voicer.noteOff(note, amplitude, group);\n",[169,895,897],{"class":171,"line":896},86,[169,898,899],{},"  } else {\n",[169,901,903],{"class":171,"line":902},87,[169,904,905],{},"    voicer.noteOn(note, amplitude, group);\n",[169,907,909],{"class":171,"line":908},88,[169,910,416],{},[169,912,914],{"class":171,"line":913},89,[169,915,375],{},[169,917,919],{"class":171,"line":918},90,[169,920,176],{"emptyLinePlaceholder":175},[169,922,924],{"class":171,"line":923},91,[169,925,176],{"emptyLinePlaceholder":175},[169,927,929],{"class":171,"line":928},92,[169,930,931],{},"\u002F\u002F Fill the output buffer and write to I2S DMA\n",[169,933,935],{"class":171,"line":934},93,[169,936,937],{},"static void write_buffer() {\n",[169,939,941],{"class":171,"line":940},94,[169,942,943],{},"  float samp = 0.0f;\n",[169,945,947],{"class":171,"line":946},95,[169,948,949],{},"  size_t bytes_written;\n",[169,951,953],{"class":171,"line":952},96,[169,954,176],{"emptyLinePlaceholder":175},[169,956,958],{"class":171,"line":957},97,[169,959,960],{},"  \u002F\u002Fi2s_write(i2s_num, output, output.size(), &bytes_written, portMAX_DELAY);\n",[169,962,964],{"class":171,"line":963},98,[169,965,176],{"emptyLinePlaceholder":175},[169,967,969],{"class":171,"line":968},99,[169,970,971],{},"  for (int i = 0; i \u003C DMA_BUF_LEN; i++) {\n",[169,973,975],{"class":171,"line":974},100,[169,976,977],{},"    samp = voicer.tick();\n",[169,979,981],{"class":171,"line":980},101,[169,982,983],{},"    \u002F\u002F Scale to 8-bit integer range\n",[169,985,987],{"class":171,"line":986},102,[169,988,989],{},"    \u002F\u002F samp *= 255.0f;\n",[169,991,993],{"class":171,"line":992},103,[169,994,995],{},"    \u002F\u002F or??\n",[169,997,999],{"class":171,"line":998},104,[169,1000,1001],{},"    samp *= 1024.0f;\n",[169,1003,1005],{"class":171,"line":1004},105,[169,1006,1007],{},"    \u002F\u002F Shift to MSB of 16-bit int for internal DAC\n",[169,1009,1011],{"class":171,"line":1010},106,[169,1012,1013],{},"    out_buf[i * 2] = out_buf[i * 2 + 1] = (uint16_t)samp \u003C\u003C 4;\n",[169,1015,1017],{"class":171,"line":1016},107,[169,1018,1019],{},"    \u002F\u002Fout_buf[i * 2] = out_buf[i * 2 + 1] = (uint16_t)samp \u003C\u003C 8;\n",[169,1021,1023],{"class":171,"line":1022},108,[169,1024,416],{},[169,1026,1028],{"class":171,"line":1027},109,[169,1029,1030],{},"  \u002F\u002F Write with max delay. We want to push buffers as fast as we\n",[169,1032,1034],{"class":171,"line":1033},110,[169,1035,1036],{},"  \u002F\u002F can into DMA memory. If DMA memory isn't transmitted yet this\n",[169,1038,1040],{"class":171,"line":1039},111,[169,1041,1042],{},"  \u002F\u002F will yield the task until the interrupt fires when DMA buffer has\n",[169,1044,1046],{"class":171,"line":1045},112,[169,1047,1048],{},"  \u002F\u002F space again. If we aren't keeping up with the real-time deadline,\n",[169,1050,1052],{"class":171,"line":1051},113,[169,1053,1054],{},"  \u002F\u002F audio will glitch and the task will completely consume the CPU,\n",[169,1056,1058],{"class":171,"line":1057},114,[169,1059,1060],{},"  \u002F\u002F not allowing any task switching interrupts to be processed.\n",[169,1062,1064],{"class":171,"line":1063},115,[169,1065,1066],{},"  i2s_write(i2s_num, out_buf, sizeof(out_buf), &bytes_written, portMAX_DELAY);\n",[169,1068,1070],{"class":171,"line":1069},116,[169,1071,176],{"emptyLinePlaceholder":175},[169,1073,1075],{"class":171,"line":1074},117,[169,1076,176],{"emptyLinePlaceholder":175},[169,1078,1080],{"class":171,"line":1079},118,[169,1081,1082],{},"  \u002F\u002F You could put a taskYIELD() here to ensure other tasks always have a chance to run.\n",[169,1084,1086],{"class":171,"line":1085},119,[169,1087,1088],{},"  \u002F\u002F taskYIELD();\n",[169,1090,1092],{"class":171,"line":1091},120,[169,1093,375],{},[169,1095,1097],{"class":171,"line":1096},121,[169,1098,176],{"emptyLinePlaceholder":175},[169,1100,1102],{"class":171,"line":1101},122,[169,1103,1104],{},"static void audio_task(void *userData) {\n",[169,1106,1108],{"class":171,"line":1107},123,[169,1109,1110],{},"  while (1) {\n",[169,1112,1114],{"class":171,"line":1113},124,[169,1115,1116],{},"    write_buffer();\n",[169,1118,1120],{"class":171,"line":1119},125,[169,1121,416],{},[169,1123,1125],{"class":171,"line":1124},126,[169,1126,375],{},[85,1128,1130],{"id":1129},"paul-stoffregen-teensy-audio","Paul Stoffregen Teensy Audio",[10,1132,1133],{},"Paul has created an incredible audio library which even allows users to create synths with a graphical tool. The pricing + shipping + availability is quite a problem, though; because I often find his store out of stock. Seeedstudio has created a fork of his library, allegedly compatible with more boards. I could not get it to work though. Still, I find that it's a crappy move on behalf of Seeedstudio unless they've paid Paul a good sum of money for using his work like that.",[10,1135,1136,1137,1142,1143,1148,1149],{},"If you live in a more first-world kind of place, its' definitively a good option though. ",[43,1138,1141],{"href":1139,"rel":1140},"https:\u002F\u002Fgithub.com\u002FPaulStoffregen\u002FAudio",[47],"The repo"," and ",[43,1144,1147],{"href":1145,"rel":1146},"https:\u002F\u002Fwww.pjrc.com\u002Fteensy\u002Ftd_libs_Audio.html",[47],"more info about the lib\u002Fhardware here",". The kits that might've become out of stock on his store, might be available through Mouser or other electronic shops. There is a single board computer project using Teensy that could make the hardware setup part of the workshop trivial: ",[43,1150,1153],{"href":1151,"rel":1152},"https:\u002F\u002Fblog.tindie.com\u002F2024\u002F08\u002Fteensy-based-single-board-computer\u002F",[47],"Tindie: Teensy based single board computer",[56,1155,1157],{"id":1156},"built-in-dacs","Built-in DACs",[10,1159,1160],{},"I don't understand too well why MCUs while having so many peripherals, they so rarely integrate a good DAC. Or perhaps it's simply what MCUs are typically chosen by brands to make into arduino-compatible boards. I think it might have to do with how complicated is to orchestrate audio synthesis with the control changes, and even more so if there is an audio input.",[10,1162,1163,1164,1169,1170,1175],{},"There is this beautiful ",[43,1165,1168],{"href":1166,"rel":1167},"https:\u002F\u002Fnote.com\u002Fsolder_state\u002Fn\u002Fn76a6b0a4a5d6",[47],"example by Hagiwo(\u002Fハギヲ)",". He used a ",[43,1171,1174],{"href":1172,"rel":1173},"https:\u002F\u002Fwww.seeedstudio.com\u002FSeeed-XIAO-RA4M1-p-5943.html?utm_source=seeed&utm_medium=wiki&utm&product=xiao-ra4m1",[47],"tiny MCU board by SeeedStudio"," which features a 12 bit DAC and created a tone generator; which I would expect could be easily turned into a more complete synth; as these MCUs are quite powerful. He does go into pretty bare-metal aspects, though, which makes the concept a bit less friendly in the perspective of a \"first synthesizer\" experience.",[1177,1178,1180],"h1",{"id":1179},"conclusions","Conclusions",[56,1182,1184],{"id":1183},"best-option-in-this-context","Best option in this context",[10,1186,1187],{},"The way to go, like I knew from the beginning, is Mozzi with some Arduino ATMEGA chip. The other approaches would be excellent candidates for a more advanced, or longer workshop. The price tag on the first workshop is much lower because it requires possibly less expensive materials; the expected lesser prior knowledge and involvement from the students will require the workshop design to target a narrow spectrum of theory; being the main objective to hook the people's interest by creating something satisfying to play with.",[56,1189,1191],{"id":1190},"nonconventionality","Nonconventionality",[10,1193,1194],{},"In order to produce a satisfying result, the target design almost must be non-conventional in sound and\u002For interaction, because a conventional design will lead to comparisons in the realm of synths that exist on the market. Also, conventional interaction is tricky to attain without expensive prototyping tools and advanced design techniques: things like keyboards, velocity sensitivity, parameter modulations; are all minimally expected on a conventional synthesizer, yet very difficult to attain as beginners.",[56,1196,1198],{"id":1197},"next-steps","Next steps",[10,1200,1201],{},"The next step for this workshop design process is to define what exactly are interesting yet simple synthesizers to aim for as result of the workshop; also to define a scope of basic DSP related knowledge and how these contents are taught through the experience of building the synthesizer. Also, a scope of possible customizations must be defined and included in the contents; so that the 'diy' aspect of participating on this workshop makes sense.",[1203,1204,1205],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":164,"searchDepth":179,"depth":179,"links":1207},[1208,1209,1215,1216,1217,1218],{"id":58,"depth":179,"text":48},{"id":67,"depth":179,"text":68,"children":1210},[1211,1212,1213,1214],{"id":87,"depth":185,"text":88},{"id":150,"depth":185,"text":151},{"id":424,"depth":185,"text":425},{"id":1129,"depth":185,"text":1130},{"id":1156,"depth":179,"text":1157},{"id":1183,"depth":179,"text":1184},{"id":1190,"depth":179,"text":1191},{"id":1197,"depth":179,"text":1198},[1220],"blog","2024-10-01",null,"md",{},"\u002Fposts\u002F2024-10-01-digital-synth-factibility",{"title":5,"description":12},"posts\u002F2024-10-01-digital-synth-factibility",[1229,1230,1231],"area_workshops","tool_arduino","process-notes",false,"SUML99bFesCzV1eFZZ4vqG9Rrv2mA1APVexm9g5OR_8",1792083598492]