Audio processing with normalized [0.0, 1.0] domain
up vote
1
down vote
favorite
I've a code that process buffers of samples/audio data. Here's the code:
#include <iostream>
#include <math.h>
#include <cstring>
#include <algorithm>
#define PI 3.141592653589793238
#define TWOPI 6.283185307179586476
const int blockSize = 256;
const double mSampleRate = 44100.0;
const double mHostPitch = 2.0;
const double mRadiansPerSample = TWOPI / mSampleRate;
double mGainNormalizedValues[blockSize];
double mPitchNormalizedValues[blockSize];
double mOffsetNormalizedValues[blockSize];
class Oscillator
{
public:
double mPhase = 0.0;
double minPitch = -48.0;
double maxPitch = 48.0;
double rangePitch = maxPitch - minPitch;
double pitchPd = log(2.0) / 12.0;
double minOffset = -900.0;
double maxOffset = 900.0;
double rangeOffset = maxOffset - minOffset;
Oscillator() { }
void ProcessBuffer(double voiceFrequency, int blockSize, double *left, double *right) {
// precomputed data
double bp0 = voiceFrequency * mHostPitch;
// process block
for (int sampleIndex = 0; sampleIndex < blockSize; sampleIndex++) {
double output = (sin(mPhase)) * mGainNormalizedValues[sampleIndex];
*left++ += output;
*right++ += output;
// next phase
mPhase += std::clamp(mRadiansPerSample * (bp0 * WarpPitch(mPitchNormalizedValues[sampleIndex])) + WarpOffset(mOffsetNormalizedValues[sampleIndex]), 0.0, PI);
while (mPhase >= TWOPI) { mPhase -= TWOPI; }
}
}
inline double WarpPitch(double normalizedValue) { return exp((minPitch + normalizedValue * rangePitch) * pitchPd); }
inline double WarpOffset(double normalizedValue) { return minOffset + normalizedValue * rangeOffset; }
};
int main(int argc, const char *argv) {
int numBuffer = 1024;
int counterBuffer = 0;
Oscillator oscillator;
// I fill the buffer often
while (counterBuffer < numBuffer) {
// init buffer
double bufferLeft[blockSize];
double bufferRight[blockSize];
memset(&bufferLeft, 0, blockSize * sizeof(double));
memset(&bufferRight, 0, blockSize * sizeof(double));
// emulate params values for this buffer
for(int i = 0; i < blockSize; i++) {
mGainNormalizedValues[i] = i / (double)blockSize;
mPitchNormalizedValues[i] = i / (double)blockSize;
mOffsetNormalizedValues[i] = i / (double)blockSize;
}
// process osc buffer
oscillator.ProcessBuffer(130.81278, blockSize, &bufferLeft[0], &bufferRight[0]);
// do somethings with buffer
counterBuffer++;
}
}
Basically:
- init a Oscillator object
- for each buffer, fill some param arrays with values (gain, pitch, offset); gain remain normalized
[0.0, 1.0]
, while pitch and offset range in-48/48
and-900/900
- then I iterate the buffer, calculating the Oscillator's sine due to pitch and offset, and I apply a gain; later, I move the phase, incrementing it
The whole domain of operations are normalized [0.0, 1.0]
. But when I need to manage pitch and offset, I need to switch domain and use different values (i.e. the Warp functions).
This required lots of computations and process. I'd like to avoid it, so I can improve the code and performances.
How would you do it? Can I keep into [0.0, 1.0]
? Could I improve the performance?
c++ performance signal-processing
add a comment |
up vote
1
down vote
favorite
I've a code that process buffers of samples/audio data. Here's the code:
#include <iostream>
#include <math.h>
#include <cstring>
#include <algorithm>
#define PI 3.141592653589793238
#define TWOPI 6.283185307179586476
const int blockSize = 256;
const double mSampleRate = 44100.0;
const double mHostPitch = 2.0;
const double mRadiansPerSample = TWOPI / mSampleRate;
double mGainNormalizedValues[blockSize];
double mPitchNormalizedValues[blockSize];
double mOffsetNormalizedValues[blockSize];
class Oscillator
{
public:
double mPhase = 0.0;
double minPitch = -48.0;
double maxPitch = 48.0;
double rangePitch = maxPitch - minPitch;
double pitchPd = log(2.0) / 12.0;
double minOffset = -900.0;
double maxOffset = 900.0;
double rangeOffset = maxOffset - minOffset;
Oscillator() { }
void ProcessBuffer(double voiceFrequency, int blockSize, double *left, double *right) {
// precomputed data
double bp0 = voiceFrequency * mHostPitch;
// process block
for (int sampleIndex = 0; sampleIndex < blockSize; sampleIndex++) {
double output = (sin(mPhase)) * mGainNormalizedValues[sampleIndex];
*left++ += output;
*right++ += output;
// next phase
mPhase += std::clamp(mRadiansPerSample * (bp0 * WarpPitch(mPitchNormalizedValues[sampleIndex])) + WarpOffset(mOffsetNormalizedValues[sampleIndex]), 0.0, PI);
while (mPhase >= TWOPI) { mPhase -= TWOPI; }
}
}
inline double WarpPitch(double normalizedValue) { return exp((minPitch + normalizedValue * rangePitch) * pitchPd); }
inline double WarpOffset(double normalizedValue) { return minOffset + normalizedValue * rangeOffset; }
};
int main(int argc, const char *argv) {
int numBuffer = 1024;
int counterBuffer = 0;
Oscillator oscillator;
// I fill the buffer often
while (counterBuffer < numBuffer) {
// init buffer
double bufferLeft[blockSize];
double bufferRight[blockSize];
memset(&bufferLeft, 0, blockSize * sizeof(double));
memset(&bufferRight, 0, blockSize * sizeof(double));
// emulate params values for this buffer
for(int i = 0; i < blockSize; i++) {
mGainNormalizedValues[i] = i / (double)blockSize;
mPitchNormalizedValues[i] = i / (double)blockSize;
mOffsetNormalizedValues[i] = i / (double)blockSize;
}
// process osc buffer
oscillator.ProcessBuffer(130.81278, blockSize, &bufferLeft[0], &bufferRight[0]);
// do somethings with buffer
counterBuffer++;
}
}
Basically:
- init a Oscillator object
- for each buffer, fill some param arrays with values (gain, pitch, offset); gain remain normalized
[0.0, 1.0]
, while pitch and offset range in-48/48
and-900/900
- then I iterate the buffer, calculating the Oscillator's sine due to pitch and offset, and I apply a gain; later, I move the phase, incrementing it
The whole domain of operations are normalized [0.0, 1.0]
. But when I need to manage pitch and offset, I need to switch domain and use different values (i.e. the Warp functions).
This required lots of computations and process. I'd like to avoid it, so I can improve the code and performances.
How would you do it? Can I keep into [0.0, 1.0]
? Could I improve the performance?
c++ performance signal-processing
Does it make sense to add optimization that rely on the phase offset per step to be stable for a while, or is it always expected to fluctuate on a sample by sample basis?
– harold
Nov 13 at 15:57
@harold: I could control rate it and save on cycles, but let consider the worst case; so yes, its always expected to fluctuate sample by sample. But feel free to give an example that can be stable for a while, it would still be interessant.
– markzzz
Nov 13 at 15:59
add a comment |
up vote
1
down vote
favorite
up vote
1
down vote
favorite
I've a code that process buffers of samples/audio data. Here's the code:
#include <iostream>
#include <math.h>
#include <cstring>
#include <algorithm>
#define PI 3.141592653589793238
#define TWOPI 6.283185307179586476
const int blockSize = 256;
const double mSampleRate = 44100.0;
const double mHostPitch = 2.0;
const double mRadiansPerSample = TWOPI / mSampleRate;
double mGainNormalizedValues[blockSize];
double mPitchNormalizedValues[blockSize];
double mOffsetNormalizedValues[blockSize];
class Oscillator
{
public:
double mPhase = 0.0;
double minPitch = -48.0;
double maxPitch = 48.0;
double rangePitch = maxPitch - minPitch;
double pitchPd = log(2.0) / 12.0;
double minOffset = -900.0;
double maxOffset = 900.0;
double rangeOffset = maxOffset - minOffset;
Oscillator() { }
void ProcessBuffer(double voiceFrequency, int blockSize, double *left, double *right) {
// precomputed data
double bp0 = voiceFrequency * mHostPitch;
// process block
for (int sampleIndex = 0; sampleIndex < blockSize; sampleIndex++) {
double output = (sin(mPhase)) * mGainNormalizedValues[sampleIndex];
*left++ += output;
*right++ += output;
// next phase
mPhase += std::clamp(mRadiansPerSample * (bp0 * WarpPitch(mPitchNormalizedValues[sampleIndex])) + WarpOffset(mOffsetNormalizedValues[sampleIndex]), 0.0, PI);
while (mPhase >= TWOPI) { mPhase -= TWOPI; }
}
}
inline double WarpPitch(double normalizedValue) { return exp((minPitch + normalizedValue * rangePitch) * pitchPd); }
inline double WarpOffset(double normalizedValue) { return minOffset + normalizedValue * rangeOffset; }
};
int main(int argc, const char *argv) {
int numBuffer = 1024;
int counterBuffer = 0;
Oscillator oscillator;
// I fill the buffer often
while (counterBuffer < numBuffer) {
// init buffer
double bufferLeft[blockSize];
double bufferRight[blockSize];
memset(&bufferLeft, 0, blockSize * sizeof(double));
memset(&bufferRight, 0, blockSize * sizeof(double));
// emulate params values for this buffer
for(int i = 0; i < blockSize; i++) {
mGainNormalizedValues[i] = i / (double)blockSize;
mPitchNormalizedValues[i] = i / (double)blockSize;
mOffsetNormalizedValues[i] = i / (double)blockSize;
}
// process osc buffer
oscillator.ProcessBuffer(130.81278, blockSize, &bufferLeft[0], &bufferRight[0]);
// do somethings with buffer
counterBuffer++;
}
}
Basically:
- init a Oscillator object
- for each buffer, fill some param arrays with values (gain, pitch, offset); gain remain normalized
[0.0, 1.0]
, while pitch and offset range in-48/48
and-900/900
- then I iterate the buffer, calculating the Oscillator's sine due to pitch and offset, and I apply a gain; later, I move the phase, incrementing it
The whole domain of operations are normalized [0.0, 1.0]
. But when I need to manage pitch and offset, I need to switch domain and use different values (i.e. the Warp functions).
This required lots of computations and process. I'd like to avoid it, so I can improve the code and performances.
How would you do it? Can I keep into [0.0, 1.0]
? Could I improve the performance?
c++ performance signal-processing
I've a code that process buffers of samples/audio data. Here's the code:
#include <iostream>
#include <math.h>
#include <cstring>
#include <algorithm>
#define PI 3.141592653589793238
#define TWOPI 6.283185307179586476
const int blockSize = 256;
const double mSampleRate = 44100.0;
const double mHostPitch = 2.0;
const double mRadiansPerSample = TWOPI / mSampleRate;
double mGainNormalizedValues[blockSize];
double mPitchNormalizedValues[blockSize];
double mOffsetNormalizedValues[blockSize];
class Oscillator
{
public:
double mPhase = 0.0;
double minPitch = -48.0;
double maxPitch = 48.0;
double rangePitch = maxPitch - minPitch;
double pitchPd = log(2.0) / 12.0;
double minOffset = -900.0;
double maxOffset = 900.0;
double rangeOffset = maxOffset - minOffset;
Oscillator() { }
void ProcessBuffer(double voiceFrequency, int blockSize, double *left, double *right) {
// precomputed data
double bp0 = voiceFrequency * mHostPitch;
// process block
for (int sampleIndex = 0; sampleIndex < blockSize; sampleIndex++) {
double output = (sin(mPhase)) * mGainNormalizedValues[sampleIndex];
*left++ += output;
*right++ += output;
// next phase
mPhase += std::clamp(mRadiansPerSample * (bp0 * WarpPitch(mPitchNormalizedValues[sampleIndex])) + WarpOffset(mOffsetNormalizedValues[sampleIndex]), 0.0, PI);
while (mPhase >= TWOPI) { mPhase -= TWOPI; }
}
}
inline double WarpPitch(double normalizedValue) { return exp((minPitch + normalizedValue * rangePitch) * pitchPd); }
inline double WarpOffset(double normalizedValue) { return minOffset + normalizedValue * rangeOffset; }
};
int main(int argc, const char *argv) {
int numBuffer = 1024;
int counterBuffer = 0;
Oscillator oscillator;
// I fill the buffer often
while (counterBuffer < numBuffer) {
// init buffer
double bufferLeft[blockSize];
double bufferRight[blockSize];
memset(&bufferLeft, 0, blockSize * sizeof(double));
memset(&bufferRight, 0, blockSize * sizeof(double));
// emulate params values for this buffer
for(int i = 0; i < blockSize; i++) {
mGainNormalizedValues[i] = i / (double)blockSize;
mPitchNormalizedValues[i] = i / (double)blockSize;
mOffsetNormalizedValues[i] = i / (double)blockSize;
}
// process osc buffer
oscillator.ProcessBuffer(130.81278, blockSize, &bufferLeft[0], &bufferRight[0]);
// do somethings with buffer
counterBuffer++;
}
}
Basically:
- init a Oscillator object
- for each buffer, fill some param arrays with values (gain, pitch, offset); gain remain normalized
[0.0, 1.0]
, while pitch and offset range in-48/48
and-900/900
- then I iterate the buffer, calculating the Oscillator's sine due to pitch and offset, and I apply a gain; later, I move the phase, incrementing it
The whole domain of operations are normalized [0.0, 1.0]
. But when I need to manage pitch and offset, I need to switch domain and use different values (i.e. the Warp functions).
This required lots of computations and process. I'd like to avoid it, so I can improve the code and performances.
How would you do it? Can I keep into [0.0, 1.0]
? Could I improve the performance?
c++ performance signal-processing
c++ performance signal-processing
edited Nov 13 at 20:24
200_success
127k15148410
127k15148410
asked Nov 13 at 11:18
markzzz
1254
1254
Does it make sense to add optimization that rely on the phase offset per step to be stable for a while, or is it always expected to fluctuate on a sample by sample basis?
– harold
Nov 13 at 15:57
@harold: I could control rate it and save on cycles, but let consider the worst case; so yes, its always expected to fluctuate sample by sample. But feel free to give an example that can be stable for a while, it would still be interessant.
– markzzz
Nov 13 at 15:59
add a comment |
Does it make sense to add optimization that rely on the phase offset per step to be stable for a while, or is it always expected to fluctuate on a sample by sample basis?
– harold
Nov 13 at 15:57
@harold: I could control rate it and save on cycles, but let consider the worst case; so yes, its always expected to fluctuate sample by sample. But feel free to give an example that can be stable for a while, it would still be interessant.
– markzzz
Nov 13 at 15:59
Does it make sense to add optimization that rely on the phase offset per step to be stable for a while, or is it always expected to fluctuate on a sample by sample basis?
– harold
Nov 13 at 15:57
Does it make sense to add optimization that rely on the phase offset per step to be stable for a while, or is it always expected to fluctuate on a sample by sample basis?
– harold
Nov 13 at 15:57
@harold: I could control rate it and save on cycles, but let consider the worst case; so yes, its always expected to fluctuate sample by sample. But feel free to give an example that can be stable for a while, it would still be interessant.
– markzzz
Nov 13 at 15:59
@harold: I could control rate it and save on cycles, but let consider the worst case; so yes, its always expected to fluctuate sample by sample. But feel free to give an example that can be stable for a while, it would still be interessant.
– markzzz
Nov 13 at 15:59
add a comment |
2 Answers
2
active
oldest
votes
up vote
3
down vote
Prefer C++ headers
Instead of <math.h>
, it's better to include <cmath>
and qualify names such as std::log
.
Prefer constants to macros
Re-write pi
as a strongly-typed, scoped variable rather than a preprocessor macro. Same for 2*pi
if you really feel the need.
Manage line lengths
Some lines are very long. In many cases, they just need newlines adding (e.g. bodies of inline functions can have their own lines).
In the case of the std::clamp()
call, it's probably worth using variables to give a name to the candidate value before clamping.
Easier sizeof
Instead of recomputing the size of bufferLeft
and bufferRight
like this:
memset(&bufferLeft, 0, blockSize * sizeof(double));
memset(&bufferRight, 0, blockSize * sizeof(double));
It's easier and clearer to just use the whole array size:
memset(&bufferLeft, 0, sizeof bufferLeft);
memset(&bufferRight, 0, sizeof bufferRight);
Personally, I'd generally prefer std::fill
to match types and ensure the intention is clear:
std::fill(std::begin(bufferLeft), std::end(bufferLeft), 0.0);
std::fill(std::begin(bufferRight), std::end(bufferRight), 0.0);
Then I don't need to think about whether all-bits zero is the same as 0.0 or not.
Thanks for all tips :) I'll adapt them! What about math/sine/normalized? :D That's the core of the question...
– markzzz
Nov 13 at 14:24
add a comment |
up vote
1
down vote
This is the "constant frequency" optimization mentioned in the comments, it is of course situational..
Sine and cosine are what happens to coordinates of a unit vector that is rotated around the origin, which means that a sequence like sin(start + k*rate)
can be generated by starting a unit vector at the starting point [cos(start), sin(start)]
and then successively multiplying it by the rotation matrix
[[cos(rate), -sin(rate)],
[sin(rate), cos(rate)]]
To generate each of the values, with the result being the Y coordinate of the resulting vectors.
So for a stretch of audio in which the frequency is not itself warped (amplitude can be varied on top of this though and that ends up causing some frequency-spread of its own), only a pair of sine and cosine are needed, the rest happens with multiplication and addition. But of course this does not help if the frequency does change all the time.
I see, thanks. But Yes, frequency/pitch/gain change constantly (or control rated)
– markzzz
Nov 13 at 20:52
add a comment |
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
3
down vote
Prefer C++ headers
Instead of <math.h>
, it's better to include <cmath>
and qualify names such as std::log
.
Prefer constants to macros
Re-write pi
as a strongly-typed, scoped variable rather than a preprocessor macro. Same for 2*pi
if you really feel the need.
Manage line lengths
Some lines are very long. In many cases, they just need newlines adding (e.g. bodies of inline functions can have their own lines).
In the case of the std::clamp()
call, it's probably worth using variables to give a name to the candidate value before clamping.
Easier sizeof
Instead of recomputing the size of bufferLeft
and bufferRight
like this:
memset(&bufferLeft, 0, blockSize * sizeof(double));
memset(&bufferRight, 0, blockSize * sizeof(double));
It's easier and clearer to just use the whole array size:
memset(&bufferLeft, 0, sizeof bufferLeft);
memset(&bufferRight, 0, sizeof bufferRight);
Personally, I'd generally prefer std::fill
to match types and ensure the intention is clear:
std::fill(std::begin(bufferLeft), std::end(bufferLeft), 0.0);
std::fill(std::begin(bufferRight), std::end(bufferRight), 0.0);
Then I don't need to think about whether all-bits zero is the same as 0.0 or not.
Thanks for all tips :) I'll adapt them! What about math/sine/normalized? :D That's the core of the question...
– markzzz
Nov 13 at 14:24
add a comment |
up vote
3
down vote
Prefer C++ headers
Instead of <math.h>
, it's better to include <cmath>
and qualify names such as std::log
.
Prefer constants to macros
Re-write pi
as a strongly-typed, scoped variable rather than a preprocessor macro. Same for 2*pi
if you really feel the need.
Manage line lengths
Some lines are very long. In many cases, they just need newlines adding (e.g. bodies of inline functions can have their own lines).
In the case of the std::clamp()
call, it's probably worth using variables to give a name to the candidate value before clamping.
Easier sizeof
Instead of recomputing the size of bufferLeft
and bufferRight
like this:
memset(&bufferLeft, 0, blockSize * sizeof(double));
memset(&bufferRight, 0, blockSize * sizeof(double));
It's easier and clearer to just use the whole array size:
memset(&bufferLeft, 0, sizeof bufferLeft);
memset(&bufferRight, 0, sizeof bufferRight);
Personally, I'd generally prefer std::fill
to match types and ensure the intention is clear:
std::fill(std::begin(bufferLeft), std::end(bufferLeft), 0.0);
std::fill(std::begin(bufferRight), std::end(bufferRight), 0.0);
Then I don't need to think about whether all-bits zero is the same as 0.0 or not.
Thanks for all tips :) I'll adapt them! What about math/sine/normalized? :D That's the core of the question...
– markzzz
Nov 13 at 14:24
add a comment |
up vote
3
down vote
up vote
3
down vote
Prefer C++ headers
Instead of <math.h>
, it's better to include <cmath>
and qualify names such as std::log
.
Prefer constants to macros
Re-write pi
as a strongly-typed, scoped variable rather than a preprocessor macro. Same for 2*pi
if you really feel the need.
Manage line lengths
Some lines are very long. In many cases, they just need newlines adding (e.g. bodies of inline functions can have their own lines).
In the case of the std::clamp()
call, it's probably worth using variables to give a name to the candidate value before clamping.
Easier sizeof
Instead of recomputing the size of bufferLeft
and bufferRight
like this:
memset(&bufferLeft, 0, blockSize * sizeof(double));
memset(&bufferRight, 0, blockSize * sizeof(double));
It's easier and clearer to just use the whole array size:
memset(&bufferLeft, 0, sizeof bufferLeft);
memset(&bufferRight, 0, sizeof bufferRight);
Personally, I'd generally prefer std::fill
to match types and ensure the intention is clear:
std::fill(std::begin(bufferLeft), std::end(bufferLeft), 0.0);
std::fill(std::begin(bufferRight), std::end(bufferRight), 0.0);
Then I don't need to think about whether all-bits zero is the same as 0.0 or not.
Prefer C++ headers
Instead of <math.h>
, it's better to include <cmath>
and qualify names such as std::log
.
Prefer constants to macros
Re-write pi
as a strongly-typed, scoped variable rather than a preprocessor macro. Same for 2*pi
if you really feel the need.
Manage line lengths
Some lines are very long. In many cases, they just need newlines adding (e.g. bodies of inline functions can have their own lines).
In the case of the std::clamp()
call, it's probably worth using variables to give a name to the candidate value before clamping.
Easier sizeof
Instead of recomputing the size of bufferLeft
and bufferRight
like this:
memset(&bufferLeft, 0, blockSize * sizeof(double));
memset(&bufferRight, 0, blockSize * sizeof(double));
It's easier and clearer to just use the whole array size:
memset(&bufferLeft, 0, sizeof bufferLeft);
memset(&bufferRight, 0, sizeof bufferRight);
Personally, I'd generally prefer std::fill
to match types and ensure the intention is clear:
std::fill(std::begin(bufferLeft), std::end(bufferLeft), 0.0);
std::fill(std::begin(bufferRight), std::end(bufferRight), 0.0);
Then I don't need to think about whether all-bits zero is the same as 0.0 or not.
answered Nov 13 at 14:07
Toby Speight
21.9k536107
21.9k536107
Thanks for all tips :) I'll adapt them! What about math/sine/normalized? :D That's the core of the question...
– markzzz
Nov 13 at 14:24
add a comment |
Thanks for all tips :) I'll adapt them! What about math/sine/normalized? :D That's the core of the question...
– markzzz
Nov 13 at 14:24
Thanks for all tips :) I'll adapt them! What about math/sine/normalized? :D That's the core of the question...
– markzzz
Nov 13 at 14:24
Thanks for all tips :) I'll adapt them! What about math/sine/normalized? :D That's the core of the question...
– markzzz
Nov 13 at 14:24
add a comment |
up vote
1
down vote
This is the "constant frequency" optimization mentioned in the comments, it is of course situational..
Sine and cosine are what happens to coordinates of a unit vector that is rotated around the origin, which means that a sequence like sin(start + k*rate)
can be generated by starting a unit vector at the starting point [cos(start), sin(start)]
and then successively multiplying it by the rotation matrix
[[cos(rate), -sin(rate)],
[sin(rate), cos(rate)]]
To generate each of the values, with the result being the Y coordinate of the resulting vectors.
So for a stretch of audio in which the frequency is not itself warped (amplitude can be varied on top of this though and that ends up causing some frequency-spread of its own), only a pair of sine and cosine are needed, the rest happens with multiplication and addition. But of course this does not help if the frequency does change all the time.
I see, thanks. But Yes, frequency/pitch/gain change constantly (or control rated)
– markzzz
Nov 13 at 20:52
add a comment |
up vote
1
down vote
This is the "constant frequency" optimization mentioned in the comments, it is of course situational..
Sine and cosine are what happens to coordinates of a unit vector that is rotated around the origin, which means that a sequence like sin(start + k*rate)
can be generated by starting a unit vector at the starting point [cos(start), sin(start)]
and then successively multiplying it by the rotation matrix
[[cos(rate), -sin(rate)],
[sin(rate), cos(rate)]]
To generate each of the values, with the result being the Y coordinate of the resulting vectors.
So for a stretch of audio in which the frequency is not itself warped (amplitude can be varied on top of this though and that ends up causing some frequency-spread of its own), only a pair of sine and cosine are needed, the rest happens with multiplication and addition. But of course this does not help if the frequency does change all the time.
I see, thanks. But Yes, frequency/pitch/gain change constantly (or control rated)
– markzzz
Nov 13 at 20:52
add a comment |
up vote
1
down vote
up vote
1
down vote
This is the "constant frequency" optimization mentioned in the comments, it is of course situational..
Sine and cosine are what happens to coordinates of a unit vector that is rotated around the origin, which means that a sequence like sin(start + k*rate)
can be generated by starting a unit vector at the starting point [cos(start), sin(start)]
and then successively multiplying it by the rotation matrix
[[cos(rate), -sin(rate)],
[sin(rate), cos(rate)]]
To generate each of the values, with the result being the Y coordinate of the resulting vectors.
So for a stretch of audio in which the frequency is not itself warped (amplitude can be varied on top of this though and that ends up causing some frequency-spread of its own), only a pair of sine and cosine are needed, the rest happens with multiplication and addition. But of course this does not help if the frequency does change all the time.
This is the "constant frequency" optimization mentioned in the comments, it is of course situational..
Sine and cosine are what happens to coordinates of a unit vector that is rotated around the origin, which means that a sequence like sin(start + k*rate)
can be generated by starting a unit vector at the starting point [cos(start), sin(start)]
and then successively multiplying it by the rotation matrix
[[cos(rate), -sin(rate)],
[sin(rate), cos(rate)]]
To generate each of the values, with the result being the Y coordinate of the resulting vectors.
So for a stretch of audio in which the frequency is not itself warped (amplitude can be varied on top of this though and that ends up causing some frequency-spread of its own), only a pair of sine and cosine are needed, the rest happens with multiplication and addition. But of course this does not help if the frequency does change all the time.
answered Nov 13 at 16:20
harold
98357
98357
I see, thanks. But Yes, frequency/pitch/gain change constantly (or control rated)
– markzzz
Nov 13 at 20:52
add a comment |
I see, thanks. But Yes, frequency/pitch/gain change constantly (or control rated)
– markzzz
Nov 13 at 20:52
I see, thanks. But Yes, frequency/pitch/gain change constantly (or control rated)
– markzzz
Nov 13 at 20:52
I see, thanks. But Yes, frequency/pitch/gain change constantly (or control rated)
– markzzz
Nov 13 at 20:52
add a comment |
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f207547%2faudio-processing-with-normalized-0-0-1-0-domain%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Does it make sense to add optimization that rely on the phase offset per step to be stable for a while, or is it always expected to fluctuate on a sample by sample basis?
– harold
Nov 13 at 15:57
@harold: I could control rate it and save on cycles, but let consider the worst case; so yes, its always expected to fluctuate sample by sample. But feel free to give an example that can be stable for a while, it would still be interessant.
– markzzz
Nov 13 at 15:59