Deluge Firmware 1.3.0
Build date: 2025.11.26
Loading...
Searching...
No Matches
render_wave.h
1/*
2 * Copyright © 2017-2023 Synthstrom Audible Limited
3 *
4 * This file is part of The Synthstrom Audible Deluge Firmware.
5 *
6 * The Synthstrom Audible Deluge Firmware is free software: you can redistribute it and/or modify it under the
7 * terms of the GNU General Public License as published by the Free Software Foundation,
8 * either version 3 of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
11 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 * See the GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along with this program.
15 * If not, see <https://www.gnu.org/licenses/>.
16 */
17
18#pragma once
19#include "processing/vector_rendering_function.h"
20#include "util/fixedpoint.h"
21#include "util/waves.h"
22#include <argon.hpp>
23#include <arm_neon.h>
24
25[[gnu::always_inline]] static inline void //<
26renderOscSync(auto storageFunction, auto extraInstructionsForCrossoverSampleRedo,
27 // Params
28 uint32_t& phase, uint32_t phaseIncrement, uint32_t& resetterPhase, uint32_t resetterPhaseIncrement,
29 int32_t resetterDivideByPhaseIncrement, uint32_t retriggerPhase, int32_t& numSamplesThisOscSyncSession,
30 int32_t*& bufferStartThisSync) {
31
32 bool renderedASyncFromItsStartYet = false;
33 int32_t crossoverSampleBeforeSync;
34 int32_t fadeBetweenSyncs;
35
36 /* Do a bunch of samples until we get to the next crossover sample */
37 /* A starting value that'll be added to. It's 1 because we want to include the 1 extra sample at the end - the
38 * crossover sample. */
39 uint32_t samplesIncludingNextCrossoverSample = 1;
40startRenderingASync:
41 uint32_t distanceTilNextCrossoverSample = -resetterPhase - (resetterPhaseIncrement >> 1);
42 samplesIncludingNextCrossoverSample += (uint32_t)(distanceTilNextCrossoverSample - 1) / resetterPhaseIncrement;
43 bool shouldBeginNextSyncAfter = (numSamplesThisOscSyncSession >= samplesIncludingNextCrossoverSample);
44 int32_t numSamplesThisSyncRender = shouldBeginNextSyncAfter
45 ? samplesIncludingNextCrossoverSample
46 : numSamplesThisOscSyncSession; /* Just limit it, basically. */
47
48 int32_t const* const bufferEndThisSyncRender = bufferStartThisSync + numSamplesThisSyncRender;
49 uint32_t phaseTemp = phase;
50 int32_t* __restrict__ writePos = bufferStartThisSync;
51
52 storageFunction(bufferEndThisSyncRender, phaseTemp, writePos);
53
54 /* Sort out the crossover sample at the *start* of that window we just did, if there was one. */
55 if (renderedASyncFromItsStartYet) {
56 int32_t average = (*bufferStartThisSync >> 1) + (crossoverSampleBeforeSync >> 1);
57 int32_t halfDifference = (*bufferStartThisSync >> 1) - (crossoverSampleBeforeSync >> 1);
58 int32_t sineValue = getSine(fadeBetweenSyncs >> 1);
59 *bufferStartThisSync = average + (q31_mult(halfDifference, sineValue));
60 }
61
62 if (shouldBeginNextSyncAfter) {
63 /* We've just done a crossover (i.e. hit a sync point) at the end of that window, so start thinking about that
64 * and planning the next window. */
65 bufferStartThisSync += samplesIncludingNextCrossoverSample - 1;
66 crossoverSampleBeforeSync = *bufferStartThisSync;
67 numSamplesThisOscSyncSession -= samplesIncludingNextCrossoverSample - 1;
68 extraInstructionsForCrossoverSampleRedo(samplesIncludingNextCrossoverSample);
69
70 /* We want this to always show one sample late at this point (why again?). */
71 resetterPhase += resetterPhaseIncrement * (samplesIncludingNextCrossoverSample - renderedASyncFromItsStartYet);
72 /* The first time we get here, it won't yet be, so make it so. */
73
74 /* The result of that comes out as between "-0.5 and 0.5", represented as +-(1<<14) */
75 fadeBetweenSyncs = multiply_32x32_rshift32((int32_t)resetterPhase, resetterDivideByPhaseIncrement)
76 << 17; /* And this makes it "full-scale", so "1" is 1<<32. */
77 phase = multiply_32x32_rshift32(fadeBetweenSyncs, phaseIncrement) + retriggerPhase;
78
79 phase -= phaseIncrement; /* Because we're going back and redoing the last sample. */
80 renderedASyncFromItsStartYet = true;
81 samplesIncludingNextCrossoverSample = 2; /* Make this 1 higher now, because resetterPhase's value is 1 sample
82 later than what it "is in reality". */
83 goto startRenderingASync;
84 }
85
86 /* We're not beginning a next sync, so are not going to reset phase, so need to update (increment) it to keep it
87 * valid. */
88 phase += phaseIncrement * numSamplesThisSyncRender;
89}
90
91auto renderWavetableLoop(auto bufferStartThisSync, auto firstCycleNumber, auto bandHere, auto phaseIncrement,
92 auto crossCycleStrength2, auto crossCycleStrength2Increment, auto kernel) {
93 return [&](int32_t const* const bufferEndThisSyncRender, uint32_t phaseTemp, int32_t* __restrict__ writePos) {
94 doRenderingLoop(bufferStartThisSync, bufferEndThisSyncRender, firstCycleNumber, bandHere, phaseTemp,
95 phaseIncrement, crossCycleStrength2, crossCycleStrength2Increment, kernel);
96 };
97}
98
100inline Argon<int32_t> createAmplitudeVector(int32_t amplitude, int32_t amplitude_increment) {
101 // amplitude + amplitudeIncrement * lane_n
102 auto amplitudeVector = Argon<int32_t>{amplitude}.MultiplyAdd(amplitude_increment, int32x4_t{1, 2, 3, 4});
103
104 // TODO(@stellar-aria): investigate where the doubling comes from (likely an unshifted smmul)
105 return amplitudeVector >> 1; // halve amplitude
106}