Import existing source tree; original VCS history is no longer available. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1830 lines
36 KiB
C
1830 lines
36 KiB
C
/*(LGPL)
|
|
---------------------------------------------------------------------------
|
|
a_wca.c - WCA, the Wave Construction API
|
|
---------------------------------------------------------------------------
|
|
* Copyright (C) 2002, David Olofson
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 2.1 of the License, or (at
|
|
* your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
/* # of frames to process per loop in most functions */
|
|
#define BLOCK_FRAMES 64
|
|
|
|
#define MAX_SPECTRUM_OSCILLATORS 128
|
|
|
|
#define ONEDIV32K 3.0517578125e-5
|
|
#define ONEDIV65K 1.52587890625e-5
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "kobolog.h"
|
|
#include "a_wca.h"
|
|
#include "a_math.h"
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Framework for Buffer Based Processing
|
|
----------------------------------------------------------*/
|
|
|
|
/*
|
|
* Parameters
|
|
*/
|
|
static audio_wave_t *s_w = NULL; /* Target waveform */
|
|
static int s_stereo = 0; /* 1 if waveform is stereo */
|
|
static float s_fs = 44100.0f; /* Target sample rate (Hz) */
|
|
static float s_dt = 1.0f/44100.0f; /* Target delta time (s) */
|
|
/*
|
|
* State
|
|
*/
|
|
|
|
/*
|
|
* IMPORTANT: These two MUST NOT, under any circumstances,
|
|
* be allowed to increment beyond s_w->samples!
|
|
* If they do, and you're use this framework
|
|
* stuff, all hell will break lose.
|
|
*
|
|
* (One could use signs instead, but what's the point?
|
|
* You have no business outside waveforms anyway.)
|
|
*/
|
|
static unsigned s_rpos = 0; /* Current target read position */
|
|
static unsigned s_wpos = 0; /* Current target write position */
|
|
|
|
|
|
static void _init_processing(audio_wave_t *w)
|
|
{
|
|
s_w = w;
|
|
|
|
s_fs = (float)w->rate;
|
|
s_dt = 1.0f / s_fs;
|
|
|
|
s_rpos = s_wpos = 0;
|
|
|
|
switch(s_w->format)
|
|
{
|
|
case AF_STEREO8:
|
|
case AF_STEREO16:
|
|
case AF_STEREO32:
|
|
s_stereo = 1;
|
|
break;
|
|
case AF_MONO8:
|
|
case AF_MONO16:
|
|
case AF_MONO32:
|
|
case AF_MIDI:
|
|
s_stereo = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns the number of frames left to process, if 'pos'
|
|
* is the current position. If there are more than 'limit'
|
|
* frames left to process, 'limit' is returned.
|
|
*/
|
|
static inline unsigned _next_block(unsigned pos, unsigned limit)
|
|
{
|
|
unsigned frames;
|
|
frames = s_w->samples - (pos >> s_stereo);
|
|
if(frames > limit)
|
|
return limit;
|
|
else
|
|
return frames;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Internal Toolkit
|
|
----------------------------------------------------------*/
|
|
|
|
/*
|
|
* NOTE:
|
|
* These work with floats in the 0 dB range [-32768, 32767],
|
|
* regardless of waveform format.
|
|
*/
|
|
|
|
static inline void read_sample(audio_wave_t *w, unsigned s, float *inl, float *inr)
|
|
{
|
|
switch(w->format)
|
|
{
|
|
case AF_MONO8:
|
|
*inr = *inl = (float)(w->data.si8[s]<<8);
|
|
break;
|
|
case AF_STEREO8:
|
|
*inl = (float)(w->data.si8[s*2]<<8);
|
|
*inr = (float)(w->data.si8[s*2+1]<<8);
|
|
break;
|
|
case AF_MONO16:
|
|
*inr = *inl = (float)w->data.si16[s];
|
|
break;
|
|
case AF_STEREO16:
|
|
*inl = (float)w->data.si16[s*2];
|
|
*inr = (float)w->data.si16[s*2+1];
|
|
break;
|
|
case AF_MONO32:
|
|
*inr = *inl = w->data.f32[s] * 32768.0f;
|
|
break;
|
|
case AF_STEREO32:
|
|
*inl = w->data.f32[s*2] * 32768.0f;
|
|
*inr = w->data.f32[s*2+1] * 32768.0f;
|
|
break;
|
|
case AF_MIDI:
|
|
*inr = *inl = *inr = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static inline void write_sample(audio_wave_t *w, unsigned s, float outl, float outr)
|
|
{
|
|
if(outl < -32768.0f)
|
|
outl = -32768.0f;
|
|
else if(outl > 32767.0f)
|
|
outl = 32767.0f;
|
|
if(outr < -32768.0f)
|
|
outr = -32768.0f;
|
|
else if(outr > 32767.0f)
|
|
outr = 32767.0f;
|
|
|
|
switch(w->format)
|
|
{
|
|
case AF_MONO8:
|
|
w->data.si8[s] = (int)outl >> 8;
|
|
break;
|
|
case AF_STEREO8:
|
|
w->data.si8[s*2] = (int)outl >> 8;
|
|
w->data.si8[s*2+1] = (int)outr >> 8;
|
|
break;
|
|
case AF_MONO16:
|
|
w->data.si16[s] = (Sint16)outl;
|
|
break;
|
|
case AF_STEREO16:
|
|
w->data.si16[s*2] = (Sint16)outl;
|
|
w->data.si16[s*2+1] = (Sint16)outr;
|
|
break;
|
|
case AF_MONO32:
|
|
w->data.f32[s] = outl * ONEDIV32K;
|
|
break;
|
|
case AF_STEREO32:
|
|
w->data.f32[s*2] = outl * ONEDIV32K;
|
|
w->data.f32[s*2+1] = outr * ONEDIV32K;
|
|
break;
|
|
case AF_MIDI:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static inline void add_sample(audio_wave_t *w, unsigned s, float outl, float outr)
|
|
{
|
|
float l, r;
|
|
switch(w->format)
|
|
{
|
|
case AF_MONO8:
|
|
l = (float)(w->data.si8[s]<<8) + outl;
|
|
if(l > 32767.0f)
|
|
w->data.si8[s] = 127;
|
|
else if(l < -32768.0f)
|
|
w->data.si8[s] = -128;
|
|
else
|
|
w->data.si8[s] = (int)l >> 8;
|
|
break;
|
|
case AF_STEREO8:
|
|
s <<= 1;
|
|
l = (float)(w->data.si8[s]<<8) + outl;
|
|
if(l > 32767.0f)
|
|
w->data.si8[s] = 127;
|
|
else if(l < -32768.0f)
|
|
w->data.si8[s] = -128;
|
|
else
|
|
w->data.si8[s] = (int)l >> 8;
|
|
++s;
|
|
r = (float)(w->data.si8[s]<<8) + outr;
|
|
if(r > 32767.0f)
|
|
w->data.si8[s] = 127;
|
|
else if(r < -32768.0f)
|
|
w->data.si8[s] = -128;
|
|
else
|
|
w->data.si8[s] = (int)r >> 8;
|
|
break;
|
|
case AF_MONO16:
|
|
l = (float)w->data.si16[s] + outl;
|
|
if(l > 32767.0f)
|
|
w->data.si16[s] = 32767;
|
|
else if(l < -32768.0f)
|
|
w->data.si16[s] = -32768;
|
|
else
|
|
w->data.si16[s] = (Sint16)l;
|
|
break;
|
|
case AF_STEREO16:
|
|
s <<= 1;
|
|
l = (float)w->data.si16[s] + outl;
|
|
if(l > 32767.0f)
|
|
w->data.si16[s] = 32767;
|
|
else if(l < -32768.0f)
|
|
w->data.si16[s] = -32768;
|
|
else
|
|
w->data.si16[s] = (Sint16)l;
|
|
++s;
|
|
r = (float)w->data.si16[s] + outr;
|
|
if(r > 32767.0f)
|
|
w->data.si16[s] = 32767;
|
|
else if(r < -32768.0f)
|
|
w->data.si16[s] = -32768;
|
|
else
|
|
w->data.si16[s] = (Sint16)r;
|
|
break;
|
|
case AF_MONO32:
|
|
w->data.f32[s] += outl * ONEDIV32K;
|
|
break;
|
|
case AF_STEREO32:
|
|
w->data.f32[s*2] += outl * ONEDIV32K;
|
|
w->data.f32[s*2+1] += outr * ONEDIV32K;
|
|
break;
|
|
case AF_MIDI:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Block based versions
|
|
*/
|
|
|
|
/*
|
|
* Read 'frames' samples into the array(s).
|
|
*
|
|
* Only 'l' is used on mono waveforms!
|
|
* Reading past the end of the waveform is NOT ALLOWED.
|
|
*
|
|
* Upon returning, s_rpos will index the first sample after the read block.
|
|
*/
|
|
static void read_samples(float *inl, float *inr, unsigned frames)
|
|
{
|
|
unsigned wp = 0;
|
|
switch(s_w->format)
|
|
{
|
|
case AF_MONO8:
|
|
{
|
|
Sint8 *d = s_w->data.si8;
|
|
while(wp < frames)
|
|
inl[wp++] = (float)(d[s_rpos++]<<8);
|
|
break;
|
|
}
|
|
case AF_STEREO8:
|
|
{
|
|
Sint8 *d = s_w->data.si8;
|
|
while(wp < frames)
|
|
{
|
|
inl[wp] = (float)(d[s_rpos++]<<8);
|
|
inr[wp] = (float)(d[s_rpos++]<<8);
|
|
++wp;
|
|
}
|
|
break;
|
|
}
|
|
case AF_MONO16:
|
|
{
|
|
Sint16 *d = s_w->data.si16;
|
|
while(wp < frames)
|
|
inl[wp++] = (float)(d[s_rpos++]);
|
|
break;
|
|
}
|
|
case AF_STEREO16:
|
|
{
|
|
Sint16 *d = s_w->data.si16;
|
|
while(wp < frames)
|
|
{
|
|
inl[wp] = (float)(d[s_rpos++]);
|
|
inr[wp] = (float)(d[s_rpos++]);
|
|
++wp;
|
|
}
|
|
break;
|
|
}
|
|
case AF_MONO32:
|
|
{
|
|
float *d = s_w->data.f32;
|
|
while(wp < frames)
|
|
inl[wp++] = d[s_rpos++] * 32768.0f;
|
|
break;
|
|
}
|
|
case AF_STEREO32:
|
|
{
|
|
float *d = s_w->data.f32;
|
|
while(wp < frames)
|
|
{
|
|
inl[wp] = d[s_rpos++] * 32768.0f;
|
|
inr[wp] = d[s_rpos++] * 32768.0f;
|
|
++wp;
|
|
}
|
|
break;
|
|
}
|
|
case AF_MIDI:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Write 'frames' samples from the array(s).
|
|
*
|
|
* Only 'l' is used on mono waveforms!
|
|
* Writing past the end of the waveform is NOT ALLOWED.
|
|
* Data is clipped to the limits of the waveform data format.
|
|
*
|
|
* Upon returning, s_wpos will index the first sample after the written block.
|
|
*/
|
|
#define __CLIP(s) \
|
|
if(s < -32768.0f) \
|
|
s = -32768.0f; \
|
|
else if(s > 32767.0f) \
|
|
s = 32767.0f;
|
|
static void write_samples(float *outl, float *outr, unsigned frames)
|
|
{
|
|
unsigned rp = 0;
|
|
switch(s_w->format)
|
|
{
|
|
case AF_MONO8:
|
|
{
|
|
Sint8 *d = s_w->data.si8;
|
|
while(rp < frames)
|
|
{
|
|
float l = outl[rp++];
|
|
__CLIP(l)
|
|
d[s_wpos++] = (int)l >> 8;
|
|
}
|
|
break;
|
|
}
|
|
case AF_STEREO8:
|
|
{
|
|
Sint8 *d = s_w->data.si8;
|
|
while(rp < frames)
|
|
{
|
|
float l = outl[rp];
|
|
float r = outr[rp];
|
|
__CLIP(l)
|
|
__CLIP(r)
|
|
d[s_wpos++] = (int)l >> 8;
|
|
d[s_wpos++] = (int)r >> 8;
|
|
++rp;
|
|
}
|
|
break;
|
|
}
|
|
case AF_MONO16:
|
|
{
|
|
Sint16 *d = s_w->data.si16;
|
|
while(rp < frames)
|
|
{
|
|
float l = outl[rp++];
|
|
__CLIP(l)
|
|
d[s_wpos++] = (Sint16)l;
|
|
}
|
|
break;
|
|
}
|
|
case AF_STEREO16:
|
|
{
|
|
Sint16 *d = s_w->data.si16;
|
|
while(rp < frames)
|
|
{
|
|
float l = outl[rp];
|
|
float r = outr[rp];
|
|
__CLIP(l)
|
|
__CLIP(r)
|
|
d[s_wpos++] = (Sint16)l;
|
|
d[s_wpos++] = (Sint16)r;
|
|
++rp;
|
|
}
|
|
break;
|
|
}
|
|
case AF_MONO32:
|
|
{
|
|
float *d = s_w->data.f32;
|
|
while(rp < frames)
|
|
d[s_wpos++] = outl[rp++] * ONEDIV32K;
|
|
break;
|
|
}
|
|
case AF_STEREO32:
|
|
{
|
|
float *d = s_w->data.f32;
|
|
while(rp < frames)
|
|
{
|
|
d[s_wpos++] = outl[rp] * ONEDIV32K;
|
|
d[s_wpos++] = outl[rp] * ONEDIV32K;
|
|
++rp;
|
|
}
|
|
break;
|
|
}
|
|
case AF_MIDI:
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
/*
|
|
* Add 'frames' samples from the array(s) to the waveform contents.
|
|
*
|
|
* Only 'l' is used on mono waveforms!
|
|
* Working past the end of the waveform is NOT ALLOWED.
|
|
* Resulting data is clipped to the limits of the waveform data format.
|
|
*
|
|
* Upon returning, s_wpos will index the first sample after the written block.
|
|
*/
|
|
static void add_samples(float *outl, float *outr, unsigned frames)
|
|
{
|
|
unsigned rp = 0;
|
|
switch(s_w->format)
|
|
{
|
|
case AF_MONO8:
|
|
{
|
|
Sint8 *d = s_w->data.si8;
|
|
while(rp < frames)
|
|
{
|
|
float l = outl[rp++] + (float)(d[s_wpos]<<8);
|
|
__CLIP(l)
|
|
d[s_wpos++] = (int)l >> 8;
|
|
}
|
|
break;
|
|
}
|
|
case AF_STEREO8:
|
|
{
|
|
Sint8 *d = s_w->data.si8;
|
|
while(rp < frames)
|
|
{
|
|
float l = outl[rp] + (float)(d[s_wpos]<<8);
|
|
float r = outr[rp] + (float)(d[s_wpos+1]<<8);
|
|
__CLIP(l)
|
|
__CLIP(r)
|
|
d[s_wpos++] = (int)l >> 8;
|
|
d[s_wpos++] = (int)r >> 8;
|
|
++rp;
|
|
}
|
|
break;
|
|
}
|
|
case AF_MONO16:
|
|
{
|
|
Sint16 *d = s_w->data.si16;
|
|
while(rp < frames)
|
|
{
|
|
float l = outl[rp++] + (float)d[s_wpos];
|
|
__CLIP(l)
|
|
d[s_wpos++] = (Sint16)l;
|
|
}
|
|
break;
|
|
}
|
|
case AF_STEREO16:
|
|
{
|
|
Sint16 *d = s_w->data.si16;
|
|
while(rp < frames)
|
|
{
|
|
float l = outl[rp] + (float)d[s_wpos];
|
|
float r = outr[rp] + (float)d[s_wpos+1];
|
|
__CLIP(l)
|
|
__CLIP(r)
|
|
d[s_wpos++] = (Sint16)l;
|
|
d[s_wpos++] = (Sint16)r;
|
|
++rp;
|
|
}
|
|
break;
|
|
}
|
|
case AF_MONO32:
|
|
{
|
|
float *d = s_w->data.f32;
|
|
while(rp < frames)
|
|
d[s_wpos++] += outl[rp++] * ONEDIV32K;
|
|
break;
|
|
}
|
|
case AF_STEREO32:
|
|
{
|
|
float *d = s_w->data.f32;
|
|
while(rp < frames)
|
|
{
|
|
d[s_wpos++] += outl[rp] * ONEDIV32K;
|
|
d[s_wpos++] += outl[rp] * ONEDIV32K;
|
|
++rp;
|
|
}
|
|
break;
|
|
}
|
|
case AF_MIDI:
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
#undef __CLIP
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
The WCA calls
|
|
----------------------------------------------------------*/
|
|
|
|
|
|
void wca_reset(void)
|
|
{
|
|
int i;
|
|
for(i = 0; i < _WCA_MODTARGETS; ++i)
|
|
wca_mod_reset(i);
|
|
wca_val(WCA_AMPLITUDE, 1.0f);
|
|
wca_val(WCA_FREQUENCY, 100.0f);
|
|
wca_val(WCA_LIMIT, 100000.0f);
|
|
}
|
|
|
|
|
|
/*
|
|
* Simple envelope generator.
|
|
TODO: This could use some serious optimizations...
|
|
*/
|
|
|
|
typedef struct modulator_t
|
|
{
|
|
/* Parameters */
|
|
unsigned steps; /* # of sections */
|
|
float v[WCA_MAX_ENV_STEPS]; /* target value */
|
|
float t[WCA_MAX_ENV_STEPS]; /* section start time */
|
|
float d[WCA_MAX_ENV_STEPS]; /* duration of section */
|
|
float mod_f, mod_a, mod_d; /* Modulation component */
|
|
|
|
/* State */
|
|
unsigned step; /* Current section */
|
|
unsigned done; /* samples of current section done */
|
|
unsigned remain; /* samples left of current section */
|
|
} modulator_t;
|
|
|
|
void _env_reset(modulator_t *e)
|
|
{
|
|
e->steps = 0;
|
|
e->v[0] = e->t[0] = e->d[0] = 0.0f;
|
|
}
|
|
|
|
void _env_add(modulator_t *e, float duration, float v)
|
|
{
|
|
if(e->steps >= WCA_MAX_ENV_STEPS)
|
|
{
|
|
log_printf(ELOG, "audio: Envelope overflow!\n");
|
|
return;
|
|
}
|
|
e->v[e->steps] = v;
|
|
e->d[e->steps] = duration;
|
|
if(e->steps)
|
|
e->t[e->steps] = e->t[e->steps-1] + e->d[e->steps-1];
|
|
else
|
|
e->t[e->steps] = 0.0f;
|
|
++e->steps;
|
|
}
|
|
|
|
#if 0
|
|
/*
|
|
* Dog slow sample-by-sample API. (KILLME)
|
|
*/
|
|
static inline float _env_output(modulator_t *e, float t)
|
|
{
|
|
float output, w;
|
|
int step = 0;
|
|
while(step < e->steps)
|
|
if(e->t[step] + e->d[step] > t)
|
|
break;
|
|
else
|
|
++step;
|
|
if(step >= e->steps)
|
|
output = e->v[e->steps-1];
|
|
else if(0 == step)
|
|
output = e->v[0] * t / e->d[0];
|
|
else
|
|
{
|
|
float ip = (t - e->t[step]) / e->d[step];
|
|
output = e->v[step - 1] * (1.0f - ip) + e->v[step] * ip;
|
|
}
|
|
|
|
w = t * e->mod_f * 2.0f * M_PI;
|
|
output *= 1.0f + sin(w) * e->mod_d;
|
|
output += sin(w) * e->mod_a;
|
|
return output;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* New block based interface
|
|
*/
|
|
|
|
/* Initialize modulator 'e' for block based processing. */
|
|
static void _env_start(modulator_t *e)
|
|
{
|
|
e->step = 0;
|
|
e->remain = e->d[0] * s_fs;
|
|
e->done = 0;
|
|
}
|
|
|
|
/* Generate 'frame' samples of output from modulator 'e'. */
|
|
static void _env_process(modulator_t *e, float *out, unsigned frames)
|
|
{
|
|
while(frames)
|
|
{
|
|
unsigned i = 0;
|
|
unsigned frag;
|
|
float begv, endv, dv, ip, t;
|
|
|
|
if(e->step >= e->steps)
|
|
{
|
|
/* Beyond the end ==> flat forever */
|
|
if(0 == e->steps)
|
|
endv = 0.0;
|
|
else
|
|
endv = e->v[e->steps-1];
|
|
for(; i < frames; ++i)
|
|
out[i] = endv;
|
|
return;
|
|
}
|
|
|
|
frag = frames < e->remain ? frames : e->remain;
|
|
|
|
if(frag)
|
|
{
|
|
if(0 == e->step)
|
|
{
|
|
/* First section */
|
|
begv = 0.0f;
|
|
endv = e->v[0];
|
|
}
|
|
else
|
|
{
|
|
/* All other sections */
|
|
begv = e->v[e->step - 1];
|
|
endv = e->v[e->step];
|
|
}
|
|
|
|
t = (float)e->done * s_dt;
|
|
dv = (endv - begv) / e->d[e->step] * s_dt;
|
|
ip = t / e->d[e->step];
|
|
begv = endv * ip + begv * (1.0f - ip);
|
|
while(i < frag)
|
|
{
|
|
out[i++] = begv;
|
|
begv += dv;
|
|
}
|
|
|
|
out += frag;
|
|
frames -= frag;
|
|
e->done += frag;
|
|
e->remain -= frag;
|
|
}
|
|
|
|
if(!e->remain)
|
|
{
|
|
/* Next section! */
|
|
++e->step;
|
|
if(e->step < e->steps)
|
|
{
|
|
/*
|
|
FIXME: This rounds the start of each section to the nearest sample.
|
|
FIXME: Normally, that wouldn't be an issue (although a proper band
|
|
FIXME: limited rendition of envelopes would be nice), but here, the
|
|
FIXME: errors will add up! This might matter with lots of sections
|
|
FIXME: and/or low sample rates.
|
|
*/
|
|
e->remain = e->d[e->step] * s_fs;
|
|
e->done = 0;
|
|
}
|
|
/*
|
|
* NOTE:
|
|
* e->step does the whole job in the 'else'
|
|
* case, so we don't have to set the others.
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Global envelope generators.
|
|
*/
|
|
static modulator_t env[_WCA_MODTARGETS];
|
|
|
|
|
|
static void _env_start_all(void)
|
|
{
|
|
int i;
|
|
for(i = 0; i < _WCA_MODTARGETS; ++i)
|
|
_env_start(env + i);
|
|
}
|
|
|
|
|
|
void wca_mod_reset(wca_modtargets_t target)
|
|
{
|
|
if(target < 0)
|
|
return;
|
|
if(target >= _WCA_MODTARGETS)
|
|
return;
|
|
_env_reset(&env[target]);
|
|
wca_mod(target, 0, 0, 0);
|
|
}
|
|
|
|
|
|
void wca_env(wca_modtargets_t target, float duration, float v)
|
|
{
|
|
if(target < 0)
|
|
return;
|
|
if(target >= _WCA_MODTARGETS)
|
|
return;
|
|
_env_add(&env[target], duration, v);
|
|
}
|
|
|
|
void wca_mod(wca_modtargets_t target, float frequency,
|
|
float amplitude, float depth)
|
|
{
|
|
if(target < 0)
|
|
return;
|
|
if(target >= _WCA_MODTARGETS)
|
|
return;
|
|
env[target].mod_f = frequency;
|
|
env[target].mod_a = amplitude;
|
|
env[target].mod_d = depth;
|
|
}
|
|
|
|
|
|
void wca_val(wca_modtargets_t target, float v)
|
|
{
|
|
wca_mod_reset(target);
|
|
wca_env(target, 0, v);
|
|
wca_mod(target, 0, 0, 0);
|
|
}
|
|
|
|
|
|
#include "a_wcaosc.h"
|
|
|
|
void wca_osc(int wid, wca_waveform_t wf, wca_mixmodes_t mm)
|
|
{
|
|
unsigned s, frames;
|
|
char sync[BLOCK_FRAMES];
|
|
float olev = 1.0f;
|
|
float nyqvist = s_fs * 0.5f;
|
|
|
|
audio_wave_t *wave = audio_wave_get(wid);
|
|
if(!wave)
|
|
return;
|
|
|
|
_init_processing(wave);
|
|
_env_start_all();
|
|
noise_reset();
|
|
osc_w = 0.0;
|
|
osc_yit = 0.0f;
|
|
noise_out = 0.0f;
|
|
|
|
switch(mm)
|
|
{
|
|
case WCA_ADD:
|
|
case WCA_MUL:
|
|
case WCA_FM:
|
|
case WCA_FM_ADD:
|
|
memset(sync, 0, sizeof(sync));
|
|
break;
|
|
case WCA_SYNC:
|
|
case WCA_SYNC_ADD:
|
|
break;
|
|
}
|
|
|
|
while( (frames = _next_block(s_wpos, BLOCK_FRAMES)) )
|
|
{
|
|
float inl[BLOCK_FRAMES];
|
|
float inr[BLOCK_FRAMES];
|
|
|
|
float a[BLOCK_FRAMES];
|
|
float bal[BLOCK_FRAMES];
|
|
float f[BLOCK_FRAMES];
|
|
float limit[BLOCK_FRAMES];
|
|
float mod1[BLOCK_FRAMES];
|
|
float mod2[BLOCK_FRAMES];
|
|
float mod3[BLOCK_FRAMES];
|
|
|
|
float out[BLOCK_FRAMES];
|
|
|
|
_env_process(&env[WCA_AMPLITUDE], a, frames);
|
|
_env_process(&env[WCA_BALANCE], bal, frames);
|
|
_env_process(&env[WCA_FREQUENCY], f, frames);
|
|
_env_process(&env[WCA_LIMIT], limit, frames);
|
|
_env_process(&env[WCA_MOD1], mod1, frames);
|
|
_env_process(&env[WCA_MOD2], mod2, frames);
|
|
_env_process(&env[WCA_MOD3], mod3, frames);
|
|
|
|
for(s = 0; s < frames; ++s)
|
|
if(limit[s] > nyqvist)
|
|
limit[s] = nyqvist;
|
|
|
|
read_samples(inl, inr, frames);
|
|
|
|
/* Handle FM and SYNC modes*/
|
|
switch(mm)
|
|
{
|
|
case WCA_ADD:
|
|
case WCA_MUL:
|
|
break;
|
|
case WCA_FM:
|
|
case WCA_FM_ADD:
|
|
if(s_stereo)
|
|
for(s = 0; s < frames; ++s)
|
|
f[s] *= 1.0f + (inl[s] + inr[s]) *
|
|
ONEDIV65K * bal[s];
|
|
else
|
|
for(s = 0; s < frames; ++s)
|
|
f[s] *= 1.0f + inl[s] *
|
|
ONEDIV65K * bal[s];
|
|
break;
|
|
case WCA_SYNC:
|
|
case WCA_SYNC_ADD:
|
|
/*
|
|
FIXME: Storing retrig points as a list of "timestamps" would probably
|
|
FIXME: be more efficient than this per-sample array hack...
|
|
FIXME: More importantly, that can provide sub-sample accurate sync
|
|
FIXME: timing. Fractional timing would have to be derived by looking
|
|
FIXME: at the samples before and after each zero crossing.
|
|
*/
|
|
if(s_stereo)
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
float lev = inl[s] + inr[s];
|
|
sync[s] = (olev > 0.0f) &&
|
|
(lev < 0.0f);
|
|
olev = lev;
|
|
}
|
|
else
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
float lev = inl[s];
|
|
sync[s] = (olev > 0.0f) &&
|
|
(lev < 0.0f);
|
|
olev = lev;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Oscillators! */
|
|
switch(wf)
|
|
{
|
|
case WCA_DC:
|
|
for(s = 0; s < frames; ++s)
|
|
out[s] = 1.0f;
|
|
break;
|
|
case WCA_SINE:
|
|
_osc_sine(sync, f, mod1, out, frames);
|
|
break;
|
|
case WCA_HALFSINE:
|
|
_osc_halfsine(sync, f, mod1, out, frames);
|
|
break;
|
|
case WCA_RECTSINE:
|
|
_osc_rectsine(sync, f, mod1, out, frames);
|
|
break;
|
|
case WCA_PULSE:
|
|
_osc_pulse(sync, f, mod1, out, frames);
|
|
break;
|
|
case WCA_TRIANGLE:
|
|
_osc_triangle(sync, f, mod1, out, frames);
|
|
break;
|
|
case WCA_SINEMORPH:
|
|
_osc_sinemorph(sync, f, mod1, mod2, limit, out, frames);
|
|
break;
|
|
case WCA_BLMORPH:
|
|
_osc_blmorph(sync, f, mod1, mod2, mod3, limit,
|
|
out, frames);
|
|
break;
|
|
case WCA_BLCROSS:
|
|
_osc_blcross(sync, f, mod1, mod2, mod3, limit,
|
|
out, frames);
|
|
break;
|
|
case WCA_NOISE:
|
|
_osc_noise(sync, f, out, frames);
|
|
break;
|
|
case WCA_SPECTRUM:
|
|
_osc_spectrum(sync, f, mod1, mod2, limit, out, frames);
|
|
break;
|
|
case WCA_ASPECTRUM:
|
|
_osc_aspectrum(sync, f, mod1, mod2, limit, out, frames);
|
|
break;
|
|
case WCA_HSPECTRUM:
|
|
_osc_hspectrum(sync, f, mod1, mod2, mod3, limit,
|
|
out, frames);
|
|
break;
|
|
case WCA_AHSPECTRUM:
|
|
_osc_ahspectrum(sync, f, mod1, mod2, mod3, limit,
|
|
out, frames);
|
|
break;
|
|
}
|
|
|
|
/* Output */
|
|
switch(mm)
|
|
{
|
|
case WCA_ADD:
|
|
case WCA_FM_ADD:
|
|
case WCA_SYNC_ADD:
|
|
if(s_stereo)
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
float sout = out[s] * a[s] * 32767.0f;
|
|
inl[s] += sout;
|
|
inr[s] += sout;
|
|
}
|
|
else
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
float sout = out[s] * a[s] * 32767.0f;
|
|
inl[s] += sout;
|
|
}
|
|
break;
|
|
case WCA_MUL:
|
|
if(s_stereo)
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
float sout = inl[s] * out[s] * 0.5f;
|
|
sout *= bal[s];
|
|
sout *= a[s];
|
|
inl[s] = inl[s] * (1.0f - bal[s]) + sout;
|
|
inr[s] = inr[s] * (1.0f - bal[s]) + sout;
|
|
}
|
|
else
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
float sout = inl[s] * out[s] * 0.5f;
|
|
sout *= bal[s];
|
|
sout *= a[s];
|
|
inl[s] = inl[s] * (1.0f - bal[s]) + sout;
|
|
}
|
|
break;
|
|
case WCA_FM:
|
|
case WCA_SYNC:
|
|
if(s_stereo)
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
float sout = out[s] * a[s] * 32767.0f;
|
|
inl[s] = sout;
|
|
inr[s] = sout;
|
|
}
|
|
else
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
float sout = out[s] * a[s] * 32767.0f;
|
|
inl[s] = sout;
|
|
}
|
|
break;
|
|
}
|
|
write_samples(inl, inr, frames);
|
|
}
|
|
}
|
|
|
|
|
|
void wca_filter(int wid, wca_filtertype_t ft)
|
|
{
|
|
unsigned s, frames;
|
|
float ll = 0.0f, bl = 0.0f, hl = 0.0f;
|
|
float lr = 0.0f, br = 0.0f, hr = 0.0f;
|
|
float d1l = 0.0f;
|
|
float d1r = 0.0f;
|
|
audio_wave_t *wave = audio_wave_get(wid);
|
|
if(!wave)
|
|
return;
|
|
|
|
switch(ft)
|
|
{
|
|
case WCA_ALLPASS:
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
_init_processing(wave);
|
|
_env_start_all();
|
|
|
|
while( (frames = _next_block(s_wpos, BLOCK_FRAMES)) )
|
|
{
|
|
float f[BLOCK_FRAMES];
|
|
float q[BLOCK_FRAMES];
|
|
float l[BLOCK_FRAMES];
|
|
float r[BLOCK_FRAMES];
|
|
float amp[BLOCK_FRAMES];
|
|
float fe[BLOCK_FRAMES];
|
|
float mod1[BLOCK_FRAMES];
|
|
|
|
_env_process(&env[WCA_AMPLITUDE], amp, frames);
|
|
_env_process(&env[WCA_FREQUENCY], fe, frames);
|
|
_env_process(&env[WCA_MOD1], mod1, frames);
|
|
|
|
read_samples(l, r, frames);
|
|
|
|
/* Generate f and q buffers */
|
|
switch(ft)
|
|
{
|
|
case WCA_ALLPASS:
|
|
case WCA_LOWPASS_6DB:
|
|
case WCA_HIGHPASS_6DB:
|
|
for(s = 0; s < frames; ++s)
|
|
if(fe[s] > s_fs)
|
|
f[s] = 1.0f;
|
|
else
|
|
f[s] = fe[s] * s_dt;
|
|
break;
|
|
case WCA_LOWPASS_12DB:
|
|
case WCA_HIGHPASS_12DB:
|
|
case WCA_BANDPASS_12DB:
|
|
case WCA_NOTCH_12DB:
|
|
case WCA_PEAK_12DB:
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
float qlim;
|
|
/*
|
|
* Here we have some safety limits to keep the
|
|
* filter from blowing up...
|
|
*/
|
|
if(fe[s] > s_fs * 0.5f)
|
|
fe[s] = s_fs * 0.5f;
|
|
f[s] = 2.0f * sin(M_PI * fe[s] * s_dt * 0.5f);
|
|
q[s] = 1.0f / amp[s];
|
|
if(q[s] > 1.0f)
|
|
q[s] = 1.0f;
|
|
|
|
qlim = s_fs / fe[s];
|
|
if(qlim < 5.0f)
|
|
{
|
|
qlim *= qlim * qlim;
|
|
qlim /= 125.0f;
|
|
if(q[s] > qlim)
|
|
q[s] = qlim;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Perform the actual filtering */
|
|
switch(ft)
|
|
{
|
|
case WCA_ALLPASS:
|
|
case WCA_LOWPASS_6DB:
|
|
if(s_stereo) for(s = 0; s < frames; ++s)
|
|
{
|
|
d1r += (r[s] - d1r) * f[s];
|
|
r[s] = r[s] * mod1[s] + d1r * (1.0f - mod1[s]);
|
|
}
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
d1l += (l[s] - d1l) * f[s];
|
|
l[s] = l[s] * mod1[s] + d1l * (1.0f - mod1[s]);
|
|
}
|
|
break;
|
|
case WCA_HIGHPASS_6DB:
|
|
if(s_stereo) for(s = 0; s < frames; ++s)
|
|
{
|
|
d1r += (r[s] - d1r) * f[s];
|
|
r[s] = r[s] * mod1[s] + (r[s] - d1r) *
|
|
(1.0f - mod1[s]);
|
|
}
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
d1l += (l[s] - d1l) * f[s];
|
|
l[s] = l[s] * mod1[s] + (l[s] - d1l) *
|
|
(1.0f - mod1[s]);
|
|
}
|
|
break;
|
|
case WCA_LOWPASS_12DB:
|
|
/*
|
|
* 2x oversampling - although this quick hack
|
|
* performs no input interpolation, and just
|
|
* drops every other output sample.
|
|
*/
|
|
if(s_stereo) for(s = 0; s < frames; ++s)
|
|
{
|
|
lr += f[s]*br;
|
|
hr = r[s] - lr - q[s]*br;
|
|
br += f[s]*hr;
|
|
|
|
lr += f[s]*br;
|
|
hr = r[s] - lr - q[s]*br;
|
|
br += f[s]*hr;
|
|
|
|
r[s] = r[s] * mod1[s] + lr * (1.0f - mod1[s]);
|
|
}
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
ll += f[s]*bl;
|
|
hl = l[s] - ll - q[s]*bl;
|
|
bl += f[s]*hl;
|
|
|
|
ll += f[s]*bl;
|
|
hl = l[s] - ll - q[s]*bl;
|
|
bl += f[s]*hl;
|
|
|
|
l[s] = l[s] * mod1[s] + ll * (1.0f - mod1[s]);
|
|
}
|
|
break;
|
|
case WCA_HIGHPASS_12DB:
|
|
if(s_stereo) for(s = 0; s < frames; ++s)
|
|
{
|
|
lr += f[s]*br;
|
|
hr = r[s] - lr - q[s]*br;
|
|
br += f[s]*hr;
|
|
|
|
lr += f[s]*br;
|
|
hr = r[s] - lr - q[s]*br;
|
|
br += f[s]*hr;
|
|
|
|
r[s] = r[s] * mod1[s] + hr * (1.0f - mod1[s]);
|
|
}
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
ll += f[s]*bl;
|
|
hl = l[s] - ll - q[s]*bl;
|
|
bl += f[s]*hl;
|
|
|
|
ll += f[s]*bl;
|
|
hl = l[s] - ll - q[s]*bl;
|
|
bl += f[s]*hl;
|
|
|
|
l[s] = l[s] * mod1[s] + hl * (1.0f - mod1[s]);
|
|
}
|
|
break;
|
|
case WCA_BANDPASS_12DB:
|
|
if(s_stereo) for(s = 0; s < frames; ++s)
|
|
{
|
|
lr += f[s]*br;
|
|
hr = r[s] - lr - q[s]*br;
|
|
br += f[s]*hr;
|
|
|
|
lr += f[s]*br;
|
|
hr = r[s] - lr - q[s]*br;
|
|
br += f[s]*hr;
|
|
|
|
r[s] = r[s] * mod1[s] + br * (1.0f - mod1[s]);
|
|
}
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
ll += f[s]*bl;
|
|
hl = l[s] - ll - q[s]*bl;
|
|
bl += f[s]*hl;
|
|
|
|
ll += f[s]*bl;
|
|
hl = l[s] - ll - q[s]*bl;
|
|
bl += f[s]*hl;
|
|
|
|
l[s] = l[s] * mod1[s] + bl * (1.0f - mod1[s]);
|
|
}
|
|
break;
|
|
case WCA_NOTCH_12DB:
|
|
if(s_stereo) for(s = 0; s < frames; ++s)
|
|
{
|
|
lr += f[s]*br;
|
|
hr = r[s] - lr - q[s]*br;
|
|
br += f[s]*hr;
|
|
|
|
lr += f[s]*br;
|
|
hr = r[s] - lr - q[s]*br;
|
|
br += f[s]*hr;
|
|
|
|
r[s] = r[s] * mod1[s] + (lr + hr) *
|
|
(1.0f - mod1[s]);
|
|
}
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
ll += f[s]*bl;
|
|
hl = l[s] - ll - q[s]*bl;
|
|
bl += f[s]*hl;
|
|
|
|
ll += f[s]*bl;
|
|
hl = l[s] - ll - q[s]*bl;
|
|
bl += f[s]*hl;
|
|
|
|
l[s] = l[s] * mod1[s] + (ll + hl) *
|
|
(1.0f - mod1[s]);
|
|
}
|
|
break;
|
|
case WCA_PEAK_12DB:
|
|
if(s_stereo) for(s = 0; s < frames; ++s)
|
|
{
|
|
lr += f[s]*br;
|
|
hr = r[s] - lr - q[s]*br;
|
|
br += f[s]*hr;
|
|
|
|
lr += f[s]*br;
|
|
hr = r[s] - lr - q[s]*br;
|
|
br += f[s]*hr;
|
|
|
|
r[s] = r[s] * mod1[s] + (lr + hr + br) *
|
|
(1.0f - mod1[s]);
|
|
}
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
ll += f[s]*bl;
|
|
hl = l[s] - ll - q[s]*bl;
|
|
bl += f[s]*hl;
|
|
|
|
ll += f[s]*bl;
|
|
hl = l[s] - ll - q[s]*bl;
|
|
bl += f[s]*hl;
|
|
|
|
l[s] = l[s] * mod1[s] + (ll + hl + bl) *
|
|
(1.0f - mod1[s]);
|
|
}
|
|
break;
|
|
}
|
|
|
|
write_samples(l, r, frames);
|
|
}
|
|
}
|
|
|
|
|
|
void wca_gain(int wid)
|
|
{
|
|
unsigned s, frames;
|
|
float a[BLOCK_FRAMES];
|
|
audio_wave_t *wave = audio_wave_get(wid);
|
|
if(!wave)
|
|
return;
|
|
|
|
_init_processing(wave);
|
|
_env_start_all();
|
|
|
|
switch(wave->format)
|
|
{
|
|
case AF_MIDI:
|
|
return;
|
|
case AF_STEREO32:
|
|
{
|
|
float *d = wave->data.f32;
|
|
while( (frames = _next_block(s_wpos, BLOCK_FRAMES)) )
|
|
{
|
|
_env_process(&env[WCA_AMPLITUDE], a, frames);
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
d[s_wpos++] *= a[s];
|
|
d[s_wpos++] *= a[s];
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case AF_MONO32:
|
|
{
|
|
float *d = wave->data.f32;
|
|
while( (frames = _next_block(s_wpos, BLOCK_FRAMES)) )
|
|
{
|
|
_env_process(&env[WCA_AMPLITUDE], a, frames);
|
|
for(s = 0; s < frames; ++s)
|
|
d[s_wpos++] *= a[s];
|
|
}
|
|
break;
|
|
}
|
|
case AF_STEREO16:
|
|
{
|
|
Sint16 *d = wave->data.si16;
|
|
while( (frames = _next_block(s_wpos, BLOCK_FRAMES)) )
|
|
{
|
|
_env_process(&env[WCA_AMPLITUDE], a, frames);
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
float r = (float)d[s_wpos] * a[s];
|
|
if(r > 32767.0)
|
|
d[s_wpos] = 32767;
|
|
else if(r < -32768.0)
|
|
d[s_wpos] = -32768;
|
|
else
|
|
d[s_wpos] = (Sint16)r;
|
|
++s_wpos;
|
|
|
|
r = (float)d[s_wpos] * a[s];
|
|
if(r > 32767.0)
|
|
d[s_wpos] = 32767;
|
|
else if(r < -32768.0)
|
|
d[s_wpos] = -32768;
|
|
else
|
|
d[s_wpos] = (Sint16)r;
|
|
++s_wpos;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case AF_MONO16:
|
|
{
|
|
Sint16 *d = wave->data.si16;
|
|
while( (frames = _next_block(s_wpos, BLOCK_FRAMES)) )
|
|
{
|
|
_env_process(&env[WCA_AMPLITUDE], a, frames);
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
float r = (float)d[s_wpos] * a[s];
|
|
if(r > 32767.0)
|
|
d[s_wpos] = 32767;
|
|
else if(r < -32768.0)
|
|
d[s_wpos] = -32768;
|
|
else
|
|
d[s_wpos] = (Sint16)r;
|
|
++s_wpos;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case AF_STEREO8:
|
|
{
|
|
Sint8 *d = wave->data.si8;
|
|
while( (frames = _next_block(s_wpos, BLOCK_FRAMES)) )
|
|
{
|
|
_env_process(&env[WCA_AMPLITUDE], a, frames);
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
float r = (float)d[s_wpos] * a[s];
|
|
if(r > 127.0f)
|
|
d[s_wpos] = 127;
|
|
else if(r < -128.0f)
|
|
d[s_wpos] = -128;
|
|
else
|
|
d[s_wpos] = (Sint16)r;
|
|
++s_wpos;
|
|
|
|
r = (float)d[s_wpos] * a[s];
|
|
if(r > 127.0f)
|
|
d[s_wpos] = 127;
|
|
else if(r < -128.0f)
|
|
d[s_wpos] = -128;
|
|
else
|
|
d[s_wpos] = (Sint16)r;
|
|
++s_wpos;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case AF_MONO8:
|
|
{
|
|
Sint8 *d = wave->data.si8;
|
|
while( (frames = _next_block(s_wpos, BLOCK_FRAMES)) )
|
|
{
|
|
_env_process(&env[WCA_AMPLITUDE], a, frames);
|
|
for(s = 0; s < frames; ++s)
|
|
{
|
|
float r = (float)d[s_wpos] * a[s];
|
|
if(r > 127.0f)
|
|
d[s_wpos] = 127;
|
|
else if(r < -128.0f)
|
|
d[s_wpos] = -128;
|
|
else
|
|
d[s_wpos] = (Sint16)r;
|
|
++s_wpos;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#if 0
|
|
void wca_mix(int src_wid, int dst_wid)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
TODO: Output saturation.
|
|
*/
|
|
void wca_enhance(int wid, int f, float level)
|
|
{
|
|
unsigned s, samples;
|
|
int bpl, bpr, a;
|
|
int outl, outr, gain, vu;
|
|
int d1l, d2l, d1r, d2r;
|
|
int ldl, ldr, lf, h;
|
|
int release;
|
|
int sl = 0, sr = 0;
|
|
int samp = 0;
|
|
audio_wave_t *wave = audio_wave_get(wid);
|
|
if(!wave)
|
|
return;
|
|
|
|
_init_processing(wave);
|
|
|
|
if(AF_MIDI == wave->format)
|
|
{
|
|
log_printf(ELOG, "wca_enhance(): MIDI not supported!\n");
|
|
return;
|
|
}
|
|
|
|
a = (int)(level * 32768.0);
|
|
lf = (f * 256 * 2) / wave->rate;
|
|
if(lf > 256)
|
|
lf = 256;
|
|
f = (int)(512.0f * sin(M_PI * (float)f / wave->rate));
|
|
if(f > 256)
|
|
f = 256;
|
|
release = (20 << 16) / wave->rate;
|
|
if(release > 65536)
|
|
release = 65536;
|
|
d1l = d1r = d2l = d2r = 0;
|
|
ldl = ldr = 0;
|
|
gain = 0;
|
|
samples = wave->samples;
|
|
|
|
switch (wave->format)
|
|
{
|
|
case AF_MONO8:
|
|
case AF_MONO16:
|
|
case AF_MONO32:
|
|
for(s = 0; s < samples; ++s)
|
|
{
|
|
switch (wave->format)
|
|
{
|
|
case AF_MONO8:
|
|
samp = wave->data.si8[s] << 8;
|
|
break;
|
|
case AF_MONO16:
|
|
samp = wave->data.si16[s];
|
|
break;
|
|
case AF_MONO32:
|
|
samp = (int)(wave->data.f32[s] * 32768.0);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* 12 dB LP + BP + HP */
|
|
d2l += f * d1l >> 8;
|
|
h = (samp << 4) - d2l - d1l;
|
|
d1l += f * h >> 8;
|
|
bpl = d1l >> 4;
|
|
|
|
/* Octave shift up + 6 dB gain */
|
|
outl = abs(bpl) << 2;
|
|
|
|
/* 6 dB HPF on the artificial treble */
|
|
ldl += (outl - ldl) * lf >> 8;
|
|
outl -= ldl;
|
|
|
|
/* Use BP level to control artificial treble level
|
|
*/
|
|
vu = abs(bpl);
|
|
vu = vu * a >> 15;
|
|
if(vu > gain)
|
|
{
|
|
/* Fast attacks! */
|
|
if(vu > 65535)
|
|
gain = 65535;
|
|
else
|
|
gain = vu;
|
|
}
|
|
else
|
|
gain -= gain * release >> 16;
|
|
|
|
/* Artificial treble level */
|
|
outl = outl * gain >> 16;
|
|
|
|
/* Add in the original signal */
|
|
outl += samp;
|
|
|
|
/* Clip + output */
|
|
if(outl > 32767)
|
|
samp = 32767;
|
|
else if(outl < -32768)
|
|
samp = -32768;
|
|
else
|
|
samp = outl;
|
|
|
|
switch (wave->format)
|
|
{
|
|
case AF_MONO8:
|
|
wave->data.si8[s] = samp >> 8;
|
|
break;
|
|
case AF_MONO16:
|
|
wave->data.si16[s] = (Sint16)samp;
|
|
break;
|
|
case AF_MONO32:
|
|
wave->data.f32[s] = (float)samp * ONEDIV32K;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case AF_STEREO8:
|
|
case AF_STEREO16:
|
|
case AF_STEREO32:
|
|
samples <<= 1;
|
|
for(s = 0; s < samples; s += 2)
|
|
{
|
|
switch (wave->format)
|
|
{
|
|
case AF_STEREO8:
|
|
sl = wave->data.si8[s] << 8;
|
|
sr = wave->data.si8[s+1] << 8;
|
|
break;
|
|
case AF_STEREO16:
|
|
sl = wave->data.si16[s];
|
|
sr = wave->data.si16[s+1];
|
|
break;
|
|
case AF_STEREO32:
|
|
sl = (int)(wave->data.f32[s] * 32768.0);
|
|
sr = (int)(wave->data.f32[s+1] * 32768.0);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* 12 dB BP */
|
|
d2l += f * d1l >> 8;
|
|
h = (sl << 4) - d2l - d1l;
|
|
d1l += f * h >> 8;
|
|
bpl = d1l >> 4;
|
|
|
|
d2r += f * d1r >> 8;
|
|
h = (sr << 4) - d2r - d1r;
|
|
d1r += f * h >> 8;
|
|
bpr = d1l >> 4;
|
|
|
|
/* Octave shift up + 6 dB gain */
|
|
outl = abs(bpl) << 2;
|
|
outr = abs(bpr) << 2;
|
|
|
|
/* 6 dB HPF on the artificial treble */
|
|
ldl += (outl - ldl) * lf >> 8;
|
|
ldr += (outr - ldr) * lf >> 8;
|
|
outl -= ldl;
|
|
outr -= ldr;
|
|
|
|
/* Use BP level to control artificial treble level
|
|
*/
|
|
vu = (abs(bpl) + abs(bpr)) >> 1;
|
|
vu = vu * a >> 15;
|
|
if(vu > gain)
|
|
{
|
|
/* Fast attacks! */
|
|
if(vu > 65535)
|
|
gain = 65535;
|
|
else
|
|
gain = vu;
|
|
}
|
|
else
|
|
gain -= gain * release >> 16;
|
|
|
|
/* Artificial treble level */
|
|
outl = outl * gain >> 16;
|
|
outr = outr * gain >> 16;
|
|
|
|
/* Add in the original signal */
|
|
outl += sl;
|
|
outr += sr;
|
|
|
|
/* Clip + output */
|
|
if(outl > 32767)
|
|
sl = 32767;
|
|
else if(outl < -32768)
|
|
sl = -32768;
|
|
else
|
|
sl = outl;
|
|
if(outr > 32767)
|
|
sr = 32767;
|
|
else if(outr < -32768)
|
|
sr = -32768;
|
|
else
|
|
sr = outr;
|
|
|
|
switch (wave->format)
|
|
{
|
|
case AF_STEREO8:
|
|
wave->data.si8[s] = sl >> 8;
|
|
wave->data.si8[s+1] = sr >> 8;
|
|
break;
|
|
case AF_STEREO16:
|
|
wave->data.si16[s] = (Sint16)sl;
|
|
wave->data.si16[s+1] = (Sint16)sr;
|
|
break;
|
|
case AF_STEREO32:
|
|
wave->data.f32[s] = (float)sl * ONEDIV32K;
|
|
wave->data.f32[s+1] = (float)sr * ONEDIV32K;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
case AF_MIDI:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void wca_gate(int wid, int f, float min, float thr, float att)
|
|
{
|
|
unsigned s, samples;
|
|
int thresh, min_gain;
|
|
int lpl, lpr, hpl, hpr, h;
|
|
int outl, outr, gain, vu;
|
|
int d1l, d2l, d1r, d2r;
|
|
int attack, release, track, track_level;
|
|
int sl = 0, sr = 0;
|
|
audio_wave_t *wave = audio_wave_get(wid);
|
|
if(!wave)
|
|
return;
|
|
|
|
_init_processing(wave);
|
|
|
|
if(AF_MIDI == wave->format)
|
|
{
|
|
log_printf(ELOG, "wca_gate(): MIDI not supported!\n");
|
|
return;
|
|
}
|
|
|
|
f = (int)(512.0f * sin(M_PI * (float)f / wave->rate));
|
|
if(f > 256)
|
|
f = 256;
|
|
|
|
attack = (5000 << 15) / wave->rate;
|
|
if(attack > 32767)
|
|
attack = 32767;
|
|
|
|
release = (10 << 15) / wave->rate;
|
|
if(release > 32768)
|
|
release = 32767;
|
|
|
|
thresh = (int)(thr * 32767.0);
|
|
|
|
min_gain = (int)(min * 32767.0);
|
|
if(min_gain > 32767)
|
|
min_gain = 32767;
|
|
|
|
track = (att * 32768.0 * 256.0f) / wave->rate;
|
|
if(track > 32767*256)
|
|
track = 32767*256;
|
|
track_level = 0;
|
|
|
|
d1l = d1r = d2l = d2r = 0;
|
|
gain = 0;
|
|
samples = wave->samples;
|
|
|
|
switch (wave->format)
|
|
{
|
|
case AF_MONO8:
|
|
case AF_MONO16:
|
|
case AF_MONO32:
|
|
for(s = 0; s < samples; ++s)
|
|
{
|
|
switch (wave->format)
|
|
{
|
|
case AF_MONO8:
|
|
sl = wave->data.si8[s] << 8;
|
|
break;
|
|
case AF_MONO16:
|
|
sl = wave->data.si16[s];
|
|
break;
|
|
case AF_MONO32:
|
|
sl = (int)(wave->data.f32[s] * 32768.0);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* 12 dB LP / HP split */
|
|
d2l += f * d1l >> 8;
|
|
h = (sl << 4) - d2l - d1l;
|
|
d1l += f * h >> 8;
|
|
lpl = d2l >> 4;
|
|
hpl = (d1l + h) >> 4;
|
|
|
|
/* Auto Threshold Tracking */
|
|
vu = abs(sl);
|
|
if(vu > (thresh>>8))
|
|
track_level += ((vu<<8) - track_level) * track >> 16;
|
|
else
|
|
track_level += ((vu<<8) - track_level) * track >> 16;
|
|
|
|
/* Envelope generator */
|
|
vu = abs(hpl);
|
|
if(vu > thresh + (track_level>>8))
|
|
gain += (32767 - gain) * attack >> 14;
|
|
else
|
|
{
|
|
gain -= gain * release >> 16;
|
|
if(gain < min_gain)
|
|
gain = min_gain;
|
|
}
|
|
|
|
/* Gate the hp part */
|
|
outl = hpl * gain >> 15;
|
|
|
|
/* Add in the LP part */
|
|
outl += lpl;
|
|
|
|
/* Clip + output */
|
|
if(outl > 32767)
|
|
sl = 32767;
|
|
else if(outl < -32768)
|
|
sl = -32768;
|
|
else
|
|
sl = outl;
|
|
|
|
switch (wave->format)
|
|
{
|
|
case AF_MONO8:
|
|
wave->data.si8[s] = sl >> 8;
|
|
break;
|
|
case AF_MONO16:
|
|
wave->data.si16[s] = (Sint16)sl;
|
|
break;
|
|
case AF_MONO32:
|
|
wave->data.f32[s] = (float)sl * ONEDIV32K;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case AF_STEREO8:
|
|
case AF_STEREO16:
|
|
case AF_STEREO32:
|
|
samples <<= 1;
|
|
for(s = 0; s < samples; s += 2)
|
|
{
|
|
switch (wave->format)
|
|
{
|
|
case AF_STEREO8:
|
|
sl = wave->data.si8[s] << 8;
|
|
sr = wave->data.si8[s+1] << 8;
|
|
break;
|
|
case AF_STEREO16:
|
|
sl = wave->data.si16[s];
|
|
sr = wave->data.si16[s+1];
|
|
break;
|
|
case AF_STEREO32:
|
|
sl = (int)(wave->data.f32[s] * 32768.0);
|
|
sr = (int)(wave->data.f32[s+1] * 32768.0);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* 12 dB LP / HP split */
|
|
d2l += f * d1l >> 8;
|
|
h = (sl << 4) - d2l - d1l;
|
|
d1l += f * h >> 8;
|
|
lpl = d2l >> 4;
|
|
hpl = (d1l + h) >> 4;
|
|
|
|
d2r += f * d1r >> 8;
|
|
h = (sr << 4) - d2r - d1r;
|
|
d1r += f * h >> 8;
|
|
lpr = d2r >> 4;
|
|
hpr = (d1r + h) >> 4;
|
|
|
|
/* Auto Threshold Tracking */
|
|
vu = (abs(sl) + abs(sr)) >> 1;
|
|
if(vu > (thresh>>8))
|
|
track_level += ((vu<<8) - track_level) * track >> 16;
|
|
else
|
|
track_level += ((vu<<8) - track_level) * track >> 16;
|
|
|
|
/* Envelope generator */
|
|
vu = (abs(hpl) + abs(hpr)) >> 1;
|
|
if(vu > thresh + (track_level>>8))
|
|
gain += (32767 - gain) * attack >> 14;
|
|
else
|
|
{
|
|
gain -= gain * release >> 16;
|
|
if(gain < min_gain)
|
|
gain = min_gain;
|
|
}
|
|
|
|
/* Gate the hp part */
|
|
outl = hpl * gain >> 15;
|
|
outr = hpr * gain >> 15;
|
|
|
|
/* Add in the LP part */
|
|
outl += lpl;
|
|
outr += lpr;
|
|
|
|
/* Clip + output */
|
|
if(outl > 32767)
|
|
sl = 32767;
|
|
else if(outl < -32768)
|
|
sl = -32768;
|
|
else
|
|
sl = outl;
|
|
if(outr > 32767)
|
|
sr = 32767;
|
|
else if(outr < -32768)
|
|
sr = -32768;
|
|
else
|
|
sr = outr;
|
|
|
|
switch (wave->format)
|
|
{
|
|
case AF_STEREO8:
|
|
wave->data.si8[s] = sl >> 8;
|
|
wave->data.si8[s+1] = sr >> 8;
|
|
break;
|
|
case AF_STEREO16:
|
|
wave->data.si16[s] = (Sint16)sl;
|
|
wave->data.si16[s+1] = (Sint16)sr;
|
|
break;
|
|
case AF_STEREO32:
|
|
wave->data.f32[s] = (float)sl * ONEDIV32K;
|
|
wave->data.f32[s+1] = (float)sr * ONEDIV32K;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case AF_MIDI:
|
|
break;
|
|
}
|
|
}
|