cinderwave 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ * text=auto eol=lf
2
+ *.png binary
3
+ *.wav binary
4
+ *.uf2 binary
@@ -0,0 +1,67 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ host:
11
+ name: host build + tests + demo render
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Install toolchain
17
+ run: sudo apt-get update && sudo apt-get install -y cmake g++
18
+
19
+ - name: Configure
20
+ run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
21
+
22
+ - name: Build
23
+ run: cmake --build build --config Release -j
24
+
25
+ - name: Run tests
26
+ run: ctest --test-dir build --output-on-failure
27
+
28
+ - name: Render demo pattern
29
+ run: ./build/cinderwave_render cinderwave_demo.wav
30
+
31
+ - name: Upload demo WAV
32
+ uses: actions/upload-artifact@v4
33
+ with:
34
+ name: cinderwave-demo-wav
35
+ path: cinderwave_demo.wav
36
+
37
+ firmware:
38
+ name: RP2040 firmware build (.uf2)
39
+ runs-on: ubuntu-latest
40
+ steps:
41
+ - uses: actions/checkout@v4
42
+
43
+ - name: Install ARM toolchain
44
+ run: >
45
+ sudo apt-get update &&
46
+ sudo apt-get install -y cmake gcc-arm-none-eabi
47
+ libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib
48
+ build-essential python3
49
+
50
+ - name: Fetch Pico SDK
51
+ run: |
52
+ git clone --depth 1 --branch 2.1.0 https://github.com/raspberrypi/pico-sdk.git "$HOME/pico-sdk"
53
+ cd "$HOME/pico-sdk" && git submodule update --init --depth 1
54
+
55
+ - name: Configure firmware
56
+ env:
57
+ PICO_SDK_PATH: /home/runner/pico-sdk
58
+ run: cmake -S firmware/rp2040 -B firmware/rp2040/build -DCMAKE_BUILD_TYPE=Release
59
+
60
+ - name: Build firmware
61
+ run: cmake --build firmware/rp2040/build -j
62
+
63
+ - name: Upload UF2
64
+ uses: actions/upload-artifact@v4
65
+ with:
66
+ name: cinderwave-firmware-uf2
67
+ path: firmware/rp2040/build/*.uf2
@@ -0,0 +1,73 @@
1
+ name: PyPI
2
+
3
+ # Builds the host renderer as a native wheel on each OS (it is a compiled C++
4
+ # binary, so there is no pure-Python wheel) plus an sdist, then publishes to
5
+ # PyPI via Trusted Publishing on a version tag. No API token needed.
6
+ #
7
+ # One-time setup on PyPI: add a Trusted Publisher for project `cinderwave` ->
8
+ # owner `Makeph`, repo `cinderwave`, workflow `pypi.yml`, environment `pypi`.
9
+
10
+ on:
11
+ push:
12
+ tags: ["v*"]
13
+ workflow_dispatch:
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ wheels:
20
+ name: wheel (${{ matrix.os }})
21
+ runs-on: ${{ matrix.os }}
22
+ strategy:
23
+ fail-fast: false
24
+ matrix:
25
+ # macos-14 -> arm64, windows -> amd64. Linux and Intel-mac binary wheels
26
+ # are intentionally omitted: `python -m build` on a plain ubuntu runner
27
+ # emits a raw `linux_x86_64` tag that PyPI rejects (needs manylinux via
28
+ # cibuildwheel), and the macos-13 runner queue stalls. Linux/Intel-mac
29
+ # users install from the sdist.
30
+ os: [macos-14, windows-latest]
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+ - uses: actions/setup-python@v5
34
+ with:
35
+ python-version: "3.12"
36
+ - name: Build wheel
37
+ run: |
38
+ python -m pip install --upgrade build
39
+ python -m build --wheel
40
+ - uses: actions/upload-artifact@v4
41
+ with:
42
+ name: wheel-${{ matrix.os }}
43
+ path: dist/*.whl
44
+
45
+ sdist:
46
+ runs-on: ubuntu-latest
47
+ steps:
48
+ - uses: actions/checkout@v4
49
+ - uses: actions/setup-python@v5
50
+ with:
51
+ python-version: "3.12"
52
+ - run: |
53
+ python -m pip install --upgrade build
54
+ python -m build --sdist
55
+ - uses: actions/upload-artifact@v4
56
+ with:
57
+ name: sdist
58
+ path: dist/*.tar.gz
59
+
60
+ publish:
61
+ name: publish to PyPI
62
+ needs: [wheels, sdist]
63
+ runs-on: ubuntu-latest
64
+ if: startsWith(github.ref, 'refs/tags/v')
65
+ environment: pypi
66
+ permissions:
67
+ id-token: write
68
+ steps:
69
+ - uses: actions/download-artifact@v4
70
+ with:
71
+ path: dist
72
+ merge-multiple: true
73
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,25 @@
1
+ # Build output
2
+ build/
3
+ out/
4
+ cmake-build-*/
5
+ *.o
6
+ *.obj
7
+ *.a
8
+ *.lib
9
+ *.exe
10
+
11
+ # Rendered audio artifacts
12
+ *.wav
13
+
14
+ # RP2040 build products
15
+ firmware/rp2040/build/
16
+ *.uf2
17
+ *.elf
18
+ *.bin
19
+
20
+ # Editor/OS cruft
21
+ .vscode/
22
+ .idea/
23
+ *.swp
24
+ .DS_Store
25
+ Thumbs.db
@@ -0,0 +1,54 @@
1
+ cmake_minimum_required(VERSION 3.16)
2
+ project(cinderwave LANGUAGES CXX)
3
+
4
+ set(CMAKE_CXX_STANDARD 17)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+ set(CMAKE_CXX_EXTENSIONS OFF)
7
+
8
+ if(NOT CMAKE_BUILD_TYPE)
9
+ set(CMAKE_BUILD_TYPE Release)
10
+ endif()
11
+
12
+ if(MSVC)
13
+ add_compile_options(/W4)
14
+ else()
15
+ add_compile_options(-Wall -Wextra -Wpedantic)
16
+ endif()
17
+
18
+ # ---- Platform-independent DSP core -----------------------------------------
19
+ add_library(cinderwave_core STATIC
20
+ src/oscillator.cpp
21
+ src/envelope.cpp
22
+ src/filter.cpp
23
+ src/voice.cpp
24
+ src/sequencer.cpp
25
+ src/synth.cpp
26
+ )
27
+ target_include_directories(cinderwave_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
28
+
29
+ # ---- Host renderer: bakes a demo pattern to a .wav -------------------------
30
+ add_executable(cinderwave_render host/render.cpp)
31
+ target_link_libraries(cinderwave_render PRIVATE cinderwave_core)
32
+
33
+ # Install rule -- used by scikit-build-core to ship the host renderer as a pip
34
+ # console script. Only the renderer is installed (not the test binary).
35
+ include(GNUInstallDirs)
36
+ if(DEFINED SKBUILD_SCRIPTS_DIR)
37
+ install(TARGETS cinderwave_render RUNTIME DESTINATION "${SKBUILD_SCRIPTS_DIR}")
38
+ else()
39
+ install(TARGETS cinderwave_render RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
40
+ endif()
41
+
42
+ # ---- Tests -----------------------------------------------------------------
43
+ enable_testing()
44
+ add_executable(cinderwave_tests
45
+ tests/test_main.cpp
46
+ tests/test_oscillator.cpp
47
+ tests/test_envelope.cpp
48
+ tests/test_filter.cpp
49
+ tests/test_sequencer.cpp
50
+ tests/test_voice.cpp
51
+ )
52
+ target_include_directories(cinderwave_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests)
53
+ target_link_libraries(cinderwave_tests PRIVATE cinderwave_core)
54
+ add_test(NAME cinderwave_tests COMMAND cinderwave_tests)
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Makeph
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.1
2
+ Name: cinderwave
3
+ Version: 0.1.0
4
+ Summary: Host renderer for the Cinderwave acid groovebox: bakes patterns to WAV through the same zero-dependency C++17 DSP core that runs on the RP2040 hardware.
5
+ Keywords: synthesizer,audio,dsp,acid,groovebox,rp2040,cli
6
+ Author: Makeph
7
+ License: MIT
8
+ Classifier: Environment :: Console
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: C++
11
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Sound Synthesis
12
+ Project-URL: Homepage, https://github.com/Makeph/cinderwave
13
+ Project-URL: Repository, https://github.com/Makeph/cinderwave
14
+ Project-URL: Issues, https://github.com/Makeph/cinderwave/issues
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+
18
+ # Cinderwave
19
+
20
+ **An open-hardware acid groovebox that fits on a $4 microcontroller.**
21
+
22
+ Cinderwave is a monophonic synth voice and 16-step sequencer in the TB-303
23
+ idiom — band-limited oscillators, a resonant zero-delay-feedback filter,
24
+ per-step accent and slide — running on a Raspberry Pi Pico (RP2040). The entire
25
+ DSP core is platform-independent C++17 with **no dynamic allocation, no
26
+ exceptions, and no dependencies**, so the *exact same code* that drives the
27
+ hardware also renders a `.wav` on your desktop.
28
+
29
+ ```
30
+ ┌───────────┐ ┌──────────┐ ┌──────────────┐ ┌─────────┐
31
+ │ 16-step │──▶│ Oscillator│──▶│ SVF filter │──▶│ VCA │──▶ out
32
+ │ sequencer │ │ saw/sqr/ │ │ (cutoff env │ │ (amp │
33
+ │ note/gate/│ │ tri/sine │ │ + resonance)│ │ env) │
34
+ │ accent/ │ │ PolyBLEP │ └──────▲───────┘ └────▲────┘
35
+ │ slide │ └───────────┘ filter env amp env
36
+ └───────────┘
37
+ ```
38
+
39
+ ## Why it's fun
40
+
41
+ - **Band-limited oscillators** — PolyBLEP saw and square, so the high notes
42
+ don't alias into mush on a cheap DAC.
43
+ - **Cytomic TPT state-variable filter** — the *good* resonant filter, stable and
44
+ self-oscillating across the whole cutoff range.
45
+ - **Real acid behavior** — accent boosts amplitude *and* opens the filter;
46
+ slide portamentos between steps without retriggering the envelope.
47
+ - **One codebase, two worlds** — flash it to a Pico, or `cmake && ctest` it on
48
+ your laptop and bake the demo pattern to audio.
49
+
50
+ ## Quick start (no hardware needed)
51
+
52
+ ```sh
53
+ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
54
+ cmake --build build -j
55
+ ctest --test-dir build --output-on-failure # DSP unit tests
56
+ ./build/cinderwave_render out.wav # bake the demo acid line to WAV
57
+ ```
58
+
59
+ `cinderwave_render` writes ~8 seconds of the built-in demo pattern (a driving
60
+ 130 BPM bassline with accents and slides) to a 16-bit mono WAV.
61
+
62
+ ## Build the hardware
63
+
64
+ Full bill of materials, GPIO pin map, and two audio-output options (a budget
65
+ PWM + RC filter, or a PCM5102A I2S DAC) live in
66
+ **[hardware/BUILD.md](hardware/BUILD.md)**.
67
+
68
+ Minimum viable Cinderwave:
69
+
70
+ | Part | Qty | Notes |
71
+ |-----------------------------|-----|----------------------------------------|
72
+ | Raspberry Pi Pico (RP2040) | 1 | the whole synth |
73
+ | 10 kΩ linear potentiometer | 3 | cutoff, resonance, tempo |
74
+ | Tactile button | 1 | play / stop |
75
+ | 1.8 kΩ resistor + 10 nF cap | 1 | RC reconstruction filter (~16 kHz) |
76
+ | 10 µF cap + 3.5 mm jack | 1 | DC-blocked line out |
77
+
78
+ ## Flash the firmware
79
+
80
+ ```sh
81
+ cd firmware/rp2040
82
+ export PICO_SDK_PATH=/path/to/pico-sdk
83
+ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
84
+ cmake --build build -j
85
+ ```
86
+
87
+ Hold **BOOTSEL** while plugging in the Pico, then drop
88
+ `build/cinderwave_firmware.uf2` onto the `RPI-RP2` drive. It boots straight into
89
+ the demo pattern; the three pots take over cutoff, resonance, and tempo, and the
90
+ button toggles playback.
91
+
92
+ ## Layout
93
+
94
+ ```
95
+ include/cinderwave/ Public API — the frozen DSP contract (headers only)
96
+ src/ DSP core: oscillator, envelope, filter, voice, sequencer, synth
97
+ host/ Desktop WAV renderer (uses the identical core)
98
+ firmware/rp2040/ Pico SDK target: PWM audio + pot/button controls
99
+ tests/ Dependency-free unit tests (built and run in CI)
100
+ hardware/ Build guide, BOM, wiring, pin map
101
+ ```
102
+
103
+ ## Continuous integration
104
+
105
+ Every push builds the core, runs the unit tests, renders the demo WAV, and
106
+ cross-compiles the RP2040 `.uf2` — both artifacts are uploaded from the run. See
107
+ [.github/workflows/ci.yml](.github/workflows/ci.yml).
108
+
109
+ ## License
110
+
111
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,94 @@
1
+ # Cinderwave
2
+
3
+ **An open-hardware acid groovebox that fits on a $4 microcontroller.**
4
+
5
+ Cinderwave is a monophonic synth voice and 16-step sequencer in the TB-303
6
+ idiom — band-limited oscillators, a resonant zero-delay-feedback filter,
7
+ per-step accent and slide — running on a Raspberry Pi Pico (RP2040). The entire
8
+ DSP core is platform-independent C++17 with **no dynamic allocation, no
9
+ exceptions, and no dependencies**, so the *exact same code* that drives the
10
+ hardware also renders a `.wav` on your desktop.
11
+
12
+ ```
13
+ ┌───────────┐ ┌──────────┐ ┌──────────────┐ ┌─────────┐
14
+ │ 16-step │──▶│ Oscillator│──▶│ SVF filter │──▶│ VCA │──▶ out
15
+ │ sequencer │ │ saw/sqr/ │ │ (cutoff env │ │ (amp │
16
+ │ note/gate/│ │ tri/sine │ │ + resonance)│ │ env) │
17
+ │ accent/ │ │ PolyBLEP │ └──────▲───────┘ └────▲────┘
18
+ │ slide │ └───────────┘ filter env amp env
19
+ └───────────┘
20
+ ```
21
+
22
+ ## Why it's fun
23
+
24
+ - **Band-limited oscillators** — PolyBLEP saw and square, so the high notes
25
+ don't alias into mush on a cheap DAC.
26
+ - **Cytomic TPT state-variable filter** — the *good* resonant filter, stable and
27
+ self-oscillating across the whole cutoff range.
28
+ - **Real acid behavior** — accent boosts amplitude *and* opens the filter;
29
+ slide portamentos between steps without retriggering the envelope.
30
+ - **One codebase, two worlds** — flash it to a Pico, or `cmake && ctest` it on
31
+ your laptop and bake the demo pattern to audio.
32
+
33
+ ## Quick start (no hardware needed)
34
+
35
+ ```sh
36
+ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
37
+ cmake --build build -j
38
+ ctest --test-dir build --output-on-failure # DSP unit tests
39
+ ./build/cinderwave_render out.wav # bake the demo acid line to WAV
40
+ ```
41
+
42
+ `cinderwave_render` writes ~8 seconds of the built-in demo pattern (a driving
43
+ 130 BPM bassline with accents and slides) to a 16-bit mono WAV.
44
+
45
+ ## Build the hardware
46
+
47
+ Full bill of materials, GPIO pin map, and two audio-output options (a budget
48
+ PWM + RC filter, or a PCM5102A I2S DAC) live in
49
+ **[hardware/BUILD.md](hardware/BUILD.md)**.
50
+
51
+ Minimum viable Cinderwave:
52
+
53
+ | Part | Qty | Notes |
54
+ |-----------------------------|-----|----------------------------------------|
55
+ | Raspberry Pi Pico (RP2040) | 1 | the whole synth |
56
+ | 10 kΩ linear potentiometer | 3 | cutoff, resonance, tempo |
57
+ | Tactile button | 1 | play / stop |
58
+ | 1.8 kΩ resistor + 10 nF cap | 1 | RC reconstruction filter (~16 kHz) |
59
+ | 10 µF cap + 3.5 mm jack | 1 | DC-blocked line out |
60
+
61
+ ## Flash the firmware
62
+
63
+ ```sh
64
+ cd firmware/rp2040
65
+ export PICO_SDK_PATH=/path/to/pico-sdk
66
+ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
67
+ cmake --build build -j
68
+ ```
69
+
70
+ Hold **BOOTSEL** while plugging in the Pico, then drop
71
+ `build/cinderwave_firmware.uf2` onto the `RPI-RP2` drive. It boots straight into
72
+ the demo pattern; the three pots take over cutoff, resonance, and tempo, and the
73
+ button toggles playback.
74
+
75
+ ## Layout
76
+
77
+ ```
78
+ include/cinderwave/ Public API — the frozen DSP contract (headers only)
79
+ src/ DSP core: oscillator, envelope, filter, voice, sequencer, synth
80
+ host/ Desktop WAV renderer (uses the identical core)
81
+ firmware/rp2040/ Pico SDK target: PWM audio + pot/button controls
82
+ tests/ Dependency-free unit tests (built and run in CI)
83
+ hardware/ Build guide, BOM, wiring, pin map
84
+ ```
85
+
86
+ ## Continuous integration
87
+
88
+ Every push builds the core, runs the unit tests, renders the demo WAV, and
89
+ cross-compiles the RP2040 `.uf2` — both artifacts are uploaded from the run. See
90
+ [.github/workflows/ci.yml](.github/workflows/ci.yml).
91
+
92
+ ## License
93
+
94
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,40 @@
1
+ cmake_minimum_required(VERSION 3.13)
2
+
3
+ # Pull in the Raspberry Pi Pico SDK (must be located before project()).
4
+ include(pico_sdk_import.cmake)
5
+
6
+ project(cinderwave_firmware C CXX ASM)
7
+
8
+ set(CMAKE_C_STANDARD 11)
9
+ set(CMAKE_CXX_STANDARD 17)
10
+
11
+ pico_sdk_init()
12
+
13
+ add_executable(cinderwave_firmware
14
+ main.cpp
15
+ # The platform-independent DSP core, compiled straight into the firmware.
16
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../src/oscillator.cpp
17
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../src/envelope.cpp
18
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../src/filter.cpp
19
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../src/voice.cpp
20
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../src/sequencer.cpp
21
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../src/synth.cpp
22
+ )
23
+
24
+ target_include_directories(cinderwave_firmware PRIVATE
25
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../include
26
+ )
27
+
28
+ target_link_libraries(cinderwave_firmware
29
+ pico_stdlib
30
+ hardware_pwm
31
+ hardware_adc
32
+ hardware_irq
33
+ )
34
+
35
+ # Enable USB serial for debug logging; disable UART.
36
+ pico_enable_stdio_usb(cinderwave_firmware 1)
37
+ pico_enable_stdio_uart(cinderwave_firmware 0)
38
+
39
+ # Produce .uf2 / .hex / .bin / .map alongside the .elf.
40
+ pico_add_extra_outputs(cinderwave_firmware)
@@ -0,0 +1,30 @@
1
+ #include "cinderwave/synth.h"
2
+
3
+ #include "wav.h"
4
+
5
+ #include <cstddef>
6
+ #include <cstdio>
7
+ #include <string>
8
+ #include <vector>
9
+
10
+ int main(int argc, char** argv) {
11
+ const std::string path = argc > 1 ? argv[1] : "cinderwave_demo.wav";
12
+ constexpr float durationSeconds = 8.0f;
13
+ const int sampleRate = cw::kSampleRate;
14
+ const int sampleCount = static_cast<int>(durationSeconds * static_cast<float>(sampleRate));
15
+
16
+ cw::Synth synth(sampleRate);
17
+ synth.loadDemoPattern();
18
+ synth.sequencer().start();
19
+
20
+ std::vector<float> samples(static_cast<std::size_t>(sampleCount));
21
+ synth.renderBlock(samples.data(), sampleCount);
22
+
23
+ if (!cwhost::writeWav(path, samples, sampleRate)) {
24
+ std::fprintf(stderr, "failed to write %s\n", path.c_str());
25
+ return 1;
26
+ }
27
+
28
+ std::printf("wrote %s (%.2f seconds)\n", path.c_str(), durationSeconds);
29
+ return 0;
30
+ }
@@ -0,0 +1,49 @@
1
+ // Minimal 16-bit PCM mono WAV writer — no dependencies, little-endian hosts.
2
+ #ifndef CINDERWAVE_HOST_WAV_H
3
+ #define CINDERWAVE_HOST_WAV_H
4
+
5
+ #include <cstdint>
6
+ #include <cstdio>
7
+ #include <vector>
8
+ #include <string>
9
+
10
+ namespace cwhost {
11
+
12
+ // Writes `samples` (nominally [-1, 1]) as a 16-bit mono WAV. Returns true on
13
+ // success. Values are clamped before quantization.
14
+ inline bool writeWav(const std::string& path, const std::vector<float>& samples,
15
+ int sampleRate) {
16
+ FILE* f = std::fopen(path.c_str(), "wb");
17
+ if (!f) return false;
18
+
19
+ auto u32 = [&](uint32_t v) { std::fwrite(&v, 4, 1, f); };
20
+ auto u16 = [&](uint16_t v) { std::fwrite(&v, 2, 1, f); };
21
+
22
+ const uint32_t dataBytes = static_cast<uint32_t>(samples.size()) * 2u;
23
+ std::fwrite("RIFF", 1, 4, f);
24
+ u32(36u + dataBytes);
25
+ std::fwrite("WAVE", 1, 4, f);
26
+ std::fwrite("fmt ", 1, 4, f);
27
+ u32(16u); // PCM chunk size
28
+ u16(1); // PCM format
29
+ u16(1); // mono
30
+ u32(static_cast<uint32_t>(sampleRate));
31
+ u32(static_cast<uint32_t>(sampleRate) * 2u); // byte rate
32
+ u16(2); // block align
33
+ u16(16); // bits per sample
34
+ std::fwrite("data", 1, 4, f);
35
+ u32(dataBytes);
36
+
37
+ for (float s : samples) {
38
+ if (s > 1.0f) s = 1.0f;
39
+ if (s < -1.0f) s = -1.0f;
40
+ int16_t q = static_cast<int16_t>(s * 32767.0f);
41
+ u16(static_cast<uint16_t>(q));
42
+ }
43
+ std::fclose(f);
44
+ return true;
45
+ }
46
+
47
+ } // namespace cwhost
48
+
49
+ #endif // CINDERWAVE_HOST_WAV_H
@@ -0,0 +1,32 @@
1
+ // Cinderwave — open hardware groovebox for the RP2040.
2
+ // Platform-independent DSP core. No dynamic allocation, no exceptions,
3
+ // no hardware headers here: everything below runs identically on a Pico
4
+ // and on a desktop host that renders to WAV.
5
+ #ifndef CINDERWAVE_CONFIG_H
6
+ #define CINDERWAVE_CONFIG_H
7
+
8
+ #include <cstdint>
9
+
10
+ namespace cw {
11
+
12
+ // Audio sample type. float on host and RP2040 (the M0+ has no FPU, but the
13
+ // pico SDK's software float is fast enough for one voice at 32 kHz; the
14
+ // firmware converts to 12-bit PWM / 16-bit I2S at the sink).
15
+ using Sample = float;
16
+
17
+ // Default engine sample rate. Chosen so a single PWM cycle on the RP2040
18
+ // (125 MHz / 32000 ≈ 3906 counts) still gives ~11.9 bits of amplitude range.
19
+ constexpr int kSampleRate = 32000;
20
+
21
+ // Sequencer geometry.
22
+ constexpr int kNumSteps = 16; // one bar of 16th notes
23
+ constexpr int kNumTracks = 1; // monosynth groovebox (one voice, one lane)
24
+
25
+ // Clamp helper shared across the DSP core.
26
+ inline Sample clampf(Sample x, Sample lo, Sample hi) {
27
+ return x < lo ? lo : (x > hi ? hi : x);
28
+ }
29
+
30
+ } // namespace cw
31
+
32
+ #endif // CINDERWAVE_CONFIG_H
@@ -0,0 +1,45 @@
1
+ // Linear-attack, exponential decay/release ADSR. Gate-driven so the sequencer
2
+ // can retrigger it per step (with accent scaling the peak).
3
+ #ifndef CINDERWAVE_ENVELOPE_H
4
+ #define CINDERWAVE_ENVELOPE_H
5
+
6
+ #include "cinderwave/config.h"
7
+
8
+ namespace cw {
9
+
10
+ class Envelope {
11
+ public:
12
+ enum class Stage : uint8_t { Idle, Attack, Decay, Sustain, Release };
13
+
14
+ explicit Envelope(int sampleRate = kSampleRate);
15
+
16
+ void setSampleRate(int sampleRate);
17
+ void setAttack(Sample seconds);
18
+ void setDecay(Sample seconds);
19
+ void setSustain(Sample level); // [0, 1]
20
+ void setRelease(Sample seconds);
21
+
22
+ // Rising edge starts Attack; falling edge starts Release.
23
+ void gate(bool on);
24
+ void reset(); // silence, Idle
25
+
26
+ Stage stage() const { return stage_; }
27
+ bool active() const { return stage_ != Stage::Idle; }
28
+
29
+ // Advance one sample; returns current level in [0, 1].
30
+ Sample process();
31
+
32
+ private:
33
+ int sampleRate_;
34
+ Stage stage_ = Stage::Idle;
35
+ Sample level_ = 0.0f;
36
+ Sample sustain_ = 0.7f;
37
+ // Per-stage increments/coefficients derived from the time setters.
38
+ Sample attackInc_ = 1.0f; // linear per-sample rise
39
+ Sample decayCoef_ = 0.0f; // exponential coefficient toward sustain
40
+ Sample releaseCoef_ = 0.0f; // exponential coefficient toward 0
41
+ };
42
+
43
+ } // namespace cw
44
+
45
+ #endif // CINDERWAVE_ENVELOPE_H