{"id":19735977,"url":"https://github.com/janwilczek/adc24-workshop","last_synced_at":"2025-02-27T21:43:39.499Z","repository":{"id":261471531,"uuid":"868050920","full_name":"JanWilczek/adc24-workshop","owner":"JanWilczek","description":"Companion repository to the Audio Developer Conference 2024 \"DSP in Practice\" workshop","archived":false,"fork":false,"pushed_at":"2024-11-11T14:07:19.000Z","size":71,"stargazers_count":7,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-10T18:52:52.145Z","etag":null,"topics":["audio-effect","audio-plugin","audio-programming","c-plus-plus","dsp","python","signal-processing"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JanWilczek.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-05T10:52:10.000Z","updated_at":"2025-01-09T19:59:38.000Z","dependencies_parsed_at":"2024-11-06T18:54:32.770Z","dependency_job_id":null,"html_url":"https://github.com/JanWilczek/adc24-workshop","commit_stats":null,"previous_names":["janwilczek/adc24-workshop"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JanWilczek%2Fadc24-workshop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JanWilczek%2Fadc24-workshop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JanWilczek%2Fadc24-workshop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JanWilczek%2Fadc24-workshop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JanWilczek","download_url":"https://codeload.github.com/JanWilczek/adc24-workshop/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241062518,"owners_count":19902903,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["audio-effect","audio-plugin","audio-programming","c-plus-plus","dsp","python","signal-processing"],"created_at":"2024-11-12T01:04:29.288Z","updated_at":"2025-02-27T21:43:39.480Z","avatar_url":"https://github.com/JanWilczek.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DSP in Practice Workshop Repository\n\nWorkshop hosted at the Audio Developer Conference 2024 in Bristol.\n\n[Slides](docs/ADC24_Workshop_Slides_DSP_In_Practice.pdf)\n\n## Getting started\n\nFollow these instructions **before** attending the workshop. There is high risk that because of low WiFi bandwidth at the conference venue, it will take too long or be impossible to set it up on the spot.\n\n### General prerequisites\n\nYou need to have `git` installed and in your PATH. Here are the commands that we will use in this workshop:\n\n```bash\ngit checkout \u003cbranch-name\u003e # check out the branch with the given name\ngit checkout -b \u003cnew-branch-name\u003e # create a new branch and check it out\ngit status # show the status of the working tree\ngit diff # show the difference between the working tree and the last commit\ngit add \u003cpath-to-file\u003e # stage a file (especially important for untracked files)\ngit commit -a -m \"\u003ccommit-message\u003e\" # commit all changes with a specific message\n```\n\nWAV files and PNG files require `git-lfs`. It should be bundled with your `git` installation but you may need to manually initialize it inside the repository. After cloning this repository, run\n\n```bash\ngit lfs install\ngit lfs fetch\ngit lfs checkout\n```\n\nYou can try playing back files in the _data_ folder to check if it worked correctly.\n\nThe complete solution to all the tasks are present on the `main` branch. If at any time during the workshop you need to check the reference solution, compare the difference between your working tree and the `main` branch.\n\n```bash\ngit diff main\n```\n\nIn Visual Studio Code, there's a wonderful GitLens plugin that allows you to visually compare against the `main` branch via \"GitLens: Compare Working Tree with...\" command.\n\n### Python environment setup\n\nFor this workshop, you need to have Python 3 installed and in your PATH. Code was tested with Python 3.11 and 3.12.\n\nIt's most comfortable to work in Python using `venv` (virtual environment).\n\n```bash\n# create virtual environment\npython3 -m venv venv\n\n# activate it\nsource venv/bin/activate # macOS, Linux\n.\\venv\\bin\\Scripts\\Acitvate.ps1 # Windows\n\n# install the dependencies within the environment\npip install .\n```\n\nNow, you should be able to run Python tests:\n\n```bash\npython -m unittest py/test/test_flanger.py\n```\n\nYou also should be able to run the audio processing script:\n\n\n```bash\npython py/main.py data/saw200.0Hz5.0s.wav\n```\n\n### C++ setup\n\nThis workshop requires the following tools installed on your machine and available in your PATH:\n\n- a **working C++ compiler** that supports C++ 20,\n- CMake 3.22 or newer,\n- a build system like ninja (recommended), MSBuild (bundled with Visual Studio), xcode-build (bundled with Xcode), or make (UNIX Makefiles)\n\nOnce this is in place, you can run classic CMake commands:\n\n```bash\n# Downloads C++ dependencies and generates the project for ninja (check CMakePresets.json for other generators)\ncmake --preset default\n\n# Builds the project\ncmake --build --preset default\n```\n\nC++ code in this repository is based on my [`audio-plugin-template` repository](https://github.com/JanWilczek/audio-plugin-template).\n\n#### Mac users\n\nThe repostitory works with Xcode 16. Older versions of Xcode may be problematic.\n\n#### Windows users\n\nOn Windows, to have the compiler on your PATH, you need to run a batch script. Assuming, you have Visual Studio 2022 installed, you can run the following command to initialize C++ environment in your shell.\n\n```batch\n\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat\"\n```\n\n#### Linux users\n\nTo create a Python virtual environment, I needed to install `python3.12-venv` package with\n\n```bash\nsudo apt install python3.12-venv\n```\n\nBefore generating the project above, you need to install [JUCE dependencies](https://github.com/juce-framework/JUCE/blob/master/docs/Linux%20Dependencies.md) with the following command:\n\n```bash\nsudo apt update\nsudo apt install libasound2-dev libjack-jackd2-dev \\\n    ladspa-sdk \\\n    libcurl4-openssl-dev  \\\n    libfreetype-dev libfontconfig1-dev \\\n    libx11-dev libxcomposite-dev libxcursor-dev libxext-dev libxinerama-dev libxrandr-dev libxrender-dev \\\n    libwebkit2gtk-4.1-dev \\\n    libglu1-mesa-dev mesa-common-dev\n```\n\n## Workshop tasks\n\nThis workshop consists of several tasks in a logical progression. The goal is to build a working flanger plugin in C++ using the JUCE C++ framework.\n\nYou should start each task by checking out the appropriate branch (`task1`, `task2`, etc.).\n\n### Task 1 (`task1` branch): Design\n\n```bash\ngit checkout task1\n```\n\nGiven this DSP block diagram of a flanger plugin,\n\n![Workshop Flanger Annotated](docs/img/WorkshopFlangerAnnotated.png)\n_Figure 1. Flanger DSP diagram._\n\nwrite down flanger update equations.\n\nLegend:\n\n- $x[n]$ is the input signal\n- $y[n]$ is the output signal\n- $x_h[n]$ is a helper signal (used for convenience)\n- $D$ is the length of the delay line\n- $\\text{feedforward}$, $\\text{feedback}$, and $\\text{blend}$ are coefficients (all equal to 0.7 for a flanger)\n- $s_\\text{LFO,unipolar}[n]$ is the unipolar LFO signal (a sine in the [0, 1] range)\n- $m$ is the modulated delay value\n- $x_h[n-D/2]$ denotes the helper signal delayed by $D/2$ samples\n\n\u003e [!NOTE]\n\u003e $m$ depends on $n$ but I write $m$ instead of $m[n]$ for simplicity. If you want, you can make the dependence explicit 😉\n\n- [ ] Write down the equation for $y[n]$ (the output).\n- [ ] Write down the equation for $x_h[n]$ (the helper signal).\n- [ ] Write down the equation for the value of the variable delay $m$ at the $n$-th processed sample.\n\nThese equations are the basis of the prototype.\n\n### Task 2 (`task2` branch): Prototype\n\n```bash\ngit checkout task2\n```\n\n- [ ] Run `python py/main.py data/saw200.0Hz5.0s.wav` and check that audibly the output signal is the same as the input signal\n    - That's because the flanger returns the input sample in the `process_sample()` function.\n- [ ] Inspect the spectrograms generated in the _output_ folder. Are they identical visually?\n- [ ] Properly initialize the `Flanger` class instance\n    - [ ] Set `max_delay` property of the `Flanger` to 2 milliseconds in samples\n    - [ ] Instantiate the `delay_line` property as a `FractionalDelayLine` instance with `max_delay`\n    - [ ] Compute $D/2$: half of `max_delay` and store in `middle_delay` property\n    - [ ] Initialize `feedback`, `feedforward`, and `blend` properties to 0.7\n- [ ] Implement flanger update equations without the LFO in `process_sample()` in _flanger.py_\n    - [ ] Write down your update equations in the `process_sample()` function as comments\n    - [ ] Calculate the value of the helper signal sample $x_h[n]$\n    - [ ] Calculate the output sample $y[n]$ (use a fixed delay instead of a modulated delay for now)\n    - [ ] \"Update the buffers\": write $x_h[n]$ into the delay line\n    - [ ] Return the output sample from the function\n    - [ ] Run `python py/main.py data/saw200.0Hz5.0s.wav` and check if you hear a spectral coloration of the input.\n    - [ ] Inspect the spectrograms generated in the _output_ folder. How is the spectrogram of the output different from the spectrogram of the input? Was it expected?\n- [ ] Add an LFO\n    - [ ] Instantiate an `LFO` instance in the `Flanger` constructor\n    - [ ] Immediately set its freuqency to 0.1 Hz\n    - [ ] In `process_sample()`, `get_next_value()` of the LFO and transform it to unipolar [0, 1] range\n    - [ ] Calculate the delay value of the modulated tap for the current sample\n    - [ ] Replace the previously fixed delay value in output sample computation with the LFO-modulated one\n    - [ ] Run `python py/main.py data/saw200.0Hz5.0s.wav` and check if you hear the influence of the LFO (the signal slowly changes its timbre)\n    - [ ] Inspect the spectrograms generated in the _output_ folder. How is the spectrogram of the output different from the \"fixed modulated delay\" version? Was it expected?\n\n### Task 3 (`task3` branch): Implementation part 1\n\n```bash\ngit checkout task3\n```\n\nIn this part, your goal is to create a basic working implementation of the flanger plugin. For this purpose, we'll reuse my [`audio-plugin-template` repository](https://github.com/JanWilczek/audio-plugin-template).\n\nThis is the class diagram depicting the connections between the classes in a completed project (except handling multiple channels).\n\n![Plugin class diagram](docs/img/PluginClassDiagram.png)\n_Figure 2. Plugin class diagram._\n\nI intentionally set a high warning level and enabled \"set warnings as errors\" compilation option. As C++ developers we should utilize to the fullest what the compiler provides because compilers are highly specialized expert pieces of software. In plugin development we have the luxury of starting often with green-field projects (i.e., create a codebase from scratch). Starting out is the best time to enable said options. Every time you run into a compilation error, try to understand what compiler tells you and fix the warning in code.\n\nIn this part, you can put all your code into _Flanger.h_.\n\n- [ ] Generate and compile the C++ project as indicated in the [getting started section](#getting-started). Does the project build successfully? Can you see the \"installation\" message?\n- [ ] Run the end-2-end test with `ctest --preset \u003cused-preset\u003e`. Is the output file the same as the input file?\n- [ ] Run the digital audio workstation (DAW) of your choice. Can you put the \"Workshop Flanger\" plugin on a track? Does it leave the input unaltered?\n- [ ] Add feedforward, feedback, and blend coefficients as class members of `Flanger`\n- [ ] Add `FractionalDelayLine` member to the `Flanger`\n- [ ] Implement `Flanger::reset()` where you should reset relevant member(s).\n- [ ] Add `maxDelay_` and `middleDelay_` members to the `Flanger` class (analogous to the Python class)\n- [ ] Initialize them in `prepare()` with the same lengths in samples as in Python\n- [ ] Implement update equations in `processSample()` as you did in Python. Use a fixed delay tap instead of a modulated delay tap (exactly as the initial version in Python); you'll add LFO later\n- [ ] Build your project\n- [ ] Run the end-2-end unit test with `ctest --preset \u003cused-preset\u003e` again. Can you hear a spectral coloration in the output file? Is it the same as in the initial (fixed-delay) Python implementation?\n\n### Task 4 (`task4` branch): Implementation part 2\n\n```bash\ngit checkout task4\n```\n\nIn the final part of this workshop, you will add the LFO and expose its frequency as a plugin parameter using JUCE APIs. You can implement your own LFO class, however, for simplicity, we'll use JUCE's `juce::dsp::Oscillator`. We'll expose the parameter in the GUI using `juce::GenericAudioProcessorEditor`. Finally, we'll add stereo processing using `juce::dsp::ProcessorDuplicator`; that's why we used `prepare()`/`process()`/`reset()` member function trio.\n\nUsually, we would implement parameter smoothing in this part but `juce::dsp::Oscillator` does that for us already.\n\n\u003e [!NOTE]\n\u003e `juce::dsp::Oscillator` adds the generated sample to its input. Thus, to obtain the generated sample, we must call `Oscillator`'s `processSample()` member function with `0`, i.e., `const auto generatedSample = myOscillatorInstance_.processSample(0)`.\n\n- [ ] Add an LFO\n    - [ ] Add a `juce::dsp::Oscillator` member and initialize it with a sine oscillator\n    - [ ] `prepare()` it in `Flanger::prepare()`\n    - [ ] `reset()` it in `Flanger::reset()`\n    - [ ] Set its frequency to 0.1 Hz in `Flanger::prepare()`\n    - [ ] Compute the unipolar LFO value in `Flanger::processSample()`\n    - [ ] Calculate the modulated delay value and use it in the output sample calculation in `Flanger::processSample()`\n    - [ ] Compile the project\n    - [ ] Run the end-2-end unit test with `ctest --preset \u003cused-preset\u003e` again. Can you hear the influence of the LFO in the output file? Is it the same as in the final (modulated-delay) Python implementation?\n- [ ] Make parameters (LFO frequency) adjustable\n    - [ ] Create a `juce::AudioParameterValueTreeState apvts_` member in `PluginProcessor`\n    - [ ] Create a `juce::AudioParameterFloat` instance upon `PluginProcessor` construction and add it to the `apvts_`; use the following parameters:\n        * ID \"flangerFrequencyHz\"\n        * version hint 1\n        * name \"LFO frequency\"\n        * range [0.1, 10] Hz with steps of 0.01\n        * default value 0.1 Hz\n        * label \"Hz\"\n    - [ ] Retrieve the pointer to the parameter (or to its raw value) and store it in a `PluginProcessor`'s member\n    - [ ] Create a `Flanger::Parameters` struct with a single `wolfsound::Frequency` member; this is to pass the LFO frequency to the flanger\n    - [ ] Initialize the member with 0.1\n    - [ ] Create a setter for the parameters, i.e., `Flanger::setParameters(const Parameters\u0026)` where you update the frequency of the `juce::dsp::Oscillator` instance\n    - [ ] Set flanger's parameters with this setter in `processBlock()` after retrieving the plugin parameter value\n- [ ] Return a `juce::GenericAudioProcessorEditor` in `PluginProcessor::createEditor()` (we're just working on the DSP part after all)\n- [ ] Compile the plugin\n- [ ] Run the plugin in the DAW. Can you adjust the LFO frequency and the change is audible?\n- [ ] Make the plugin stereo using JUCE’s `juce::dsp::ProcessorDuplicator`\n    - [ ] Change `PluginProcessor::flanger_` member type to `juce::dsp::ProcessorDuplicator\u003cFlanger\u003cSampleType\u003e, Flanger\u003cSampleType\u003e::Parameters\u003e`\n    - [ ] Add an alias in `Flanger::Parameters`: `using Ptr = std::shared_ptr\u003cParameters\u003e`\n    - [ ] Add a `Parameters::Ptr` member in `Flanger`\n    - [ ] Add an `explicit` constructor of `Flanger` that takes `Parameters::Ptr` as its only argument and stores it in the just added member (you may need to default the default constructor implementation)\n    - [ ] Call `setParameters(*parameters_)` in `process()` before the actual processing begins\n    - [ ] Instead of calling `flanger_.setParameters(newParameters)` in `processBlock()`, set the shared state with `*flanger_.state = newParameters`\n    - [ ] Compile the project\n    - [ ] Run `AudioProcessor.StereoTest` with `ctest --preset \u003cused-preset\u003e -R \"StereoTest\"`. Does it pass?\n    - [ ] In the DAW of your choice, load _data/saw200.0Hz5.0s_stereo.wav_ on a track and put the plugin on the same track. Are both channels processed in the same way? Are changes to LFO frequency reflected accordingly?\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjanwilczek%2Fadc24-workshop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjanwilczek%2Fadc24-workshop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjanwilczek%2Fadc24-workshop/lists"}