node free; pitch shift; time scale
This commit is contained in:
parent
e092599816
commit
f47b6ece21
5
Makefile
5
Makefile
|
@ -76,7 +76,10 @@ else
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
CPPFLAGS += -DHAVE_CEIL -DCP_USE_CGTYPES=0 -DCP_USE_DOUBLES=0 -DTINYSPLINE_FLOAT_PRECISION -DHAVE_FLOOR -DHAVE_FMOD -DHAVE_LRINT -DHAVE_LRINTF $(includeflag) -MD $(WARNING_FLAGS) -I. -DVER=\"$(VER)\" -DINFO=\"$(INFO)\"
|
CPPFLAGS += -DHAVE_CEIL -DCP_USE_CGTYPES=0 -DCP_USE_DOUBLES=0 -DTINYSPLINE_FLOAT_PRECISION -DHAVE_FLOOR -DHAVE_FMOD -DHAVE_LRINT -DHAVE_LRINTF $(includeflag) -MD $(WARNING_FLAGS) -I. -DVER=\"$(VER)\" -DINFO=\"$(INFO)\" -DENABLE_SINC_MEDIUM_CONVERTER -DENABLE_SINC_FAST_CONVERTER
|
||||||
|
|
||||||
|
# ENABLE_SINC_[BEST|FAST|MEDIUM]_CONVERTER
|
||||||
|
# default, fast and medium available in game at runtime; best available in editor
|
||||||
|
|
||||||
PKGCMD = tar --directory $(BIN) --exclude="./*.a" --exclude="./obj" -czf $(DISTDIR)/$(DIST) .
|
PKGCMD = tar --directory $(BIN) --exclude="./*.a" --exclude="./obj" -czf $(DISTDIR)/$(DIST) .
|
||||||
ZIP = .tar.gz
|
ZIP = .tar.gz
|
||||||
|
|
30
docs/sound.md
Normal file
30
docs/sound.md
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# SOUND
|
||||||
|
|
||||||
|
Primum's sound system is well integrated and robust. It comes with flexibility in mind, to enable sounds to easily change as the result of occuring gameplay.
|
||||||
|
|
||||||
|
At the highest level, audio sources feed into a series of nodes, which eventually output to the mixer. All DSP is handled as floating point.
|
||||||
|
|
||||||
|
The game should specify a SAMPLERATE, BUF_FRAMES, and CHANNELS.
|
||||||
|
|
||||||
|
SAMPLERATE: samples per second
|
||||||
|
CHANNELS: number of output channels.
|
||||||
|
BUF_FRAMES: Number of frames to keep in the buffer. Smaller is lower latency. Too small might mean choppy audio.
|
||||||
|
|
||||||
|
## Audio sources
|
||||||
|
Audio sources include wav files, midi files, mod files, etc.
|
||||||
|
|
||||||
|
## Audio instances
|
||||||
|
Audio sources can be played multiple times after loaded into the game. Each instance
|
||||||
|
|
||||||
|
## DSPs / nodes
|
||||||
|
Nodes are how audio ends up coming out of the player's speakers. Only one node is specified at runtime: the master node, which outputs its inputs to the computers' speakers. It has 256 inputs.
|
||||||
|
|
||||||
|
Each node can have any number of inputs to it, defined when the node is created.
|
||||||
|
|
||||||
|
Nodes are run starting with the master node, in a pull fashion. The master node checks for inputs, and from each input pulls audio foward.
|
||||||
|
|
||||||
|
Audio instances are a type of node with a single output, and can be fed directly into the master node.
|
||||||
|
|
||||||
|
A node has inputs and outputs. All inputs into a node are mixed together, and then processed by the node. The node might clip them, apply a filter, or do any other number of things.
|
||||||
|
|
||||||
|
## Scripting
|
|
@ -7,9 +7,11 @@ var dsp_node = {
|
||||||
|
|
||||||
id: undefined,
|
id: undefined,
|
||||||
get volume() { return cmd(175, this.id); },
|
get volume() { return cmd(175, this.id); },
|
||||||
set volume(x) { cmd(176, this.id,x); },
|
set volume(x) { x = Math.clamp(x,0,1); cmd(176, this.id,x); },
|
||||||
|
get db() { return 20*Math.log10(Math.abs(this.volume)); },
|
||||||
|
set db(x) { x = Math.clamp(x,-100,0); this.volume = Math.pow(10, x/20); },
|
||||||
get pan() { return cmd(178,this.id); },
|
get pan() { return cmd(178,this.id); },
|
||||||
set pan(x) { cmd(179,this.id,x); },
|
set pan(x) { x = Math.clamp(x,-1,1); cmd(179,this.id,x); },
|
||||||
off(t) { cmd(183, this.id, t); }, /* True to turn off */
|
off(t) { cmd(183, this.id, t); }, /* True to turn off */
|
||||||
bypass(t) { cmd(184, this.id, t); }, /* True to bypass filter effect */
|
bypass(t) { cmd(184, this.id, t); }, /* True to bypass filter effect */
|
||||||
unplug() { cmd(164, this.id); },
|
unplug() { cmd(164, this.id); },
|
||||||
|
@ -26,6 +28,8 @@ var dsp_source = Object.copy(dsp_node,{
|
||||||
end(){},
|
end(){},
|
||||||
get loop() { return cmd(194,this.id); },
|
get loop() { return cmd(194,this.id); },
|
||||||
set loop(x) { cmd(195,this.id, x);},
|
set loop(x) { cmd(195,this.id, x);},
|
||||||
|
get timescale() { return cmd(201,this.id); },
|
||||||
|
set timescale(x) { cmd(202,this.id,x); },
|
||||||
get frame() { return cmd(196,this.id); },
|
get frame() { return cmd(196,this.id); },
|
||||||
set frame(x) { cmd(199, this.id, x); },
|
set frame(x) { cmd(199, this.id, x); },
|
||||||
frames() { return cmd(197,this.id); },
|
frames() { return cmd(197,this.id); },
|
||||||
|
@ -68,6 +72,14 @@ var DSP = {
|
||||||
delay(secs,decay) {
|
delay(secs,decay) {
|
||||||
return dsp_node.make(cmd(185, secs, decay));
|
return dsp_node.make(cmd(185, secs, decay));
|
||||||
},
|
},
|
||||||
|
fwd_delay(secs, decay) {
|
||||||
|
return dsp_node.make(cmd(207,secs,decay));
|
||||||
|
},
|
||||||
|
allpass(secs, decay) {
|
||||||
|
var fwd = DSP.fwd_delay(secs,-decay);
|
||||||
|
var fbk = DSP.delay(secs,decay);
|
||||||
|
fwd.plugin(fbk);
|
||||||
|
},
|
||||||
lpf(f) {
|
lpf(f) {
|
||||||
return dsp_node.make(cmd(186,f));
|
return dsp_node.make(cmd(186,f));
|
||||||
},
|
},
|
||||||
|
@ -77,6 +89,9 @@ var DSP = {
|
||||||
mod(path) {
|
mod(path) {
|
||||||
return dsp_node.make(cmd(188,path));
|
return dsp_node.make(cmd(188,path));
|
||||||
},
|
},
|
||||||
|
midi(midi,sf) {
|
||||||
|
return dsp_node.make(cmd(206,midi,sf));
|
||||||
|
},
|
||||||
crush(rate, depth) {
|
crush(rate, depth) {
|
||||||
return dsp_node.make(cmd(189,rate,depth));
|
return dsp_node.make(cmd(189,rate,depth));
|
||||||
},
|
},
|
||||||
|
@ -89,6 +104,18 @@ var DSP = {
|
||||||
noise_gate(floor) {
|
noise_gate(floor) {
|
||||||
return dsp_node.make(cmd(192,floor));
|
return dsp_node.make(cmd(192,floor));
|
||||||
},
|
},
|
||||||
|
pitchshift(octaves) {
|
||||||
|
return dsp_node.make(cmd(200,octaves));
|
||||||
|
},
|
||||||
|
noise() {
|
||||||
|
return dsp_node.make(cmd(203));
|
||||||
|
},
|
||||||
|
pink() {
|
||||||
|
return dsp_node.make(cmd(204));
|
||||||
|
},
|
||||||
|
red() {
|
||||||
|
return dsp_node.make(cmd(205));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
Sound.bus.master = dsp_node.make(cmd(180));
|
Sound.bus.master = dsp_node.make(cmd(180));
|
||||||
|
|
|
@ -75,7 +75,7 @@ struct datastream *ds_openvideo(const char *path)
|
||||||
});
|
});
|
||||||
|
|
||||||
ds->ring = ringnew(ds->ring, 8192);
|
ds->ring = ringnew(ds->ring, 8192);
|
||||||
plugin_node(make_node(ds, soundstream_fillbuf), masterbus);
|
plugin_node(make_node(ds, soundstream_fillbuf, NULL), masterbus);
|
||||||
|
|
||||||
plm_set_video_decode_callback(ds->plm, render_frame, ds);
|
plm_set_video_decode_callback(ds->plm, render_frame, ds);
|
||||||
plm_set_audio_decode_callback(ds->plm, render_audio, ds);
|
plm_set_audio_decode_callback(ds->plm, render_audio, ds);
|
||||||
|
|
|
@ -1218,7 +1218,7 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
|
||||||
ret = ptr2js(masterbus);
|
ret = ptr2js(masterbus);
|
||||||
break;
|
break;
|
||||||
case 181:
|
case 181:
|
||||||
ret = ptr2js(make_node(NULL,NULL));
|
ret = ptr2js(make_node(NULL,NULL,NULL));
|
||||||
break;
|
break;
|
||||||
case 182:
|
case 182:
|
||||||
str = js2str(argv[1]);
|
str = js2str(argv[1]);
|
||||||
|
@ -1277,6 +1277,33 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
|
||||||
case 199:
|
case 199:
|
||||||
((sound*)((dsp_node*)js2ptr(argv[1]))->data)->frame = js2number(argv[2]);
|
((sound*)((dsp_node*)js2ptr(argv[1]))->data)->frame = js2number(argv[2]);
|
||||||
break;
|
break;
|
||||||
|
case 200:
|
||||||
|
ret = ptr2js(dsp_pitchshift(js2number(argv[1])));
|
||||||
|
break;
|
||||||
|
case 201:
|
||||||
|
ret = num2js(((sound*)((dsp_node*)js2ptr(argv[1]))->data)->timescale);
|
||||||
|
break;
|
||||||
|
case 202:
|
||||||
|
YughWarn("%g", js2number(argv[2]));
|
||||||
|
((sound*)((dsp_node*)js2ptr(argv[1]))->data)->timescale = js2number(argv[2]);
|
||||||
|
break;
|
||||||
|
case 203:
|
||||||
|
ret = ptr2js(dsp_whitenoise());
|
||||||
|
break;
|
||||||
|
case 204:
|
||||||
|
ret = ptr2js(dsp_pinknoise());
|
||||||
|
break;
|
||||||
|
case 205:
|
||||||
|
ret = ptr2js(dsp_rednoise());
|
||||||
|
break;
|
||||||
|
case 206:
|
||||||
|
str = js2str(argv[1]);
|
||||||
|
str2 = js2str(argv[2]);
|
||||||
|
ret = ptr2js(dsp_midi(str, make_soundfont(str2)));
|
||||||
|
break;
|
||||||
|
case 207:
|
||||||
|
ret = ptr2js(dsp_fwd_delay(js2number(argv[1]), js2number(argv[2])));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (str)
|
if (str)
|
||||||
|
|
|
@ -7,11 +7,22 @@
|
||||||
#include "iir.h"
|
#include "iir.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "stb_ds.h"
|
#include "stb_ds.h"
|
||||||
|
#include "smbPitchShift.h"
|
||||||
|
|
||||||
#define PI 3.14159265
|
#define PI 3.14159265
|
||||||
|
|
||||||
dsp_node *masterbus = NULL;
|
dsp_node *masterbus = NULL;
|
||||||
|
|
||||||
|
void iir_free(struct dsp_iir *iir)
|
||||||
|
{
|
||||||
|
free(iir->a);
|
||||||
|
free(iir->b);
|
||||||
|
free(iir->x);
|
||||||
|
free(iir->y);
|
||||||
|
free(iir);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void interleave(soundbyte *a, soundbyte *b, soundbyte *stereo, int frames)
|
void interleave(soundbyte *a, soundbyte *b, soundbyte *stereo, int frames)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < frames; i++) {
|
for (int i = 0; i < frames; i++) {
|
||||||
|
@ -20,6 +31,13 @@ void interleave(soundbyte *a, soundbyte *b, soundbyte *stereo, int frames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void deinterleave(soundbyte *stereo, soundbyte *out, int frames, int channels, int chout)
|
||||||
|
{
|
||||||
|
chout--;
|
||||||
|
for (int i = 0; i < frames; i++)
|
||||||
|
out[i] = stereo[i*channels+chout];
|
||||||
|
}
|
||||||
|
|
||||||
void mono_to_stero(soundbyte *a, soundbyte *stereo, int frames)
|
void mono_to_stero(soundbyte *a, soundbyte *stereo, int frames)
|
||||||
{
|
{
|
||||||
interleave(a,a,stereo, frames);
|
interleave(a,a,stereo, frames);
|
||||||
|
@ -37,7 +55,7 @@ void mono_expand(soundbyte *buffer, int to, int frames)
|
||||||
|
|
||||||
dsp_node *dsp_mixer_node()
|
dsp_node *dsp_mixer_node()
|
||||||
{
|
{
|
||||||
return make_node(NULL, NULL);
|
return make_node(NULL, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dsp_init()
|
void dsp_init()
|
||||||
|
@ -75,7 +93,7 @@ void filter_am_mod(dsp_node *mod, soundbyte *buffer, int frames)
|
||||||
|
|
||||||
dsp_node *dsp_am_mod(dsp_node *mod)
|
dsp_node *dsp_am_mod(dsp_node *mod)
|
||||||
{
|
{
|
||||||
return make_node(mod, filter_am_mod);
|
return make_node(mod, filter_am_mod, node_free);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add b into a */
|
/* Add b into a */
|
||||||
|
@ -120,8 +138,7 @@ void dsp_node_run(dsp_node *node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dsp_node *make_node(void *data, void (*proc)(void *data, soundbyte *out, int samples), void (*fr)(void *data))
|
||||||
dsp_node *make_node(void *data, void (*proc)(void *in, soundbyte *out, int samples))
|
|
||||||
{
|
{
|
||||||
dsp_node *self = malloc(sizeof(dsp_node));
|
dsp_node *self = malloc(sizeof(dsp_node));
|
||||||
memset(self, 0, sizeof(*self));
|
memset(self, 0, sizeof(*self));
|
||||||
|
@ -206,7 +223,7 @@ dsp_node *dsp_phasor(float amp, float freq, float (*filter)(float))
|
||||||
p->freq = freq;
|
p->freq = freq;
|
||||||
p->phase = 0;
|
p->phase = 0;
|
||||||
p->filter = filter;
|
p->filter = filter;
|
||||||
return make_node(p, filter_phasor);
|
return make_node(p, filter_phasor, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void filter_rectify(void *data, soundbyte *out, int n)
|
void filter_rectify(void *data, soundbyte *out, int n)
|
||||||
|
@ -216,7 +233,7 @@ void filter_rectify(void *data, soundbyte *out, int n)
|
||||||
|
|
||||||
dsp_node *dsp_rectify()
|
dsp_node *dsp_rectify()
|
||||||
{
|
{
|
||||||
return make_node(NULL, filter_rectify);
|
return make_node(NULL, filter_rectify, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
soundbyte sample_whitenoise()
|
soundbyte sample_whitenoise()
|
||||||
|
@ -232,32 +249,26 @@ void gen_whitenoise(void *data, soundbyte *out, int n)
|
||||||
|
|
||||||
dsp_node *dsp_whitenoise()
|
dsp_node *dsp_whitenoise()
|
||||||
{
|
{
|
||||||
return make_node(NULL, gen_whitenoise);
|
return make_node(NULL, gen_whitenoise, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void gen_pinknoise(void *data, soundbyte *out, int n)
|
void gen_pinknoise(struct dsp_iir *pink, soundbyte *out, int n)
|
||||||
{
|
{
|
||||||
double a[7] = {1.0, 0.0555179, 0.0750759, 0.1538520, 0.3104856, 0.5329522, 0.0168980};
|
gen_whitenoise(NULL, out, n);
|
||||||
double b[7] = {0.99886, 0.99332, 0.969, 0.8665, 0.55, -0.7616, 0.115926};
|
|
||||||
|
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n*CHANNELS; i++) {
|
||||||
double pink;
|
soundbyte sum = 0;
|
||||||
double white = sample_whitenoise();
|
for (int j = 0; j < 6; j++) {
|
||||||
|
pink->x[j] = pink->x[j]*pink->b[j] + out[i]*pink->a[j];
|
||||||
|
sum += pink->x[j];
|
||||||
|
}
|
||||||
|
pink->x[6] = out[i] * 0.115926;
|
||||||
|
|
||||||
for (int k = 0; k < 5; k++) {
|
out[i] = sum + out[i] * 0.5362 + pink->x[6];
|
||||||
b[k] = a[k]*b[k] + white * b[k];
|
out[i] *= 0.11;
|
||||||
pink += b[k];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pink += b[5] + white*0.5362;
|
/* * https://www.firstpr.com.au/dsp/pink-noise/
|
||||||
b[5] = white*0.115926;
|
|
||||||
|
|
||||||
out[i] = pink;
|
|
||||||
}
|
|
||||||
mono_expand(out,CHANNELS,n);
|
|
||||||
/*
|
|
||||||
* The above is a loopified version of this
|
|
||||||
* https://www.firstpr.com.au/dsp/pink-noise/
|
|
||||||
b0 = 0.99886 * b0 + white * 0.0555179;
|
b0 = 0.99886 * b0 + white * 0.0555179;
|
||||||
b1 = 0.99332 * b1 + white * 0.0750759;
|
b1 = 0.99332 * b1 + white * 0.0750759;
|
||||||
b2 = 0.96900 * b2 + white * 0.1538520;
|
b2 = 0.96900 * b2 + white * 0.1538520;
|
||||||
|
@ -271,7 +282,69 @@ void gen_pinknoise(void *data, soundbyte *out, int n)
|
||||||
|
|
||||||
dsp_node *dsp_pinknoise()
|
dsp_node *dsp_pinknoise()
|
||||||
{
|
{
|
||||||
return make_node(NULL, gen_pinknoise);
|
struct dsp_iir *pink = malloc(sizeof(*pink));
|
||||||
|
*pink = make_iir(6);
|
||||||
|
float pinka[7] = {0.0555179, 0.0750759, 0.1538520, 0.3104856, 0.5329522, -0.0168980, 0.115926};
|
||||||
|
float pinkb[7] = {0.99886, 0.99332, 0.969, 0.8665, 0.55, -0.7616, 0.115926};
|
||||||
|
memcpy(pink->a, pinka, 7*sizeof(float));
|
||||||
|
memcpy(pink->b, pinkb, 7*sizeof(float));
|
||||||
|
|
||||||
|
return make_node(pink, gen_pinknoise, iir_free);
|
||||||
|
}
|
||||||
|
|
||||||
|
void filter_rednoise(soundbyte *last, soundbyte *out, int frames)
|
||||||
|
{
|
||||||
|
gen_whitenoise(NULL, out, frames);
|
||||||
|
for (int i = 0; i < frames*CHANNELS; i++) {
|
||||||
|
out[i] = (*last + (0.02*out[i]))/1.02;
|
||||||
|
*last = out[i];
|
||||||
|
out[i] *= 3.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dsp_node *dsp_rednoise()
|
||||||
|
{
|
||||||
|
soundbyte *last = malloc(sizeof(soundbyte));
|
||||||
|
*last = 0;
|
||||||
|
return make_node(last,filter_rednoise, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void filter_pitchshift(float *octaves, soundbyte *buffer, int frames)
|
||||||
|
{
|
||||||
|
soundbyte ch1[frames];
|
||||||
|
for (int i = 0; i < frames; i++)
|
||||||
|
ch1[i] = buffer[i*CHANNELS];
|
||||||
|
smbPitchShift(*octaves, frames, 1024, 4, SAMPLERATE, ch1, buffer);
|
||||||
|
mono_expand(buffer, CHANNELS, frames);
|
||||||
|
}
|
||||||
|
|
||||||
|
dsp_node *dsp_pitchshift(float octaves)
|
||||||
|
{
|
||||||
|
float *oct = malloc(sizeof(float));
|
||||||
|
*oct = octaves;
|
||||||
|
return make_node(oct, filter_pitchshift, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct timescale
|
||||||
|
{
|
||||||
|
float rate;
|
||||||
|
SRC_STATE *src;
|
||||||
|
};
|
||||||
|
|
||||||
|
static long *src_cb(struct timescale *ts, float **data)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void filter_timescale(struct timescale *ts, soundbyte *buffer, int frames)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
dsp_node *dsp_timescale(float scale)
|
||||||
|
{
|
||||||
|
struct timescale *ts = malloc(sizeof(*ts));
|
||||||
|
ts->rate = scale;
|
||||||
|
ts->src = src_callback_new(src_cb, SRC_SINC_FASTEST, scale, NULL, ts);
|
||||||
|
return make_node(ts, filter_timescale, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
soundbyte iir_filter(struct dsp_iir iir, soundbyte val)
|
soundbyte iir_filter(struct dsp_iir iir, soundbyte val)
|
||||||
|
@ -303,18 +376,19 @@ void filter_iir(struct dsp_iir *iir, soundbyte *buffer, int frames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
dsp_node *dsp_lpf(float freq)
|
dsp_node *dsp_lpf(float freq)
|
||||||
{
|
{
|
||||||
struct dsp_iir *iir = malloc(sizeof(*iir));
|
struct dsp_iir *iir = malloc(sizeof(*iir));
|
||||||
*iir = bqlp_dcof(2*freq/SAMPLERATE, 5);
|
*iir = bqlp_dcof(2*freq/SAMPLERATE, 5);
|
||||||
return make_node(iir, filter_iir);
|
return make_node(iir, filter_iir, iir_free);
|
||||||
}
|
}
|
||||||
|
|
||||||
dsp_node *dsp_hpf(float freq)
|
dsp_node *dsp_hpf(float freq)
|
||||||
{
|
{
|
||||||
struct dsp_iir *iir = malloc(sizeof(*iir));
|
struct dsp_iir *iir = malloc(sizeof(*iir));
|
||||||
*iir = bqhp_dcof(2*freq/SAMPLERATE,5);
|
*iir = bqhp_dcof(2*freq/SAMPLERATE,5);
|
||||||
return make_node(iir, filter_iir);
|
return make_node(iir, filter_iir, iir_free);
|
||||||
}
|
}
|
||||||
|
|
||||||
void filter_delay(delay *d, soundbyte *buf, int frames)
|
void filter_delay(delay *d, soundbyte *buf, int frames)
|
||||||
|
@ -325,6 +399,12 @@ void filter_delay(delay *d, soundbyte *buf, int frames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void delay_free(delay *d)
|
||||||
|
{
|
||||||
|
ringfree(d->ring);
|
||||||
|
free(d);
|
||||||
|
}
|
||||||
|
|
||||||
dsp_node *dsp_delay(double sec, double decay)
|
dsp_node *dsp_delay(double sec, double decay)
|
||||||
{
|
{
|
||||||
delay *d = malloc(sizeof(*d));
|
delay *d = malloc(sizeof(*d));
|
||||||
|
@ -333,7 +413,27 @@ dsp_node *dsp_delay(double sec, double decay)
|
||||||
d->ring = NULL;
|
d->ring = NULL;
|
||||||
d->ring = ringnew(d->ring, sec*CHANNELS*SAMPLERATE*2); /* Circular buffer size is enough to have the delay */
|
d->ring = ringnew(d->ring, sec*CHANNELS*SAMPLERATE*2); /* Circular buffer size is enough to have the delay */
|
||||||
ringheader(d->ring)->write += CHANNELS*SAMPLERATE*sec;
|
ringheader(d->ring)->write += CHANNELS*SAMPLERATE*sec;
|
||||||
return make_node(d, filter_delay);
|
return make_node(d, filter_delay, delay_free);
|
||||||
|
}
|
||||||
|
|
||||||
|
void filter_fwd_delay(delay *d, soundbyte *buf, int frames)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < frames*CHANNELS; i++) {
|
||||||
|
ringpush(d->ring, buf[i]);
|
||||||
|
buf[i] += ringshift(d->ring)*d->decay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dsp_node *dsp_fwd_delay(double sec, double decay)
|
||||||
|
{
|
||||||
|
delay *d = malloc(sizeof(*d));
|
||||||
|
d-> ms_delay = sec;
|
||||||
|
d->decay = decay;
|
||||||
|
d->ring = NULL;
|
||||||
|
d->ring = ringnew(d->ring, sec*CHANNELS*SAMPLERATE*2);
|
||||||
|
ringheader(d->ring)->write += CHANNELS*SAMPLERATE*sec;
|
||||||
|
YughWarn("FWD DELAY");
|
||||||
|
return make_node(d, filter_fwd_delay, delay_free);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get decay constant for a given pole */
|
/* Get decay constant for a given pole */
|
||||||
|
@ -407,7 +507,7 @@ dsp_node *dsp_adsr(unsigned int atk, unsigned int dec, unsigned int sus, unsigne
|
||||||
adsr->rls = rls + adsr->sus;
|
adsr->rls = rls + adsr->sus;
|
||||||
adsr->rls_t = tau2pole(rls / 3000.f);
|
adsr->rls_t = tau2pole(rls / 3000.f);
|
||||||
|
|
||||||
return make_node(adsr, dsp_adsr_fillbuf);
|
return make_node(adsr, dsp_adsr_fillbuf, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void filter_noise_gate(float *floor, soundbyte *out, int frames)
|
void filter_noise_gate(float *floor, soundbyte *out, int frames)
|
||||||
|
@ -419,7 +519,7 @@ dsp_node *dsp_noise_gate(float floor)
|
||||||
{
|
{
|
||||||
float *v = malloc(sizeof(float));
|
float *v = malloc(sizeof(float));
|
||||||
*v = floor;
|
*v = floor;
|
||||||
return make_node(v, filter_noise_gate);
|
return make_node(v, filter_noise_gate, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void filter_limiter(float *ceil, soundbyte *out, int n)
|
void filter_limiter(float *ceil, soundbyte *out, int n)
|
||||||
|
@ -431,7 +531,7 @@ dsp_node *dsp_limiter(float ceil)
|
||||||
{
|
{
|
||||||
float *v = malloc(sizeof(float));
|
float *v = malloc(sizeof(float));
|
||||||
*v = ceil;
|
*v = ceil;
|
||||||
return make_node(v, filter_limiter);
|
return make_node(v, filter_limiter, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dsp_compressor_fillbuf(struct dsp_compressor *comp, soundbyte *out, int n)
|
void dsp_compressor_fillbuf(struct dsp_compressor *comp, soundbyte *out, int n)
|
||||||
|
@ -470,22 +570,21 @@ dsp_node *dsp_compressor()
|
||||||
|
|
||||||
struct dsp_compressor *c = malloc(sizeof(*c));
|
struct dsp_compressor *c = malloc(sizeof(*c));
|
||||||
*c = new;
|
*c = new;
|
||||||
return make_node(c, dsp_compressor_fillbuf);
|
return make_node(c, dsp_compressor_fillbuf, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Assumes 2 channels in a frame */
|
/* Assumes 2 channels in a frame */
|
||||||
void pan_frames(soundbyte *out, float deg, int frames)
|
void pan_frames(soundbyte *out, float deg, int frames)
|
||||||
{
|
{
|
||||||
if (deg == 0.f) return;
|
if (deg == 0.f) return;
|
||||||
if (deg < -100) deg = -100.f;
|
if (deg < -1) deg = -1.f;
|
||||||
else if (deg > 100) deg = 100.f;
|
else if (deg > 1) deg = 1.f;
|
||||||
|
|
||||||
float db1, db2;
|
float db1, db2;
|
||||||
float pct = deg / 100.f;
|
|
||||||
|
|
||||||
if (deg > 0) {
|
if (deg > 0) {
|
||||||
db1 = pct2db(1 - pct);
|
db1 = pct2db(1 - deg);
|
||||||
db2 = pct2db(pct);
|
db2 = pct2db(deg);
|
||||||
for (int i = 0; i < frames; i++) {
|
for (int i = 0; i < frames; i++) {
|
||||||
soundbyte L = out[i*2];
|
soundbyte L = out[i*2];
|
||||||
soundbyte R = out[i*2+1];
|
soundbyte R = out[i*2+1];
|
||||||
|
@ -493,8 +592,8 @@ void pan_frames(soundbyte *out, float deg, int frames)
|
||||||
out[i*2+1] = (R + fgain(L, db2))/2;
|
out[i*2+1] = (R + fgain(L, db2))/2;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
db1 = pct2db(1 + pct);
|
db1 = pct2db(1 + deg);
|
||||||
db2 = pct2db(-1*pct);
|
db2 = pct2db(-1*deg);
|
||||||
for (int i = 0; i < frames; i++) {
|
for (int i = 0; i < frames; i++) {
|
||||||
soundbyte L = out[i*2];
|
soundbyte L = out[i*2];
|
||||||
soundbyte R = out[i*2+1];
|
soundbyte R = out[i*2+1];
|
||||||
|
@ -544,5 +643,5 @@ dsp_node *dsp_bitcrush(float sr, float res)
|
||||||
struct bitcrush *b = malloc(sizeof(*b));
|
struct bitcrush *b = malloc(sizeof(*b));
|
||||||
b->sr = sr;
|
b->sr = sr;
|
||||||
b->depth = res;
|
b->depth = res;
|
||||||
return make_node(b, filter_bitcrush);
|
return make_node(b, filter_bitcrush, NULL);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "sound.h"
|
#include "sound.h"
|
||||||
#include "cbuf.h"
|
#include "cbuf.h"
|
||||||
#include "script.h"
|
#include "script.h"
|
||||||
|
#include "iir.h"
|
||||||
|
|
||||||
/* a DSP node, when processed, sums its inputs, and stores the result of proc in its cache */
|
/* a DSP node, when processed, sums its inputs, and stores the result of proc in its cache */
|
||||||
typedef struct dsp_node {
|
typedef struct dsp_node {
|
||||||
|
@ -28,10 +29,11 @@ void dsp_init();
|
||||||
/* Get the output of a node */
|
/* Get the output of a node */
|
||||||
soundbyte *dsp_node_out(dsp_node *node);
|
soundbyte *dsp_node_out(dsp_node *node);
|
||||||
void dsp_node_run(dsp_node *node);
|
void dsp_node_run(dsp_node *node);
|
||||||
dsp_node *make_node(void *data, void (*proc)(void *data, soundbyte *out, int samples));
|
dsp_node *make_node(void *data, void (*proc)(void *data, soundbyte *out, int samples), void (*fr)(void *data));
|
||||||
void plugin_node(dsp_node *from, dsp_node *to);
|
void plugin_node(dsp_node *from, dsp_node *to);
|
||||||
void unplug_node(dsp_node *node);
|
void unplug_node(dsp_node *node);
|
||||||
void node_free(dsp_node *node);
|
void node_free(dsp_node *node);
|
||||||
|
void filter_iir(struct dsp_iir *iir, soundbyte *buffer, int frames);
|
||||||
|
|
||||||
void scale_soundbytes(soundbyte *a, float scale, int frames);
|
void scale_soundbytes(soundbyte *a, float scale, int frames);
|
||||||
void sum_soundbytes(soundbyte *a, soundbyte *b, int frames);
|
void sum_soundbytes(soundbyte *a, soundbyte *b, int frames);
|
||||||
|
@ -71,6 +73,8 @@ typedef struct {
|
||||||
} delay;
|
} delay;
|
||||||
|
|
||||||
dsp_node *dsp_delay(double sec, double decay);
|
dsp_node *dsp_delay(double sec, double decay);
|
||||||
|
dsp_node *dsp_fwd_delay(double sec, double decay);
|
||||||
|
dsp_node *dsp_pitchshift(float octaves);
|
||||||
|
|
||||||
struct dsp_compressor {
|
struct dsp_compressor {
|
||||||
double ratio;
|
double ratio;
|
||||||
|
@ -91,6 +95,7 @@ struct phasor phasor_make(unsigned int sr, float freq);
|
||||||
|
|
||||||
dsp_node *dsp_whitenoise();
|
dsp_node *dsp_whitenoise();
|
||||||
dsp_node *dsp_pinknoise();
|
dsp_node *dsp_pinknoise();
|
||||||
|
dsp_node *dsp_rednoise();
|
||||||
|
|
||||||
float sin_phasor(float p);
|
float sin_phasor(float p);
|
||||||
float square_phasor(float p);
|
float square_phasor(float p);
|
||||||
|
|
|
@ -40,6 +40,7 @@ struct dsp_iir sp_hp(double fcf);
|
||||||
|
|
||||||
double chevy_pct_to_e(double pct);
|
double chevy_pct_to_e(double pct);
|
||||||
|
|
||||||
|
struct dsp_iir make_iir(int order);
|
||||||
struct dsp_iir bqlp_dcof(double fcf, float Q);
|
struct dsp_iir bqlp_dcof(double fcf, float Q);
|
||||||
struct dsp_iir bqhp_dcof(double fcf, float Q);
|
struct dsp_iir bqhp_dcof(double fcf, float Q);
|
||||||
struct dsp_iir bqbpq_dcof(double fcf, float Q);
|
struct dsp_iir bqbpq_dcof(double fcf, float Q);
|
||||||
|
|
|
@ -74,6 +74,13 @@ tsf *make_soundfont(const char *path)
|
||||||
return sf;
|
return sf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void dsp_midi_free(struct dsp_midi_song *ms)
|
||||||
|
{
|
||||||
|
free(ms->midi);
|
||||||
|
tsf_close(ms->sf);
|
||||||
|
free(ms);
|
||||||
|
}
|
||||||
|
|
||||||
dsp_node *dsp_midi(const char *midi, tsf *sf)
|
dsp_node *dsp_midi(const char *midi, tsf *sf)
|
||||||
{
|
{
|
||||||
long rawlen;
|
long rawlen;
|
||||||
|
@ -82,7 +89,8 @@ dsp_node *dsp_midi(const char *midi, tsf *sf)
|
||||||
ms->time = 0.0;
|
ms->time = 0.0;
|
||||||
ms->midi = tml_load_memory(raw, rawlen);
|
ms->midi = tml_load_memory(raw, rawlen);
|
||||||
ms->sf = tsf_copy(sf);
|
ms->sf = tsf_copy(sf);
|
||||||
return make_node(ms, dsp_midi_fillbuf);
|
free(midi);
|
||||||
|
return make_node(ms, dsp_midi_fillbuf, dsp_midi_free);
|
||||||
}
|
}
|
||||||
|
|
||||||
void play_song(const char *midi, const char *sf)
|
void play_song(const char *midi, const char *sf)
|
||||||
|
|
317
source/engine/sound/smbPitchShift.h
Normal file
317
source/engine/sound/smbPitchShift.h
Normal file
|
@ -0,0 +1,317 @@
|
||||||
|
/****************************************************************************
|
||||||
|
*
|
||||||
|
* NAME: smbPitchShift.c
|
||||||
|
* VERSION: 1.2
|
||||||
|
* HOME URL: http://blogs.zynaptiq.com/bernsee
|
||||||
|
* KNOWN BUGS: none
|
||||||
|
*
|
||||||
|
* SYNOPSIS: Routine for doing pitch shifting while maintaining
|
||||||
|
* duration using the Short Time Fourier Transform.
|
||||||
|
*
|
||||||
|
* DESCRIPTION: The routine takes a pitchShift factor value which is between 0.5
|
||||||
|
* (one octave down) and 2. (one octave up). A value of exactly 1 does not change
|
||||||
|
* the pitch. numSampsToProcess tells the routine how many samples in indata[0...
|
||||||
|
* numSampsToProcess-1] should be pitch shifted and moved to outdata[0 ...
|
||||||
|
* numSampsToProcess-1]. The two buffers can be identical (ie. it can process the
|
||||||
|
* data in-place). fftFrameSize defines the FFT frame size used for the
|
||||||
|
* processing. Typical values are 1024, 2048 and 4096. It may be any value <=
|
||||||
|
* MAX_FRAME_LENGTH but it MUST be a power of 2. osamp is the STFT
|
||||||
|
* oversampling factor which also determines the overlap between adjacent STFT
|
||||||
|
* frames. It should at least be 4 for moderate scaling ratios. A value of 32 is
|
||||||
|
* recommended for best quality. sampleRate takes the sample rate for the signal
|
||||||
|
* in unit Hz, ie. 44100 for 44.1 kHz audio. The data passed to the routine in
|
||||||
|
* indata[] should be in the range [-1.0, 1.0), which is also the output range
|
||||||
|
* for the data, make sure you scale the data accordingly (for 16bit signed integers
|
||||||
|
* you would have to divide (and multiply) by 32768).
|
||||||
|
*
|
||||||
|
* COPYRIGHT 1999-2015 Stephan M. Bernsee <s.bernsee [AT] zynaptiq [DOT] com>
|
||||||
|
*
|
||||||
|
* The Wide Open License (WOL)
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, distribute and sell this software and its
|
||||||
|
* documentation for any purpose is hereby granted without fee, provided that
|
||||||
|
* the above copyright notice and this license appear in all source copies.
|
||||||
|
* THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF
|
||||||
|
* ANY KIND. See http://www.dspguru.com/wol.htm for more information.
|
||||||
|
*
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#define M_PI 3.14159265358979323846
|
||||||
|
#define MAX_FRAME_LENGTH 8192
|
||||||
|
#define false 0
|
||||||
|
#define true 1
|
||||||
|
|
||||||
|
void smbFft(float *fftBuffer, long fftFrameSize, long sign);
|
||||||
|
double smbAtan2(double x, double y);
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
void smbPitchShift(float pitchShift, long numSampsToProcess, long fftFrameSize, long osamp, float sampleRate, float *indata, float *outdata)
|
||||||
|
/*
|
||||||
|
Routine smbPitchShift(). See top of file for explanation
|
||||||
|
Purpose: doing pitch shifting while maintaining duration using the Short
|
||||||
|
Time Fourier Transform.
|
||||||
|
Author: (c)1999-2015 Stephan M. Bernsee <s.bernsee [AT] zynaptiq [DOT] com>
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
|
||||||
|
static float gInFIFO[MAX_FRAME_LENGTH];
|
||||||
|
static float gOutFIFO[MAX_FRAME_LENGTH];
|
||||||
|
static float gFFTworksp[2*MAX_FRAME_LENGTH];
|
||||||
|
static float gLastPhase[MAX_FRAME_LENGTH/2+1];
|
||||||
|
static float gSumPhase[MAX_FRAME_LENGTH/2+1];
|
||||||
|
static float gOutputAccum[2*MAX_FRAME_LENGTH];
|
||||||
|
static float gAnaFreq[MAX_FRAME_LENGTH];
|
||||||
|
static float gAnaMagn[MAX_FRAME_LENGTH];
|
||||||
|
static float gSynFreq[MAX_FRAME_LENGTH];
|
||||||
|
static float gSynMagn[MAX_FRAME_LENGTH];
|
||||||
|
static long gRover = false, gInit = false;
|
||||||
|
double magn, phase, tmp, window, real, imag;
|
||||||
|
double freqPerBin, expct;
|
||||||
|
long i,k, qpd, index, inFifoLatency, stepSize, fftFrameSize2;
|
||||||
|
|
||||||
|
/* set up some handy variables */
|
||||||
|
fftFrameSize2 = fftFrameSize/2;
|
||||||
|
stepSize = fftFrameSize/osamp;
|
||||||
|
freqPerBin = sampleRate/(double)fftFrameSize;
|
||||||
|
expct = 2.*M_PI*(double)stepSize/(double)fftFrameSize;
|
||||||
|
inFifoLatency = fftFrameSize-stepSize;
|
||||||
|
if (gRover == false) gRover = inFifoLatency;
|
||||||
|
|
||||||
|
/* initialize our static arrays */
|
||||||
|
if (gInit == false) {
|
||||||
|
memset(gInFIFO, 0, MAX_FRAME_LENGTH*sizeof(float));
|
||||||
|
memset(gOutFIFO, 0, MAX_FRAME_LENGTH*sizeof(float));
|
||||||
|
memset(gFFTworksp, 0, 2*MAX_FRAME_LENGTH*sizeof(float));
|
||||||
|
memset(gLastPhase, 0, (MAX_FRAME_LENGTH/2+1)*sizeof(float));
|
||||||
|
memset(gSumPhase, 0, (MAX_FRAME_LENGTH/2+1)*sizeof(float));
|
||||||
|
memset(gOutputAccum, 0, 2*MAX_FRAME_LENGTH*sizeof(float));
|
||||||
|
memset(gAnaFreq, 0, MAX_FRAME_LENGTH*sizeof(float));
|
||||||
|
memset(gAnaMagn, 0, MAX_FRAME_LENGTH*sizeof(float));
|
||||||
|
gInit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* main processing loop */
|
||||||
|
for (i = 0; i < numSampsToProcess; i++){
|
||||||
|
|
||||||
|
/* As long as we have not yet collected enough data just read in */
|
||||||
|
gInFIFO[gRover] = indata[i];
|
||||||
|
outdata[i] = gOutFIFO[gRover-inFifoLatency];
|
||||||
|
gRover++;
|
||||||
|
|
||||||
|
/* now we have enough data for processing */
|
||||||
|
if (gRover >= fftFrameSize) {
|
||||||
|
gRover = inFifoLatency;
|
||||||
|
|
||||||
|
/* do windowing and re,im interleave */
|
||||||
|
for (k = 0; k < fftFrameSize;k++) {
|
||||||
|
window = -.5*cos(2.*M_PI*(double)k/(double)fftFrameSize)+.5;
|
||||||
|
gFFTworksp[2*k] = gInFIFO[k] * window;
|
||||||
|
gFFTworksp[2*k+1] = 0.;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ***************** ANALYSIS ******************* */
|
||||||
|
/* do transform */
|
||||||
|
smbFft(gFFTworksp, fftFrameSize, -1);
|
||||||
|
|
||||||
|
/* this is the analysis step */
|
||||||
|
for (k = 0; k <= fftFrameSize2; k++) {
|
||||||
|
|
||||||
|
/* de-interlace FFT buffer */
|
||||||
|
real = gFFTworksp[2*k];
|
||||||
|
imag = gFFTworksp[2*k+1];
|
||||||
|
|
||||||
|
/* compute magnitude and phase */
|
||||||
|
magn = 2.*sqrt(real*real + imag*imag);
|
||||||
|
phase = atan2(imag,real);
|
||||||
|
|
||||||
|
/* compute phase difference */
|
||||||
|
tmp = phase - gLastPhase[k];
|
||||||
|
gLastPhase[k] = phase;
|
||||||
|
|
||||||
|
/* subtract expected phase difference */
|
||||||
|
tmp -= (double)k*expct;
|
||||||
|
|
||||||
|
/* map delta phase into +/- Pi interval */
|
||||||
|
qpd = tmp/M_PI;
|
||||||
|
if (qpd >= 0) qpd += qpd&1;
|
||||||
|
else qpd -= qpd&1;
|
||||||
|
tmp -= M_PI*(double)qpd;
|
||||||
|
|
||||||
|
/* get deviation from bin frequency from the +/- Pi interval */
|
||||||
|
tmp = osamp*tmp/(2.*M_PI);
|
||||||
|
|
||||||
|
/* compute the k-th partials' true frequency */
|
||||||
|
tmp = (double)k*freqPerBin + tmp*freqPerBin;
|
||||||
|
|
||||||
|
/* store magnitude and true frequency in analysis arrays */
|
||||||
|
gAnaMagn[k] = magn;
|
||||||
|
gAnaFreq[k] = tmp;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ***************** PROCESSING ******************* */
|
||||||
|
/* this does the actual pitch shifting */
|
||||||
|
memset(gSynMagn, 0, fftFrameSize*sizeof(float));
|
||||||
|
memset(gSynFreq, 0, fftFrameSize*sizeof(float));
|
||||||
|
for (k = 0; k <= fftFrameSize2; k++) {
|
||||||
|
index = k*pitchShift;
|
||||||
|
if (index <= fftFrameSize2) {
|
||||||
|
gSynMagn[index] += gAnaMagn[k];
|
||||||
|
gSynFreq[index] = gAnaFreq[k] * pitchShift;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ***************** SYNTHESIS ******************* */
|
||||||
|
/* this is the synthesis step */
|
||||||
|
for (k = 0; k <= fftFrameSize2; k++) {
|
||||||
|
|
||||||
|
/* get magnitude and true frequency from synthesis arrays */
|
||||||
|
magn = gSynMagn[k];
|
||||||
|
tmp = gSynFreq[k];
|
||||||
|
|
||||||
|
/* subtract bin mid frequency */
|
||||||
|
tmp -= (double)k*freqPerBin;
|
||||||
|
|
||||||
|
/* get bin deviation from freq deviation */
|
||||||
|
tmp /= freqPerBin;
|
||||||
|
|
||||||
|
/* take osamp into account */
|
||||||
|
tmp = 2.*M_PI*tmp/osamp;
|
||||||
|
|
||||||
|
/* add the overlap phase advance back in */
|
||||||
|
tmp += (double)k*expct;
|
||||||
|
|
||||||
|
/* accumulate delta phase to get bin phase */
|
||||||
|
gSumPhase[k] += tmp;
|
||||||
|
phase = gSumPhase[k];
|
||||||
|
|
||||||
|
/* get real and imag part and re-interleave */
|
||||||
|
gFFTworksp[2*k] = magn*cos(phase);
|
||||||
|
gFFTworksp[2*k+1] = magn*sin(phase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* zero negative frequencies */
|
||||||
|
for (k = fftFrameSize+2; k < 2*fftFrameSize; k++) gFFTworksp[k] = 0.;
|
||||||
|
|
||||||
|
/* do inverse transform */
|
||||||
|
smbFft(gFFTworksp, fftFrameSize, 1);
|
||||||
|
|
||||||
|
/* do windowing and add to output accumulator */
|
||||||
|
for(k=0; k < fftFrameSize; k++) {
|
||||||
|
window = -.5*cos(2.*M_PI*(double)k/(double)fftFrameSize)+.5;
|
||||||
|
gOutputAccum[k] += 2.*window*gFFTworksp[2*k]/(fftFrameSize2*osamp);
|
||||||
|
}
|
||||||
|
for (k = 0; k < stepSize; k++) gOutFIFO[k] = gOutputAccum[k];
|
||||||
|
|
||||||
|
/* shift accumulator */
|
||||||
|
memmove(gOutputAccum, gOutputAccum+stepSize, fftFrameSize*sizeof(float));
|
||||||
|
|
||||||
|
/* move input FIFO */
|
||||||
|
for (k = 0; k < inFifoLatency; k++) gInFIFO[k] = gInFIFO[k+stepSize];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
void smbFft(float *fftBuffer, long fftFrameSize, long sign)
|
||||||
|
/*
|
||||||
|
FFT routine, (C)1996 S.M.Bernsee. Sign = -1 is FFT, 1 is iFFT (inverse)
|
||||||
|
Fills fftBuffer[0...2*fftFrameSize-1] with the Fourier transform of the
|
||||||
|
time domain data in fftBuffer[0...2*fftFrameSize-1]. The FFT array takes
|
||||||
|
and returns the cosine and sine parts in an interleaved manner, ie.
|
||||||
|
fftBuffer[0] = cosPart[0], fftBuffer[1] = sinPart[0], asf. fftFrameSize
|
||||||
|
must be a power of 2. It expects a complex input signal (see footnote 2),
|
||||||
|
ie. when working with 'common' audio signals our input signal has to be
|
||||||
|
passed as {in[0],0.,in[1],0.,in[2],0.,...} asf. In that case, the transform
|
||||||
|
of the frequencies of interest is in fftBuffer[0...fftFrameSize].
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
float wr, wi, arg, *p1, *p2, temp;
|
||||||
|
float tr, ti, ur, ui, *p1r, *p1i, *p2r, *p2i;
|
||||||
|
long i, bitm, j, le, le2, k;
|
||||||
|
|
||||||
|
for (i = 2; i < 2*fftFrameSize-2; i += 2) {
|
||||||
|
for (bitm = 2, j = 0; bitm < 2*fftFrameSize; bitm <<= 1) {
|
||||||
|
if (i & bitm) j++;
|
||||||
|
j <<= 1;
|
||||||
|
}
|
||||||
|
if (i < j) {
|
||||||
|
p1 = fftBuffer+i; p2 = fftBuffer+j;
|
||||||
|
temp = *p1; *(p1++) = *p2;
|
||||||
|
*(p2++) = temp; temp = *p1;
|
||||||
|
*p1 = *p2; *p2 = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (k = 0, le = 2; k < (long)(log(fftFrameSize)/log(2.)+.5); k++) {
|
||||||
|
le <<= 1;
|
||||||
|
le2 = le>>1;
|
||||||
|
ur = 1.0;
|
||||||
|
ui = 0.0;
|
||||||
|
arg = M_PI / (le2>>1);
|
||||||
|
wr = cos(arg);
|
||||||
|
wi = sign*sin(arg);
|
||||||
|
for (j = 0; j < le2; j += 2) {
|
||||||
|
p1r = fftBuffer+j; p1i = p1r+1;
|
||||||
|
p2r = p1r+le2; p2i = p2r+1;
|
||||||
|
for (i = j; i < 2*fftFrameSize; i += le) {
|
||||||
|
tr = *p2r * ur - *p2i * ui;
|
||||||
|
ti = *p2r * ui + *p2i * ur;
|
||||||
|
*p2r = *p1r - tr; *p2i = *p1i - ti;
|
||||||
|
*p1r += tr; *p1i += ti;
|
||||||
|
p1r += le; p1i += le;
|
||||||
|
p2r += le; p2i += le;
|
||||||
|
}
|
||||||
|
tr = ur*wr - ui*wi;
|
||||||
|
ui = ur*wi + ui*wr;
|
||||||
|
ur = tr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
12/12/02, smb
|
||||||
|
|
||||||
|
PLEASE NOTE:
|
||||||
|
|
||||||
|
There have been some reports on domain errors when the atan2() function was used
|
||||||
|
as in the above code. Usually, a domain error should not interrupt the program flow
|
||||||
|
(maybe except in Debug mode) but rather be handled "silently" and a global variable
|
||||||
|
should be set according to this error. However, on some occasions people ran into
|
||||||
|
this kind of scenario, so a replacement atan2() function is provided here.
|
||||||
|
|
||||||
|
If you are experiencing domain errors and your program stops, simply replace all
|
||||||
|
instances of atan2() with calls to the smbAtan2() function below.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
double smbAtan2(double x, double y)
|
||||||
|
{
|
||||||
|
double signx;
|
||||||
|
if (x > 0.) signx = 1.;
|
||||||
|
else signx = -1.;
|
||||||
|
|
||||||
|
if (x == 0.) return 0.;
|
||||||
|
if (y == 0.) return signx * M_PI / 2.;
|
||||||
|
|
||||||
|
return atan2(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------------------------------------------
|
||||||
|
// -----------------------------------------------------------------------------------------------------------------
|
||||||
|
// -----------------------------------------------------------------------------------------------------------------
|
|
@ -121,7 +121,7 @@ dsp_node *dsp_mod(const char *path)
|
||||||
void *data = slurp_file(path, &modsize);
|
void *data = slurp_file(path, &modsize);
|
||||||
pocketmod_context *mod = malloc(sizeof(*mod));
|
pocketmod_context *mod = malloc(sizeof(*mod));
|
||||||
pocketmod_init(mod, data, modsize, SAMPLERATE);
|
pocketmod_init(mod, data, modsize, SAMPLERATE);
|
||||||
return make_node(mod, filter_mod);
|
return make_node(mod, filter_mod, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sound_init() {
|
void sound_init() {
|
||||||
|
@ -242,11 +242,17 @@ void sound_fillbuf(struct sound *s, soundbyte *buf, int n) {
|
||||||
else
|
else
|
||||||
end = 1;
|
end = 1;
|
||||||
|
|
||||||
|
if (s->timescale != 1) {
|
||||||
|
src_callback_read(s->src, s->timescale, frames, buf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
soundbyte *in = s->data->data;
|
soundbyte *in = s->data->data;
|
||||||
|
|
||||||
for (int i = 0; i < frames; i++) {
|
for (int i = 0; i < frames; i++) {
|
||||||
for (int j = 0; j < CHANNELS; j++)
|
for (int j = 0; j < CHANNELS; j++)
|
||||||
buf[i * CHANNELS + j] = in[s->frame*CHANNELS + j];
|
buf[i * CHANNELS + j] = in[s->frame*CHANNELS + j];
|
||||||
|
|
||||||
s->frame++;
|
s->frame++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,18 +266,28 @@ void sound_fillbuf(struct sound *s, soundbyte *buf, int n) {
|
||||||
void free_source(struct sound *s)
|
void free_source(struct sound *s)
|
||||||
{
|
{
|
||||||
JS_FreeValue(js, s->hook);
|
JS_FreeValue(js, s->hook);
|
||||||
|
src_delete(s->src);
|
||||||
free(s);
|
free(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static long *src_cb(struct sound *s, float **data)
|
||||||
|
{
|
||||||
|
long needed = BUF_FRAMES/s->timescale;
|
||||||
|
*data = s->data->data+s->frame;
|
||||||
|
s->frame += needed;
|
||||||
|
return needed;
|
||||||
|
}
|
||||||
|
|
||||||
struct dsp_node *dsp_source(char *path)
|
struct dsp_node *dsp_source(char *path)
|
||||||
{
|
{
|
||||||
struct sound *self = malloc(sizeof(*self));
|
struct sound *self = malloc(sizeof(*self));
|
||||||
self->frame = 0;
|
self->frame = 0;
|
||||||
self->data = make_sound(path);
|
self->data = make_sound(path);
|
||||||
self->loop = false;
|
self->loop = false;
|
||||||
|
self->src = src_callback_new(src_cb, SRC_SINC_MEDIUM_QUALITY, 2, NULL, self);
|
||||||
|
self->timescale = 1;
|
||||||
self->hook = JS_UNDEFINED;
|
self->hook = JS_UNDEFINED;
|
||||||
dsp_node *n = make_node(self, sound_fillbuf);
|
dsp_node *n = make_node(self, sound_fillbuf, free_source);
|
||||||
n->data_free = free_source;
|
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#define SOUND_H
|
#define SOUND_H
|
||||||
|
|
||||||
#include "script.h"
|
#include "script.h"
|
||||||
|
#include "samplerate.h"
|
||||||
|
|
||||||
typedef float soundbyte;
|
typedef float soundbyte;
|
||||||
|
|
||||||
|
@ -12,6 +13,8 @@ typedef struct sound {
|
||||||
unsigned int frame; /* Pointing to the current frame on the wav */
|
unsigned int frame; /* Pointing to the current frame on the wav */
|
||||||
struct wav *data;
|
struct wav *data;
|
||||||
int loop;
|
int loop;
|
||||||
|
float timescale;
|
||||||
|
SRC_STATE *src;
|
||||||
JSValue hook;
|
JSValue hook;
|
||||||
} sound;
|
} sound;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue