audio reworked
This commit is contained in:
parent
d047ceb07b
commit
44febe1c6d
|
@ -17,6 +17,6 @@ All objects in the Yugine can have an associated script. This script can perform
|
||||||
|update(dt)|called once per game frame tick|
|
|update(dt)|called once per game frame tick|
|
||||||
|physupdate(dt)|called once per physics tick|
|
|physupdate(dt)|called once per physics tick|
|
||||||
|stop|called when the object is killed|
|
|stop|called when the object is killed|
|
||||||
|debug|use draw functions with the object's world position|
|
|debug|use draw functions with the object's world position, during debug pass|
|
||||||
|gui|draw functions in screen space|
|
|gui|draw functions in screen space, during gameplay gui pass|
|
||||||
|draw|draw functions in world space|
|
|draw|draw functions in world space|
|
51
scripts/ai.js
Normal file
51
scripts/ai.js
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
var AI = {
|
||||||
|
tick() {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
sequence(list) {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
race(list) {
|
||||||
|
return function() {
|
||||||
|
var good = false;
|
||||||
|
list.forEach(x => if (x()) good = true);
|
||||||
|
return good;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
do(times, list) {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
sync(list) {
|
||||||
|
return function() {
|
||||||
|
var good = true;
|
||||||
|
list.forEach(x => if (!x()) good = false);
|
||||||
|
return good;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
moveto(pos) {
|
||||||
|
return function() {
|
||||||
|
var dir = pos.sub(this.pos);
|
||||||
|
if (Vector.length(dir) < 10) return true;
|
||||||
|
|
||||||
|
this.velocity = Vector.normalize(pos.sub(this.pos)).scale(20);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
wait(secs) {
|
||||||
|
secs ??= 1;
|
||||||
|
var accum = 0;
|
||||||
|
return function() {
|
||||||
|
accum += Game.dt;
|
||||||
|
if (accum >= secs)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
|
@ -522,6 +522,11 @@ Object.defineProperty(String.prototype, 'endswith', {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(String.prototype, 'pct', {
|
||||||
|
value: function(val) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Object.defineProperty(String.prototype, 'uc', { value: function() { return this.toUpperCase(); } });
|
Object.defineProperty(String.prototype, 'uc', { value: function() { return this.toUpperCase(); } });
|
||||||
|
|
||||||
Object.defineProperty(String.prototype, 'lc', {value:function() { return this.toLowerCase(); }});
|
Object.defineProperty(String.prototype, 'lc', {value:function() { return this.toLowerCase(); }});
|
||||||
|
|
|
@ -314,6 +314,7 @@ var timer = {
|
||||||
remain: 1,
|
remain: 1,
|
||||||
loop: false,
|
loop: false,
|
||||||
on: false,
|
on: false,
|
||||||
|
apptime: false, /* If true, based on app's time instead of game world time */
|
||||||
start() {
|
start() {
|
||||||
this.on = true;
|
this.on = true;
|
||||||
},
|
},
|
||||||
|
@ -337,7 +338,7 @@ var timer = {
|
||||||
this.restart();
|
this.restart();
|
||||||
},
|
},
|
||||||
|
|
||||||
pct() { return this.remain / this.time; },
|
pct() { return 1 - (this.remain / this.time); },
|
||||||
|
|
||||||
kill() {
|
kill() {
|
||||||
Register.unregister_obj(this);
|
Register.unregister_obj(this);
|
||||||
|
|
|
@ -17,6 +17,8 @@ actor.kill = function(actor){};
|
||||||
actor.delay = function(fn, seconds) {};
|
actor.delay = function(fn, seconds) {};
|
||||||
actor.clock = function(fn){};
|
actor.clock = function(fn){};
|
||||||
|
|
||||||
|
var Empyrean = Object.create(actor);
|
||||||
|
|
||||||
var gameobject = {
|
var gameobject = {
|
||||||
impl: {
|
impl: {
|
||||||
full_path() {
|
full_path() {
|
||||||
|
@ -58,16 +60,10 @@ var gameobject = {
|
||||||
},
|
},
|
||||||
|
|
||||||
cry(file) {
|
cry(file) {
|
||||||
Sound.play(file);
|
var p = Sound.play(file, Sound.bus.sfx);
|
||||||
return;
|
var killfn = p.kill.bind(p);
|
||||||
|
this.timers.push(killfn);
|
||||||
if (this.curcry && !Sound.finished(this.curcry)) return;
|
return killfn;
|
||||||
this.curcry = Sound.play(file);
|
|
||||||
var r = this.curcry;
|
|
||||||
Log.warn(r);
|
|
||||||
var fn = function() { Log.warn(r); if (r) Sound.stop(r); };
|
|
||||||
this.timers.push(fn);
|
|
||||||
return fn;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
set max_velocity(x) { cmd(151, this.body, x); },
|
set max_velocity(x) { cmd(151, this.body, x); },
|
||||||
|
@ -324,7 +320,6 @@ var gameobject = {
|
||||||
if (!k.startswith("on_")) continue;
|
if (!k.startswith("on_")) continue;
|
||||||
var signal = k.fromfirst("on_");
|
var signal = k.fromfirst("on_");
|
||||||
Event.observe(signal, obj, obj[k]);
|
Event.observe(signal, obj, obj[k]);
|
||||||
Log.warn("REGISTERED " + signal);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
obj.components.forEach(function(x) {
|
obj.components.forEach(function(x) {
|
||||||
|
|
|
@ -1,39 +1,90 @@
|
||||||
var Audio = {
|
var dsp_node = {
|
||||||
|
make(id) {
|
||||||
|
var n = Object.create(this);
|
||||||
|
n.id = id;
|
||||||
|
return n;
|
||||||
|
},
|
||||||
|
|
||||||
|
id: undefined,
|
||||||
|
get volume() { return cmd(175, this.id); },
|
||||||
|
set volume(x) { cmd(176, this.id,x); },
|
||||||
|
get pan() { return cmd(178,this.id); },
|
||||||
|
set pan(x) { cmd(179,this.id,x); },
|
||||||
|
off(t) { cmd(183, this.id, t); }, /* True to turn off */
|
||||||
|
bypass(t) { cmd(184, this.id, t); }, /* True to bypass filter effect */
|
||||||
|
unplug() { cmd(164, this.id); },
|
||||||
|
plugin(to) { cmd(177, this.id, to.id); },
|
||||||
|
kill() {
|
||||||
|
if (this._dead) return;
|
||||||
|
this._dead = true;
|
||||||
|
cmd(193, this.id); },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var dsp_source = Object.copy(dsp_node,{
|
||||||
|
end(){},
|
||||||
|
get loop() { return cmd(194,this.id); },
|
||||||
|
set loop(x) { cmd(195,this.id, x);},
|
||||||
|
});
|
||||||
|
|
||||||
var Sound = {
|
var Sound = {
|
||||||
|
bus: {},
|
||||||
sounds: [], /* array of loaded sound files */
|
sounds: [], /* array of loaded sound files */
|
||||||
play(file) {
|
play(file, bus) {
|
||||||
if (!IO.exists(file)) {
|
if (!IO.exists(file)) {
|
||||||
Log.error(`Cannot play sound ${file}: does not exist.`);
|
Log.error(`Cannot play sound ${file}: does not exist.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var p = cmd(14,file);
|
var src = DSP.source(file);
|
||||||
return p;
|
bus ??= Sound.bus.master;
|
||||||
},
|
src.plugin(bus);
|
||||||
|
return src;
|
||||||
finished(sound) {
|
|
||||||
return cmd(165, sound);
|
|
||||||
},
|
|
||||||
|
|
||||||
stop(sound) {
|
|
||||||
cmd(164, sound);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
music(midi, sf) {
|
music(midi, sf) {
|
||||||
cmd(13, midi, sf);
|
cmd(13, midi, sf);
|
||||||
},
|
},
|
||||||
|
|
||||||
musicstop() {
|
|
||||||
cmd(15);
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Between 0 and 100 */
|
|
||||||
set volume(x) { cmd(19, x); },
|
|
||||||
get volume() { 0; },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var DSP = {
|
||||||
|
mix(to) {
|
||||||
|
var n = dsp_node.make(cmd(181));
|
||||||
|
if (to) n.plugin(to);
|
||||||
|
return n;
|
||||||
|
},
|
||||||
|
source(path) {
|
||||||
|
var src = Object.create(dsp_source);
|
||||||
|
src.id = cmd(182,path,src);
|
||||||
|
return src;
|
||||||
|
},
|
||||||
|
delay(secs,decay) {
|
||||||
|
return dsp_node.make(cmd(185, secs, decay));
|
||||||
|
},
|
||||||
|
lpf(f) {
|
||||||
|
return dsp_node.make(cmd(186,f));
|
||||||
|
},
|
||||||
|
hpf(f) {
|
||||||
|
return dsp_node.make(cmd(187,f));
|
||||||
|
},
|
||||||
|
mod(path) {
|
||||||
|
return dsp_node.make(cmd(188,path));
|
||||||
|
},
|
||||||
|
crush(rate, depth) {
|
||||||
|
return dsp_node.make(cmd(189,rate,depth));
|
||||||
|
},
|
||||||
|
compressor() {
|
||||||
|
return dsp_node.make(cmd(190));
|
||||||
|
},
|
||||||
|
limiter(ceil) {
|
||||||
|
return dsp_node.make(cmd(191,ceil));
|
||||||
|
},
|
||||||
|
noise_gate(floor) {
|
||||||
|
return dsp_node.make(cmd(192,floor));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Sound.bus.master = dsp_node.make(cmd(180));
|
||||||
|
Sound.master = Sound.bus.master;
|
||||||
|
|
||||||
Sound.play.doc = "Play the given file once.";
|
Sound.play.doc = "Play the given file once.";
|
||||||
Sound.doc = {};
|
Sound.doc = {};
|
||||||
Sound.doc.volume = "Set the master volume. 0 is no sound and 100 is loudest.";
|
Sound.doc.volume = "Set the master volume. 0 is no sound and 100 is loudest.";
|
||||||
|
|
|
@ -3,90 +3,50 @@
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
struct circbuf {
|
static inline unsigned int powof2(unsigned int x)
|
||||||
int16_t *data;
|
{
|
||||||
uint32_t read;
|
x = x-1;
|
||||||
uint32_t write;
|
x |= (x >> 1);
|
||||||
unsigned int len;
|
x |= (x >> 2);
|
||||||
|
x |= (x >> 4);
|
||||||
|
x |= (x >> 8);
|
||||||
|
x |= (x >> 16);
|
||||||
|
return x+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct rheader
|
||||||
|
{
|
||||||
|
unsigned int read;
|
||||||
|
unsigned int write;
|
||||||
|
int len;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct circbuf *circbuf_make(size_t size, unsigned int len);
|
#define ringheader(r) ((struct rheader *)r-1)
|
||||||
struct circbuf circbuf_init(size_t size, unsigned int len);
|
|
||||||
void cbuf_push(struct circbuf *buf, short data);
|
static inline void *ringmake(void *ring, size_t elemsize, unsigned int n)
|
||||||
short cbuf_shift(struct circbuf *buf);
|
{
|
||||||
|
n = powof2(n);
|
||||||
#endif
|
if (ring) {
|
||||||
|
struct rheader *h = ringheader(ring);
|
||||||
#ifdef CBUF_IMPLEMENT
|
if (n <= h->len) return h+1;
|
||||||
|
h = realloc(h, elemsize*n+sizeof(struct rheader));
|
||||||
#include "assert.h"
|
return h+1;
|
||||||
#include "stdlib.h"
|
}
|
||||||
|
|
||||||
unsigned int powof2(unsigned int num)
|
struct rheader *h = malloc(elemsize*n+sizeof(struct rheader));
|
||||||
{
|
h->len = n; h->read = 0; h->write = 0;
|
||||||
if (num != 0) {
|
return h+1;
|
||||||
num--;
|
}
|
||||||
num |= (num >> 1);
|
|
||||||
num |= (num >> 2);
|
#define ringnew(r,n) (r = ringmake(r, sizeof(*r),n))
|
||||||
num |= (num >> 4);
|
#define ringfree(r) ((r) ? free(ringheader(r)) : 0)
|
||||||
num |= (num >> 8);
|
#define ringmask(r,v) (v & (ringheader(r)->len-1))
|
||||||
num |= (num >> 16);
|
#define ringpush(r,v) (r[ringmask(r,ringheader(r)->write++)] = v)
|
||||||
num++;
|
#define ringshift(r) (r[ringmask(r,ringheader(r)->read++)])
|
||||||
}
|
#define ringsize(r) ((r) ? ringheader(r)->write - ringheader(r)->read : 0)
|
||||||
|
#define ringfull(r) ((r) ? ringsize(r) == ringheader(r)->len : 0)
|
||||||
return num;
|
#define ringempty(r) ((r) ? ringheader(r)->read == ringheader(r)->write : 0)
|
||||||
}
|
|
||||||
|
|
||||||
int ispow2(int num)
|
|
||||||
{
|
|
||||||
return (num && !(num & (num - 1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
struct circbuf *circbuf_make(size_t size, unsigned int len)
|
|
||||||
{
|
|
||||||
struct circbuf *self = malloc(sizeof(*self));
|
|
||||||
self->len = powof2(len);
|
|
||||||
self->data = calloc(sizeof(short), self->len);
|
|
||||||
self->read = self->write = 0;
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct circbuf circbuf_init(size_t size, unsigned int len)
|
|
||||||
{
|
|
||||||
struct circbuf self;
|
|
||||||
self.len = powof2(len);
|
|
||||||
self.data = calloc(sizeof(short), self.len);
|
|
||||||
self.read = self.write = 0;
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cbuf_size(struct circbuf *buf) {
|
|
||||||
return buf->write - buf->read;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cbuf_empty(struct circbuf *buf) {
|
|
||||||
return buf->read == buf->write;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cbuf_full(struct circbuf *buf) {
|
|
||||||
return cbuf_size(buf) >= buf->len;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t cbuf_mask(struct circbuf *buf, uint32_t n) {
|
|
||||||
return n & (buf->len-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void cbuf_push(struct circbuf *buf, short data) {
|
|
||||||
//assert(!cbuf_full(buf));
|
|
||||||
|
|
||||||
buf->data[cbuf_mask(buf,buf->write++)] = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
short cbuf_shift(struct circbuf *buf) {
|
|
||||||
//assert(!cbuf_empty(buf));
|
|
||||||
return buf->data[cbuf_mask(buf, buf->read++)];
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -13,6 +13,9 @@
|
||||||
#define MSF_GIF_IMPL
|
#define MSF_GIF_IMPL
|
||||||
#include "msf_gif.h"
|
#include "msf_gif.h"
|
||||||
|
|
||||||
|
#define STB_HEXWAVE_IMPLEMENTATION
|
||||||
|
#include "stb_hexwave.h"
|
||||||
|
|
||||||
#define STB_DS_IMPLEMENTATION
|
#define STB_DS_IMPLEMENTATION
|
||||||
#include <stb_ds.h>
|
#include <stb_ds.h>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
#include "iir.h"
|
#include "iir.h"
|
||||||
#include "limits.h"
|
#include "limits.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "mix.h"
|
|
||||||
#include "resources.h"
|
#include "resources.h"
|
||||||
#include "shader.h"
|
#include "shader.h"
|
||||||
#include "sound.h"
|
#include "sound.h"
|
||||||
|
@ -17,7 +16,6 @@
|
||||||
|
|
||||||
#include "mpeg2.sglsl.h"
|
#include "mpeg2.sglsl.h"
|
||||||
|
|
||||||
#define CBUF_IMPLEMENT
|
|
||||||
#include "cbuf.h"
|
#include "cbuf.h"
|
||||||
|
|
||||||
#include "sokol/sokol_gfx.h"
|
#include "sokol/sokol_gfx.h"
|
||||||
|
@ -26,8 +24,13 @@ sg_shader vid_shader;
|
||||||
sg_pipeline vid_pipeline;
|
sg_pipeline vid_pipeline;
|
||||||
sg_bindings vid_bind;
|
sg_bindings vid_bind;
|
||||||
|
|
||||||
static void render_frame(plm_t *mpeg, plm_frame_t *frame, void *user) {
|
void soundstream_fillbuf(struct datastream *ds, soundbyte *buf, int frames) {
|
||||||
struct datastream *ds = user;
|
for (int i = 0; i < frames*CHANNELS; i++)
|
||||||
|
buf[i] = ringshift(ds->ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void render_frame(plm_t *mpeg, plm_frame_t *frame, struct datastream *ds) {
|
||||||
|
return;
|
||||||
uint8_t rgb[frame->height*frame->width*4];
|
uint8_t rgb[frame->height*frame->width*4];
|
||||||
plm_frame_to_rgba(frame, rgb, frame->width*4);
|
plm_frame_to_rgba(frame, rgb, frame->width*4);
|
||||||
sg_image_data imgd;
|
sg_image_data imgd;
|
||||||
|
@ -40,32 +43,29 @@ static void render_frame(plm_t *mpeg, plm_frame_t *frame, void *user) {
|
||||||
sg_update_image(ds->img, &imgd);
|
sg_update_image(ds->img, &imgd);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void render_audio(plm_t *mpeg, plm_samples_t *samples, void *user) {
|
static void render_audio(plm_t *mpeg, plm_samples_t *samples, struct datastream *ds) {
|
||||||
struct datastream *ds = user;
|
for (int i = 0; i < samples->count * CHANNELS; i++)
|
||||||
short t;
|
ringpush(ds->ring, samples->interleaved[i]);
|
||||||
|
|
||||||
for (int i = 0; i < samples->count * CHANNELS; i++) {
|
|
||||||
t = (short)(samples->interleaved[i] * SHRT_MAX);
|
|
||||||
// cbuf_push(ds->astream->buf, t * 5);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ds_openvideo(struct datastream *ds, const char *video, const char *adriver) {
|
struct datastream *ds_openvideo(const char *path)
|
||||||
|
{
|
||||||
|
struct datastream *ds = malloc(sizeof(*ds));
|
||||||
size_t rawlen;
|
size_t rawlen;
|
||||||
void *raw;
|
void *raw;
|
||||||
raw = slurp_file(video, &rawlen);
|
raw = slurp_file(path, &rawlen);
|
||||||
ds->plm = plm_create_with_memory(raw, rawlen, 0);
|
ds->plm = plm_create_with_memory(raw, rawlen, 0);
|
||||||
free(raw);
|
free(raw);
|
||||||
|
|
||||||
if (!ds->plm) {
|
if (!ds->plm) {
|
||||||
YughError("Couldn't open %s", video);
|
YughError("Couldn't open %s", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
YughWarn("Opened %s - framerate: %f, samplerate: %d, audio streams: %i, duration: %f",
|
YughWarn("Opened %s - framerate: %f, samplerate: %d,audio streams: %d, duration: %g",
|
||||||
video,
|
path,
|
||||||
plm_get_framerate(ds->plm),
|
plm_get_framerate(ds->plm),
|
||||||
plm_get_samplerate(ds->plm),
|
plm_get_samplerate(ds->plm),
|
||||||
|
plm_get_num_audio_streams(ds->plm),
|
||||||
plm_get_duration(ds->plm));
|
plm_get_duration(ds->plm));
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,28 +74,8 @@ void ds_openvideo(struct datastream *ds, const char *video, const char *adriver)
|
||||||
.height = plm_get_height(ds->plm)
|
.height = plm_get_height(ds->plm)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ds->ring = ringnew(ds->ring, 8192);
|
||||||
ds->astream = soundstream_make();
|
plugin_node(make_node(ds, soundstream_fillbuf), masterbus);
|
||||||
struct dsp_filter astream_filter;
|
|
||||||
astream_filter.data = &ds->astream;
|
|
||||||
astream_filter.filter = soundstream_fillbuf;
|
|
||||||
|
|
||||||
// struct dsp_filter lpf = lpf_make(8, 10000);
|
|
||||||
struct dsp_filter lpf = lpf_make(1, 200);
|
|
||||||
struct dsp_iir *iir = lpf.data;
|
|
||||||
iir->in = astream_filter;
|
|
||||||
|
|
||||||
struct dsp_filter hpf = hpf_make(1, 2000);
|
|
||||||
struct dsp_iir *hiir = hpf.data;
|
|
||||||
hiir->in = astream_filter;
|
|
||||||
|
|
||||||
/*
|
|
||||||
struct dsp_filter llpf = lp_fir_make(20);
|
|
||||||
struct dsp_fir *fir = llpf.data;
|
|
||||||
fir->in = astream_filter;
|
|
||||||
*/
|
|
||||||
|
|
||||||
// first_free_bus(astream_filter);
|
|
||||||
|
|
||||||
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);
|
||||||
|
@ -108,19 +88,19 @@ void ds_openvideo(struct datastream *ds, const char *video, const char *adriver)
|
||||||
plm_set_audio_lead_time(ds->plm, BUF_FRAMES / SAMPLERATE);
|
plm_set_audio_lead_time(ds->plm, BUF_FRAMES / SAMPLERATE);
|
||||||
|
|
||||||
ds->playing = true;
|
ds->playing = true;
|
||||||
|
|
||||||
|
return ds;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MakeDatastream() {
|
void MakeDatastream() {
|
||||||
vid_shader = sg_make_shader(mpeg2_shader_desc(sg_query_backend()));}
|
vid_shader = sg_make_shader(mpeg2_shader_desc(sg_query_backend()));
|
||||||
|
}
|
||||||
|
|
||||||
void ds_advance(struct datastream *ds, double s) {
|
void ds_advance(struct datastream *ds, double s) {
|
||||||
if (ds->playing) {
|
if (ds->playing) plm_decode(ds->plm, s);
|
||||||
plm_decode(ds->plm, s);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ds_seek(struct datastream *ds, double time) {
|
void ds_seek(struct datastream *ds, double time) {
|
||||||
// clear_raw(ds->audio_device);
|
|
||||||
plm_seek(ds->plm, time, false);
|
plm_seek(ds->plm, time, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,8 +120,7 @@ void ds_stop(struct datastream *ds) {
|
||||||
plm_destroy(ds->plm);
|
plm_destroy(ds->plm);
|
||||||
ds->plm = NULL;
|
ds->plm = NULL;
|
||||||
}
|
}
|
||||||
if (ds->audio_device)
|
|
||||||
close_audio_device(ds->audio_device);
|
|
||||||
ds->playing = false;
|
ds->playing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#include <pl_mpeg.h>
|
#include <pl_mpeg.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include "dsp.h"
|
||||||
|
#include "utringbuffer.h"
|
||||||
|
|
||||||
#include "sokol/sokol_gfx.h"
|
#include "sokol/sokol_gfx.h"
|
||||||
|
|
||||||
|
@ -12,17 +14,16 @@ struct datastream {
|
||||||
plm_t *plm;
|
plm_t *plm;
|
||||||
double last_time;
|
double last_time;
|
||||||
int playing;
|
int playing;
|
||||||
int audio_device;
|
|
||||||
sg_image img;
|
sg_image img;
|
||||||
int width;
|
int width;
|
||||||
int height;
|
int height;
|
||||||
struct soundstream *astream;
|
soundbyte *ring;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Texture;
|
struct Texture;
|
||||||
|
|
||||||
void MakeDatastream();
|
void MakeDatastream();
|
||||||
void ds_openvideo(struct datastream *ds, const char *path, const char *adriver);
|
struct datastream *ds_openvideo(const char *path);
|
||||||
struct Texture *ds_maketexture(struct datastream *);
|
struct Texture *ds_maketexture(struct datastream *);
|
||||||
void ds_advance(struct datastream *ds, double);
|
void ds_advance(struct datastream *ds, double);
|
||||||
void ds_seek(struct datastream *ds, double);
|
void ds_seek(struct datastream *ds, double);
|
||||||
|
|
|
@ -10,10 +10,10 @@
|
||||||
#include "input.h"
|
#include "input.h"
|
||||||
#include "level.h"
|
#include "level.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "mix.h"
|
#include "dsp.h"
|
||||||
#include "music.h"
|
#include "music.h"
|
||||||
#include "2dphysics.h"
|
#include "2dphysics.h"
|
||||||
|
#include "datastream.h"
|
||||||
#include "sound.h"
|
#include "sound.h"
|
||||||
#include "sprite.h"
|
#include "sprite.h"
|
||||||
#include "stb_ds.h"
|
#include "stb_ds.h"
|
||||||
|
@ -546,12 +546,12 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
|
||||||
|
|
||||||
case 14:
|
case 14:
|
||||||
str = JS_ToCString(js, argv[1]);
|
str = JS_ToCString(js, argv[1]);
|
||||||
//ret = ptr2js(play_sound(make_sound(str)));
|
ret = ptr2js(dsp_source(str));
|
||||||
play_oneshot(make_sound(str));
|
((sound*)((dsp_node*)js2ptr(ret))->data)->hook = argv[2];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 15:
|
case 15:
|
||||||
music_stop();
|
// music_stop();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 16:
|
case 16:
|
||||||
|
@ -1162,10 +1162,10 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
|
||||||
ret = int2js(rename(str, str2));
|
ret = int2js(rename(str, str2));
|
||||||
break;
|
break;
|
||||||
case 164:
|
case 164:
|
||||||
sound_stop(js2ptr(argv[1]));
|
unplug_node(js2ptr(argv[1]));
|
||||||
break;
|
break;
|
||||||
case 165:
|
case 165:
|
||||||
ret = bool2js(sound_paused(js2ptr(argv[1])));
|
// ret = bool2js(sound_paused(js2ptr(argv[1])));
|
||||||
break;
|
break;
|
||||||
case 166:
|
case 166:
|
||||||
str = js2str(argv[1]);
|
str = js2str(argv[1]);
|
||||||
|
@ -1194,6 +1194,77 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
|
||||||
str = js2str(argv[1]);
|
str = js2str(argv[1]);
|
||||||
capture_screen(js2number(argv[2]), js2number(argv[3]), js2number(argv[4]), js2number(argv[5]), str);
|
capture_screen(js2number(argv[2]), js2number(argv[3]), js2number(argv[4]), js2number(argv[5]), str);
|
||||||
break;
|
break;
|
||||||
|
case 174:
|
||||||
|
str = js2str(argv[1]);
|
||||||
|
ds_openvideo(str);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 175:
|
||||||
|
ret = num2js(((dsp_node*)js2ptr(argv[1]))->gain);
|
||||||
|
break;
|
||||||
|
case 176:
|
||||||
|
((dsp_node*)js2ptr(argv[1]))->gain = js2number(argv[2]);
|
||||||
|
break;
|
||||||
|
case 177:
|
||||||
|
plugin_node(js2ptr(argv[1]), js2ptr(argv[2]));
|
||||||
|
break;
|
||||||
|
case 178:
|
||||||
|
ret = num2js(((dsp_node*)js2ptr(argv[1]))->pan);
|
||||||
|
break;
|
||||||
|
case 179:
|
||||||
|
((dsp_node*)js2ptr(argv[1]))->pan=js2number(argv[2]);
|
||||||
|
break;
|
||||||
|
case 180:
|
||||||
|
ret = ptr2js(masterbus);
|
||||||
|
break;
|
||||||
|
case 181:
|
||||||
|
ret = ptr2js(make_node(NULL,NULL));
|
||||||
|
break;
|
||||||
|
case 182:
|
||||||
|
str = js2str(argv[1]);
|
||||||
|
ret = ptr2js(dsp_source(str));
|
||||||
|
((sound*)((dsp_node*)js2ptr(ret))->data)->hook = JS_DupValue(js,argv[2]);
|
||||||
|
break;
|
||||||
|
case 183:
|
||||||
|
((dsp_node*)js2ptr(argv[1]))->off = js2bool(argv[2]);
|
||||||
|
break;
|
||||||
|
case 184:
|
||||||
|
((dsp_node*)js2ptr(argv[1]))->pass = js2bool(argv[2]);
|
||||||
|
break;
|
||||||
|
case 185:
|
||||||
|
ret = ptr2js(dsp_delay(js2number(argv[1]), js2number(argv[2])));
|
||||||
|
break;
|
||||||
|
case 186:
|
||||||
|
ret = ptr2js(dsp_lpf(js2number(argv[1])));
|
||||||
|
break;
|
||||||
|
case 187:
|
||||||
|
ret = ptr2js(dsp_hpf(js2number(argv[1])));
|
||||||
|
break;
|
||||||
|
case 188:
|
||||||
|
str = js2str(argv[1]);
|
||||||
|
ret = ptr2js(dsp_mod(str));
|
||||||
|
break;
|
||||||
|
case 189:
|
||||||
|
ret = ptr2js(dsp_bitcrush(js2number(argv[1]), js2number(argv[2])));
|
||||||
|
break;
|
||||||
|
case 190:
|
||||||
|
ret = ptr2js(dsp_compressor());
|
||||||
|
break;
|
||||||
|
case 191:
|
||||||
|
ret = ptr2js(dsp_limiter(js2number(argv[1])));
|
||||||
|
break;
|
||||||
|
case 192:
|
||||||
|
ret = ptr2js(dsp_noise_gate(js2number(argv[1])));
|
||||||
|
break;
|
||||||
|
case 193:
|
||||||
|
node_free(js2ptr(argv[1]));
|
||||||
|
break;
|
||||||
|
case 194:
|
||||||
|
ret = bool2js(((sound*)((dsp_node*)js2ptr(argv[1]))->data)->loop);
|
||||||
|
break;
|
||||||
|
case 195:
|
||||||
|
((sound*)((dsp_node*)js2ptr(argv[1]))->data)->loop = js2bool(argv[2]);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (str)
|
if (str)
|
||||||
|
@ -1701,10 +1772,11 @@ JSValue duk_anim(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
|
||||||
}
|
}
|
||||||
|
|
||||||
JSValue duk_make_timer(JSContext *js, JSValueConst this, int argc, JSValueConst *argv) {
|
JSValue duk_make_timer(JSContext *js, JSValueConst this, int argc, JSValueConst *argv) {
|
||||||
double secs = js2number(argv[1]);
|
// double secs = js2number(argv[1]);
|
||||||
struct callee *c = make_callee(argv[0], argv[3]);
|
// struct callee *c = make_callee(argv[0], argv[3]);
|
||||||
int id = timer_make(secs, call_callee, c, 1, js2bool(argv[2]));
|
// int id = timer_make(secs, call_callee, c, 1, js2bool(argv[2]));
|
||||||
return JS_NewInt64(js, id);
|
// return JS_NewInt64(js, id);
|
||||||
|
return JS_NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
JSValue duk_cmd_points(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
|
JSValue duk_cmd_points(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
|
||||||
|
|
871
source/engine/pocketmod.h
Normal file
871
source/engine/pocketmod.h
Normal file
|
@ -0,0 +1,871 @@
|
||||||
|
/* See end of file for license */
|
||||||
|
|
||||||
|
#ifndef POCKETMOD_H_INCLUDED
|
||||||
|
#define POCKETMOD_H_INCLUDED
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct pocketmod_context pocketmod_context;
|
||||||
|
int pocketmod_init(pocketmod_context *c, const void *data, int size, int rate);
|
||||||
|
int pocketmod_render(pocketmod_context *c, void *buffer, int size);
|
||||||
|
int pocketmod_loop_count(pocketmod_context *c);
|
||||||
|
|
||||||
|
#ifndef POCKETMOD_MAX_CHANNELS
|
||||||
|
#define POCKETMOD_MAX_CHANNELS 32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef POCKETMOD_MAX_SAMPLES
|
||||||
|
#define POCKETMOD_MAX_SAMPLES 31
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
signed char *data; /* Sample data buffer */
|
||||||
|
unsigned int length; /* Data length (in bytes) */
|
||||||
|
} _pocketmod_sample;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned char dirty; /* Pitch/volume dirty flags */
|
||||||
|
unsigned char sample; /* Sample number (0..31) */
|
||||||
|
unsigned char volume; /* Base volume without tremolo (0..64) */
|
||||||
|
unsigned char balance; /* Stereo balance (0..255) */
|
||||||
|
unsigned short period; /* Note period (113..856) */
|
||||||
|
unsigned short delayed; /* Delayed note period (113..856) */
|
||||||
|
unsigned short target; /* Target period (for tone portamento) */
|
||||||
|
unsigned char finetune; /* Note finetune (0..15) */
|
||||||
|
unsigned char loop_count; /* E6x loop counter */
|
||||||
|
unsigned char loop_line; /* E6x target line */
|
||||||
|
unsigned char lfo_step; /* Vibrato/tremolo LFO step counter */
|
||||||
|
unsigned char lfo_type[2]; /* LFO type for vibrato/tremolo */
|
||||||
|
unsigned char effect; /* Current effect (0x0..0xf or 0xe0..0xef) */
|
||||||
|
unsigned char param; /* Raw effect parameter value */
|
||||||
|
unsigned char param3; /* Parameter memory for 3xx */
|
||||||
|
unsigned char param4; /* Parameter memory for 4xy */
|
||||||
|
unsigned char param7; /* Parameter memory for 7xy */
|
||||||
|
unsigned char param9; /* Parameter memory for 9xx */
|
||||||
|
unsigned char paramE1; /* Parameter memory for E1x */
|
||||||
|
unsigned char paramE2; /* Parameter memory for E2x */
|
||||||
|
unsigned char paramEA; /* Parameter memory for EAx */
|
||||||
|
unsigned char paramEB; /* Parameter memory for EBx */
|
||||||
|
unsigned char real_volume; /* Volume (with tremolo adjustment) */
|
||||||
|
float position; /* Position in sample data buffer */
|
||||||
|
float increment; /* Position increment per output sample */
|
||||||
|
} _pocketmod_chan;
|
||||||
|
|
||||||
|
struct pocketmod_context
|
||||||
|
{
|
||||||
|
/* Read-only song data */
|
||||||
|
_pocketmod_sample samples[POCKETMOD_MAX_SAMPLES];
|
||||||
|
unsigned char *source; /* Pointer to source MOD data */
|
||||||
|
unsigned char *order; /* Pattern order table */
|
||||||
|
unsigned char *patterns; /* Start of pattern data */
|
||||||
|
unsigned char length; /* Patterns in the order (1..128) */
|
||||||
|
unsigned char reset; /* Pattern to loop back to (0..127) */
|
||||||
|
unsigned char num_patterns; /* Patterns in the file (1..128) */
|
||||||
|
unsigned char num_samples; /* Sample count (15 or 31) */
|
||||||
|
unsigned char num_channels; /* Channel count (1..32) */
|
||||||
|
|
||||||
|
/* Timing variables */
|
||||||
|
int samples_per_second; /* Sample rate (set by user) */
|
||||||
|
int ticks_per_line; /* A.K.A. song speed (initially 6) */
|
||||||
|
float samples_per_tick; /* Depends on sample rate and BPM */
|
||||||
|
|
||||||
|
/* Loop detection state */
|
||||||
|
unsigned char visited[16]; /* Bit mask of previously visited patterns */
|
||||||
|
int loop_count; /* How many times the song has looped */
|
||||||
|
|
||||||
|
/* Render state */
|
||||||
|
_pocketmod_chan channels[POCKETMOD_MAX_CHANNELS];
|
||||||
|
unsigned char pattern_delay;/* EEx pattern delay counter */
|
||||||
|
unsigned int lfo_rng; /* RNG used for the random LFO waveform */
|
||||||
|
|
||||||
|
/* Position in song (from least to most granular) */
|
||||||
|
signed char pattern; /* Current pattern in order */
|
||||||
|
signed char line; /* Current line in pattern */
|
||||||
|
short tick; /* Current tick in line */
|
||||||
|
float sample; /* Current sample in tick */
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef POCKETMOD_IMPLEMENTATION
|
||||||
|
|
||||||
|
/* Memorize a parameter unless the new value is zero */
|
||||||
|
#define POCKETMOD_MEM(dst, src) do { \
|
||||||
|
(dst) = (src) ? (src) : (dst); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/* Same thing, but memorize each nibble separately */
|
||||||
|
#define POCKETMOD_MEM2(dst, src) do { \
|
||||||
|
(dst) = (((src) & 0x0f) ? ((src) & 0x0f) : ((dst) & 0x0f)) \
|
||||||
|
| (((src) & 0xf0) ? ((src) & 0xf0) : ((dst) & 0xf0)); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/* Shortcut to sample metadata (sample must be nonzero) */
|
||||||
|
#define POCKETMOD_SAMPLE(c, sample) ((c)->source + 12 + 30 * (sample))
|
||||||
|
|
||||||
|
/* Channel dirty flags */
|
||||||
|
#define POCKETMOD_PITCH 0x01
|
||||||
|
#define POCKETMOD_VOLUME 0x02
|
||||||
|
|
||||||
|
/* The size of one sample in bytes */
|
||||||
|
#define POCKETMOD_SAMPLE_SIZE sizeof(float[2])
|
||||||
|
|
||||||
|
/* Finetune adjustment table. Three octaves for each finetune setting. */
|
||||||
|
static const signed char _pocketmod_finetune[16][36] = {
|
||||||
|
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
{ -6, -6, -5, -5, -4, -3, -3, -3, -3, -3, -3, -3, -3, -3, -2, -3, -2, -2, -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0},
|
||||||
|
{-12,-12,-10,-11, -8, -8, -7, -7, -6, -6, -6, -6, -6, -6, -5, -5, -4, -4, -4, -3, -3, -3, -3, -2, -3, -3, -2, -3, -3, -2, -2, -2, -2, -2, -2, -1},
|
||||||
|
{-18,-17,-16,-16,-13,-12,-12,-11,-10,-10,-10, -9, -9, -9, -8, -8, -7, -6, -6, -5, -5, -5, -5, -4, -5, -4, -3, -4, -4, -3, -3, -3, -3, -2, -2, -2},
|
||||||
|
{-24,-23,-21,-21,-18,-17,-16,-15,-14,-13,-13,-12,-12,-12,-11,-10, -9, -8, -8, -7, -7, -7, -7, -6, -6, -6, -5, -5, -5, -4, -4, -4, -4, -3, -3, -3},
|
||||||
|
{-30,-29,-26,-26,-23,-21,-20,-19,-18,-17,-17,-16,-15,-14,-13,-13,-11,-11,-10, -9, -9, -9, -8, -7, -8, -7, -6, -6, -6, -5, -5, -5, -5, -4, -4, -4},
|
||||||
|
{-36,-34,-32,-31,-27,-26,-24,-23,-22,-21,-20,-19,-18,-17,-16,-15,-14,-13,-12,-11,-11,-10,-10, -9, -9, -9, -7, -8, -7, -6, -6, -6, -6, -5, -5, -4},
|
||||||
|
{-42,-40,-37,-36,-32,-30,-29,-27,-25,-24,-23,-22,-21,-20,-18,-18,-16,-15,-14,-13,-13,-12,-12,-10,-10,-10, -9, -9, -9, -8, -7, -7, -7, -6, -6, -5},
|
||||||
|
{ 51, 48, 46, 42, 42, 38, 36, 34, 32, 30, 24, 27, 25, 24, 23, 21, 21, 19, 18, 17, 16, 15, 14, 14, 12, 12, 12, 10, 10, 10, 9, 8, 8, 8, 7, 7},
|
||||||
|
{ 44, 42, 40, 37, 37, 35, 32, 31, 29, 27, 25, 24, 22, 21, 20, 19, 18, 17, 16, 15, 15, 14, 13, 12, 11, 10, 10, 9, 9, 9, 8, 7, 7, 7, 6, 6},
|
||||||
|
{ 38, 36, 34, 32, 31, 30, 28, 27, 25, 24, 22, 21, 19, 18, 17, 16, 16, 15, 14, 13, 13, 12, 11, 11, 9, 9, 9, 8, 7, 7, 7, 6, 6, 6, 5, 5},
|
||||||
|
{ 31, 30, 29, 26, 26, 25, 24, 22, 21, 20, 18, 17, 16, 15, 14, 13, 13, 12, 12, 11, 11, 10, 9, 9, 8, 7, 8, 7, 6, 6, 6, 5, 5, 5, 5, 5},
|
||||||
|
{ 25, 24, 23, 21, 21, 20, 19, 18, 17, 16, 14, 14, 13, 12, 11, 10, 11, 10, 10, 9, 9, 8, 7, 7, 6, 6, 6, 5, 5, 5, 5, 4, 4, 4, 3, 4},
|
||||||
|
{ 19, 18, 17, 16, 16, 15, 15, 14, 13, 12, 11, 10, 9, 9, 9, 8, 8, 18, 7, 7, 7, 6, 5, 6, 5, 4, 5, 4, 4, 4, 4, 3, 3, 3, 3, 3},
|
||||||
|
{ 12, 12, 12, 10, 11, 11, 10, 10, 9, 8, 7, 7, 6, 6, 6, 5, 6, 5, 5, 5, 5, 4, 4, 4, 3, 3, 3, 3, 2, 3, 3, 2, 2, 2, 2, 2},
|
||||||
|
{ 6, 6, 6, 5, 6, 6, 6, 5, 5, 5, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Min/max helper functions */
|
||||||
|
static int _pocketmod_min(int x, int y) { return x < y ? x : y; }
|
||||||
|
static int _pocketmod_max(int x, int y) { return x > y ? x : y; }
|
||||||
|
|
||||||
|
/* Clamp a volume value to the 0..64 range */
|
||||||
|
static int _pocketmod_clamp_volume(int x)
|
||||||
|
{
|
||||||
|
x = _pocketmod_max(x, 0x00);
|
||||||
|
x = _pocketmod_min(x, 0x40);
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zero out a block of memory */
|
||||||
|
static void _pocketmod_zero(void *data, int size)
|
||||||
|
{
|
||||||
|
char *byte = data, *end = byte + size;
|
||||||
|
while (byte != end) { *byte++ = 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert a period (at finetune = 0) to a note index in 0..35 */
|
||||||
|
static int _pocketmod_period_to_note(int period)
|
||||||
|
{
|
||||||
|
switch (period) {
|
||||||
|
case 856: return 0; case 808: return 1; case 762: return 2;
|
||||||
|
case 720: return 3; case 678: return 4; case 640: return 5;
|
||||||
|
case 604: return 6; case 570: return 7; case 538: return 8;
|
||||||
|
case 508: return 9; case 480: return 10; case 453: return 11;
|
||||||
|
case 428: return 12; case 404: return 13; case 381: return 14;
|
||||||
|
case 360: return 15; case 339: return 16; case 320: return 17;
|
||||||
|
case 302: return 18; case 285: return 19; case 269: return 20;
|
||||||
|
case 254: return 21; case 240: return 22; case 226: return 23;
|
||||||
|
case 214: return 24; case 202: return 25; case 190: return 26;
|
||||||
|
case 180: return 27; case 170: return 28; case 160: return 29;
|
||||||
|
case 151: return 30; case 143: return 31; case 135: return 32;
|
||||||
|
case 127: return 33; case 120: return 34; case 113: return 35;
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table-based sine wave oscillator */
|
||||||
|
static int _pocketmod_sin(int step)
|
||||||
|
{
|
||||||
|
/* round(sin(x * pi / 32) * 255) for x in 0..15 */
|
||||||
|
static const unsigned char sin[16] = {
|
||||||
|
0x00, 0x19, 0x32, 0x4a, 0x62, 0x78, 0x8e, 0xa2,
|
||||||
|
0xb4, 0xc5, 0xd4, 0xe0, 0xec, 0xf4, 0xfa, 0xfe
|
||||||
|
};
|
||||||
|
int x = sin[step & 0x0f];
|
||||||
|
x = (step & 0x1f) < 0x10 ? x : 0xff - x;
|
||||||
|
return step < 0x20 ? x : -x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Oscillators for vibrato/tremolo effects */
|
||||||
|
static int _pocketmod_lfo(pocketmod_context *c, _pocketmod_chan *ch, int step)
|
||||||
|
{
|
||||||
|
switch (ch->lfo_type[ch->effect == 7] & 3) {
|
||||||
|
case 0: return _pocketmod_sin(step & 0x3f); /* Sine */
|
||||||
|
case 1: return 0xff - ((step & 0x3f) << 3); /* Saw */
|
||||||
|
case 2: return (step & 0x3f) < 0x20 ? 0xff : -0xff; /* Square */
|
||||||
|
case 3: return (c->lfo_rng & 0x1ff) - 0xff; /* Random */
|
||||||
|
default: return 0; /* Hush little compiler */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _pocketmod_update_pitch(pocketmod_context *c, _pocketmod_chan *ch)
|
||||||
|
{
|
||||||
|
/* Don't do anything if the period is zero */
|
||||||
|
ch->increment = 0.0f;
|
||||||
|
if (ch->period) {
|
||||||
|
float period = ch->period;
|
||||||
|
|
||||||
|
/* Apply vibrato (if active) */
|
||||||
|
if (ch->effect == 0x4 || ch->effect == 0x6) {
|
||||||
|
int step = (ch->param4 >> 4) * ch->lfo_step;
|
||||||
|
int rate = ch->param4 & 0x0f;
|
||||||
|
period += _pocketmod_lfo(c, ch, step) * rate / 128.0f;
|
||||||
|
|
||||||
|
/* Apply arpeggio (if active) */
|
||||||
|
} else if (ch->effect == 0x0 && ch->param) {
|
||||||
|
static const float arpeggio[16] = { /* 2^(X/12) for X in 0..15 */
|
||||||
|
1.000000f, 1.059463f, 1.122462f, 1.189207f,
|
||||||
|
1.259921f, 1.334840f, 1.414214f, 1.498307f,
|
||||||
|
1.587401f, 1.681793f, 1.781797f, 1.887749f,
|
||||||
|
2.000000f, 2.118926f, 2.244924f, 2.378414f
|
||||||
|
};
|
||||||
|
int step = (ch->param >> ((2 - c->tick % 3) << 2)) & 0x0f;
|
||||||
|
period /= arpeggio[step];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calculate sample buffer position increment */
|
||||||
|
ch->increment = 3546894.6f / (period * c->samples_per_second);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clear the pitch dirty flag */
|
||||||
|
ch->dirty &= ~POCKETMOD_PITCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _pocketmod_update_volume(pocketmod_context *c, _pocketmod_chan *ch)
|
||||||
|
{
|
||||||
|
int volume = ch->volume;
|
||||||
|
if (ch->effect == 0x7) {
|
||||||
|
int step = ch->lfo_step * (ch->param7 >> 4);
|
||||||
|
volume += _pocketmod_lfo(c, ch, step) * (ch->param7 & 0x0f) >> 6;
|
||||||
|
}
|
||||||
|
ch->real_volume = _pocketmod_clamp_volume(volume);
|
||||||
|
ch->dirty &= ~POCKETMOD_VOLUME;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _pocketmod_pitch_slide(_pocketmod_chan *ch, int amount)
|
||||||
|
{
|
||||||
|
int max = 856 + _pocketmod_finetune[ch->finetune][ 0];
|
||||||
|
int min = 113 + _pocketmod_finetune[ch->finetune][35];
|
||||||
|
ch->period += amount;
|
||||||
|
ch->period = _pocketmod_max(ch->period, min);
|
||||||
|
ch->period = _pocketmod_min(ch->period, max);
|
||||||
|
ch->dirty |= POCKETMOD_PITCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _pocketmod_volume_slide(_pocketmod_chan *ch, int param)
|
||||||
|
{
|
||||||
|
/* Undocumented quirk: If both x and y are nonzero, then the value of x */
|
||||||
|
/* takes precedence. (Yes, there are songs that rely on this behavior.) */
|
||||||
|
int change = (param & 0xf0) ? (param >> 4) : -(param & 0x0f);
|
||||||
|
ch->volume = _pocketmod_clamp_volume(ch->volume + change);
|
||||||
|
ch->dirty |= POCKETMOD_VOLUME;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _pocketmod_next_line(pocketmod_context *c)
|
||||||
|
{
|
||||||
|
unsigned char (*data)[4];
|
||||||
|
int i, pos, pattern_break = -1;
|
||||||
|
|
||||||
|
/* When entering a new pattern order index, mark it as "visited" */
|
||||||
|
if (c->line == 0) {
|
||||||
|
c->visited[c->pattern >> 3] |= 1 << (c->pattern & 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Move to the next pattern if this was the last line */
|
||||||
|
if (++c->line == 64) {
|
||||||
|
if (++c->pattern == c->length) {
|
||||||
|
c->pattern = c->reset;
|
||||||
|
}
|
||||||
|
c->line = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find the pattern data for the current line */
|
||||||
|
pos = (c->order[c->pattern] * 64 + c->line) * c->num_channels * 4;
|
||||||
|
data = (unsigned char(*)[4]) (c->patterns + pos);
|
||||||
|
for (i = 0; i < c->num_channels; i++) {
|
||||||
|
|
||||||
|
/* Decode columns */
|
||||||
|
int sample = (data[i][0] & 0xf0) | (data[i][2] >> 4);
|
||||||
|
int period = ((data[i][0] & 0x0f) << 8) | data[i][1];
|
||||||
|
int effect = ((data[i][2] & 0x0f) << 8) | data[i][3];
|
||||||
|
|
||||||
|
/* Memorize effect parameter values */
|
||||||
|
_pocketmod_chan *ch = &c->channels[i];
|
||||||
|
ch->effect = (effect >> 8) != 0xe ? (effect >> 8) : (effect >> 4);
|
||||||
|
ch->param = (effect >> 8) != 0xe ? (effect & 0xff) : (effect & 0x0f);
|
||||||
|
|
||||||
|
/* Set sample */
|
||||||
|
if (sample) {
|
||||||
|
if (sample <= POCKETMOD_MAX_SAMPLES) {
|
||||||
|
unsigned char *sample_data = POCKETMOD_SAMPLE(c, sample);
|
||||||
|
ch->sample = sample;
|
||||||
|
ch->finetune = sample_data[2] & 0x0f;
|
||||||
|
ch->volume = _pocketmod_min(sample_data[3], 0x40);
|
||||||
|
if (ch->effect != 0xED) {
|
||||||
|
ch->dirty |= POCKETMOD_VOLUME;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ch->sample = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set note */
|
||||||
|
if (period) {
|
||||||
|
int note = _pocketmod_period_to_note(period);
|
||||||
|
period += _pocketmod_finetune[ch->finetune][note];
|
||||||
|
if (ch->effect != 0x3) {
|
||||||
|
if (ch->effect != 0xED) {
|
||||||
|
ch->period = period;
|
||||||
|
ch->dirty |= POCKETMOD_PITCH;
|
||||||
|
ch->position = 0.0f;
|
||||||
|
ch->lfo_step = 0;
|
||||||
|
} else {
|
||||||
|
ch->delayed = period;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle pattern effects */
|
||||||
|
switch (ch->effect) {
|
||||||
|
|
||||||
|
/* Memorize parameters */
|
||||||
|
case 0x3: POCKETMOD_MEM(ch->param3, ch->param); /* Fall through */
|
||||||
|
case 0x5: POCKETMOD_MEM(ch->target, period); break;
|
||||||
|
case 0x4: POCKETMOD_MEM2(ch->param4, ch->param); break;
|
||||||
|
case 0x7: POCKETMOD_MEM2(ch->param7, ch->param); break;
|
||||||
|
case 0xE1: POCKETMOD_MEM(ch->paramE1, ch->param); break;
|
||||||
|
case 0xE2: POCKETMOD_MEM(ch->paramE2, ch->param); break;
|
||||||
|
case 0xEA: POCKETMOD_MEM(ch->paramEA, ch->param); break;
|
||||||
|
case 0xEB: POCKETMOD_MEM(ch->paramEB, ch->param); break;
|
||||||
|
|
||||||
|
/* 8xx: Set stereo balance (nonstandard) */
|
||||||
|
case 0x8: {
|
||||||
|
ch->balance = ch->param;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
/* 9xx: Set sample offset */
|
||||||
|
case 0x9: {
|
||||||
|
if (period != 0 || sample != 0) {
|
||||||
|
ch->param9 = ch->param ? ch->param : ch->param9;
|
||||||
|
ch->position = ch->param9 << 8;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
/* Bxx: Jump to pattern */
|
||||||
|
case 0xB: {
|
||||||
|
c->pattern = ch->param < c->length ? ch->param : 0;
|
||||||
|
c->line = -1;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
/* Cxx: Set volume */
|
||||||
|
case 0xC: {
|
||||||
|
ch->volume = _pocketmod_clamp_volume(ch->param);
|
||||||
|
ch->dirty |= POCKETMOD_VOLUME;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
/* Dxy: Pattern break */
|
||||||
|
case 0xD: {
|
||||||
|
pattern_break = (ch->param >> 4) * 10 + (ch->param & 15);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
/* E4x: Set vibrato waveform */
|
||||||
|
case 0xE4: {
|
||||||
|
ch->lfo_type[0] = ch->param;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
/* E5x: Set sample finetune */
|
||||||
|
case 0xE5: {
|
||||||
|
ch->finetune = ch->param;
|
||||||
|
ch->dirty |= POCKETMOD_PITCH;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
/* E6x: Pattern loop */
|
||||||
|
case 0xE6: {
|
||||||
|
if (ch->param) {
|
||||||
|
if (!ch->loop_count) {
|
||||||
|
ch->loop_count = ch->param;
|
||||||
|
c->line = ch->loop_line;
|
||||||
|
} else if (--ch->loop_count) {
|
||||||
|
c->line = ch->loop_line;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ch->loop_line = c->line - 1;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
/* E7x: Set tremolo waveform */
|
||||||
|
case 0xE7: {
|
||||||
|
ch->lfo_type[1] = ch->param;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
/* E8x: Set stereo balance (nonstandard) */
|
||||||
|
case 0xE8: {
|
||||||
|
ch->balance = ch->param << 4;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
/* EEx: Pattern delay */
|
||||||
|
case 0xEE: {
|
||||||
|
c->pattern_delay = ch->param;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
/* Fxx: Set speed */
|
||||||
|
case 0xF: {
|
||||||
|
if (ch->param != 0) {
|
||||||
|
if (ch->param < 0x20) {
|
||||||
|
c->ticks_per_line = ch->param;
|
||||||
|
} else {
|
||||||
|
float rate = c->samples_per_second;
|
||||||
|
c->samples_per_tick = rate / (0.4f * ch->param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pattern breaks are handled here, so that only one jump happens even */
|
||||||
|
/* when multiple Dxy commands appear on the same line. (You guessed it: */
|
||||||
|
/* There are songs that rely on this behavior!) */
|
||||||
|
if (pattern_break != -1) {
|
||||||
|
c->line = (pattern_break < 64 ? pattern_break : 0) - 1;
|
||||||
|
if (++c->pattern == c->length) {
|
||||||
|
c->pattern = c->reset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _pocketmod_next_tick(pocketmod_context *c)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* Move to the next line if this was the last tick */
|
||||||
|
if (++c->tick == c->ticks_per_line) {
|
||||||
|
if (c->pattern_delay > 0) {
|
||||||
|
c->pattern_delay--;
|
||||||
|
} else {
|
||||||
|
_pocketmod_next_line(c);
|
||||||
|
}
|
||||||
|
c->tick = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make per-tick adjustments for all channels */
|
||||||
|
for (i = 0; i < c->num_channels; i++) {
|
||||||
|
_pocketmod_chan *ch = &c->channels[i];
|
||||||
|
int param = ch->param;
|
||||||
|
|
||||||
|
/* Advance the LFO random number generator */
|
||||||
|
c->lfo_rng = 0x0019660d * c->lfo_rng + 0x3c6ef35f;
|
||||||
|
|
||||||
|
/* Handle effects that may happen on any tick of a line */
|
||||||
|
switch (ch->effect) {
|
||||||
|
|
||||||
|
/* 0xy: Arpeggio */
|
||||||
|
case 0x0: {
|
||||||
|
ch->dirty |= POCKETMOD_PITCH;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
/* E9x: Retrigger note every x ticks */
|
||||||
|
case 0xE9: {
|
||||||
|
if (!(param && c->tick % param)) {
|
||||||
|
ch->position = 0.0f;
|
||||||
|
ch->lfo_step = 0;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
/* ECx: Cut note after x ticks */
|
||||||
|
case 0xEC: {
|
||||||
|
if (c->tick == param) {
|
||||||
|
ch->volume = 0;
|
||||||
|
ch->dirty |= POCKETMOD_VOLUME;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
/* EDx: Delay note for x ticks */
|
||||||
|
case 0xED: {
|
||||||
|
if (c->tick == param && ch->sample) {
|
||||||
|
ch->dirty |= POCKETMOD_VOLUME | POCKETMOD_PITCH;
|
||||||
|
ch->period = ch->delayed;
|
||||||
|
ch->position = 0.0f;
|
||||||
|
ch->lfo_step = 0;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle effects that only happen on the first tick of a line */
|
||||||
|
if (c->tick == 0) {
|
||||||
|
switch (ch->effect) {
|
||||||
|
case 0xE1: _pocketmod_pitch_slide(ch, -ch->paramE1); break;
|
||||||
|
case 0xE2: _pocketmod_pitch_slide(ch, +ch->paramE2); break;
|
||||||
|
case 0xEA: _pocketmod_volume_slide(ch, ch->paramEA << 4); break;
|
||||||
|
case 0xEB: _pocketmod_volume_slide(ch, ch->paramEB & 15); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle effects that are not applied on the first tick of a line */
|
||||||
|
} else {
|
||||||
|
switch (ch->effect) {
|
||||||
|
|
||||||
|
/* 1xx: Portamento up */
|
||||||
|
case 0x1: {
|
||||||
|
_pocketmod_pitch_slide(ch, -param);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
/* 2xx: Portamento down */
|
||||||
|
case 0x2: {
|
||||||
|
_pocketmod_pitch_slide(ch, +param);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
/* 5xy: Volume slide + tone portamento */
|
||||||
|
case 0x5: {
|
||||||
|
_pocketmod_volume_slide(ch, param);
|
||||||
|
} /* Fall through */
|
||||||
|
|
||||||
|
/* 3xx: Tone portamento */
|
||||||
|
case 0x3: {
|
||||||
|
int rate = ch->param3;
|
||||||
|
int order = ch->period < ch->target;
|
||||||
|
int closer = ch->period + (order ? rate : -rate);
|
||||||
|
int new_order = closer < ch->target;
|
||||||
|
ch->period = new_order == order ? closer : ch->target;
|
||||||
|
ch->dirty |= POCKETMOD_PITCH;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
/* 6xy: Volume slide + vibrato */
|
||||||
|
case 0x6: {
|
||||||
|
_pocketmod_volume_slide(ch, param);
|
||||||
|
} /* Fall through */
|
||||||
|
|
||||||
|
/* 4xy: Vibrato */
|
||||||
|
case 0x4: {
|
||||||
|
ch->lfo_step++;
|
||||||
|
ch->dirty |= POCKETMOD_PITCH;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
/* 7xy: Tremolo */
|
||||||
|
case 0x7: {
|
||||||
|
ch->lfo_step++;
|
||||||
|
ch->dirty |= POCKETMOD_VOLUME;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
/* Axy: Volume slide */
|
||||||
|
case 0xA: {
|
||||||
|
_pocketmod_volume_slide(ch, param);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update channel volume/pitch if either is out of date */
|
||||||
|
if (ch->dirty & POCKETMOD_VOLUME) { _pocketmod_update_volume(c, ch); }
|
||||||
|
if (ch->dirty & POCKETMOD_PITCH) { _pocketmod_update_pitch(c, ch); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _pocketmod_render_channel(pocketmod_context *c,
|
||||||
|
_pocketmod_chan *chan,
|
||||||
|
float *output,
|
||||||
|
int samples_to_write)
|
||||||
|
{
|
||||||
|
/* Gather some loop data */
|
||||||
|
_pocketmod_sample *sample = &c->samples[chan->sample - 1];
|
||||||
|
unsigned char *data = POCKETMOD_SAMPLE(c, chan->sample);
|
||||||
|
const int loop_start = ((data[4] << 8) | data[5]) << 1;
|
||||||
|
const int loop_length = ((data[6] << 8) | data[7]) << 1;
|
||||||
|
const int loop_end = loop_length > 2 ? loop_start + loop_length : 0xffffff;
|
||||||
|
const float sample_end = 1 + _pocketmod_min(loop_end, sample->length);
|
||||||
|
|
||||||
|
/* Calculate left/right levels */
|
||||||
|
const float volume = chan->real_volume / (float) (128 * 64 * 4);
|
||||||
|
const float level_l = volume * (1.0f - chan->balance / 255.0f);
|
||||||
|
const float level_r = volume * (0.0f + chan->balance / 255.0f);
|
||||||
|
|
||||||
|
/* Write samples */
|
||||||
|
int i, num;
|
||||||
|
do {
|
||||||
|
|
||||||
|
/* Calculate how many samples we can write in one go */
|
||||||
|
num = (sample_end - chan->position) / chan->increment;
|
||||||
|
num = _pocketmod_min(num, samples_to_write);
|
||||||
|
|
||||||
|
/* Resample and write 'num' samples */
|
||||||
|
for (i = 0; i < num; i++) {
|
||||||
|
int x0 = chan->position;
|
||||||
|
#ifdef POCKETMOD_NO_INTERPOLATION
|
||||||
|
float s = sample->data[x0];
|
||||||
|
#else
|
||||||
|
int x1 = x0 + 1 - loop_length * (x0 + 1 >= loop_end);
|
||||||
|
float t = chan->position - x0;
|
||||||
|
float s = (1.0f - t) * sample->data[x0] + t * sample->data[x1];
|
||||||
|
#endif
|
||||||
|
chan->position += chan->increment;
|
||||||
|
*output++ += level_l * s;
|
||||||
|
*output++ += level_r * s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rewind the sample when reaching the loop point */
|
||||||
|
if (chan->position >= loop_end) {
|
||||||
|
chan->position -= loop_length;
|
||||||
|
|
||||||
|
/* Cut the sample if the end is reached */
|
||||||
|
} else if (chan->position >= sample->length) {
|
||||||
|
chan->position = -1.0f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
samples_to_write -= num;
|
||||||
|
} while (num > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _pocketmod_ident(pocketmod_context *c, unsigned char *data, int size)
|
||||||
|
{
|
||||||
|
int i, j;
|
||||||
|
|
||||||
|
/* 31-instrument files are at least 1084 bytes long */
|
||||||
|
if (size >= 1084) {
|
||||||
|
|
||||||
|
/* The format tag is located at offset 1080 */
|
||||||
|
unsigned char *tag = data + 1080;
|
||||||
|
|
||||||
|
/* List of recognized format tags (possibly incomplete) */
|
||||||
|
static const struct {
|
||||||
|
char name[5];
|
||||||
|
char channels;
|
||||||
|
} tags[] = {
|
||||||
|
/* TODO: FLT8 intentionally omitted because I haven't been able */
|
||||||
|
/* to find a specimen to test its funky pattern pairing format */
|
||||||
|
{"M.K.", 4}, {"M!K!", 4}, {"FLT4", 4}, {"4CHN", 4},
|
||||||
|
{"OKTA", 8}, {"OCTA", 8}, {"CD81", 8}, {"FA08", 8},
|
||||||
|
{"1CHN", 1}, {"2CHN", 2}, {"3CHN", 3}, {"4CHN", 4},
|
||||||
|
{"5CHN", 5}, {"6CHN", 6}, {"7CHN", 7}, {"8CHN", 8},
|
||||||
|
{"9CHN", 9}, {"10CH", 10}, {"11CH", 11}, {"12CH", 12},
|
||||||
|
{"13CH", 13}, {"14CH", 14}, {"15CH", 15}, {"16CH", 16},
|
||||||
|
{"17CH", 17}, {"18CH", 18}, {"19CH", 19}, {"20CH", 20},
|
||||||
|
{"21CH", 21}, {"22CH", 22}, {"23CH", 23}, {"24CH", 24},
|
||||||
|
{"25CH", 25}, {"26CH", 26}, {"27CH", 27}, {"28CH", 28},
|
||||||
|
{"29CH", 29}, {"30CH", 30}, {"31CH", 31}, {"32CH", 32}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Check the format tag to determine if this is a 31-sample MOD */
|
||||||
|
for (i = 0; i < (int) (sizeof(tags) / sizeof(*tags)); i++) {
|
||||||
|
if (tags[i].name[0] == tag[0] && tags[i].name[1] == tag[1]
|
||||||
|
&& tags[i].name[2] == tag[2] && tags[i].name[3] == tag[3]) {
|
||||||
|
c->num_channels = tags[i].channels;
|
||||||
|
c->length = data[950];
|
||||||
|
c->reset = data[951];
|
||||||
|
c->order = &data[952];
|
||||||
|
c->patterns = &data[1084];
|
||||||
|
c->num_samples = 31;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A 15-instrument MOD has to be at least 600 bytes long */
|
||||||
|
if (size < 600) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check that the song title only contains ASCII bytes (or null) */
|
||||||
|
for (i = 0; i < 20; i++) {
|
||||||
|
if (data[i] != '\0' && (data[i] < ' ' || data[i] > '~')) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check that sample names only contain ASCII bytes (or null) */
|
||||||
|
for (i = 0; i < 15; i++) {
|
||||||
|
for (j = 0; j < 22; j++) {
|
||||||
|
char chr = data[20 + i * 30 + j];
|
||||||
|
if (chr != '\0' && (chr < ' ' || chr > '~')) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* It looks like we have an older 15-instrument MOD */
|
||||||
|
c->length = data[470];
|
||||||
|
c->reset = data[471];
|
||||||
|
c->order = &data[472];
|
||||||
|
c->patterns = &data[600];
|
||||||
|
c->num_samples = 15;
|
||||||
|
c->num_channels = 4;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pocketmod_init(pocketmod_context *c, const void *data, int size, int rate)
|
||||||
|
{
|
||||||
|
int i, remaining, header_bytes, pattern_bytes;
|
||||||
|
unsigned char *byte = (unsigned char*) c;
|
||||||
|
signed char *sample_data;
|
||||||
|
|
||||||
|
/* Check that arguments look more or less sane */
|
||||||
|
if (!c || !data || rate <= 0 || size <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zero out the whole context and identify the MOD type */
|
||||||
|
_pocketmod_zero(c, sizeof(pocketmod_context));
|
||||||
|
c->source = (unsigned char*) data;
|
||||||
|
if (!_pocketmod_ident(c, c->source, size)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check that we are compiled with support for enough channels */
|
||||||
|
if (c->num_channels > POCKETMOD_MAX_CHANNELS) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check that we have enough sample slots for this file */
|
||||||
|
if (POCKETMOD_MAX_SAMPLES < 31) {
|
||||||
|
byte = (unsigned char*) data + 20;
|
||||||
|
for (i = 0; i < c->num_samples; i++) {
|
||||||
|
unsigned int length = 2 * ((byte[22] << 8) | byte[23]);
|
||||||
|
if (i >= POCKETMOD_MAX_SAMPLES && length > 2) {
|
||||||
|
return 0; /* Can't fit this sample */
|
||||||
|
}
|
||||||
|
byte += 30;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check that the song length is in valid range (1..128) */
|
||||||
|
if (c->length == 0 || c->length > 128) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure that the reset pattern doesn't take us out of bounds */
|
||||||
|
if (c->reset >= c->length) {
|
||||||
|
c->reset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Count how many patterns there are in the file */
|
||||||
|
c->num_patterns = 0;
|
||||||
|
for (i = 0; i < 128 && c->order[i] < 128; i++) {
|
||||||
|
c->num_patterns = _pocketmod_max(c->num_patterns, c->order[i]);
|
||||||
|
}
|
||||||
|
pattern_bytes = 256 * c->num_channels * ++c->num_patterns;
|
||||||
|
header_bytes = (int) ((char*) c->patterns - (char*) data);
|
||||||
|
|
||||||
|
/* Check that each pattern in the order is within file bounds */
|
||||||
|
for (i = 0; i < c->length; i++) {
|
||||||
|
if (header_bytes + 256 * c->num_channels * c->order[i] > size) {
|
||||||
|
return 0; /* Reading this pattern would be a buffer over-read! */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check that the pattern data doesn't extend past the end of the file */
|
||||||
|
if (header_bytes + pattern_bytes > size) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Load sample payload data, truncating ones that extend outside the file */
|
||||||
|
remaining = size - header_bytes - pattern_bytes;
|
||||||
|
sample_data = (signed char*) data + header_bytes + pattern_bytes;
|
||||||
|
for (i = 0; i < c->num_samples; i++) {
|
||||||
|
unsigned char *data = POCKETMOD_SAMPLE(c, i + 1);
|
||||||
|
unsigned int length = ((data[0] << 8) | data[1]) << 1;
|
||||||
|
_pocketmod_sample *sample = &c->samples[i];
|
||||||
|
sample->data = sample_data;
|
||||||
|
sample->length = _pocketmod_min(length > 2 ? length : 0, remaining);
|
||||||
|
sample_data += sample->length;
|
||||||
|
remaining -= sample->length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set up ProTracker default panning for all channels */
|
||||||
|
for (i = 0; i < c->num_channels; i++) {
|
||||||
|
c->channels[i].balance = 0x80 + ((((i + 1) >> 1) & 1) ? 0x20 : -0x20);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prepare to render from the start */
|
||||||
|
c->ticks_per_line = 6;
|
||||||
|
c->samples_per_second = rate;
|
||||||
|
c->samples_per_tick = rate / 50.0f;
|
||||||
|
c->lfo_rng = 0xbadc0de;
|
||||||
|
c->line = -1;
|
||||||
|
c->tick = c->ticks_per_line - 1;
|
||||||
|
_pocketmod_next_tick(c);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pocketmod_render(pocketmod_context *c, void *buffer, int buffer_size)
|
||||||
|
{
|
||||||
|
int i, samples_rendered = 0;
|
||||||
|
int samples_remaining = buffer_size / POCKETMOD_SAMPLE_SIZE;
|
||||||
|
if (c && buffer) {
|
||||||
|
float (*output)[2] = (float(*)[2]) buffer;
|
||||||
|
while (samples_remaining > 0) {
|
||||||
|
|
||||||
|
/* Calculate the number of samples left in this tick */
|
||||||
|
int num = (int) (c->samples_per_tick - c->sample);
|
||||||
|
num = _pocketmod_min(num + !num, samples_remaining);
|
||||||
|
|
||||||
|
/* Render and mix 'num' samples from each channel */
|
||||||
|
_pocketmod_zero(output, num * POCKETMOD_SAMPLE_SIZE);
|
||||||
|
for (i = 0; i < c->num_channels; i++) {
|
||||||
|
_pocketmod_chan *chan = &c->channels[i];
|
||||||
|
if (chan->sample != 0 && chan->position >= 0.0f) {
|
||||||
|
_pocketmod_render_channel(c, chan, *output, num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
samples_remaining -= num;
|
||||||
|
samples_rendered += num;
|
||||||
|
output += num;
|
||||||
|
|
||||||
|
/* Advance song position by 'num' samples */
|
||||||
|
if ((c->sample += num) >= c->samples_per_tick) {
|
||||||
|
c->sample -= c->samples_per_tick;
|
||||||
|
_pocketmod_next_tick(c);
|
||||||
|
|
||||||
|
/* Stop if a new pattern was reached */
|
||||||
|
if (c->line == 0 && c->tick == 0) {
|
||||||
|
|
||||||
|
/* Increment loop counter as needed */
|
||||||
|
if (c->visited[c->pattern >> 3] & (1 << (c->pattern & 7))) {
|
||||||
|
_pocketmod_zero(c->visited, sizeof(c->visited));
|
||||||
|
c->loop_count++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return samples_rendered * POCKETMOD_SAMPLE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pocketmod_loop_count(pocketmod_context *c)
|
||||||
|
{
|
||||||
|
return c->loop_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* #ifdef POCKETMOD_IMPLEMENTATION */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* #ifndef POCKETMOD_H_INCLUDED */
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 rombankzero
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
*******************************************************************************/
|
|
@ -211,6 +211,15 @@ void script_eval_w_env(const char *s, JSValue env, const char *file) {
|
||||||
JS_FreeValue(js, v);
|
JS_FreeValue(js, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void call_env(JSValue env, const char *eval)
|
||||||
|
{
|
||||||
|
if (!JS_IsObject(env)) return;
|
||||||
|
JSValue fn = JS_GetPropertyStr(js, env, eval);
|
||||||
|
JSValue v = JS_EvalThis(js, env, eval, strlen(eval), "script", JS_EVAL_FLAGS);
|
||||||
|
js_print_exception(v);
|
||||||
|
JS_FreeValue(js,v);
|
||||||
|
}
|
||||||
|
|
||||||
void file_eval_env(const char *file, JSValue env)
|
void file_eval_env(const char *file, JSValue env)
|
||||||
{
|
{
|
||||||
size_t len;
|
size_t len;
|
||||||
|
@ -222,6 +231,7 @@ void file_eval_env(const char *file, JSValue env)
|
||||||
}
|
}
|
||||||
|
|
||||||
void script_call_sym(JSValue sym) {
|
void script_call_sym(JSValue sym) {
|
||||||
|
if (!JS_IsFunction(js, sym)) return;
|
||||||
struct callee c;
|
struct callee c;
|
||||||
c.fn = sym;
|
c.fn = sym;
|
||||||
c.obj = JS_GetGlobalObject(js);
|
c.obj = JS_GetGlobalObject(js);
|
||||||
|
@ -239,6 +249,9 @@ void out_memusage(const char *file)
|
||||||
|
|
||||||
JSValue js_callee_exec(struct callee *c, int argc, JSValue *argv)
|
JSValue js_callee_exec(struct callee *c, int argc, JSValue *argv)
|
||||||
{
|
{
|
||||||
|
if (JS_IsUndefined(c->fn)) return JS_NULL;
|
||||||
|
if (JS_IsUndefined(c->obj)) return JS_NULL;
|
||||||
|
|
||||||
JSValue ret = JS_Call(js, c->fn, c->obj, argc, argv);
|
JSValue ret = JS_Call(js, c->fn, c->obj, argc, argv);
|
||||||
js_print_exception(ret);
|
js_print_exception(ret);
|
||||||
JS_FreeValue(js, ret);
|
JS_FreeValue(js, ret);
|
||||||
|
@ -271,19 +284,18 @@ void script_callee(struct callee c, int argc, JSValue *argv) {
|
||||||
js_callee_exec(&c, argc, argv);
|
js_callee_exec(&c, argc, argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct callee *make_callee(JSValue fn, JSValue obj)
|
struct callee make_callee(JSValue fn, JSValue obj)
|
||||||
{
|
{
|
||||||
struct callee *c = malloc(sizeof(*c));
|
struct callee c;
|
||||||
c->fn = JS_DupValue(js, fn);
|
c.fn = JS_DupValue(js, fn);
|
||||||
c->obj = JS_DupValue(js, obj);
|
c.obj = JS_DupValue(js, obj);
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
void free_callee(struct callee *c)
|
void free_callee(struct callee c)
|
||||||
{
|
{
|
||||||
JS_FreeValue(js,c->fn);
|
JS_FreeValue(js,c.fn);
|
||||||
JS_FreeValue(js,c->obj);
|
JS_FreeValue(js,c.obj);
|
||||||
free(c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_signal(const char *signal, int argc, JSValue *argv)
|
void send_signal(const char *signal, int argc, JSValue *argv)
|
||||||
|
|
|
@ -27,8 +27,8 @@ time_t jso_file(const char *file);
|
||||||
JSValue script_runfile(const char *file);
|
JSValue script_runfile(const char *file);
|
||||||
void script_update(double dt);
|
void script_update(double dt);
|
||||||
void script_draw();
|
void script_draw();
|
||||||
struct callee *make_callee(JSValue fn, JSValue obj);
|
struct callee make_callee(JSValue fn, JSValue obj);
|
||||||
void free_callee(struct callee *c);
|
void free_callee(struct callee c);
|
||||||
|
|
||||||
void duk_run_err();
|
void duk_run_err();
|
||||||
void js_dump_stack();
|
void js_dump_stack();
|
||||||
|
@ -42,6 +42,7 @@ void call_callee(struct callee *c);
|
||||||
void script_callee(struct callee c, int argc, JSValue *argv);
|
void script_callee(struct callee c, int argc, JSValue *argv);
|
||||||
int script_has_sym(void *sym);
|
int script_has_sym(void *sym);
|
||||||
void script_eval_w_env(const char *s, JSValue env, const char *file);
|
void script_eval_w_env(const char *s, JSValue env, const char *file);
|
||||||
|
void call_env(JSValue env, const char *eval);
|
||||||
void file_eval_env(const char *file, JSValue env);
|
void file_eval_env(const char *file, JSValue env);
|
||||||
|
|
||||||
time_t file_mod_secs(const char *file);
|
time_t file_mod_secs(const char *file);
|
||||||
|
|
|
@ -10,191 +10,164 @@
|
||||||
|
|
||||||
#define PI 3.14159265
|
#define PI 3.14159265
|
||||||
|
|
||||||
struct dsp_filter *filters;
|
dsp_node *masterbus = NULL;
|
||||||
|
|
||||||
struct dsp_filter make_dsp(void *data, void (*in)(void *data, soundbyte *out, int n)) {
|
|
||||||
struct dsp_filter new;
|
|
||||||
new.data = data;
|
|
||||||
new.filter = in;
|
|
||||||
|
|
||||||
return new;
|
|
||||||
|
|
||||||
if (arrlen(filters) == 0) {
|
|
||||||
|
|
||||||
|
void interleave(soundbyte *a, soundbyte *b, soundbyte *stereo, int frames)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < frames; i++) {
|
||||||
|
stereo[i*2] = a[i];
|
||||||
|
stereo[i*2+1] = b[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void dsp_run(struct dsp_filter filter, soundbyte *out, int n) {
|
void mono_to_stero(soundbyte *a, soundbyte *stereo, int frames)
|
||||||
filter.dirty = 1; // Always on for testing
|
{
|
||||||
|
interleave(a,a,stereo, frames);
|
||||||
|
}
|
||||||
|
|
||||||
if (!filter.dirty)
|
void mono_expand(soundbyte *buffer, int to, int frames)
|
||||||
|
{
|
||||||
|
soundbyte hold[frames];
|
||||||
|
memcpy(hold, buffer, sizeof(soundbyte)*frames);
|
||||||
|
|
||||||
|
for (int i = 0; i < frames; i++)
|
||||||
|
for (int j = 0; j < to; j++)
|
||||||
|
buffer[i*to+j] = hold[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
dsp_node *dsp_mixer_node()
|
||||||
|
{
|
||||||
|
return make_node(NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dsp_init()
|
||||||
|
{
|
||||||
|
masterbus = dsp_limiter(1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
soundbyte *dsp_node_out(dsp_node *node)
|
||||||
|
{
|
||||||
|
zero_soundbytes(node->cache, BUF_FRAMES*CHANNELS);
|
||||||
|
|
||||||
|
if (node->off) return node->cache;
|
||||||
|
|
||||||
|
/* Sum all inputs */
|
||||||
|
for (int i = 0; i < arrlen(node->ins); i++) {
|
||||||
|
soundbyte *out = dsp_node_out(node->ins[i]);
|
||||||
|
sum_soundbytes(node->cache, out, BUF_FRAMES*CHANNELS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If there's a filter, run it */
|
||||||
|
if (!node->pass && node->proc)
|
||||||
|
node->proc(node->data, node->cache, BUF_FRAMES);
|
||||||
|
|
||||||
|
scale_soundbytes(node->cache, node->gain, BUF_FRAMES*CHANNELS);
|
||||||
|
pan_frames(node->cache, node->pan, BUF_FRAMES);
|
||||||
|
|
||||||
|
return node->cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
void filter_am_mod(dsp_node *mod, soundbyte *buffer, int frames)
|
||||||
|
{
|
||||||
|
soundbyte *m = dsp_node_out(mod);
|
||||||
|
for (int i = 0; i < frames*CHANNELS; i++) buffer[i] *= m[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
dsp_node *dsp_am_mod(dsp_node *mod)
|
||||||
|
{
|
||||||
|
return make_node(mod, filter_am_mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add b into a */
|
||||||
|
void sum_soundbytes(soundbyte *a, soundbyte *b, int samples)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < samples; i++) a[i] += b[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
void norm_soundbytes(soundbyte *a, float lvl, int samples)
|
||||||
|
{
|
||||||
|
float tar = lvl;
|
||||||
|
float max = 0 ;
|
||||||
|
for (int i = 0; i < samples; i++) max = (fabsf(a[i] > max) ? fabsf(a[i]) : max);
|
||||||
|
float mult = max/tar;
|
||||||
|
scale_soundbytes(a, mult, samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
void scale_soundbytes(soundbyte *a, float scale, int samples)
|
||||||
|
{
|
||||||
|
if (scale == 1) return;
|
||||||
|
for (int i = 0; i < samples; i++) a[i] *= scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
void zero_soundbytes(soundbyte *a, int samples)
|
||||||
|
{
|
||||||
|
memset(a, 0, sizeof(soundbyte)*samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_soundbytes(soundbyte *a, soundbyte *b, int samples)
|
||||||
|
{
|
||||||
|
zero_soundbytes(a, samples);
|
||||||
|
sum_soundbytes(a,b,samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dsp_node_run(dsp_node *node)
|
||||||
|
{
|
||||||
|
zero_soundbytes(node->cache, BUF_FRAMES*CHANNELS);
|
||||||
|
|
||||||
|
for (int i = 0; i < arrlen(node->ins); i++) {
|
||||||
|
soundbyte *out = dsp_node_out(node->ins[i]);
|
||||||
|
sum_soundbytes(node->cache, out, BUF_FRAMES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
dsp_node *make_node(void *data, void (*proc)(void *in, soundbyte *out, int samples))
|
||||||
|
{
|
||||||
|
dsp_node *self = malloc(sizeof(dsp_node));
|
||||||
|
memset(self, 0, sizeof(*self));
|
||||||
|
self->data = data;
|
||||||
|
self->proc = proc;
|
||||||
|
self->pass = 0;
|
||||||
|
self->gain = 1;
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
void node_free(dsp_node *node)
|
||||||
|
{
|
||||||
|
unplug_node(node);
|
||||||
|
if (node->data)
|
||||||
|
if (node->data_free) node->data_free(node->data);
|
||||||
|
else free(node->data);
|
||||||
|
|
||||||
|
free(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
void plugin_node(dsp_node *from, dsp_node *to)
|
||||||
|
{
|
||||||
|
if (from->out) return;
|
||||||
|
arrput(to->ins, from);
|
||||||
|
from->out = to;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unplug the given node from its output */
|
||||||
|
void unplug_node(dsp_node *node)
|
||||||
|
{
|
||||||
|
if (!node->out) return;
|
||||||
|
|
||||||
|
for (int i = 0; arrlen(node->out->ins); i++)
|
||||||
|
if (node == node->out->ins[i]) {
|
||||||
|
arrdelswap(node->out->ins, i);
|
||||||
|
node->out = NULL;
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (int i = 0; i < filter.inputs; i++)
|
|
||||||
dsp_run(*(filter.in[i]), out, n);
|
|
||||||
|
|
||||||
filter.filter(filter.data, out, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
void dsp_filter_addin(struct dsp_filter filter, struct dsp_filter *in)
|
|
||||||
{
|
|
||||||
if (filter.inputs > 5) {
|
|
||||||
YughError("Too many inputs in filter.", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
filter.in[filter.inputs++] = in;
|
|
||||||
}
|
|
||||||
|
|
||||||
void am_mod(struct dsp_ammod *mod, soundbyte *c, int n)
|
|
||||||
{
|
|
||||||
dsp_run(mod->ina, mod->abuf, n);
|
|
||||||
dsp_run(mod->inb, mod->bbuf, n);
|
|
||||||
|
|
||||||
// for (int i = 0; i < n*CHANNELS; i++)
|
|
||||||
// c[i] = (mod->abuf[i]*mod->bbuf[i])>>15;
|
|
||||||
}
|
|
||||||
|
|
||||||
void fm_mod(float *in1, float *in2, float *out, int n)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct wav make_wav(float freq, int sr, int ch) {
|
|
||||||
struct wav new;
|
|
||||||
new.ch = ch;
|
|
||||||
new.samplerate = sr;
|
|
||||||
new.frames = sr/freq;
|
|
||||||
new.data = calloc(new.frames*new.ch, sizeof(soundbyte));
|
|
||||||
|
|
||||||
return new;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct wav gen_sine(float amp, float freq, int sr, int ch)
|
|
||||||
{
|
|
||||||
struct wav new = make_wav(freq, sr, ch);
|
|
||||||
|
|
||||||
if (amp > 1) amp = 1;
|
|
||||||
if (amp < 0) amp = 0;
|
|
||||||
soundbyte samp = amp*SHRT_MAX;
|
|
||||||
|
|
||||||
soundbyte *data = (soundbyte*)new.data;
|
|
||||||
|
|
||||||
for (int i = 0; i < new.frames; i++) {
|
|
||||||
soundbyte val = samp * sin(2*PI*((float)i / new.frames));
|
|
||||||
|
|
||||||
for (int j = 0; j < new.ch; j++) {
|
|
||||||
data[i*new.ch+j] = val;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
YughInfo("Made sine with %i frames.", new.frames);
|
typedef struct {
|
||||||
|
float amp;
|
||||||
return new;
|
float freq;
|
||||||
}
|
float phase; /* from 0 to 1, marking where we are */
|
||||||
|
float (*filter)(float phase);
|
||||||
struct wav gen_square(float amp, float freq, int sr, int ch)
|
} phasor;
|
||||||
{
|
|
||||||
struct wav new = make_wav(freq, sr, ch);
|
|
||||||
|
|
||||||
int crossover = new.frames/2;
|
|
||||||
|
|
||||||
if (amp > 1) amp = 1;
|
|
||||||
if (amp < 0) amp = 0;
|
|
||||||
|
|
||||||
soundbyte samp = amp * SHRT_MAX;
|
|
||||||
|
|
||||||
soundbyte *data = (soundbyte*)new.data;
|
|
||||||
|
|
||||||
for (int i = 0; i < new.frames; i++) {
|
|
||||||
soundbyte val = -2 * floor(2 * i / new.frames) + 1;
|
|
||||||
for (int j = 0; j < new.ch; j++) {
|
|
||||||
data[i*new.frames+j] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct wav gen_triangle(float amp, float freq, int sr, int ch)
|
|
||||||
{
|
|
||||||
struct wav new = make_wav(freq, sr, ch);
|
|
||||||
|
|
||||||
if (amp > 1) amp = 1;
|
|
||||||
if (amp < 0) amp = 0;
|
|
||||||
|
|
||||||
soundbyte *data = (soundbyte*)new.data;
|
|
||||||
|
|
||||||
for (int i = 0; i < new.frames; i++) {
|
|
||||||
soundbyte val = 2 * fabsf( (i/new.frames) - floor( (i/new.frames) + 0.5));
|
|
||||||
for (int j = 0; j < new.ch; j++) {
|
|
||||||
data[i+j] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct wav gen_saw(float amp, float freq, int sr, int ch)
|
|
||||||
{
|
|
||||||
struct wav new = make_wav(freq, sr, ch);
|
|
||||||
|
|
||||||
if (amp > 1) amp = 1;
|
|
||||||
if (amp < 0) amp = 0;
|
|
||||||
|
|
||||||
soundbyte samp = amp*SHRT_MAX;
|
|
||||||
|
|
||||||
soundbyte *data = (soundbyte*)new.data;
|
|
||||||
|
|
||||||
for (int i = 0; i < new.frames; i++) {
|
|
||||||
soundbyte val = samp * 2 * i/sr - samp;
|
|
||||||
for (int j = 0; j < new.ch; j++) {
|
|
||||||
data[i+j] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct dsp_filter dsp_filter(void *data, void (*filter)(void *data, soundbyte *out, int samples))
|
|
||||||
{
|
|
||||||
struct dsp_filter new;
|
|
||||||
new.data = data;
|
|
||||||
new.filter = filter;
|
|
||||||
new.inputs = 0;
|
|
||||||
new.bus = NULL;
|
|
||||||
return new;
|
|
||||||
}
|
|
||||||
|
|
||||||
void dsp_rectify(soundbyte *in, soundbyte *out, int n)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < n; i++)
|
|
||||||
out[i] = abs(in[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct phasor phasor_make(unsigned int sr, float freq)
|
|
||||||
{
|
|
||||||
struct phasor new;
|
|
||||||
new.sr = sr;
|
|
||||||
new.cur = 0.f;
|
|
||||||
new.freq = freq;
|
|
||||||
|
|
||||||
new.cstep = 0;
|
|
||||||
new.clen = new.sr / new.freq;
|
|
||||||
new.cache = malloc(new.clen * sizeof(float));
|
|
||||||
|
|
||||||
for (int i = 0; i < new.clen; i++) {
|
|
||||||
new.cache[i] = (float)i / new.clen;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new;
|
|
||||||
}
|
|
||||||
|
|
||||||
float phasor_step(struct phasor *p)
|
|
||||||
{
|
|
||||||
p->cur += p->freq/p->sr;
|
|
||||||
if (p->cur >= 1.f) p->cur = 0.f;
|
|
||||||
return p->cur;
|
|
||||||
}
|
|
||||||
|
|
||||||
float sin_phasor(float p)
|
float sin_phasor(float p)
|
||||||
{
|
{
|
||||||
|
@ -216,47 +189,72 @@ float tri_phasor(float p)
|
||||||
return 4*(p * 0.5f ? p : (1-p)) - 1;
|
return 4*(p * 0.5f ? p : (1-p)) - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void osc_fillbuf(struct osc *osc, soundbyte *buf, int n)
|
void filter_phasor(phasor *p, soundbyte *buffer, int frames)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < frames; i++) {
|
||||||
soundbyte val = SHRT_MAX * osc->f(phasor_step(&osc->p));
|
buffer[i] = p->filter(p->phase) * p->amp;
|
||||||
buf[i*CHANNELS] = buf[i*CHANNELS+1] = val;
|
p->phase += p->freq/SAMPLERATE;
|
||||||
}
|
}
|
||||||
|
p->phase = p->phase - (int)p->phase;
|
||||||
|
mono_expand(buffer, CHANNELS, frames);
|
||||||
|
}
|
||||||
|
|
||||||
|
dsp_node *dsp_phasor(float amp, float freq, float (*filter)(float))
|
||||||
|
{
|
||||||
|
phasor *p = malloc(sizeof(*p));
|
||||||
|
p->amp = amp;
|
||||||
|
p->freq = freq;
|
||||||
|
p->phase = 0;
|
||||||
|
p->filter = filter;
|
||||||
|
return make_node(p, filter_phasor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void filter_rectify(void *data, soundbyte *out, int n)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < n; i++) out[i] = abs(out[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
dsp_node *dsp_rectify()
|
||||||
|
{
|
||||||
|
return make_node(NULL, filter_rectify);
|
||||||
|
}
|
||||||
|
|
||||||
|
soundbyte sample_whitenoise()
|
||||||
|
{
|
||||||
|
return ((float)rand()/(float)(RAND_MAX/2))-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void gen_whitenoise(void *data, soundbyte *out, int n)
|
void gen_whitenoise(void *data, soundbyte *out, int n)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) out[i] = sample_whitenoise();
|
||||||
for (int j = 0; j < CHANNELS; j++) {
|
mono_expand(out, CHANNELS, n);
|
||||||
out[i*CHANNELS+j] = (rand()>>15) - USHRT_MAX;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dsp_node *dsp_whitenoise()
|
||||||
|
{
|
||||||
|
return make_node(NULL, gen_whitenoise);
|
||||||
}
|
}
|
||||||
|
|
||||||
void gen_pinknoise(void *data, soundbyte *out, int n)
|
void gen_pinknoise(void *data, soundbyte *out, int n)
|
||||||
{
|
{
|
||||||
gen_whitenoise(NULL, out, n);
|
double a[7] = {1.0, 0.0555179, 0.0750759, 0.1538520, 0.3104856, 0.5329522, 0.0168980};
|
||||||
|
double b[7] = {0.99886, 0.99332, 0.969, 0.8665, 0.55, -0.7616, 0.115926};
|
||||||
double b[2][7] = {0};
|
|
||||||
double ccof[6] = {0.99886, 0.99332, 0.96900, 0.8550, 0.55000, -0.76160};
|
|
||||||
double dcof[6] = {0.0555179, 0.0750759, 0.1538520, 0.3104856, 0.5329522, 0.0168960};
|
|
||||||
|
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
for (int j = 0; j < CHANNELS; j++) {
|
|
||||||
double pink;
|
double pink;
|
||||||
double white = (double)out[i*CHANNELS+j]/SHRT_MAX;
|
double white = sample_whitenoise();
|
||||||
|
|
||||||
for (int k = 0; k < 5; k++) {
|
for (int k = 0; k < 5; k++) {
|
||||||
b[j][k] = ccof[k]*b[j][k] + white * dcof[k];
|
b[k] = a[k]*b[k] + white * b[k];
|
||||||
pink += b[j][k];
|
pink += b[k];
|
||||||
}
|
}
|
||||||
|
|
||||||
pink += b[j][5] + white*0.5362;
|
pink += b[5] + white*0.5362;
|
||||||
b[j][5] = white*0.115926;
|
b[5] = white*0.115926;
|
||||||
|
|
||||||
out[i*CHANNELS+j] = pink * SHRT_MAX;
|
out[i] = pink;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
mono_expand(out,CHANNELS,n);
|
||||||
/*
|
/*
|
||||||
* The above is a loopified version of this
|
* The above is a loopified version of this
|
||||||
* https://www.firstpr.com.au/dsp/pink-noise/
|
* https://www.firstpr.com.au/dsp/pink-noise/
|
||||||
|
@ -271,179 +269,71 @@ void gen_pinknoise(void *data, soundbyte *out, int n)
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
soundbyte iir_filter(struct dsp_iir *miir, soundbyte val)
|
dsp_node *dsp_pinknoise()
|
||||||
{
|
{
|
||||||
struct dsp_iir iir = *miir;
|
return make_node(NULL, gen_pinknoise);
|
||||||
|
}
|
||||||
|
|
||||||
float a = 0.f;
|
soundbyte iir_filter(struct dsp_iir iir, soundbyte val)
|
||||||
|
{
|
||||||
iir.dx[0] = (float)val/SHRT_MAX;
|
iir.y[0] = 0.0;
|
||||||
for (int i = 0; i < iir.n; i++)
|
|
||||||
a += iir.ccof[i] * iir.dx[i];
|
|
||||||
|
|
||||||
for (int i = iir.n-1; i > 0; i--)
|
|
||||||
iir.dx[i] = iir.dx[i-1];
|
|
||||||
|
|
||||||
|
iir.x[0] = val;
|
||||||
|
|
||||||
for (int i = 0; i < iir.n; i++)
|
for (int i = 0; i < iir.n; i++)
|
||||||
a -= iir.dcof[i] * iir.dy[i];
|
iir.y[0] += iir.a[i] * iir.x[i];
|
||||||
|
|
||||||
iir.dy[0] = a;
|
for (int i = 1; i < iir.n; i++)
|
||||||
|
iir.y[0] -= iir.b[i] * iir.y[i];
|
||||||
|
|
||||||
for (int i = iir.n-1; i > 0; i--)
|
/* Shift values in */
|
||||||
iir.dy[i] = iir.dy[i-1];
|
for (int i = iir.n-1; i > 0; i--) {
|
||||||
|
iir.x[i] = iir.x[i-1];
|
||||||
|
iir.y[i] = iir.y[i-1];
|
||||||
|
|
||||||
return a * SHRT_MAX;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void dsp_iir_fillbuf(struct dsp_iir *iir, soundbyte *out, int n)
|
return iir.y[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
void filter_iir(struct dsp_iir *iir, soundbyte *buffer, int frames)
|
||||||
{
|
{
|
||||||
dsp_run(iir->in, out, n);
|
for (int i = 0; i < frames; i++) {
|
||||||
|
soundbyte v = iir_filter(*iir, buffer[i*CHANNELS]);
|
||||||
for (int i = 0; i < n; i++) {
|
for (int j = 0; j < CHANNELS; j++) buffer[i*CHANNELS+j] = v;
|
||||||
soundbyte v = iir_filter(iir, out[i*CHANNELS]);
|
|
||||||
|
|
||||||
for (int j = 0; j < CHANNELS; j++) {
|
|
||||||
out[i*CHANNELS+j] = v;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct dsp_filter lpf_make(int poles, float freq)
|
dsp_node *dsp_lpf(float freq)
|
||||||
{
|
{
|
||||||
struct dsp_iir *new = malloc(sizeof(*new));
|
struct dsp_iir *iir = malloc(sizeof(*iir));
|
||||||
(*new) = make_iir(3, 1);
|
*iir = bqlp_dcof(2*freq/SAMPLERATE, 5);
|
||||||
|
return make_node(iir, filter_iir);
|
||||||
double fcf = new->freq*2/SAMPLERATE;
|
|
||||||
|
|
||||||
double sf = sf_bwlp(poles, fcf);
|
|
||||||
|
|
||||||
YughInfo("Making LPF filter, fcf: %f, coeffs: %i, scale %1.15lf", fcf, new->n, sf);
|
|
||||||
|
|
||||||
int *ccof = ccof_bwlp(new->n);
|
|
||||||
new->dcof = dcof_bwlp(new->n, fcf);
|
|
||||||
|
|
||||||
for (int i = 0; i < new->n; i++)
|
|
||||||
new->ccof[i] = (float)ccof[i] * sf;
|
|
||||||
|
|
||||||
new->dcof[0] = 0.f;
|
|
||||||
|
|
||||||
free(ccof);
|
|
||||||
|
|
||||||
YughInfo("LPF coefficients are:", 0);
|
|
||||||
|
|
||||||
for (int i = 0; i < new->n; i++)
|
|
||||||
YughInfo("%f, %f", new->ccof[i], new->dcof[i]);
|
|
||||||
|
|
||||||
struct dsp_filter lpf;
|
|
||||||
lpf.data = new;
|
|
||||||
lpf.filter = dsp_iir_fillbuf;
|
|
||||||
|
|
||||||
return lpf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct dsp_filter hpf_make(int poles, float freq)
|
dsp_node *dsp_hpf(float freq)
|
||||||
{
|
{
|
||||||
struct dsp_iir *new = malloc(sizeof(*new));
|
struct dsp_iir *iir = malloc(sizeof(*iir));
|
||||||
*new = make_iir(3, 1);
|
*iir = bqhp_dcof(2*freq/SAMPLERATE,5);
|
||||||
|
return make_node(iir, filter_iir);
|
||||||
double fcf = new->freq*2/SAMPLERATE;
|
|
||||||
double sf = sf_bwhp(new->n, fcf);
|
|
||||||
|
|
||||||
int *ccof = ccof_bwhp(new->n);
|
|
||||||
new->dcof = dcof_bwhp(new->n, fcf);
|
|
||||||
|
|
||||||
for (int i = 0; i < new->n; i++)
|
|
||||||
new->ccof[i] = ccof[i] * sf;
|
|
||||||
|
|
||||||
for (int i = 0; i < new->n; i++)
|
|
||||||
YughInfo("%f, %f", new->ccof[i], new->dcof[i]);
|
|
||||||
|
|
||||||
free(ccof);
|
|
||||||
|
|
||||||
struct dsp_filter hpf;
|
|
||||||
hpf.data = new;
|
|
||||||
hpf.filter = dsp_iir_fillbuf;
|
|
||||||
|
|
||||||
return hpf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
soundbyte fir_filter(struct dsp_fir *fir, soundbyte val)
|
void filter_delay(delay *d, soundbyte *buf, int frames)
|
||||||
{
|
{
|
||||||
float ret = 0.f;
|
for (int i = 0; i < frames*CHANNELS; i++) {
|
||||||
fir->dx[fir->head] = (float)val/SHRT_MAX;
|
buf[i] += ringshift(d->ring)*d->decay;
|
||||||
|
ringpush(d->ring, buf[i]);
|
||||||
for (int i = 0; i < fir->n; i++) {
|
}
|
||||||
ret += fir->cof[i] * fir->dx[fir->head--];
|
|
||||||
|
|
||||||
if (fir->head < 0) fir->head = fir->n-1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret * SHRT_MAX;
|
dsp_node *dsp_delay(double sec, double decay)
|
||||||
}
|
|
||||||
|
|
||||||
void dsp_fir_fillbuf(struct dsp_fir *fir, soundbyte *out, int n)
|
|
||||||
{
|
{
|
||||||
dsp_run(fir->in, out, n);
|
delay *d = malloc(sizeof(*d));
|
||||||
|
d->ms_delay = sec;
|
||||||
for (int i = 0; i < n; i++) {
|
d->decay = decay;
|
||||||
soundbyte val = fir_filter(fir, out[i*CHANNELS]);
|
d->ring = NULL;
|
||||||
|
d->ring = ringnew(d->ring, sec*CHANNELS*SAMPLERATE*2); /* Circular buffer size is enough to have the delay */
|
||||||
for (int j = 0; j < CHANNELS; j++)
|
ringheader(d->ring)->write += CHANNELS*SAMPLERATE*sec;
|
||||||
out[i*CHANNELS + j] = val*5;
|
return make_node(d, filter_delay);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct dsp_filter lp_fir_make(float freq)
|
|
||||||
{
|
|
||||||
struct dsp_fir fir;
|
|
||||||
fir.freq = freq;
|
|
||||||
fir.n = 9;
|
|
||||||
fir.head = 0;
|
|
||||||
double fcf = freq * 2 / SAMPLERATE;
|
|
||||||
fir.dx = calloc(sizeof(float), fir.n);
|
|
||||||
fir.cof = fir_lp(fir.n, fcf);
|
|
||||||
|
|
||||||
struct dsp_filter new;
|
|
||||||
new.data = malloc(sizeof(fir));
|
|
||||||
*(struct dsp_fir*)(new.data) = fir;
|
|
||||||
new.filter = dsp_fir_fillbuf;
|
|
||||||
|
|
||||||
for (int i = 0; i < fir.n; i++) {
|
|
||||||
printf("%f\n", fir.cof[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct dsp_delay dsp_delay_make(unsigned int ms_delay)
|
|
||||||
{
|
|
||||||
struct dsp_delay new;
|
|
||||||
new.ms_delay = ms_delay;
|
|
||||||
|
|
||||||
/* Circular buffer size is enough to have the delay */
|
|
||||||
unsigned int datasize = ms_delay * CHANNELS * (SAMPLERATE / 1000);
|
|
||||||
// new.buf = circbuf_init(sizeof(soundbyte), datasize);
|
|
||||||
// new.buf.write = datasize;
|
|
||||||
|
|
||||||
// YughInfo("Buffer size is %u.", new.buf.len);
|
|
||||||
|
|
||||||
return new;
|
|
||||||
}
|
|
||||||
|
|
||||||
void dsp_delay_filbuf(struct dsp_delay *delay, soundbyte *buf, int n)
|
|
||||||
{
|
|
||||||
soundbyte cache[BUF_FRAMES*2];
|
|
||||||
dsp_run(delay->in, cache, n);
|
|
||||||
|
|
||||||
for (int i = 0; i < n*CHANNELS; i++) {
|
|
||||||
// cbuf_push(&delay->buf, cache[i] / 2);
|
|
||||||
// buf[i] = cache[i] + cbuf_shift(&delay->buf);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get decay constant for a given pole */
|
/* Get decay constant for a given pole */
|
||||||
|
@ -501,9 +391,9 @@ void dsp_adsr_fillbuf(struct dsp_adsr *adsr, soundbyte *out, int n)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct dsp_filter make_adsr(unsigned int atk, unsigned int dec, unsigned int sus, unsigned int rls)
|
dsp_node *dsp_adsr(unsigned int atk, unsigned int dec, unsigned int sus, unsigned int rls)
|
||||||
{
|
{
|
||||||
struct dsp_adsr *adsr = calloc(sizeof(*adsr), 1);
|
struct dsp_adsr *adsr = malloc(sizeof(*adsr));
|
||||||
adsr->atk = atk;
|
adsr->atk = atk;
|
||||||
/* decay to 3 tau */
|
/* decay to 3 tau */
|
||||||
adsr->atk_t = tau2pole(atk / 3000.f);
|
adsr->atk_t = tau2pole(atk / 3000.f);
|
||||||
|
@ -517,22 +407,57 @@ struct dsp_filter make_adsr(unsigned int atk, unsigned int dec, unsigned int sus
|
||||||
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_dsp(adsr, dsp_adsr_fillbuf);
|
return make_node(adsr, dsp_adsr_fillbuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct dsp_filter make_reverb()
|
void filter_noise_gate(float *floor, soundbyte *out, int frames)
|
||||||
{
|
{
|
||||||
|
for (int i = 0; i < frames*CHANNELS; i++) out[i] = fabsf(out[i]) < *floor ? 0.0 : out[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
void dsp_reverb_fillbuf(struct dsp_reverb *r, soundbyte *out, int n)
|
dsp_node *dsp_noise_gate(float floor)
|
||||||
{
|
{
|
||||||
|
float *v = malloc(sizeof(float));
|
||||||
|
*v = floor;
|
||||||
|
return make_node(v, filter_noise_gate);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct dsp_filter dsp_make_compressor()
|
void filter_limiter(float *ceil, soundbyte *out, int n)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < n*CHANNELS; i++) out[i] = fabsf(out[i]) > *ceil ? *ceil : out[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
dsp_node *dsp_limiter(float ceil)
|
||||||
|
{
|
||||||
|
float *v = malloc(sizeof(float));
|
||||||
|
*v = ceil;
|
||||||
|
return make_node(v, filter_limiter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dsp_compressor_fillbuf(struct dsp_compressor *comp, soundbyte *out, int n)
|
||||||
|
{
|
||||||
|
float val;
|
||||||
|
float db;
|
||||||
|
db = comp->target * (val - comp->threshold) / comp->ratio;
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
val = float2db(out[i*CHANNELS]);
|
||||||
|
|
||||||
|
if (val < comp->threshold) {
|
||||||
|
comp->target = comp->rls_tau * comp->target;
|
||||||
|
val += db;
|
||||||
|
} else {
|
||||||
|
comp->target = (1 - comp->atk_tau) + comp->atk_tau * comp->target; // TODO: Bake in the 1 - atk_tau
|
||||||
|
val -= db;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply same compression to both channels
|
||||||
|
out[i*CHANNELS] = out[i*CHANNELS+1] = db2float(val) * ( out[i*CHANNELS] > 0 ? 1 : -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dsp_node *dsp_compressor()
|
||||||
{
|
{
|
||||||
struct dsp_filter filter;
|
|
||||||
struct dsp_compressor new;
|
struct dsp_compressor new;
|
||||||
|
|
||||||
new.ratio = 4000;
|
new.ratio = 4000;
|
||||||
|
@ -545,73 +470,37 @@ struct dsp_filter dsp_make_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);
|
||||||
filter.data = c;
|
|
||||||
filter.filter = dsp_compressor_fillbuf;
|
|
||||||
|
|
||||||
return filter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void dsp_compressor_fillbuf(struct dsp_compressor *comp, soundbyte *out, int n)
|
/* Assumes 2 channels in a frame */
|
||||||
|
void pan_frames(soundbyte *out, float deg, int frames)
|
||||||
{
|
{
|
||||||
float val;
|
if (deg == 0.f) return;
|
||||||
float db;
|
if (deg < -100) deg = -100.f;
|
||||||
db = comp->target * (val - comp->threshold) / comp->ratio;
|
else if (deg > 100) deg = 100.f;
|
||||||
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
val = short2db(out[i*CHANNELS]);
|
|
||||||
|
|
||||||
|
|
||||||
if (val < comp->threshold) {
|
|
||||||
comp->target = comp->rls_tau * comp->target;
|
|
||||||
|
|
||||||
val += db;
|
|
||||||
} else {
|
|
||||||
comp->target = (1 - comp->atk_tau) + comp->atk_tau * comp->target; // TODO: Bake in the 1 - atk_tau
|
|
||||||
|
|
||||||
val -= db;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Apply same compression to both channels
|
|
||||||
out[i*CHANNELS] = out[i*CHANNELS+1] = db2short(val) * ( out[i*CHANNELS] > 0 ? 1 : -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void dsp_pan(float *deg, soundbyte *out, int n)
|
|
||||||
{
|
|
||||||
if (*deg < -100) *deg = -100.f;
|
|
||||||
else if (*deg > 100) *deg = 100.f;
|
|
||||||
|
|
||||||
if (*deg == 0.f) return;
|
|
||||||
|
|
||||||
float db1, db2;
|
float db1, db2;
|
||||||
float pct = *deg / 100.f;
|
float pct = deg / 100.f;
|
||||||
|
|
||||||
if (*deg > 0) {
|
if (deg > 0) {
|
||||||
db1 = pct2db(1 - pct);
|
db1 = pct2db(1 - pct);
|
||||||
db2 = pct2db(pct);
|
db2 = pct2db(pct);
|
||||||
|
for (int i = 0; i < frames; i++) {
|
||||||
|
soundbyte L = out[i*2];
|
||||||
|
soundbyte R = out[i*2+1];
|
||||||
|
out[i*2] = fgain(L, db1);
|
||||||
|
out[i*2+1] = (R + fgain(L, db2))/2;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
db1 = pct2db(1 + pct);
|
db1 = pct2db(1 + pct);
|
||||||
db2 = pct2db(-1*pct);
|
db2 = pct2db(-1*pct);
|
||||||
|
for (int i = 0; i < frames; i++) {
|
||||||
|
soundbyte L = out[i*2];
|
||||||
|
soundbyte R = out[i*2+1];
|
||||||
|
out[i*2+1] = fgain(R,db1);
|
||||||
|
out[i*2] = fgain(L, db1) + fgain(R, db2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
double pct = *deg / 100.f;
|
|
||||||
soundbyte L = out[i*CHANNELS];
|
|
||||||
soundbyte R = out[i*CHANNELS +1];
|
|
||||||
|
|
||||||
if (*deg > 0) {
|
|
||||||
out[i*CHANNELS] = short_gain(L, db1);
|
|
||||||
out[i*CHANNELS+1] = (R + short_gain(L, db2)) / 2;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
out[i*CHANNELS+1] = short_gain(R, db1);
|
|
||||||
out[i*CHANNELS] = short_gain(L, db1) + short_gain(R, db2);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -625,12 +514,35 @@ void dsp_mono(void *p, soundbyte *out, int n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void dsp_bitcrush(void *p, soundbyte *out, int n)
|
struct bitcrush {
|
||||||
|
float sr;
|
||||||
|
float depth;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define ROUND(f) ((float)((f>0.0)?floor(f+0.5):ceil(f-0.5)))
|
||||||
|
void filter_bitcrush(struct bitcrush *b, soundbyte *out, int frames)
|
||||||
{
|
{
|
||||||
|
int max = pow(2,b->depth) - 1;
|
||||||
|
int step = SAMPLERATE/b->sr;
|
||||||
|
|
||||||
// for (int i = 0; i < n; i++) {
|
int i = 0;
|
||||||
// for (int j = 0; j < CHANNELS; j++)
|
while (i < frames) {
|
||||||
// out[i*CHANNELS+j] = (out[i*CHANNELS+j] | 0xFF); /* Mask out the lower 8 bits */
|
float left = ROUND((out[0]+1.0)*max)/(max-1.0);
|
||||||
// }
|
float right = ROUND((out[1]+1.0)*max)/(max-1.0);
|
||||||
|
|
||||||
|
for (int j = 0; j < step && i < frames; j++) {
|
||||||
|
out[0] = left;
|
||||||
|
out[1] = right;
|
||||||
|
out += CHANNELS;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dsp_node *dsp_bitcrush(float sr, float res)
|
||||||
|
{
|
||||||
|
struct bitcrush *b = malloc(sizeof(*b));
|
||||||
|
b->sr = sr;
|
||||||
|
b->depth = res;
|
||||||
|
return make_node(b, filter_bitcrush);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,47 +6,46 @@
|
||||||
#define CHANNELS 2
|
#define CHANNELS 2
|
||||||
|
|
||||||
#include "sound.h"
|
#include "sound.h"
|
||||||
|
#include "cbuf.h"
|
||||||
|
#include "script.h"
|
||||||
|
|
||||||
//#include "circbuf.h"
|
/* a DSP node, when processed, sums its inputs, and stores the result of proc in its cache */
|
||||||
|
typedef struct dsp_node {
|
||||||
|
void (*proc)(void *dsp, soundbyte *buf, int samples); /* processor */
|
||||||
|
void *data; /* Node specific data to use in the proc function, passed in as dsp */
|
||||||
|
void (*data_free)(void *data);
|
||||||
|
soundbyte cache[BUF_FRAMES*CHANNELS]; /* Cached process */
|
||||||
|
struct dsp_node **ins; /* Array of in nodes */
|
||||||
|
struct dsp_node *out; /* node this one is connected to */
|
||||||
|
int pass; /* True if the filter should be bypassed */
|
||||||
|
int off; /* True if the filter shouldn't output */
|
||||||
|
float gain; /* Between 0 and 1, to attenuate this output */
|
||||||
|
float pan; /* Between -100 and +100, panning left to right in the speakers */
|
||||||
|
} dsp_node;
|
||||||
|
|
||||||
struct dsp_iir;
|
void dsp_init();
|
||||||
|
|
||||||
|
/* Get the output of a node */
|
||||||
|
soundbyte *dsp_node_out(dsp_node *node);
|
||||||
|
void dsp_node_run(dsp_node *node);
|
||||||
|
dsp_node *make_node(void *data, void (*proc)(void *data, soundbyte *out, int samples));
|
||||||
|
void plugin_node(dsp_node *from, dsp_node *to);
|
||||||
|
void unplug_node(dsp_node *node);
|
||||||
|
void node_free(dsp_node *node);
|
||||||
|
|
||||||
void dsp_rectify(soundbyte *in, soundbyte *out, int n);
|
void scale_soundbytes(soundbyte *a, float scale, int frames);
|
||||||
|
void sum_soundbytes(soundbyte *a, soundbyte *b, int frames);
|
||||||
|
void zero_soundbytes(soundbyte *a, int frames);
|
||||||
|
void set_soundbytes(soundbyte *a, soundbyte *b, int frames);
|
||||||
|
|
||||||
struct dsp_filter {
|
dsp_node *dsp_mixer_node();
|
||||||
void (*filter)(void *data, soundbyte *out, int samples);
|
dsp_node *dsp_am_mod(dsp_node *mod);
|
||||||
void *data;
|
dsp_node *dsp_rectify();
|
||||||
|
|
||||||
int inputs;
|
extern dsp_node *masterbus;
|
||||||
struct dsp_filter *in[6];
|
|
||||||
struct bus *bus;
|
|
||||||
|
|
||||||
soundbyte cache[CHANNELS*BUF_FRAMES];
|
dsp_node *dsp_hpf(float freq);
|
||||||
int dirty;
|
dsp_node *dsp_lpf(float freq);
|
||||||
};
|
|
||||||
|
|
||||||
struct dsp_filter dsp_filter(void *data, void (*filter)(void *data, soundbyte *out, int samples));
|
|
||||||
|
|
||||||
struct dsp_fir {
|
|
||||||
float freq;
|
|
||||||
int n;
|
|
||||||
int head;
|
|
||||||
float *cof;
|
|
||||||
float *dx;
|
|
||||||
|
|
||||||
struct dsp_filter in;
|
|
||||||
};
|
|
||||||
|
|
||||||
void dsp_filter_addin(struct dsp_filter filter, struct dsp_filter *in);
|
|
||||||
|
|
||||||
struct dsp_filter lp_fir_make(float freq);
|
|
||||||
|
|
||||||
void dsp_iir_fillbuf(struct dsp_iir *iir, soundbyte *out, int n);
|
|
||||||
struct dsp_filter hpf_make(int poles, float freq);
|
|
||||||
struct dsp_filter lpf_make(int poles, float freq);
|
|
||||||
struct dsp_filter bpf_make(int poles, float freq1, float freq2);
|
|
||||||
struct dsp_filter npf_make(int poles, float freq1, float freq2);
|
|
||||||
|
|
||||||
/* atk, dec, sus, rls specify the time, in miliseconds, the phase begins */
|
/* atk, dec, sus, rls specify the time, in miliseconds, the phase begins */
|
||||||
struct dsp_adsr {
|
struct dsp_adsr {
|
||||||
|
@ -63,24 +62,15 @@ struct dsp_adsr {
|
||||||
float out;
|
float out;
|
||||||
};
|
};
|
||||||
|
|
||||||
void dsp_adsr_fillbuf(struct dsp_adsr *adsr, soundbyte *out, int n);
|
dsp_node *dsp_adsr(unsigned int atk, unsigned int dec, unsigned int sus, unsigned int rls);
|
||||||
struct dsp_filter make_adsr(unsigned int atk, unsigned int dec, unsigned int sus, unsigned int rls);
|
|
||||||
|
|
||||||
struct dsp_delay {
|
typedef struct {
|
||||||
unsigned int ms_delay;
|
unsigned int ms_delay;
|
||||||
// struct circbuf buf;
|
float decay; /* Each echo should be multiplied by this number */
|
||||||
struct dsp_filter in;
|
soundbyte *ring;
|
||||||
};
|
} delay;
|
||||||
|
|
||||||
struct dsp_delay dsp_delay_make(unsigned int ms_delay);
|
dsp_node *dsp_delay(double sec, double decay);
|
||||||
void dsp_delay_filbuf(struct dsp_delay *delay, soundbyte *buf, int n);
|
|
||||||
|
|
||||||
struct dsp_ammod {
|
|
||||||
struct dsp_filter ina;
|
|
||||||
struct dsp_filter inb;
|
|
||||||
soundbyte abuf[BUF_FRAMES*CHANNELS];
|
|
||||||
soundbyte bbuf[BUF_FRAMES*CHANNELS];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct dsp_compressor {
|
struct dsp_compressor {
|
||||||
double ratio;
|
double ratio;
|
||||||
|
@ -92,69 +82,26 @@ struct dsp_compressor {
|
||||||
double rls_tau;
|
double rls_tau;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct dsp_filter dsp_make_compressor();
|
dsp_node *dsp_compressor();
|
||||||
void dsp_compressor_fillbuf(struct dsp_compressor *comp, soundbyte *out, int n);
|
|
||||||
|
|
||||||
struct dsp_limiter {
|
dsp_node *dsp_limiter(float ceil);
|
||||||
|
dsp_node *dsp_noise_gate(float floor);
|
||||||
};
|
|
||||||
|
|
||||||
struct dsp_filter dsp_make_limiter();
|
|
||||||
void dsp_limiter_fillbuf(struct dsp_limiter *lim, soundbyte *out, int n);
|
|
||||||
|
|
||||||
|
|
||||||
struct phasor {
|
|
||||||
unsigned int sr;
|
|
||||||
float cur;
|
|
||||||
float freq;
|
|
||||||
unsigned int clen;
|
|
||||||
unsigned int cstep;
|
|
||||||
float *cache;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct phasor phasor_make(unsigned int sr, float freq);
|
struct phasor phasor_make(unsigned int sr, float freq);
|
||||||
|
|
||||||
struct osc {
|
dsp_node *dsp_whitenoise();
|
||||||
float (*f)(float p);
|
dsp_node *dsp_pinknoise();
|
||||||
struct phasor p;
|
|
||||||
unsigned int frames;
|
|
||||||
unsigned int frame;
|
|
||||||
soundbyte *cache;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct wav;
|
|
||||||
|
|
||||||
struct wav gen_sine(float amp, float freq, int sr, int ch);
|
|
||||||
struct wav gen_square(float amp, float freq, int sr, int ch);
|
|
||||||
struct wav gen_triangle(float amp, float freq, int sr, int ch);
|
|
||||||
struct wav gen_saw(float amp, float freq, int sr, int ch);
|
|
||||||
|
|
||||||
void gen_whitenoise(void *data, soundbyte *out, int n);
|
|
||||||
void gen_pinknoise(void *data, soundbyte *out, int n);
|
|
||||||
|
|
||||||
|
|
||||||
float sin_phasor(float p);
|
float sin_phasor(float p);
|
||||||
float square_phasor(float p);
|
float square_phasor(float p);
|
||||||
float saw_phasor(float p);
|
float saw_phasor(float p);
|
||||||
float tri_phasor(float p);
|
float tri_phasor(float p);
|
||||||
|
|
||||||
void osc_fillbuf(struct osc *osc, soundbyte *buf, int n);
|
dsp_node *dsp_reverb();
|
||||||
|
dsp_node *dsp_sinewave(float amp, float freq);
|
||||||
void am_mod(struct dsp_ammod *mod, soundbyte *c, int n);
|
dsp_node *dsp_square(float amp, float freq, int sr, int ch);
|
||||||
|
dsp_node *dsp_bitcrush(float sr, float res);
|
||||||
struct dsp_reverb {
|
|
||||||
unsigned int time; /* Time in miliseconds for the sound to decay out */
|
|
||||||
};
|
|
||||||
|
|
||||||
struct dsp_filter make_reverb();
|
|
||||||
void dsp_reverb_fillbuf(struct dsp_reverb *r, soundbyte *out, int n);
|
|
||||||
|
|
||||||
void dsp_pan(float *deg, soundbyte *out, int n);
|
|
||||||
|
|
||||||
void dsp_mono(void *p, soundbyte *out, int n);
|
void dsp_mono(void *p, soundbyte *out, int n);
|
||||||
|
void pan_frames(soundbyte *out, float deg, int frames);
|
||||||
void dsp_bitcrush(void *p, soundbyte *out, int n);
|
|
||||||
|
|
||||||
void dsp_run(struct dsp_filter filter, soundbyte *out, int n);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -550,7 +550,6 @@ double sf_bwbp( int n, double f1f, double f2f )
|
||||||
sf_bwbs - calculates the scaling factor for a butterworth bandstop filter.
|
sf_bwbs - calculates the scaling factor for a butterworth bandstop filter.
|
||||||
The scaling factor is what the c coefficients must be multiplied by so
|
The scaling factor is what the c coefficients must be multiplied by so
|
||||||
that the filter response has a maximum value of 1.
|
that the filter response has a maximum value of 1.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
double sf_bwbs( int n, double f1f, double f2f )
|
double sf_bwbs( int n, double f1f, double f2f )
|
||||||
|
@ -582,9 +581,6 @@ double sf_bwbs( int n, double f1f, double f2f )
|
||||||
return( 1.0 / sfr );
|
return( 1.0 / sfr );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
float *fir_lp(int n, double fcf)
|
float *fir_lp(int n, double fcf)
|
||||||
{
|
{
|
||||||
float *ret = malloc(n * sizeof(*ret));
|
float *ret = malloc(n * sizeof(*ret));
|
||||||
|
@ -640,43 +636,32 @@ float *fir_bpf(int n, double fcf1, double fcf2)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Biquad filters */
|
/* Biquad filters */
|
||||||
|
struct dsp_iir make_iir(int order)
|
||||||
struct dsp_iir make_iir(int cofs, int order)
|
|
||||||
{
|
{
|
||||||
struct dsp_iir new;
|
struct dsp_iir new;
|
||||||
new.n = cofs;
|
new.n = order+1;
|
||||||
new.order = order;
|
new.a = calloc(sizeof(float), new.n);
|
||||||
|
new.b = calloc(sizeof(float), new.n);
|
||||||
new.dcof = calloc(sizeof(float), cofs *order);
|
new.x = calloc(sizeof(float), new.n);
|
||||||
new.ccof = calloc(sizeof(float), cofs *order);
|
new.y = calloc(sizeof(float), new.n);
|
||||||
new.dx = calloc(sizeof(float), cofs *order);
|
|
||||||
new.dy = calloc(sizeof(float), cofs *order);
|
|
||||||
|
|
||||||
return new;
|
return new;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct dsp_iir biquad_iir()
|
struct dsp_iir biquad_iir()
|
||||||
{
|
{
|
||||||
return make_iir(3, 1);
|
return make_iir(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void biquad_iir_fill(struct dsp_iir bq, double *a, double *b)
|
void biquad_iir_fill(struct dsp_iir bq, double *a, double *b)
|
||||||
{
|
{
|
||||||
bq.ccof[0] = (b[0] / a[0]);
|
bq.a[0] = (b[0] / a[0]);
|
||||||
bq.ccof[1] = (b[1] / a[0]);
|
bq.a[1] = (b[1] / a[0]);
|
||||||
bq.ccof[2] = (b[2] / a[0]);
|
bq.a[2] = (b[2] / a[0]);
|
||||||
bq.dcof[0] = 0.f;
|
bq.b[0] = 0.f;
|
||||||
bq.dcof[1] = (a[1] / a[0]);
|
bq.b[1] = (a[1] / a[0]);
|
||||||
bq.dcof[2] = (a[2] / a[0]);
|
bq.b[2] = (a[2] / a[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct dsp_iir bqlp_dcof(double fcf, float Q)
|
struct dsp_iir bqlp_dcof(double fcf, float Q)
|
||||||
|
@ -687,7 +672,7 @@ struct dsp_iir bqlp_dcof(double fcf, float Q)
|
||||||
double b[3];
|
double b[3];
|
||||||
double az = sin(w0) / (2 * Q);
|
double az = sin(w0) / (2 * Q);
|
||||||
|
|
||||||
b[0] = (1 - cos(w0)) / 2;
|
b[0] = (1 - cos(w0)) / 2.0;
|
||||||
b[1] = 1 - cos(w0);
|
b[1] = 1 - cos(w0);
|
||||||
b[2] = b[0];
|
b[2] = b[0];
|
||||||
|
|
||||||
|
@ -841,10 +826,23 @@ struct dsp_iir bqhs_dcof(double fcf, float Q, float dbgain)
|
||||||
return new;
|
return new;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Bipole Butterworth, Critically damped, and Bessel */
|
/* Bipole Butterworth, Critically damped, and Bessel */
|
||||||
/* https://unicorn.us.com/trading/allpolefilters.html */
|
/* https://unicorn.us.com/trading/allpolefilters.html */
|
||||||
|
/*
|
||||||
|
struct p2_iir {
|
||||||
|
int order;
|
||||||
|
int n;
|
||||||
|
float *a;
|
||||||
|
float *b;
|
||||||
|
float *x;
|
||||||
|
float *y;
|
||||||
|
}
|
||||||
|
soundbyte p2_calc(struct p2_iir iir, soundbyte val)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < iir.order; i++) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void p2_ccalc(double fcf, double p, double g, double *a, double *b)
|
void p2_ccalc(double fcf, double p, double g, double *a, double *b)
|
||||||
{
|
{
|
||||||
|
@ -867,7 +865,7 @@ struct dsp_iir p2_bwlp(double fcf)
|
||||||
double g = 1;
|
double g = 1;
|
||||||
|
|
||||||
struct dsp_iir new = biquad_iir();
|
struct dsp_iir new = biquad_iir();
|
||||||
p2_ccalc(fcf, p, g, new.ccof, new.dcof);
|
p2_ccalc(fcf, p, g, new.a, new.b);
|
||||||
|
|
||||||
return new;
|
return new;
|
||||||
}
|
}
|
||||||
|
@ -875,8 +873,8 @@ struct dsp_iir p2_bwlp(double fcf)
|
||||||
struct dsp_iir p2_bwhp(double fcf)
|
struct dsp_iir p2_bwhp(double fcf)
|
||||||
{
|
{
|
||||||
struct dsp_iir new = p2_bwlp(fcf);
|
struct dsp_iir new = p2_bwlp(fcf);
|
||||||
new.ccof[1] *= -1;
|
new.a[1] *= -1;
|
||||||
new.dcof[1] *= -1;
|
new.b[1] *= -1;
|
||||||
|
|
||||||
return new;
|
return new;
|
||||||
}
|
}
|
||||||
|
@ -887,7 +885,7 @@ struct dsp_iir p2_cdlp(double fcf)
|
||||||
double p = 2;
|
double p = 2;
|
||||||
|
|
||||||
struct dsp_iir new = biquad_iir();
|
struct dsp_iir new = biquad_iir();
|
||||||
p2_ccalc(fcf, p, g, new.ccof, new.dcof);
|
p2_ccalc(fcf, p, g, new.a, new.b);
|
||||||
|
|
||||||
return new;
|
return new;
|
||||||
}
|
}
|
||||||
|
@ -895,8 +893,8 @@ struct dsp_iir p2_cdlp(double fcf)
|
||||||
struct dsp_iir p2_cdhp(double fcf)
|
struct dsp_iir p2_cdhp(double fcf)
|
||||||
{
|
{
|
||||||
struct dsp_iir new = p2_cdlp(fcf);
|
struct dsp_iir new = p2_cdlp(fcf);
|
||||||
new.ccof[1] *= -1;
|
new.a[1] *= -1;
|
||||||
new.dcof[1] *= -1;
|
new.b[1] *= -1;
|
||||||
|
|
||||||
return new;
|
return new;
|
||||||
}
|
}
|
||||||
|
@ -907,7 +905,7 @@ struct dsp_iir p2_beslp(double fcf)
|
||||||
double p = 3;
|
double p = 3;
|
||||||
|
|
||||||
struct dsp_iir new = biquad_iir();
|
struct dsp_iir new = biquad_iir();
|
||||||
p2_ccalc(fcf, p, g, new.ccof, new.dcof);
|
p2_ccalc(fcf, p, g, new.a, new.b);
|
||||||
|
|
||||||
return new;
|
return new;
|
||||||
}
|
}
|
||||||
|
@ -915,56 +913,12 @@ struct dsp_iir p2_beslp(double fcf)
|
||||||
struct dsp_iir p2_beshp(double fcf)
|
struct dsp_iir p2_beshp(double fcf)
|
||||||
{
|
{
|
||||||
struct dsp_iir new = p2_beslp(fcf);
|
struct dsp_iir new = p2_beslp(fcf);
|
||||||
new.ccof[1] *= -1;
|
new.a[1] *= -1;
|
||||||
new.dcof[1] *= -1;
|
new.b[1] *= -1;
|
||||||
|
|
||||||
return new;
|
return new;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct dsp_iir p2_iir_order(int order)
|
|
||||||
{
|
|
||||||
struct dsp_iir new;
|
|
||||||
new.n = 3;
|
|
||||||
new.order = order;
|
|
||||||
|
|
||||||
new.ccof = calloc(sizeof(float), 3 * order);
|
|
||||||
new.dcof = calloc(sizeof(float), 3 * order);
|
|
||||||
new.dx = calloc(sizeof(float), 3 * order);
|
|
||||||
new.dy = calloc(sizeof(float), 3 * order);
|
|
||||||
|
|
||||||
return new;
|
|
||||||
}
|
|
||||||
|
|
||||||
short p2_filter(struct dsp_iir iir, short val)
|
|
||||||
{
|
|
||||||
float a = (float)val/SHRT_MAX;
|
|
||||||
|
|
||||||
for (int i = 0; i < iir.order; i++) {
|
|
||||||
int indx = i * iir.n;
|
|
||||||
|
|
||||||
iir.dx[indx] = a;
|
|
||||||
|
|
||||||
a = 0.f;
|
|
||||||
|
|
||||||
for (int j = 0; j < iir.n; j++)
|
|
||||||
a += iir.ccof[indx + j] * iir.dx[indx];
|
|
||||||
|
|
||||||
for (int j = iir.n-1; j > 0; j--)
|
|
||||||
iir.dx[indx] = iir.dx[indx-1];
|
|
||||||
|
|
||||||
for (int j = 0; j < iir.n; j++)
|
|
||||||
a -= iir.dcof[indx+j] * iir.dy[indx];
|
|
||||||
|
|
||||||
iir.dy[indx] = a;
|
|
||||||
|
|
||||||
for (int j = iir.n-1; j > 0; j--)
|
|
||||||
iir.dy[indx] = iir.dy[indx-1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return a * SHRT_MAX;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct dsp_iir che_lp(int order, double fcf, double e)
|
struct dsp_iir che_lp(int order, double fcf, double e)
|
||||||
{
|
{
|
||||||
struct dsp_iir new = p2_iir_order(order);
|
struct dsp_iir new = p2_iir_order(order);
|
||||||
|
@ -986,13 +940,13 @@ struct dsp_iir che_lp(int order, double fcf, double e)
|
||||||
s = a2*c + 2.f*a*b + 1.f;
|
s = a2*c + 2.f*a*b + 1.f;
|
||||||
double A = a2/(4.f);
|
double A = a2/(4.f);
|
||||||
|
|
||||||
new.ccof[0*i] = ep * 1.f/A;
|
new.a[0*i] = ep * 1.f/A;
|
||||||
new.ccof[1*i] = ep * -2.f/A;
|
new.a[1*i] = ep * -2.f/A;
|
||||||
new.ccof[2*i] = ep * 1.f/A;
|
new.a[2*i] = ep * 1.f/A;
|
||||||
|
|
||||||
new.dcof[0*i] = ep * 0.f;
|
new.b[0*i] = ep * 0.f;
|
||||||
new.dcof[1*i] = ep * 2.f*(1-a2*c);
|
new.b[1*i] = ep * 2.f*(1-a2*c);
|
||||||
new.dcof[2*i] = ep * -(a2*c - 2.f*a*b + 1.f);
|
new.b[2*i] = ep * -(a2*c - 2.f*a*b + 1.f);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new;
|
return new;
|
||||||
|
@ -1018,9 +972,9 @@ struct dsp_iir che_hp(int order, double fcf, double e)
|
||||||
s = a2*c + 2.f*a*b + 1.f;
|
s = a2*c + 2.f*a*b + 1.f;
|
||||||
double A = 1.f/(4.f);
|
double A = 1.f/(4.f);
|
||||||
|
|
||||||
new.ccof[0*i] = ep * 1.f/A;
|
new.a[0*i] = ep * 1.f/A;
|
||||||
new.ccof[1*i] = ep * -2.f/A;
|
new.a[1*i] = ep * -2.f/A;
|
||||||
new.ccof[2*i] = ep * 1.f/A;
|
new.a[2*i] = ep * 1.f/A;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1037,7 +991,7 @@ struct dsp_iir che_bp(int order, double s, double fcf1, double fcf2, double e)
|
||||||
double ep = 2.f/e;
|
double ep = 2.f/e;
|
||||||
|
|
||||||
int n = order / 4;
|
int n = order / 4;
|
||||||
struct dsp_iir new = p2_iir_order(order);
|
struct dsp_iir new = biquad_iir();
|
||||||
|
|
||||||
double a = cos(M_PI*(fcf1+fcf2)/2) / cos(M_PI*(fcf2-fcf1)/s);
|
double a = cos(M_PI*(fcf1+fcf2)/2) / cos(M_PI*(fcf2-fcf1)/s);
|
||||||
double a2 = pow(a, 2);
|
double a2 = pow(a, 2);
|
||||||
|
@ -1055,15 +1009,15 @@ struct dsp_iir che_bp(int order, double s, double fcf1, double fcf2, double e)
|
||||||
c = pow(r, 2) + pow(c, 2);
|
c = pow(r, 2) + pow(c, 2);
|
||||||
s = b2*c + 2.f*b*r + 1.f;
|
s = b2*c + 2.f*b*r + 1.f;
|
||||||
|
|
||||||
new.ccof[0*i] = ep * 1.f/A;
|
new.a[0*i] = ep * 1.f/A;
|
||||||
new.ccof[1*i] = ep * -2.f/A;
|
new.a[1*i] = ep * -2.f/A;
|
||||||
new.ccof[2*i] = ep * 1.f/A;
|
new.a[2*i] = ep * 1.f/A;
|
||||||
|
|
||||||
new.dcof[0*i] = 0.f;
|
new.b[0*i] = 0.f;
|
||||||
new.dcof[1*i] = ep * 4.f*a*(1.f+b*r)/s;
|
new.b[1*i] = ep * 4.f*a*(1.f+b*r)/s;
|
||||||
new.dcof[2*i] = ep * 2.f*(b2*c-2.f*a2-1.f)/s;
|
new.b[2*i] = ep * 2.f*(b2*c-2.f*a2-1.f)/s;
|
||||||
new.dcof[3*i] = ep * 4.f*a*(1.f-b*r)/s;
|
new.b[3*i] = ep * 4.f*a*(1.f-b*r)/s;
|
||||||
new.dcof[4*i] = ep * -(b2*c - 2.f*b*r + 1.f) / s;
|
new.b[4*i] = ep * -(b2*c - 2.f*b*r + 1.f) / s;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new;
|
return new;
|
||||||
|
@ -1096,16 +1050,17 @@ double ep = 2.f/e;
|
||||||
c = pow(r, 2) + pow(c, 2);
|
c = pow(r, 2) + pow(c, 2);
|
||||||
s = b2*c + 2.f*b*r + 1.f;
|
s = b2*c + 2.f*b*r + 1.f;
|
||||||
|
|
||||||
new.ccof[0*i] = ep * 1.f/A;
|
new.a[0*i] = ep * 1.f/A;
|
||||||
new.ccof[1*i] = ep * -2.f/A;
|
new.a[1*i] = ep * -2.f/A;
|
||||||
new.ccof[2*i] = ep * 1.f/A;
|
new.a[2*i] = ep * 1.f/A;
|
||||||
|
|
||||||
new.dcof[0*i] = 0.f;
|
new.b[0*i] = 0.f;
|
||||||
new.dcof[1*i] = ep * 4.f*a*(c+b*r)/s;
|
new.b[1*i] = ep * 4.f*a*(c+b*r)/s;
|
||||||
new.dcof[2*i] = ep * 2.f*(b2-2.f*a2*c-c)/s;
|
new.b[2*i] = ep * 2.f*(b2-2.f*a2*c-c)/s;
|
||||||
new.dcof[3*i] = ep * 4.f*a*(c-b*r)/s;
|
new.b[3*i] = ep * 4.f*a*(c-b*r)/s;
|
||||||
new.dcof[4*i] = ep * -(b2 - 2.f*b*r + c) / s;
|
new.b[4*i] = ep * -(b2 - 2.f*b*r + c) / s;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new;
|
return new;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
|
@ -1,21 +1,17 @@
|
||||||
#ifndef IIR_H
|
#ifndef IIR_H
|
||||||
#define IIR_H
|
#define IIR_H
|
||||||
|
|
||||||
#include "dsp.h"
|
#include "sound.h"
|
||||||
|
|
||||||
struct dsp_iir {
|
struct dsp_iir {
|
||||||
float freq;
|
|
||||||
int n; // Amount of constants
|
int n; // Amount of constants
|
||||||
int order; // How many times it's applied
|
float *a;
|
||||||
float *ccof;
|
float *b;
|
||||||
float *dcof;
|
float *x;
|
||||||
float *dx;
|
float *y;
|
||||||
float *dy;
|
|
||||||
|
|
||||||
struct dsp_filter in;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct dsp_iir make_iir(int cofs, int order);
|
struct dsp_iir make_iir(int order);
|
||||||
|
|
||||||
double *binomial_mult( int n, double *p );
|
double *binomial_mult( int n, double *p );
|
||||||
double *trinomial_mult( int n, double *b, double *c );
|
double *trinomial_mult( int n, double *b, double *c );
|
||||||
|
|
|
@ -1,117 +0,0 @@
|
||||||
#include "mix.h"
|
|
||||||
#include "stddef.h"
|
|
||||||
#include "time.h"
|
|
||||||
#include "sound.h"
|
|
||||||
#include "dsp.h"
|
|
||||||
#include <string.h>
|
|
||||||
#include "log.h"
|
|
||||||
#include "stdlib.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#define BUS_N 256
|
|
||||||
static struct bus *bus;
|
|
||||||
static int first = 0; /* First bus available */
|
|
||||||
|
|
||||||
static int first_on = -1; /* First bus to fill buffer with */
|
|
||||||
soundbyte *mastermix = NULL;
|
|
||||||
|
|
||||||
static float master_volume = 1.f;
|
|
||||||
|
|
||||||
void mix_master_vol(float v) {
|
|
||||||
if (v < 0.f) v = 0.f;
|
|
||||||
if (v > 100.f) v = 100.f;
|
|
||||||
master_volume = v / 100.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
void mixer_init() {
|
|
||||||
bus = malloc(sizeof(struct bus)*BUS_N);
|
|
||||||
mastermix = malloc(BUF_FRAMES*CHANNELS);
|
|
||||||
for (int i = 0; i < BUS_N; i++) {
|
|
||||||
bus[i].next = i+1;
|
|
||||||
bus[i].on = 0;
|
|
||||||
bus[i].id = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
bus[BUS_N-1].next = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void filter_to_bus(struct dsp_filter *f)
|
|
||||||
{
|
|
||||||
struct bus *b = first_free_bus(*f);
|
|
||||||
if (b)
|
|
||||||
f->bus = b;
|
|
||||||
}
|
|
||||||
|
|
||||||
void unplug_filter(struct dsp_filter *f)
|
|
||||||
{
|
|
||||||
if (!f->bus) return;
|
|
||||||
|
|
||||||
bus_free(f->bus);
|
|
||||||
f->bus = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct bus *first_free_bus(struct dsp_filter in) {
|
|
||||||
for (int i = 0; i < 255; i++)
|
|
||||||
if (!bus[i].on) {
|
|
||||||
bus[i].on = 1;
|
|
||||||
bus[i].in = in;
|
|
||||||
return &bus[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
if (first == -1) return NULL;
|
|
||||||
int ret = first;
|
|
||||||
first = bus[ret].next;
|
|
||||||
|
|
||||||
bus[ret].on = 1;
|
|
||||||
bus[ret].in = in;
|
|
||||||
|
|
||||||
if (first_on != -1) bus[first_on].prev = ret;
|
|
||||||
|
|
||||||
bus[ret].next = first_on;
|
|
||||||
bus[ret].prev = -1;
|
|
||||||
first_on = ret;
|
|
||||||
|
|
||||||
return &bus[ret];
|
|
||||||
}
|
|
||||||
|
|
||||||
void bus_free(struct bus *b)
|
|
||||||
{
|
|
||||||
if (!b) return;
|
|
||||||
b->on = 0;
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (first_on == b->id) first_on = b->next;
|
|
||||||
if (b->next != -1) bus[b->next].prev = b->prev;
|
|
||||||
if (b->prev != -1) bus[b->prev].next = b->next;
|
|
||||||
|
|
||||||
b->next = first;
|
|
||||||
first = b->id;
|
|
||||||
b->on = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void bus_fill_buffers(soundbyte *master, int n) {
|
|
||||||
int curbus = first_on;
|
|
||||||
// if (curbus == -1) return;
|
|
||||||
memset(master, 0, BUF_FRAMES*CHANNELS*sizeof(soundbyte));
|
|
||||||
|
|
||||||
for (int i = 0; i < 255; i++) {
|
|
||||||
if (!bus[i].on) continue;
|
|
||||||
dsp_run(bus[i].in, bus[i].buf, BUF_FRAMES);
|
|
||||||
for (int j = 0; j < BUF_FRAMES*CHANNELS; j++)
|
|
||||||
master[j] += bus[i].buf[j] * master_volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
while (curbus != -1) {
|
|
||||||
int nextbus = bus[curbus].next; /* Save this in case busses get changed during fill */
|
|
||||||
dsp_run(bus[curbus].in, bus[curbus].buf, BUF_FRAMES);
|
|
||||||
for (int i = 0; i < BUF_FRAMES*CHANNELS; i++)
|
|
||||||
master[i] += bus[curbus].buf[i] * master_volume;
|
|
||||||
|
|
||||||
curbus = nextbus;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
#ifndef MIX_H
|
|
||||||
#define MIX_H
|
|
||||||
|
|
||||||
#include "dsp.h"
|
|
||||||
#include "sound.h"
|
|
||||||
|
|
||||||
struct bus {
|
|
||||||
struct dsp_filter in;
|
|
||||||
soundbyte buf[BUF_FRAMES*CHANNELS];
|
|
||||||
float gain;
|
|
||||||
int on;
|
|
||||||
int next; /* Next available bus */
|
|
||||||
int prev;
|
|
||||||
int id;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern soundbyte *mastermix;
|
|
||||||
|
|
||||||
void mixer_init();
|
|
||||||
|
|
||||||
struct bus *first_free_bus(struct dsp_filter in);
|
|
||||||
void filter_to_bus(struct dsp_filter *f);
|
|
||||||
void unplug_filter(struct dsp_filter *f);
|
|
||||||
void bus_fill_buffers(soundbyte *master, int n);
|
|
||||||
|
|
||||||
/* Set volume between 0 and 100% */
|
|
||||||
void mix_master_vol(float v);
|
|
||||||
void bus_free(struct bus *bus);
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -3,22 +3,22 @@
|
||||||
#include "dsp.h"
|
#include "dsp.h"
|
||||||
#include "tsf.h"
|
#include "tsf.h"
|
||||||
#include "tml.h"
|
#include "tml.h"
|
||||||
#include "mix.h"
|
|
||||||
#include "sound.h"
|
#include "sound.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "resources.h"
|
#include "resources.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include "stb_ds.h"
|
||||||
|
|
||||||
#define TSF_BLOCK 32
|
#define TSF_BLOCK 32
|
||||||
|
|
||||||
struct dsp_midi_song gsong;
|
static struct {
|
||||||
struct dsp_filter songfil;
|
char *key;
|
||||||
|
tsf **value;
|
||||||
float music_pan = 0.f;
|
} *sf_hash = NULL;
|
||||||
|
|
||||||
void dsp_midi_fillbuf(struct dsp_midi_song *song, void *out, int n)
|
void dsp_midi_fillbuf(struct dsp_midi_song *song, void *out, int n)
|
||||||
{
|
{
|
||||||
soundbyte *o = (soundbyte*)out;
|
soundbyte *o = out;
|
||||||
tml_message *midi = song->midi;
|
tml_message *midi = song->midi;
|
||||||
|
|
||||||
for (int i = 0; i < n; i += TSF_BLOCK) {
|
for (int i = 0; i < n; i += TSF_BLOCK) {
|
||||||
|
@ -55,44 +55,37 @@ void dsp_midi_fillbuf(struct dsp_midi_song *song, void *out, int n)
|
||||||
song->midi = midi;
|
song->midi = midi;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct bus *musicbus;
|
tsf *make_soundfont(const char *path)
|
||||||
|
{
|
||||||
|
int idx = shgeti(sf_hash, path);
|
||||||
|
if (idx != -1) return sf_hash[idx].value;
|
||||||
|
|
||||||
void play_song(const char *midi, const char *sf)
|
long rawlen;
|
||||||
|
void *raw = slurp_file(path, &rawlen);
|
||||||
|
tsf *sf = tsf_load_memory(raw,rawlen);
|
||||||
|
free(raw);
|
||||||
|
|
||||||
|
if (!sf) { YughWarn("Soundfont %s not found.", sf); return NULL; }
|
||||||
|
tsf_set_output(sf, TSF_STEREO_INTERLEAVED, SAMPLERATE, 0.f);
|
||||||
|
// Preset on 10th MIDI channel to use percussion sound bank if possible
|
||||||
|
tsf_channel_set_bank_preset(sf, 0, 128, 0);
|
||||||
|
|
||||||
|
shput(sf_hash, path, sf);
|
||||||
|
return sf;
|
||||||
|
}
|
||||||
|
|
||||||
|
dsp_node *dsp_midi(const char *midi, tsf *sf)
|
||||||
{
|
{
|
||||||
long rawlen;
|
long rawlen;
|
||||||
void *raw = slurp_file(midi, &rawlen);
|
void *raw = slurp_file(midi, &rawlen);
|
||||||
|
struct dsp_midi_song *ms = malloc(sizeof(*ms));
|
||||||
gsong.midi = tml_load_memory(raw, rawlen);
|
ms->time = 0.0;
|
||||||
if (gsong.midi == NULL) {
|
ms->midi = tml_load_memory(raw, rawlen);
|
||||||
YughWarn("Midi %s not found.", midi);
|
ms->sf = tsf_copy(sf);
|
||||||
free(raw);
|
return make_node(ms, dsp_midi_fillbuf);
|
||||||
return;
|
|
||||||
}
|
|
||||||
free(raw);
|
|
||||||
|
|
||||||
raw = slurp_file(sf, &rawlen);
|
|
||||||
gsong.sf = tsf_load_memory(raw, rawlen);
|
|
||||||
|
|
||||||
if (gsong.sf == NULL) {
|
|
||||||
YughWarn("SF2 %s not found.", sf);
|
|
||||||
free(raw);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
free(raw);
|
|
||||||
|
|
||||||
gsong.time = 0.f;
|
|
||||||
|
|
||||||
tsf_set_output(gsong.sf, TSF_STEREO_INTERLEAVED, SAMPLERATE, 0.f);
|
|
||||||
|
|
||||||
// Preset on 10th MIDI channel to use percussion sound bank if possible
|
|
||||||
tsf_channel_set_bank_preset(gsong.sf, 9, 128, 0);
|
|
||||||
|
|
||||||
songfil.data = &gsong;
|
|
||||||
songfil.filter = dsp_midi_fillbuf;
|
|
||||||
filter_to_bus(&songfil);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void music_stop()
|
void play_song(const char *midi, const char *sf)
|
||||||
{
|
{
|
||||||
unplug_filter(&songfil);
|
plugin_node(dsp_midi(midi, make_soundfont(sf)), masterbus);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,18 @@
|
||||||
|
|
||||||
#include "tsf.h"
|
#include "tsf.h"
|
||||||
#include "tml.h"
|
#include "tml.h"
|
||||||
|
#include "dsp.h"
|
||||||
|
|
||||||
struct dsp_midi_song {
|
struct dsp_midi_song {
|
||||||
float bpm;
|
float bpm;
|
||||||
double time;
|
double time;
|
||||||
tsf *sf;
|
|
||||||
tml_message *midi;
|
tml_message *midi;
|
||||||
|
tsf *sf;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dsp_node *dsp_midi(const char *midi, tsf *sf);
|
||||||
|
tsf *make_soundfont(const char *sf);
|
||||||
void play_song(const char *midi, const char *sf);
|
void play_song(const char *midi, const char *sf);
|
||||||
void dsp_midi_fillbuf(struct dsp_midi_song *song, void *out, int n);
|
void dsp_midi_fillbuf(struct dsp_midi_song *song, void *out, int n);
|
||||||
void music_stop();
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -14,7 +14,9 @@
|
||||||
#include "stb_ds.h"
|
#include "stb_ds.h"
|
||||||
|
|
||||||
#include "dsp.h"
|
#include "dsp.h"
|
||||||
#include "mix.h"
|
|
||||||
|
#define POCKETMOD_IMPLEMENTATION
|
||||||
|
#include "pocketmod.h"
|
||||||
|
|
||||||
#include "sokol/sokol_audio.h"
|
#include "sokol/sokol_audio.h"
|
||||||
|
|
||||||
|
@ -53,82 +55,77 @@ static struct {
|
||||||
struct wav *value;
|
struct wav *value;
|
||||||
} *wavhash = NULL;
|
} *wavhash = NULL;
|
||||||
|
|
||||||
static struct wav change_channels(struct wav w, int ch) {
|
void change_channels(struct wav *w, int ch) {
|
||||||
soundbyte *data = w.data;
|
if (w->ch == ch) return;
|
||||||
int samples = ch * w.frames;
|
soundbyte *data = w->data;
|
||||||
|
int samples = ch * w->frames;
|
||||||
soundbyte *new = malloc(sizeof(soundbyte) * samples);
|
soundbyte *new = malloc(sizeof(soundbyte) * samples);
|
||||||
|
|
||||||
if (ch > w.ch) {
|
if (ch > w->ch) {
|
||||||
/* Sets all new channels equal to the first one */
|
/* Sets all new channels equal to the first one */
|
||||||
for (int i = 0; i < w.frames; i++) {
|
for (int i = 0; i < w->frames; i++) {
|
||||||
for (int j = 0; j < ch; j++)
|
for (int j = 0; j < ch; j++)
|
||||||
new[i * ch + j] = data[i];
|
new[i * ch + j] = data[i];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Simple method; just use first N channels present in wav */
|
/* Simple method; just use first N channels present in wav */
|
||||||
for (int i = 0; i < w.frames; i++)
|
for (int i = 0; i < w->frames; i++)
|
||||||
for (int j = 0; j < ch; j++)
|
for (int j = 0; j < ch; j++)
|
||||||
new[i * ch + j] = data[i * ch + j];
|
new[i * ch + j] = data[i * ch + j];
|
||||||
}
|
}
|
||||||
|
|
||||||
free(w.data);
|
free(w->data);
|
||||||
w.data = new;
|
w->data = new;
|
||||||
return w;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct wav change_samplerate(struct wav w, int rate) {
|
void resample(soundbyte *in, soundbyte *out, int in_frames, int out_frames, int channels)
|
||||||
float ratio = (float)rate / w.samplerate;
|
{
|
||||||
int outframes = w.frames * ratio;
|
float ratio = (float)in_frames/out_frames;
|
||||||
SRC_DATA ssrc;
|
SRC_DATA ssrc;
|
||||||
soundbyte *resampled = calloc(w.ch*outframes,sizeof(soundbyte));
|
ssrc.data_in = in;
|
||||||
|
ssrc.data_out = out;
|
||||||
ssrc.data_in = w.data;
|
ssrc.input_frames = in_frames;
|
||||||
ssrc.data_out = resampled;
|
ssrc.output_frames = out_frames;
|
||||||
ssrc.input_frames = w.frames;
|
|
||||||
ssrc.output_frames = outframes;
|
|
||||||
ssrc.src_ratio = ratio;
|
ssrc.src_ratio = ratio;
|
||||||
|
int err = src_simple(&ssrc, SRC_LINEAR, channels);
|
||||||
int err = src_simple(&ssrc, SRC_LINEAR, w.ch);
|
if (err)
|
||||||
if (err) {
|
|
||||||
YughError("Resampling error code %d: %s", err, src_strerror(err));
|
YughError("Resampling error code %d: %s", err, src_strerror(err));
|
||||||
free(resampled);
|
|
||||||
return w;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
free(w.data);
|
void change_samplerate(struct wav *w, int rate) {
|
||||||
w.data = resampled;
|
if (rate == w->samplerate) return;
|
||||||
w.frames = outframes;
|
float ratio = (float)rate / w->samplerate;
|
||||||
w.samplerate = rate;
|
int outframes = w->frames * ratio;
|
||||||
|
soundbyte *resampled = malloc(w->ch*outframes*sizeof(soundbyte));
|
||||||
|
resample(w->data, resampled, w->frames, outframes, w->ch);
|
||||||
|
free(w->data);
|
||||||
|
|
||||||
return w;
|
w->data = resampled;
|
||||||
}
|
w->frames = outframes;
|
||||||
|
w->samplerate = rate;
|
||||||
void wav_norm_gain(struct wav *w, double lv) {
|
|
||||||
short tarmax = db2short(lv);
|
|
||||||
short max = 0;
|
|
||||||
short *s = w->data;
|
|
||||||
for (int i = 0; i < w->frames; i++) {
|
|
||||||
for (int j = 0; j < w->ch; j++) {
|
|
||||||
max = (abs(s[i * w->ch + j]) > max) ? abs(s[i * w->ch + j]) : max;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float mult = (float)max / tarmax;
|
|
||||||
|
|
||||||
for (int i = 0; i < w->frames; i++) {
|
|
||||||
for (int j = 0; j < w->ch; j++) {
|
|
||||||
s[i * w->ch + j] *= mult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void push_sound(soundbyte *buffer, int frames, int chan)
|
void push_sound(soundbyte *buffer, int frames, int chan)
|
||||||
{
|
{
|
||||||
bus_fill_buffers(buffer, frames*chan);
|
set_soundbytes(buffer, dsp_node_out(masterbus), frames*chan);
|
||||||
|
}
|
||||||
|
|
||||||
|
void filter_mod(pocketmod_context *mod, soundbyte *buffer, int frames)
|
||||||
|
{
|
||||||
|
pocketmod_render(mod, buffer, frames*CHANNELS*sizeof(soundbyte));
|
||||||
|
}
|
||||||
|
|
||||||
|
dsp_node *dsp_mod(const char *path)
|
||||||
|
{
|
||||||
|
long modsize;
|
||||||
|
void *data = slurp_file(path, &modsize);
|
||||||
|
pocketmod_context *mod = malloc(sizeof(*mod));
|
||||||
|
pocketmod_init(mod, data, modsize, SAMPLERATE);
|
||||||
|
return make_node(mod, filter_mod);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sound_init() {
|
void sound_init() {
|
||||||
mixer_init();
|
dsp_init();
|
||||||
saudio_setup(&(saudio_desc){
|
saudio_setup(&(saudio_desc){
|
||||||
.stream_cb = push_sound,
|
.stream_cb = push_sound,
|
||||||
.sample_rate = SAMPLERATE,
|
.sample_rate = SAMPLERATE,
|
||||||
|
@ -138,6 +135,25 @@ void sound_init() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int channels;
|
||||||
|
int samplerate;
|
||||||
|
void *f;
|
||||||
|
} stream;
|
||||||
|
|
||||||
|
void mp3_filter(stream *mp3, soundbyte *buffer, int frames)
|
||||||
|
{
|
||||||
|
if (mp3->samplerate == SAMPLERATE) {
|
||||||
|
drmp3_read_pcm_frames_f32(mp3->f, frames, buffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int in_frames = (float)mp3->samplerate/SAMPLERATE;
|
||||||
|
soundbyte *decode = malloc(sizeof(*decode)*in_frames*mp3->channels);
|
||||||
|
drmp3_read_pcm_frames_f32(mp3->f, in_frames, decode);
|
||||||
|
resample(decode, buffer, in_frames, frames, CHANNELS);
|
||||||
|
}
|
||||||
|
|
||||||
struct wav *make_sound(const char *wav) {
|
struct wav *make_sound(const char *wav) {
|
||||||
int index = shgeti(wavhash, wav);
|
int index = shgeti(wavhash, wav);
|
||||||
if (index != -1)
|
if (index != -1)
|
||||||
|
@ -150,7 +166,7 @@ struct wav *make_sound(const char *wav) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct wav mwav;
|
struct wav *mwav = malloc(sizeof(*mwav));
|
||||||
long rawlen;
|
long rawlen;
|
||||||
void *raw = slurp_file(wav, &rawlen);
|
void *raw = slurp_file(wav, &rawlen);
|
||||||
if (!raw) {
|
if (!raw) {
|
||||||
|
@ -159,11 +175,11 @@ struct wav *make_sound(const char *wav) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strcmp(ext, "wav"))
|
if (!strcmp(ext, "wav"))
|
||||||
mwav.data = drwav_open_memory_and_read_pcm_frames_f32(raw, rawlen, &mwav.ch, &mwav.samplerate, &mwav.frames, NULL);
|
mwav->data = drwav_open_memory_and_read_pcm_frames_f32(raw, rawlen, &mwav->ch, &mwav->samplerate, &mwav->frames, NULL);
|
||||||
|
|
||||||
else if (!strcmp(ext, "flac")) {
|
else if (!strcmp(ext, "flac")) {
|
||||||
#ifndef NFLAC
|
#ifndef NFLAC
|
||||||
mwav.data = drflac_open_memory_and_read_pcm_frames_f32(raw, rawlen, &mwav.ch, &mwav.samplerate, &mwav.frames, NULL);
|
mwav->data = drflac_open_memory_and_read_pcm_frames_f32(raw, rawlen, &mwav->ch, &mwav->samplerate, &mwav->frames, NULL);
|
||||||
#else
|
#else
|
||||||
YughWarn("Could not load %s because Primum was built without FLAC support.", wav);
|
YughWarn("Could not load %s because Primum was built without FLAC support.", wav);
|
||||||
#endif
|
#endif
|
||||||
|
@ -171,9 +187,9 @@ struct wav *make_sound(const char *wav) {
|
||||||
else if (!strcmp(ext, "mp3")) {
|
else if (!strcmp(ext, "mp3")) {
|
||||||
#ifndef NMP3
|
#ifndef NMP3
|
||||||
drmp3_config cnf;
|
drmp3_config cnf;
|
||||||
mwav.data = drmp3_open_memory_and_read_pcm_frames_f32(raw, rawlen, &cnf, &mwav.frames, NULL);
|
mwav->data = drmp3_open_memory_and_read_pcm_frames_f32(raw, rawlen, &cnf, &mwav->frames, NULL);
|
||||||
mwav.ch = cnf.channels;
|
mwav->ch = cnf.channels;
|
||||||
mwav.samplerate = cnf.sampleRate;
|
mwav->samplerate = cnf.sampleRate;
|
||||||
#else
|
#else
|
||||||
YughWarn("Could not load %s because Primum was built without MP3 support.", wav);
|
YughWarn("Could not load %s because Primum was built without MP3 support.", wav);
|
||||||
#endif
|
#endif
|
||||||
|
@ -182,11 +198,11 @@ struct wav *make_sound(const char *wav) {
|
||||||
#ifndef NQOA
|
#ifndef NQOA
|
||||||
qoa_desc qoa;
|
qoa_desc qoa;
|
||||||
short *qoa_data = qoa_decode(raw, rawlen, &qoa);
|
short *qoa_data = qoa_decode(raw, rawlen, &qoa);
|
||||||
mwav.ch = qoa.channels;
|
mwav->ch = qoa.channels;
|
||||||
mwav.samplerate = qoa.samplerate;
|
mwav->samplerate = qoa.samplerate;
|
||||||
mwav.frames = qoa.samples;
|
mwav->frames = qoa.samples;
|
||||||
mwav.data = malloc(sizeof(soundbyte) * mwav.frames * mwav.ch);
|
mwav->data = malloc(sizeof(soundbyte) * mwav->frames * mwav->ch);
|
||||||
src_short_to_float_array(qoa_data, mwav.data, mwav.frames*mwav.ch);
|
src_short_to_float_array(qoa_data, mwav->data, mwav->frames*mwav->ch);
|
||||||
free(qoa_data);
|
free(qoa_data);
|
||||||
#else
|
#else
|
||||||
YughWarn("Could not load %s because Primum was built without QOA support.", wav);
|
YughWarn("Could not load %s because Primum was built without QOA support.", wav);
|
||||||
|
@ -194,23 +210,18 @@ struct wav *make_sound(const char *wav) {
|
||||||
} else {
|
} else {
|
||||||
YughWarn("File with unknown type '%s'.", wav);
|
YughWarn("File with unknown type '%s'.", wav);
|
||||||
free (raw);
|
free (raw);
|
||||||
|
free(mwav);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
free(raw);
|
free(raw);
|
||||||
|
|
||||||
if (mwav.samplerate != SAMPLERATE)
|
change_samplerate(mwav, SAMPLERATE);
|
||||||
mwav = change_samplerate(mwav, SAMPLERATE);
|
change_channels(mwav, CHANNELS);
|
||||||
|
|
||||||
if (mwav.ch != CHANNELS)
|
|
||||||
mwav = change_channels(mwav, CHANNELS);
|
|
||||||
|
|
||||||
mwav.gain = 1.f;
|
|
||||||
struct wav *newwav = malloc(sizeof(*newwav));
|
|
||||||
*newwav = mwav;
|
|
||||||
if (shlen(wavhash) == 0) sh_new_arena(wavhash);
|
if (shlen(wavhash) == 0) sh_new_arena(wavhash);
|
||||||
shput(wavhash, wav, newwav);
|
shput(wavhash, wav, mwav);
|
||||||
|
|
||||||
return newwav;
|
return mwav;
|
||||||
}
|
}
|
||||||
|
|
||||||
void free_sound(const char *wav) {
|
void free_sound(const char *wav) {
|
||||||
|
@ -222,68 +233,52 @@ void free_sound(const char *wav) {
|
||||||
shdel(wavhash, wav);
|
shdel(wavhash, wav);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct soundstream *soundstream_make() {
|
void sound_fillbuf(struct sound *s, soundbyte *buf, int n) {
|
||||||
struct soundstream *new = malloc(sizeof(*new));
|
int frames = s->data->frames - s->frame;
|
||||||
new->buf = circbuf_make(sizeof(short), BUF_FRAMES * CHANNELS * 2);
|
if (frames == 0) return;
|
||||||
return new;
|
int end = 0;
|
||||||
|
if (frames > n)
|
||||||
|
frames = n;
|
||||||
|
else
|
||||||
|
end = 1;
|
||||||
|
|
||||||
|
soundbyte *in = s->data->data;
|
||||||
|
|
||||||
|
for (int i = 0; i < frames; i++) {
|
||||||
|
for (int j = 0; j < CHANNELS; j++)
|
||||||
|
buf[i * CHANNELS + j] = in[s->frame*CHANNELS + j];
|
||||||
|
s->frame++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void kill_oneshot(struct sound *s) {
|
if(end) {
|
||||||
|
if (s->loop)
|
||||||
|
s->frame = 0;
|
||||||
|
call_env(s->hook, "this.end();");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void free_source(struct sound *s)
|
||||||
|
{
|
||||||
|
JS_FreeValue(js, s->hook);
|
||||||
free(s);
|
free(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
void play_oneshot(struct wav *wav) {
|
struct dsp_node *dsp_source(char *path)
|
||||||
|
{
|
||||||
struct sound *self = malloc(sizeof(*self));
|
struct sound *self = malloc(sizeof(*self));
|
||||||
self->data = wav;
|
|
||||||
self->bus = first_free_bus(dsp_filter(self, sound_fillbuf));
|
|
||||||
self->playing = 1;
|
|
||||||
self->loop = 0;
|
|
||||||
self->frame = 0;
|
self->frame = 0;
|
||||||
self->endcb = kill_oneshot;
|
self->data = make_sound(path);
|
||||||
}
|
self->loop = false;
|
||||||
|
self->hook = JS_UNDEFINED;
|
||||||
struct sound *play_sound(struct wav *wav) {
|
dsp_node *n = make_node(self, sound_fillbuf);
|
||||||
struct sound *self = calloc(1, sizeof(*self));
|
n->data_free = free_source;
|
||||||
self->data = wav;
|
return n;
|
||||||
self->bus = first_free_bus(dsp_filter(self, sound_fillbuf));
|
|
||||||
self->playing = 1;
|
|
||||||
self->loop = 0;
|
|
||||||
self->frame = 0;
|
|
||||||
self->endcb = kill_oneshot;
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sound_playing(const struct sound *s) {
|
|
||||||
return !sound_paused(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sound_paused(const struct sound *s) {
|
|
||||||
return s->bus == NULL;
|
|
||||||
}
|
|
||||||
void sound_pause(struct sound *s) {
|
|
||||||
if (s->bus == NULL) return;
|
|
||||||
bus_free(s->bus);
|
|
||||||
s->bus = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sound_resume(struct sound *s) {
|
|
||||||
if (s->bus != NULL) return;
|
|
||||||
s->bus = first_free_bus(dsp_filter(s, sound_fillbuf));
|
|
||||||
}
|
|
||||||
|
|
||||||
void sound_stop(struct sound *s) {
|
|
||||||
sound_pause(s);
|
|
||||||
s->frame = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int sound_finished(const struct sound *s) {
|
int sound_finished(const struct sound *s) {
|
||||||
return s->frame == s->data->frames;
|
return s->frame == s->data->frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
int sound_stopped(const struct sound *s) {
|
|
||||||
return s->bus == NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct mp3 make_music(const char *mp3) {
|
struct mp3 make_music(const char *mp3) {
|
||||||
// drmp3 new;
|
// drmp3 new;
|
||||||
// if (!drmp3_init_file(&new, mp3, NULL)) {
|
// if (!drmp3_init_file(&new, mp3, NULL)) {
|
||||||
|
@ -294,41 +289,6 @@ struct mp3 make_music(const char *mp3) {
|
||||||
return newmp3;
|
return newmp3;
|
||||||
}
|
}
|
||||||
|
|
||||||
void close_audio_device(int device) {
|
|
||||||
}
|
|
||||||
|
|
||||||
int open_device(const char *adriver) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sound_fillbuf(struct sound *s, soundbyte *buf, int n) {
|
|
||||||
float gainmult = pct2mult(s->data->gain);
|
|
||||||
|
|
||||||
soundbyte *in = s->data->data;
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
for (int j = 0; j < CHANNELS; j++)
|
|
||||||
buf[i * CHANNELS + j] = in[s->frame*CHANNELS + j] * gainmult;
|
|
||||||
s->frame++;
|
|
||||||
|
|
||||||
if (s->frame == s->data->frames) {
|
|
||||||
sound_stop(s);
|
|
||||||
s->endcb(s);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void mp3_fillbuf(struct sound *s, soundbyte *buf, int n) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void soundstream_fillbuf(struct soundstream *s, soundbyte *buf, int n) {
|
|
||||||
int max = 1;//s->buf->write - s->buf->read;
|
|
||||||
int lim = (max < n * CHANNELS) ? max : n * CHANNELS;
|
|
||||||
for (int i = 0; i < lim; i++) {
|
|
||||||
// buf[i] = cbuf_shift(s->buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float short2db(short val) {
|
float short2db(short val) {
|
||||||
return 20 * log10(abs(val) / SHRT_MAX);
|
return 20 * log10(abs(val) / SHRT_MAX);
|
||||||
}
|
}
|
||||||
|
@ -341,6 +301,13 @@ short short_gain(short val, float db) {
|
||||||
return (short)(pow(10, db / 20.f) * val);
|
return (short)(pow(10, db / 20.f) * val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float float2db(float val) { return 20 * log10(fabsf(val)); }
|
||||||
|
float db2float(float db) { return pow(10, db/20); }
|
||||||
|
|
||||||
|
float fgain(float val, float db) {
|
||||||
|
return pow(10,db/20.f)*val;
|
||||||
|
}
|
||||||
|
|
||||||
float pct2db(float pct) {
|
float pct2db(float pct) {
|
||||||
if (pct <= 0) return -72.f;
|
if (pct <= 0) return -72.f;
|
||||||
|
|
||||||
|
|
|
@ -1,81 +1,59 @@
|
||||||
#ifndef SOUND_H
|
#ifndef SOUND_H
|
||||||
#define SOUND_H
|
#define SOUND_H
|
||||||
|
|
||||||
#include "cbuf.h"
|
#include "script.h"
|
||||||
|
|
||||||
typedef float soundbyte;
|
typedef float soundbyte;
|
||||||
|
|
||||||
struct soundstream {
|
struct dsp_node;
|
||||||
struct circbuf *buf;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct soundstream *soundstream_make();
|
|
||||||
|
|
||||||
/* A bookmark into a wav, actually playing the sound */
|
/* A bookmark into a wav, actually playing the sound */
|
||||||
struct sound {
|
typedef struct sound {
|
||||||
int loop; /* How many times to loop */
|
|
||||||
unsigned int frame; /* Pointing to the current frame on the wav */
|
unsigned int frame; /* Pointing to the current frame on the wav */
|
||||||
int playing;
|
|
||||||
float gain;
|
|
||||||
|
|
||||||
struct wav *data;
|
struct wav *data;
|
||||||
struct bus *bus;
|
int loop;
|
||||||
|
JSValue hook;
|
||||||
void (*endcb)(struct sound*);
|
} sound;
|
||||||
};
|
|
||||||
|
|
||||||
/* Represents a sound file source, fulled loaded*/
|
/* Represents a sound file source, fulled loaded*/
|
||||||
struct wav {
|
typedef struct wav {
|
||||||
unsigned int ch;
|
unsigned int ch;
|
||||||
unsigned int samplerate;
|
unsigned int samplerate;
|
||||||
unsigned long long frames;
|
unsigned long long frames;
|
||||||
float gain; /* In dB */
|
|
||||||
soundbyte *data;
|
soundbyte *data;
|
||||||
};
|
} wav;
|
||||||
|
|
||||||
/* Represents a sound file stream */
|
/* Represents a sound file stream */
|
||||||
struct mp3 {
|
typedef struct mp3 {
|
||||||
|
|
||||||
};
|
} mp3;
|
||||||
|
|
||||||
|
typedef struct qoa {
|
||||||
|
// struct qoa_desc desc;
|
||||||
|
} qoa;
|
||||||
|
|
||||||
void sound_init();
|
void sound_init();
|
||||||
void audio_open(const char *device);
|
void audio_open(const char *device);
|
||||||
void audio_close();
|
void audio_close();
|
||||||
|
|
||||||
void sound_fillbuf(struct sound *s, soundbyte *buf, int n);
|
|
||||||
|
|
||||||
struct wav *make_sound(const char *wav);
|
struct wav *make_sound(const char *wav);
|
||||||
void free_sound(const char *wav);
|
void free_sound(const char *wav);
|
||||||
void wav_norm_gain(struct wav *w, double lv);
|
void wav_norm_gain(struct wav *w, double lv);
|
||||||
struct sound *play_sound(struct wav *wav);
|
struct dsp_node *dsp_source(char *path);
|
||||||
void play_oneshot(struct wav *wav);
|
struct dsp_node *dsp_mod(const char *path);
|
||||||
|
|
||||||
int sound_playing(const struct sound *s);
|
|
||||||
int sound_paused(const struct sound *s);
|
|
||||||
int sound_stopped(const struct sound *s);
|
|
||||||
int sound_finished(const struct sound *s);
|
int sound_finished(const struct sound *s);
|
||||||
void sound_pause(struct sound *s);
|
|
||||||
void sound_resume(struct sound *s);
|
|
||||||
void sound_stop(struct sound *s);
|
|
||||||
|
|
||||||
struct mp3 make_mp3(const char *mp3);
|
struct mp3 make_mp3(const char *mp3);
|
||||||
|
|
||||||
const char *get_audio_driver();
|
|
||||||
|
|
||||||
void soundstream_fillbuf(struct soundstream *stream, soundbyte *buf, int n);
|
|
||||||
|
|
||||||
void close_audio_device(int device);
|
|
||||||
int open_device(const char *adriver);
|
|
||||||
|
|
||||||
float short2db(short val);
|
float short2db(short val);
|
||||||
short db2short(float db);
|
short db2short(float db);
|
||||||
short short_gain(short val, float db);
|
short short_gain(short val, float db);
|
||||||
|
float fgain(float val, float db);
|
||||||
|
float float2db(float val);
|
||||||
|
float db2float(float db);
|
||||||
|
|
||||||
float pct2db(float pct);
|
float pct2db(float pct);
|
||||||
float pct2mult(float pct);
|
float pct2mult(float pct);
|
||||||
|
|
||||||
void audio_init();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
6
source/engine/thirdparty/sokol/sokol_app.h
vendored
6
source/engine/thirdparty/sokol/sokol_app.h
vendored
|
@ -3503,9 +3503,15 @@ NSMenu* menu_bar = [[NSMenu alloc] init];
|
||||||
action:@selector(unhideAllApplications:)
|
action:@selector(unhideAllApplications:)
|
||||||
keyEquivalent:@""];
|
keyEquivalent:@""];
|
||||||
|
|
||||||
|
NSMenuItem *fullscreen_item = [[NSMenuItem alloc]
|
||||||
|
initWithTitle:@"Enter Full Screen"
|
||||||
|
action:@selector(toggleFullScreen:)
|
||||||
|
keyEquivalent:@"f"];
|
||||||
|
|
||||||
[app_menu addItem:hide_menu_item];
|
[app_menu addItem:hide_menu_item];
|
||||||
[app_menu addItem:hide_others_item];
|
[app_menu addItem:hide_others_item];
|
||||||
[app_menu addItem:show_all_item];
|
[app_menu addItem:show_all_item];
|
||||||
|
[app_menu addItem:fullscreen_item];
|
||||||
[app_menu addItem:[NSMenuItem separatorItem]];
|
[app_menu addItem:[NSMenuItem separatorItem]];
|
||||||
[app_menu addItem:quit_menu_item];
|
[app_menu addItem:quit_menu_item];
|
||||||
app_menu_item.submenu = app_menu;
|
app_menu_item.submenu = app_menu;
|
||||||
|
|
|
@ -77,7 +77,7 @@ void timer_start(struct timer *t) {
|
||||||
|
|
||||||
void timer_remove(int id) {
|
void timer_remove(int id) {
|
||||||
struct timer *t = id2timer(id);
|
struct timer *t = id2timer(id);
|
||||||
free_callee(t->data);
|
// free_callee(t->data);
|
||||||
t->next = first;
|
t->next = first;
|
||||||
t->on = 0;
|
t->on = 0;
|
||||||
first = id;
|
first = id;
|
||||||
|
@ -94,7 +94,7 @@ struct timer *id2timer(int id) {
|
||||||
|
|
||||||
void timers_free()
|
void timers_free()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < arrlen(timers); i++)
|
// for (int i = 0; i < arrlen(timers); i++)
|
||||||
if (timers[i].on)
|
// if (timers[i].on)
|
||||||
free_callee(timers[i].data);
|
// free_callee(timers[i].data);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
#include "window.h"
|
#include "window.h"
|
||||||
#include "sound.h"
|
#include "sound.h"
|
||||||
#include "resources.h"
|
#include "resources.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "datastream.h"
|
#include "datastream.h"
|
||||||
|
@ -142,8 +141,10 @@ const char *engine_info()
|
||||||
static int argc;
|
static int argc;
|
||||||
static char **args;
|
static char **args;
|
||||||
|
|
||||||
|
struct datastream *bjork;
|
||||||
|
|
||||||
void c_init() {
|
void c_init() {
|
||||||
sound_init();
|
|
||||||
input_init();
|
input_init();
|
||||||
script_evalf("world_start();");
|
script_evalf("world_start();");
|
||||||
|
|
||||||
|
@ -151,6 +152,7 @@ void c_init() {
|
||||||
window_set_icon("icons/moon.gif");
|
window_set_icon("icons/moon.gif");
|
||||||
window_resize(sapp_width(), sapp_height());
|
window_resize(sapp_width(), sapp_height());
|
||||||
script_evalf("Game.init();");
|
script_evalf("Game.init();");
|
||||||
|
// bjork = ds_openvideo("bjork.mpg");
|
||||||
}
|
}
|
||||||
|
|
||||||
int frame_fps() {
|
int frame_fps() {
|
||||||
|
@ -160,6 +162,7 @@ int frame_fps() {
|
||||||
static void process_frame()
|
static void process_frame()
|
||||||
{
|
{
|
||||||
double elapsed = stm_sec(stm_laptime(&frame_t));
|
double elapsed = stm_sec(stm_laptime(&frame_t));
|
||||||
|
// ds_advance(bjork, elapsed);
|
||||||
input_poll(0);
|
input_poll(0);
|
||||||
/* Timers all update every frame - once per monitor refresh */
|
/* Timers all update every frame - once per monitor refresh */
|
||||||
timer_update(elapsed, timescale);
|
timer_update(elapsed, timescale);
|
||||||
|
@ -374,7 +377,7 @@ dam->update_activity(dam, &da, NULL, NULL);
|
||||||
stm_setup(); /* time */
|
stm_setup(); /* time */
|
||||||
start_t = frame_t = stm_now();
|
start_t = frame_t = stm_now();
|
||||||
physlast = updatelast = start_t;
|
physlast = updatelast = start_t;
|
||||||
|
sound_init();
|
||||||
resources_init();
|
resources_init();
|
||||||
phys2d_init();
|
phys2d_init();
|
||||||
script_startup();
|
script_startup();
|
||||||
|
@ -398,7 +401,6 @@ dam->update_activity(dam, &da, NULL, NULL);
|
||||||
start_desc.width = mainwin.width;
|
start_desc.width = mainwin.width;
|
||||||
start_desc.height = mainwin.height;
|
start_desc.height = mainwin.height;
|
||||||
start_desc.fullscreen = 0;
|
start_desc.fullscreen = 0;
|
||||||
|
|
||||||
sapp_run(&start_desc);
|
sapp_run(&start_desc);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
Loading…
Reference in a new issue