Shepard tone timbre is now a harmonic series (buzz).
Fixed exception from custom tablet destructor. May need to check for a memory leak now.
This commit is contained in:
parent
1a6edd85a0
commit
ef0c96f72a
|
@ -83,10 +83,10 @@ protected:
|
||||||
fParameters[idx] = val;
|
fParameters[idx] = val;
|
||||||
switch (idx) {
|
switch (idx) {
|
||||||
case ktpax:
|
case ktpax:
|
||||||
synth.setFrequencyShift(val);
|
synth.setPhaseVelocity(val);
|
||||||
break;
|
break;
|
||||||
case ktpay:
|
case ktpay:
|
||||||
synth.setTargetRatio(val);
|
synth.setTimbre(val);
|
||||||
break;
|
break;
|
||||||
case ktpaz:
|
case ktpaz:
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -7,12 +7,20 @@ Synth::Synth(double sampleRate){
|
||||||
#define frac(x) ((x) - ((long)x))
|
#define frac(x) ((x) - ((long)x))
|
||||||
constexpr double MIN_VOLUME = 0.00001;
|
constexpr double MIN_VOLUME = 0.00001;
|
||||||
|
|
||||||
|
static constexpr double amin = 50;
|
||||||
|
static constexpr double apiq = 500;
|
||||||
|
static constexpr double amax = 10000;
|
||||||
//Volume of voice as a function of sample rate independent frequency.
|
//Volume of voice as a function of sample rate independent frequency.
|
||||||
static constexpr double getAmplitude( double hz )
|
static constexpr double getAmplitude( double hz )
|
||||||
{
|
{
|
||||||
if( hz < 20.0 ) return 0.0;
|
if( hz < amin ) return 0.0;
|
||||||
if( hz < 440.0 ) return hz / 440.0;
|
if( hz < apiq ) {
|
||||||
if( hz < 10000.0 ) return (10000.0 - hz) / 9560.0;
|
double a = (hz - amin) / (apiq - amin);
|
||||||
|
return a * a;}
|
||||||
|
if( hz < amax ) {
|
||||||
|
double a = 1.0 - (hz - apiq) / (amax - apiq);
|
||||||
|
return a * a;
|
||||||
|
}
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +29,26 @@ static constexpr double getAmplitude( double hz )
|
||||||
static_assert( MIN_VOLUME > getAmplitude(10.0) );
|
static_assert( MIN_VOLUME > getAmplitude(10.0) );
|
||||||
static_assert( MIN_VOLUME > getAmplitude(20000.0));
|
static_assert( MIN_VOLUME > getAmplitude(20000.0));
|
||||||
|
|
||||||
|
//New fundamental is twice as high as old fundamental.
|
||||||
|
//Even overtones become plain overtones.
|
||||||
|
void Synth::shiftUp()
|
||||||
|
{
|
||||||
|
spectrumPhase += 1.0;
|
||||||
|
for(uint voice = 0; voice < NUM_VOICES; ++voice){
|
||||||
|
phases[voice] = phases[(2 * voice + 1) & VOICE_MASK];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//New fundamental is half as high as old fundamental.
|
||||||
|
//Overtones become even overtones.
|
||||||
|
void Synth::shiftDown()
|
||||||
|
{
|
||||||
|
spectrumPhase -= 1.0;
|
||||||
|
for(uint voice = NUM_VOICES / 2 - 1; voice < NUM_VOICES; --voice){
|
||||||
|
phases[voice * 2 + 1] = phases[voice];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Synth::process(float* output, const uint32_t frames)
|
void Synth::process(float* output, const uint32_t frames)
|
||||||
{
|
{
|
||||||
double hz;
|
double hz;
|
||||||
|
@ -29,76 +57,53 @@ void Synth::process(float* output, const uint32_t frames)
|
||||||
for(uint32_t i = 0; i < frames; i++){
|
for(uint32_t i = 0; i < frames; i++){
|
||||||
|
|
||||||
//Set pitch.
|
//Set pitch.
|
||||||
hz = hzFund *= hzShift;
|
hz = hzFund = spectrumPhase * fMin + fMin;
|
||||||
|
|
||||||
//Set timbre.
|
bool isOddHarmonic = true;
|
||||||
ratio = ratioSlewRate * ratio + (1.0 - ratioSlewRate) * targetRatio;
|
|
||||||
|
|
||||||
for(uint voice = 0; voice < NUM_VOICES; ++voice){
|
for(uint voice = 0; voice < NUM_VOICES; ++voice){
|
||||||
//Get new phase.
|
//Get new phase.
|
||||||
double phase = phases[(voice + idxFund) & VOICE_MASK];
|
double phase = phases[voice];
|
||||||
phase += hz * sampleInterval;
|
phase += hz * sampleInterval;
|
||||||
phase = frac(phase);
|
phase = frac(phase);
|
||||||
|
|
||||||
//Anti-aliasing: don't bother rendering anything over the Nyquist rate.
|
//Anti-aliasing: don't bother rendering anything over the Nyquist rate.
|
||||||
if( hz > hzNyq ) break;
|
if( hz > hzNyq ) break;
|
||||||
output[i] += getAmplitude(hz) * sin(2.0 * M_PI * phase);
|
|
||||||
|
output[i] += (!isOddHarmonic + isOddHarmonic * spectrumPhase * spectrumPhase) //Fade in odd harmonics.
|
||||||
|
* getAmplitude(hz) //Frequency response.
|
||||||
|
* sin(2.0 * M_PI * phase); //Additives.
|
||||||
|
|
||||||
//Remember phase, move to higher overtone.
|
//Remember phase, move to higher overtone.
|
||||||
phases[(voice + idxFund) & VOICE_MASK] = phase;
|
phases[voice] = phase;
|
||||||
hz *= ratio * (voice + 1.0) / (voice + 2.0);
|
hz *= (voice + 2.0) / (voice + 1.0);
|
||||||
|
isOddHarmonic = !isOddHarmonic;
|
||||||
}
|
}
|
||||||
|
|
||||||
output[i] *= volume / static_cast<double>(NUM_VOICES);
|
output[i] *= volume / static_cast<double>(NUM_VOICES);
|
||||||
|
|
||||||
|
//Wrapping.
|
||||||
|
spectrumPhase += spectrumPhaseVelocity * sampleInterval;
|
||||||
|
if( spectrumPhase > 1.0) shiftDown();
|
||||||
|
if( spectrumPhase < 0.0) shiftUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Make timbre cyclical.
|
|
||||||
if( hzShift < 1.0 )
|
|
||||||
{
|
|
||||||
//Pitch low and decreasing, shift bottom voices to top.
|
|
||||||
while( getAmplitude(hz) > MIN_VOLUME
|
|
||||||
&& getAmplitude(hzFund) < MIN_VOLUME)
|
|
||||||
{
|
|
||||||
++idxFund;
|
|
||||||
hz *= 2.0 * ratio;
|
|
||||||
hzFund *= 2.0 * ratio;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( (hzShift > 1.0) || (targetRatio > ratio))
|
|
||||||
{
|
|
||||||
//Pitch high and increasing. Shift top voices to bottom.
|
|
||||||
while( getAmplitude(hz) < MIN_VOLUME
|
|
||||||
&& getAmplitude(hzFund) > MIN_VOLUME)
|
|
||||||
{
|
|
||||||
--idxFund;
|
|
||||||
hz /= 2.0 * ratio;
|
|
||||||
hzFund /= 2.0 * ratio;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Synth::setSampleRate(double oldRate, double newRate){
|
void Synth::setSampleRate(double oldRate, double newRate){
|
||||||
sampleInterval = 1.0 / newRate;
|
sampleInterval = 1.0 / newRate;
|
||||||
hzNyq = newRate / 2;
|
hzNyq = newRate / 2;
|
||||||
ratioSlewRate = pow(ratioSlewRate, oldRate / newRate);
|
|
||||||
hzShift = pow(hzShift, newRate / oldRate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Takes value from 0 to 1 representing frequency shift factor.
|
//Takes value from 0 to 1 representing frequency shift factor.
|
||||||
//0 : lower one octave per second
|
//0 : lower one octave per second
|
||||||
//1 : raise one octave per second
|
//1 : raise one octave per second
|
||||||
void Synth::setFrequencyShift(double in){
|
void Synth::setPhaseVelocity(double in){
|
||||||
hzShift = exp2(2.0 * (in - 0.5) * sampleInterval);
|
spectrumPhaseVelocity = 2.0 * (in - 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr double minRatio = 1.0;
|
void Synth::setTimbre(double in){
|
||||||
static constexpr double maxRatio = 1.05;
|
|
||||||
//Slew to given inharmonicity ratio.
|
|
||||||
//0 : next voice is exactly harmonic.
|
|
||||||
//1 : next voice is 5% inharmonic.
|
|
||||||
void Synth::setTargetRatio(double in){
|
|
||||||
targetRatio = minRatio + in * (maxRatio - minRatio);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Synth::setVolume(double in){
|
void Synth::setVolume(double in){
|
||||||
|
|
|
@ -7,7 +7,7 @@ struct Voice
|
||||||
double phase;
|
double phase;
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr unsigned int NUM_VOICES = 64;
|
constexpr unsigned int NUM_VOICES = 256;
|
||||||
constexpr unsigned int VOICE_MASK = NUM_VOICES - 1;
|
constexpr unsigned int VOICE_MASK = NUM_VOICES - 1;
|
||||||
|
|
||||||
class Synth
|
class Synth
|
||||||
|
@ -15,23 +15,26 @@ class Synth
|
||||||
public:
|
public:
|
||||||
explicit Synth(double sampleRate);
|
explicit Synth(double sampleRate);
|
||||||
void process(float *output, uint32_t frames);
|
void process(float *output, uint32_t frames);
|
||||||
void setFrequencyShift(double in);
|
void setPhaseVelocity(double in);
|
||||||
void setTargetRatio(double in);
|
void setTimbre(double in);
|
||||||
void setSampleRate(double oldRate, double newRate);
|
void setSampleRate(double oldRate, double newRate);
|
||||||
void setVolume(double in);
|
void setVolume(double in);
|
||||||
|
void shiftUp();
|
||||||
|
void shiftDown();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//Phase must persist between run invocation.
|
//Phase must persist between run invocation.
|
||||||
//Phase of each voice, considered as a period function on the unit interval.
|
//Phase of each voice, considered a function on the unit circle.
|
||||||
std::array<double, NUM_VOICES> phases = {0};
|
std::array<double, NUM_VOICES> phases = {0};
|
||||||
//Index of lowest voice in phase array.
|
|
||||||
unsigned int idxFund = 0;
|
|
||||||
float volume = 0.0f;
|
float volume = 0.0f;
|
||||||
double hzFund = 32.7;
|
//Parameter in unit circle controlling pitch (varies by one octave).
|
||||||
double hzShift = 1.0;
|
double spectrumPhase = 1.0f;
|
||||||
|
//Revolutions per second of spectrum phase.
|
||||||
|
double spectrumPhaseVelocity = 0.0;
|
||||||
|
//Lowest fundamental frequency of blit.
|
||||||
|
static inline constexpr double fMin = 55.0;
|
||||||
|
//Current fundamental frequency of blit.
|
||||||
|
double hzFund = fMin;
|
||||||
double hzNyq = 24000.0;
|
double hzNyq = 24000.0;
|
||||||
double ratio = 2.0;
|
|
||||||
double targetRatio = 2.0;
|
|
||||||
double ratioSlewRate = 0.99999;
|
|
||||||
double sampleInterval = 1.0 / 48000.0;
|
double sampleInterval = 1.0 / 48000.0;
|
||||||
};
|
};
|
|
@ -30,7 +30,6 @@ struct Packet {
|
||||||
class Tablet {
|
class Tablet {
|
||||||
public:
|
public:
|
||||||
Tablet(uintptr_t handle);
|
Tablet(uintptr_t handle);
|
||||||
~Tablet();
|
|
||||||
bool GetPacket( Packet &pkt );
|
bool GetPacket( Packet &pkt );
|
||||||
bool initialized;
|
bool initialized;
|
||||||
std::string errormsg = "";
|
std::string errormsg = "";
|
||||||
|
|
|
@ -18,10 +18,10 @@ Tablet::Tablet(uintptr_t handle)
|
||||||
NewContext(hwnd);
|
NewContext(hwnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
Tablet::~Tablet() {
|
/*Tablet::~Tablet() {
|
||||||
if (hctx) { gpWTClose(hctx); }
|
// if (hctx) { gpWTClose(hctx); }
|
||||||
UnloadWintab();
|
UnloadWintab();
|
||||||
}
|
}*/
|
||||||
|
|
||||||
bool Tablet::GetPacket( Packet& packet ) {
|
bool Tablet::GetPacket( Packet& packet ) {
|
||||||
//Serial number of newest packet.
|
//Serial number of newest packet.
|
||||||
|
@ -54,7 +54,7 @@ void Tablet::NewContext(HWND hwnd) {
|
||||||
AXIS TabletZ = { 0 };
|
AXIS TabletZ = { 0 };
|
||||||
AXIS TabletPressure = { 0 };
|
AXIS TabletPressure = { 0 };
|
||||||
gpWTInfoA(WTI_DEFCONTEXT, 0, &ctx);
|
gpWTInfoA(WTI_DEFCONTEXT, 0, &ctx);
|
||||||
ctx.lcOptions |= CXO_MESSAGES; //TODO: checker çela
|
ctx.lcOptions |= CXO_MESSAGES; //TODO: checker <EFBFBD>ela
|
||||||
ctx.lcPktData = PACKETDATA;
|
ctx.lcPktData = PACKETDATA;
|
||||||
ctx.lcPktMode = 0;
|
ctx.lcPktMode = 0;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue