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|
|
||||
|physupdate(dt)|called once per physics tick|
|
||||
|stop|called when the object is killed|
|
||||
|debug|use draw functions with the object's world position|
|
||||
|gui|draw functions in screen space|
|
||||
|debug|use draw functions with the object's world position, during debug pass|
|
||||
|gui|draw functions in screen space, during gameplay gui pass|
|
||||
|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, 'lc', {value:function() { return this.toLowerCase(); }});
|
||||
|
|
|
@ -314,6 +314,7 @@ var timer = {
|
|||
remain: 1,
|
||||
loop: false,
|
||||
on: false,
|
||||
apptime: false, /* If true, based on app's time instead of game world time */
|
||||
start() {
|
||||
this.on = true;
|
||||
},
|
||||
|
@ -337,7 +338,7 @@ var timer = {
|
|||
this.restart();
|
||||
},
|
||||
|
||||
pct() { return this.remain / this.time; },
|
||||
pct() { return 1 - (this.remain / this.time); },
|
||||
|
||||
kill() {
|
||||
Register.unregister_obj(this);
|
||||
|
|
|
@ -17,6 +17,8 @@ actor.kill = function(actor){};
|
|||
actor.delay = function(fn, seconds) {};
|
||||
actor.clock = function(fn){};
|
||||
|
||||
var Empyrean = Object.create(actor);
|
||||
|
||||
var gameobject = {
|
||||
impl: {
|
||||
full_path() {
|
||||
|
@ -58,16 +60,10 @@ var gameobject = {
|
|||
},
|
||||
|
||||
cry(file) {
|
||||
Sound.play(file);
|
||||
return;
|
||||
|
||||
if (this.curcry && !Sound.finished(this.curcry)) return;
|
||||
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;
|
||||
var p = Sound.play(file, Sound.bus.sfx);
|
||||
var killfn = p.kill.bind(p);
|
||||
this.timers.push(killfn);
|
||||
return killfn;
|
||||
},
|
||||
|
||||
set max_velocity(x) { cmd(151, this.body, x); },
|
||||
|
@ -324,7 +320,6 @@ var gameobject = {
|
|||
if (!k.startswith("on_")) continue;
|
||||
var signal = k.fromfirst("on_");
|
||||
Event.observe(signal, obj, obj[k]);
|
||||
Log.warn("REGISTERED " + signal);
|
||||
};
|
||||
|
||||
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 = {
|
||||
bus: {},
|
||||
sounds: [], /* array of loaded sound files */
|
||||
play(file) {
|
||||
play(file, bus) {
|
||||
if (!IO.exists(file)) {
|
||||
Log.error(`Cannot play sound ${file}: does not exist.`);
|
||||
return;
|
||||
}
|
||||
var p = cmd(14,file);
|
||||
return p;
|
||||
},
|
||||
|
||||
finished(sound) {
|
||||
return cmd(165, sound);
|
||||
},
|
||||
|
||||
stop(sound) {
|
||||
cmd(164, sound);
|
||||
var src = DSP.source(file);
|
||||
bus ??= Sound.bus.master;
|
||||
src.plugin(bus);
|
||||
return src;
|
||||
},
|
||||
|
||||
music(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.doc = {};
|
||||
Sound.doc.volume = "Set the master volume. 0 is no sound and 100 is loudest.";
|
||||
|
|
|
@ -3,90 +3,50 @@
|
|||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
struct circbuf {
|
||||
int16_t *data;
|
||||
uint32_t read;
|
||||
uint32_t write;
|
||||
unsigned int len;
|
||||
static inline unsigned int powof2(unsigned int x)
|
||||
{
|
||||
x = x-1;
|
||||
x |= (x >> 1);
|
||||
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);
|
||||
struct circbuf circbuf_init(size_t size, unsigned int len);
|
||||
void cbuf_push(struct circbuf *buf, short data);
|
||||
short cbuf_shift(struct circbuf *buf);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef CBUF_IMPLEMENT
|
||||
|
||||
#include "assert.h"
|
||||
#include "stdlib.h"
|
||||
|
||||
unsigned int powof2(unsigned int num)
|
||||
{
|
||||
if (num != 0) {
|
||||
num--;
|
||||
num |= (num >> 1);
|
||||
num |= (num >> 2);
|
||||
num |= (num >> 4);
|
||||
num |= (num >> 8);
|
||||
num |= (num >> 16);
|
||||
num++;
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
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++)];
|
||||
}
|
||||
#define ringheader(r) ((struct rheader *)r-1)
|
||||
|
||||
static inline void *ringmake(void *ring, size_t elemsize, unsigned int n)
|
||||
{
|
||||
n = powof2(n);
|
||||
if (ring) {
|
||||
struct rheader *h = ringheader(ring);
|
||||
if (n <= h->len) return h+1;
|
||||
h = realloc(h, elemsize*n+sizeof(struct rheader));
|
||||
return h+1;
|
||||
}
|
||||
|
||||
struct rheader *h = malloc(elemsize*n+sizeof(struct rheader));
|
||||
h->len = n; h->read = 0; h->write = 0;
|
||||
return h+1;
|
||||
}
|
||||
|
||||
#define ringnew(r,n) (r = ringmake(r, sizeof(*r),n))
|
||||
#define ringfree(r) ((r) ? free(ringheader(r)) : 0)
|
||||
#define ringmask(r,v) (v & (ringheader(r)->len-1))
|
||||
#define ringpush(r,v) (r[ringmask(r,ringheader(r)->write++)] = v)
|
||||
#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)
|
||||
#define ringempty(r) ((r) ? ringheader(r)->read == ringheader(r)->write : 0)
|
||||
|
||||
#endif
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
#define MSF_GIF_IMPL
|
||||
#include "msf_gif.h"
|
||||
|
||||
#define STB_HEXWAVE_IMPLEMENTATION
|
||||
#include "stb_hexwave.h"
|
||||
|
||||
#define STB_DS_IMPLEMENTATION
|
||||
#include <stb_ds.h>
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
#include "iir.h"
|
||||
#include "limits.h"
|
||||
#include "log.h"
|
||||
#include "mix.h"
|
||||
#include "resources.h"
|
||||
#include "shader.h"
|
||||
#include "sound.h"
|
||||
|
@ -17,7 +16,6 @@
|
|||
|
||||
#include "mpeg2.sglsl.h"
|
||||
|
||||
#define CBUF_IMPLEMENT
|
||||
#include "cbuf.h"
|
||||
|
||||
#include "sokol/sokol_gfx.h"
|
||||
|
@ -26,8 +24,13 @@ sg_shader vid_shader;
|
|||
sg_pipeline vid_pipeline;
|
||||
sg_bindings vid_bind;
|
||||
|
||||
static void render_frame(plm_t *mpeg, plm_frame_t *frame, void *user) {
|
||||
struct datastream *ds = user;
|
||||
void soundstream_fillbuf(struct datastream *ds, soundbyte *buf, int frames) {
|
||||
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];
|
||||
plm_frame_to_rgba(frame, rgb, frame->width*4);
|
||||
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);
|
||||
}
|
||||
|
||||
static void render_audio(plm_t *mpeg, plm_samples_t *samples, void *user) {
|
||||
struct datastream *ds = user;
|
||||
short t;
|
||||
|
||||
for (int i = 0; i < samples->count * CHANNELS; i++) {
|
||||
t = (short)(samples->interleaved[i] * SHRT_MAX);
|
||||
// cbuf_push(ds->astream->buf, t * 5);
|
||||
}
|
||||
static void render_audio(plm_t *mpeg, plm_samples_t *samples, struct datastream *ds) {
|
||||
for (int i = 0; i < samples->count * CHANNELS; i++)
|
||||
ringpush(ds->ring, samples->interleaved[i]);
|
||||
}
|
||||
|
||||
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;
|
||||
void *raw;
|
||||
raw = slurp_file(video, &rawlen);
|
||||
raw = slurp_file(path, &rawlen);
|
||||
ds->plm = plm_create_with_memory(raw, rawlen, 0);
|
||||
free(raw);
|
||||
|
||||
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",
|
||||
video,
|
||||
YughWarn("Opened %s - framerate: %f, samplerate: %d,audio streams: %d, duration: %g",
|
||||
path,
|
||||
plm_get_framerate(ds->plm),
|
||||
plm_get_samplerate(ds->plm),
|
||||
|
||||
plm_get_num_audio_streams(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)
|
||||
});
|
||||
|
||||
|
||||
ds->astream = soundstream_make();
|
||||
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);
|
||||
ds->ring = ringnew(ds->ring, 8192);
|
||||
plugin_node(make_node(ds, soundstream_fillbuf), masterbus);
|
||||
|
||||
plm_set_video_decode_callback(ds->plm, render_frame, 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);
|
||||
|
||||
ds->playing = true;
|
||||
|
||||
return ds;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (ds->playing) {
|
||||
plm_decode(ds->plm, s);
|
||||
}
|
||||
if (ds->playing) plm_decode(ds->plm, s);
|
||||
}
|
||||
|
||||
void ds_seek(struct datastream *ds, double time) {
|
||||
// clear_raw(ds->audio_device);
|
||||
plm_seek(ds->plm, time, false);
|
||||
}
|
||||
|
||||
|
@ -140,8 +120,7 @@ void ds_stop(struct datastream *ds) {
|
|||
plm_destroy(ds->plm);
|
||||
ds->plm = NULL;
|
||||
}
|
||||
if (ds->audio_device)
|
||||
close_audio_device(ds->audio_device);
|
||||
|
||||
ds->playing = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include <pl_mpeg.h>
|
||||
#include <stdint.h>
|
||||
#include "dsp.h"
|
||||
#include "utringbuffer.h"
|
||||
|
||||
#include "sokol/sokol_gfx.h"
|
||||
|
||||
|
@ -12,17 +14,16 @@ struct datastream {
|
|||
plm_t *plm;
|
||||
double last_time;
|
||||
int playing;
|
||||
int audio_device;
|
||||
sg_image img;
|
||||
int width;
|
||||
int height;
|
||||
struct soundstream *astream;
|
||||
soundbyte *ring;
|
||||
};
|
||||
|
||||
struct Texture;
|
||||
|
||||
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 *);
|
||||
void ds_advance(struct datastream *ds, double);
|
||||
void ds_seek(struct datastream *ds, double);
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
#include "input.h"
|
||||
#include "level.h"
|
||||
#include "log.h"
|
||||
#include "mix.h"
|
||||
#include "dsp.h"
|
||||
#include "music.h"
|
||||
#include "2dphysics.h"
|
||||
|
||||
#include "datastream.h"
|
||||
#include "sound.h"
|
||||
#include "sprite.h"
|
||||
#include "stb_ds.h"
|
||||
|
@ -546,12 +546,12 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
|
|||
|
||||
case 14:
|
||||
str = JS_ToCString(js, argv[1]);
|
||||
//ret = ptr2js(play_sound(make_sound(str)));
|
||||
play_oneshot(make_sound(str));
|
||||
ret = ptr2js(dsp_source(str));
|
||||
((sound*)((dsp_node*)js2ptr(ret))->data)->hook = argv[2];
|
||||
break;
|
||||
|
||||
case 15:
|
||||
music_stop();
|
||||
// music_stop();
|
||||
break;
|
||||
|
||||
case 16:
|
||||
|
@ -1162,10 +1162,10 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
|
|||
ret = int2js(rename(str, str2));
|
||||
break;
|
||||
case 164:
|
||||
sound_stop(js2ptr(argv[1]));
|
||||
unplug_node(js2ptr(argv[1]));
|
||||
break;
|
||||
case 165:
|
||||
ret = bool2js(sound_paused(js2ptr(argv[1])));
|
||||
// ret = bool2js(sound_paused(js2ptr(argv[1])));
|
||||
break;
|
||||
case 166:
|
||||
str = js2str(argv[1]);
|
||||
|
@ -1194,6 +1194,77 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
|
|||
str = js2str(argv[1]);
|
||||
capture_screen(js2number(argv[2]), js2number(argv[3]), js2number(argv[4]), js2number(argv[5]), str);
|
||||
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)
|
||||
|
@ -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) {
|
||||
double secs = js2number(argv[1]);
|
||||
struct callee *c = make_callee(argv[0], argv[3]);
|
||||
int id = timer_make(secs, call_callee, c, 1, js2bool(argv[2]));
|
||||
return JS_NewInt64(js, id);
|
||||
// double secs = js2number(argv[1]);
|
||||
// struct callee *c = make_callee(argv[0], argv[3]);
|
||||
// int id = timer_make(secs, call_callee, c, 1, js2bool(argv[2]));
|
||||
// return JS_NewInt64(js, id);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
size_t len;
|
||||
|
@ -222,6 +231,7 @@ void file_eval_env(const char *file, JSValue env)
|
|||
}
|
||||
|
||||
void script_call_sym(JSValue sym) {
|
||||
if (!JS_IsFunction(js, sym)) return;
|
||||
struct callee c;
|
||||
c.fn = sym;
|
||||
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)
|
||||
{
|
||||
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);
|
||||
js_print_exception(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);
|
||||
}
|
||||
|
||||
struct callee *make_callee(JSValue fn, JSValue obj)
|
||||
struct callee make_callee(JSValue fn, JSValue obj)
|
||||
{
|
||||
struct callee *c = malloc(sizeof(*c));
|
||||
c->fn = JS_DupValue(js, fn);
|
||||
c->obj = JS_DupValue(js, obj);
|
||||
struct callee c;
|
||||
c.fn = JS_DupValue(js, fn);
|
||||
c.obj = JS_DupValue(js, obj);
|
||||
return c;
|
||||
}
|
||||
|
||||
void free_callee(struct callee *c)
|
||||
void free_callee(struct callee c)
|
||||
{
|
||||
JS_FreeValue(js,c->fn);
|
||||
JS_FreeValue(js,c->obj);
|
||||
free(c);
|
||||
JS_FreeValue(js,c.fn);
|
||||
JS_FreeValue(js,c.obj);
|
||||
}
|
||||
|
||||
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);
|
||||
void script_update(double dt);
|
||||
void script_draw();
|
||||
struct callee *make_callee(JSValue fn, JSValue obj);
|
||||
void free_callee(struct callee *c);
|
||||
struct callee make_callee(JSValue fn, JSValue obj);
|
||||
void free_callee(struct callee c);
|
||||
|
||||
void duk_run_err();
|
||||
void js_dump_stack();
|
||||
|
@ -42,6 +42,7 @@ void call_callee(struct callee *c);
|
|||
void script_callee(struct callee c, int argc, JSValue *argv);
|
||||
int script_has_sym(void *sym);
|
||||
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);
|
||||
|
||||
time_t file_mod_secs(const char *file);
|
||||
|
|
|
@ -10,191 +10,164 @@
|
|||
|
||||
#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;
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
return new;
|
||||
void mono_to_stero(soundbyte *a, soundbyte *stereo, int frames)
|
||||
{
|
||||
interleave(a,a,stereo, frames);
|
||||
}
|
||||
|
||||
if (arrlen(filters) == 0) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void dsp_run(struct dsp_filter filter, soundbyte *out, int n) {
|
||||
filter.dirty = 1; // Always on for testing
|
||||
|
||||
if (!filter.dirty)
|
||||
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);
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
struct wav gen_square(float amp, float freq, int sr, int ch)
|
||||
{
|
||||
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;
|
||||
}
|
||||
typedef struct {
|
||||
float amp;
|
||||
float freq;
|
||||
float phase; /* from 0 to 1, marking where we are */
|
||||
float (*filter)(float phase);
|
||||
} phasor;
|
||||
|
||||
float sin_phasor(float p)
|
||||
{
|
||||
|
@ -216,47 +189,72 @@ float tri_phasor(float p)
|
|||
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++) {
|
||||
soundbyte val = SHRT_MAX * osc->f(phasor_step(&osc->p));
|
||||
buf[i*CHANNELS] = buf[i*CHANNELS+1] = val;
|
||||
}
|
||||
for (int i = 0; i < frames; i++) {
|
||||
buffer[i] = p->filter(p->phase) * p->amp;
|
||||
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)
|
||||
{
|
||||
for (int i = 0; i < n; i++) {
|
||||
for (int j = 0; j < CHANNELS; j++) {
|
||||
out[i*CHANNELS+j] = (rand()>>15) - USHRT_MAX;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < n; i++) out[i] = sample_whitenoise();
|
||||
mono_expand(out, CHANNELS, n);
|
||||
}
|
||||
|
||||
dsp_node *dsp_whitenoise()
|
||||
{
|
||||
return make_node(NULL, gen_whitenoise);
|
||||
}
|
||||
|
||||
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++) {
|
||||
double pink;
|
||||
double white = sample_whitenoise();
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
for (int j = 0; j < CHANNELS; j++) {
|
||||
double pink;
|
||||
double white = (double)out[i*CHANNELS+j]/SHRT_MAX;
|
||||
for (int k = 0; k < 5; k++) {
|
||||
b[k] = a[k]*b[k] + white * b[k];
|
||||
pink += b[k];
|
||||
}
|
||||
|
||||
for (int k = 0; k < 5; k++) {
|
||||
b[j][k] = ccof[k]*b[j][k] + white * dcof[k];
|
||||
pink += b[j][k];
|
||||
}
|
||||
pink += b[5] + white*0.5362;
|
||||
b[5] = white*0.115926;
|
||||
|
||||
pink += b[j][5] + white*0.5362;
|
||||
b[j][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
|
||||
* 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;
|
||||
|
||||
float a = 0.f;
|
||||
|
||||
iir.dx[0] = (float)val/SHRT_MAX;
|
||||
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];
|
||||
|
||||
|
||||
for (int i =0; i < iir.n; i++)
|
||||
a -= iir.dcof[i] * iir.dy[i];
|
||||
|
||||
iir.dy[0] = a;
|
||||
|
||||
for (int i = iir.n-1; i > 0; i--)
|
||||
iir.dy[i] = iir.dy[i-1];
|
||||
|
||||
|
||||
|
||||
return a * SHRT_MAX;
|
||||
return make_node(NULL, gen_pinknoise);
|
||||
}
|
||||
|
||||
void dsp_iir_fillbuf(struct dsp_iir *iir, soundbyte *out, int n)
|
||||
soundbyte iir_filter(struct dsp_iir iir, soundbyte val)
|
||||
{
|
||||
dsp_run(iir->in, out, n);
|
||||
iir.y[0] = 0.0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
soundbyte v = iir_filter(iir, out[i*CHANNELS]);
|
||||
iir.x[0] = val;
|
||||
|
||||
for (int j = 0; j < CHANNELS; j++) {
|
||||
out[i*CHANNELS+j] = v;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < iir.n; i++)
|
||||
iir.y[0] += iir.a[i] * iir.x[i];
|
||||
|
||||
for (int i = 1; i < iir.n; i++)
|
||||
iir.y[0] -= iir.b[i] * iir.y[i];
|
||||
|
||||
/* Shift values in */
|
||||
for (int i = iir.n-1; i > 0; i--) {
|
||||
iir.x[i] = iir.x[i-1];
|
||||
iir.y[i] = iir.y[i-1];
|
||||
}
|
||||
|
||||
return iir.y[0];
|
||||
}
|
||||
|
||||
struct dsp_filter lpf_make(int poles, float freq)
|
||||
void filter_iir(struct dsp_iir *iir, soundbyte *buffer, int frames)
|
||||
{
|
||||
struct dsp_iir *new = malloc(sizeof(*new));
|
||||
(*new) = make_iir(3, 1);
|
||||
|
||||
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;
|
||||
for (int i = 0; i < frames; i++) {
|
||||
soundbyte v = iir_filter(*iir, buffer[i*CHANNELS]);
|
||||
for (int j = 0; j < CHANNELS; j++) buffer[i*CHANNELS+j] = v;
|
||||
}
|
||||
}
|
||||
|
||||
struct dsp_filter hpf_make(int poles, float freq)
|
||||
dsp_node *dsp_lpf(float freq)
|
||||
{
|
||||
struct dsp_iir *new = malloc(sizeof(*new));
|
||||
*new = make_iir(3, 1);
|
||||
|
||||
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;
|
||||
struct dsp_iir *iir = malloc(sizeof(*iir));
|
||||
*iir = bqlp_dcof(2*freq/SAMPLERATE, 5);
|
||||
return make_node(iir, filter_iir);
|
||||
}
|
||||
|
||||
soundbyte fir_filter(struct dsp_fir *fir, soundbyte val)
|
||||
dsp_node *dsp_hpf(float freq)
|
||||
{
|
||||
float ret = 0.f;
|
||||
fir->dx[fir->head] = (float)val/SHRT_MAX;
|
||||
|
||||
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;
|
||||
struct dsp_iir *iir = malloc(sizeof(*iir));
|
||||
*iir = bqhp_dcof(2*freq/SAMPLERATE,5);
|
||||
return make_node(iir, filter_iir);
|
||||
}
|
||||
|
||||
void dsp_fir_fillbuf(struct dsp_fir *fir, soundbyte *out, int n)
|
||||
void filter_delay(delay *d, soundbyte *buf, int frames)
|
||||
{
|
||||
dsp_run(fir->in, out, n);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
soundbyte val = fir_filter(fir, out[i*CHANNELS]);
|
||||
|
||||
for (int j = 0; j < CHANNELS; j++)
|
||||
out[i*CHANNELS + j] = val*5;
|
||||
}
|
||||
for (int i = 0; i < frames*CHANNELS; i++) {
|
||||
buf[i] += ringshift(d->ring)*d->decay;
|
||||
ringpush(d->ring, buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
struct dsp_filter lp_fir_make(float freq)
|
||||
dsp_node *dsp_delay(double sec, double decay)
|
||||
{
|
||||
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);
|
||||
}
|
||||
delay *d = malloc(sizeof(*d));
|
||||
d->ms_delay = sec;
|
||||
d->decay = decay;
|
||||
d->ring = NULL;
|
||||
d->ring = ringnew(d->ring, sec*CHANNELS*SAMPLERATE*2); /* Circular buffer size is enough to have the delay */
|
||||
ringheader(d->ring)->write += CHANNELS*SAMPLERATE*sec;
|
||||
return make_node(d, filter_delay);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
/* decay to 3 tau */
|
||||
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_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;
|
||||
|
||||
new.ratio = 4000;
|
||||
|
@ -545,74 +470,38 @@ struct dsp_filter dsp_make_compressor()
|
|||
|
||||
struct dsp_compressor *c = malloc(sizeof(*c));
|
||||
*c = new;
|
||||
|
||||
filter.data = c;
|
||||
filter.filter = dsp_compressor_fillbuf;
|
||||
|
||||
return filter;
|
||||
return make_node(c, dsp_compressor_fillbuf);
|
||||
}
|
||||
|
||||
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;
|
||||
float db;
|
||||
db = comp->target * (val - comp->threshold) / comp->ratio;
|
||||
if (deg == 0.f) return;
|
||||
if (deg < -100) deg = -100.f;
|
||||
else if (deg > 100) deg = 100.f;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
val = short2db(out[i*CHANNELS]);
|
||||
float db1, db2;
|
||||
float pct = deg / 100.f;
|
||||
|
||||
|
||||
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);
|
||||
if (deg > 0) {
|
||||
db1 = pct2db(1 - 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;
|
||||
}
|
||||
}
|
||||
|
||||
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 pct = *deg / 100.f;
|
||||
|
||||
if (*deg > 0) {
|
||||
db1 = pct2db(1 - pct);
|
||||
db2 = pct2db(pct);
|
||||
} else {
|
||||
db1 = pct2db(1 + pct);
|
||||
db2 = pct2db(-1*pct);
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
} else {
|
||||
db1 = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dsp_mono(void *p, soundbyte *out, int n)
|
||||
|
@ -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++) {
|
||||
// for (int j = 0; j < CHANNELS; j++)
|
||||
// out[i*CHANNELS+j] = (out[i*CHANNELS+j] | 0xFF); /* Mask out the lower 8 bits */
|
||||
// }
|
||||
int i = 0;
|
||||
while (i < frames) {
|
||||
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
|
||||
|
||||
#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 {
|
||||
void (*filter)(void *data, soundbyte *out, int samples);
|
||||
void *data;
|
||||
dsp_node *dsp_mixer_node();
|
||||
dsp_node *dsp_am_mod(dsp_node *mod);
|
||||
dsp_node *dsp_rectify();
|
||||
|
||||
int inputs;
|
||||
struct dsp_filter *in[6];
|
||||
struct bus *bus;
|
||||
extern dsp_node *masterbus;
|
||||
|
||||
soundbyte cache[CHANNELS*BUF_FRAMES];
|
||||
int dirty;
|
||||
};
|
||||
|
||||
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);
|
||||
dsp_node *dsp_hpf(float freq);
|
||||
dsp_node *dsp_lpf(float freq);
|
||||
|
||||
/* atk, dec, sus, rls specify the time, in miliseconds, the phase begins */
|
||||
struct dsp_adsr {
|
||||
|
@ -63,24 +62,15 @@ struct dsp_adsr {
|
|||
float out;
|
||||
};
|
||||
|
||||
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_delay {
|
||||
unsigned int ms_delay;
|
||||
// struct circbuf buf;
|
||||
struct dsp_filter in;
|
||||
};
|
||||
typedef struct {
|
||||
unsigned int ms_delay;
|
||||
float decay; /* Each echo should be multiplied by this number */
|
||||
soundbyte *ring;
|
||||
} delay;
|
||||
|
||||
struct dsp_delay dsp_delay_make(unsigned int ms_delay);
|
||||
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];
|
||||
};
|
||||
dsp_node *dsp_delay(double sec, double decay);
|
||||
|
||||
struct dsp_compressor {
|
||||
double ratio;
|
||||
|
@ -92,69 +82,26 @@ struct dsp_compressor {
|
|||
double rls_tau;
|
||||
};
|
||||
|
||||
struct dsp_filter dsp_make_compressor();
|
||||
void dsp_compressor_fillbuf(struct dsp_compressor *comp, soundbyte *out, int n);
|
||||
dsp_node *dsp_compressor();
|
||||
|
||||
struct dsp_limiter {
|
||||
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
dsp_node *dsp_limiter(float ceil);
|
||||
dsp_node *dsp_noise_gate(float floor);
|
||||
|
||||
struct phasor phasor_make(unsigned int sr, float freq);
|
||||
|
||||
struct osc {
|
||||
float (*f)(float p);
|
||||
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);
|
||||
|
||||
dsp_node *dsp_whitenoise();
|
||||
dsp_node *dsp_pinknoise();
|
||||
|
||||
float sin_phasor(float p);
|
||||
float square_phasor(float p);
|
||||
float saw_phasor(float p);
|
||||
float tri_phasor(float p);
|
||||
|
||||
void osc_fillbuf(struct osc *osc, soundbyte *buf, int n);
|
||||
|
||||
void am_mod(struct dsp_ammod *mod, soundbyte *c, int n);
|
||||
|
||||
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);
|
||||
|
||||
dsp_node *dsp_reverb();
|
||||
dsp_node *dsp_sinewave(float amp, float freq);
|
||||
dsp_node *dsp_square(float amp, float freq, int sr, int ch);
|
||||
dsp_node *dsp_bitcrush(float sr, float res);
|
||||
void dsp_mono(void *p, soundbyte *out, int n);
|
||||
|
||||
void dsp_bitcrush(void *p, soundbyte *out, int n);
|
||||
|
||||
void dsp_run(struct dsp_filter filter, soundbyte *out, int n);
|
||||
void pan_frames(soundbyte *out, float deg, int frames);
|
||||
|
||||
#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.
|
||||
The scaling factor is what the c coefficients must be multiplied by so
|
||||
that the filter response has a maximum value of 1.
|
||||
|
||||
*/
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
float *fir_lp(int n, double fcf)
|
||||
{
|
||||
float *ret = malloc(n * sizeof(*ret));
|
||||
|
@ -640,43 +636,32 @@ float *fir_bpf(int n, double fcf1, double fcf2)
|
|||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* Biquad filters */
|
||||
|
||||
struct dsp_iir make_iir(int cofs, int order)
|
||||
struct dsp_iir make_iir(int order)
|
||||
{
|
||||
struct dsp_iir new;
|
||||
new.n = cofs;
|
||||
new.order = order;
|
||||
|
||||
new.dcof = calloc(sizeof(float), cofs *order);
|
||||
new.ccof = calloc(sizeof(float), cofs *order);
|
||||
new.dx = calloc(sizeof(float), cofs *order);
|
||||
new.dy = calloc(sizeof(float), cofs *order);
|
||||
new.n = order+1;
|
||||
new.a = calloc(sizeof(float), new.n);
|
||||
new.b = calloc(sizeof(float), new.n);
|
||||
new.x = calloc(sizeof(float), new.n);
|
||||
new.y = calloc(sizeof(float), new.n);
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
bq.ccof[0] = (b[0] / a[0]);
|
||||
bq.ccof[1] = (b[1] / a[0]);
|
||||
bq.ccof[2] = (b[2] / a[0]);
|
||||
bq.dcof[0] = 0.f;
|
||||
bq.dcof[1] = (a[1] / a[0]);
|
||||
bq.dcof[2] = (a[2] / a[0]);
|
||||
bq.a[0] = (b[0] / a[0]);
|
||||
bq.a[1] = (b[1] / a[0]);
|
||||
bq.a[2] = (b[2] / a[0]);
|
||||
bq.b[0] = 0.f;
|
||||
bq.b[1] = (a[1] / a[0]);
|
||||
bq.b[2] = (a[2] / a[0]);
|
||||
}
|
||||
|
||||
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 az = sin(w0) / (2 * Q);
|
||||
|
||||
b[0] = (1 - cos(w0)) / 2;
|
||||
b[0] = (1 - cos(w0)) / 2.0;
|
||||
b[1] = 1 - cos(w0);
|
||||
b[2] = b[0];
|
||||
|
||||
|
@ -841,10 +826,23 @@ struct dsp_iir bqhs_dcof(double fcf, float Q, float dbgain)
|
|||
return new;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Bipole Butterworth, Critically damped, and Bessel */
|
||||
/* 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)
|
||||
{
|
||||
|
@ -867,7 +865,7 @@ struct dsp_iir p2_bwlp(double fcf)
|
|||
double g = 1;
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -875,8 +873,8 @@ struct dsp_iir p2_bwlp(double fcf)
|
|||
struct dsp_iir p2_bwhp(double fcf)
|
||||
{
|
||||
struct dsp_iir new = p2_bwlp(fcf);
|
||||
new.ccof[1] *= -1;
|
||||
new.dcof[1] *= -1;
|
||||
new.a[1] *= -1;
|
||||
new.b[1] *= -1;
|
||||
|
||||
return new;
|
||||
}
|
||||
|
@ -887,7 +885,7 @@ struct dsp_iir p2_cdlp(double fcf)
|
|||
double p = 2;
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -895,8 +893,8 @@ struct dsp_iir p2_cdlp(double fcf)
|
|||
struct dsp_iir p2_cdhp(double fcf)
|
||||
{
|
||||
struct dsp_iir new = p2_cdlp(fcf);
|
||||
new.ccof[1] *= -1;
|
||||
new.dcof[1] *= -1;
|
||||
new.a[1] *= -1;
|
||||
new.b[1] *= -1;
|
||||
|
||||
return new;
|
||||
}
|
||||
|
@ -907,7 +905,7 @@ struct dsp_iir p2_beslp(double fcf)
|
|||
double p = 3;
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -915,56 +913,12 @@ struct dsp_iir p2_beslp(double fcf)
|
|||
struct dsp_iir p2_beshp(double fcf)
|
||||
{
|
||||
struct dsp_iir new = p2_beslp(fcf);
|
||||
new.ccof[1] *= -1;
|
||||
new.dcof[1] *= -1;
|
||||
new.a[1] *= -1;
|
||||
new.b[1] *= -1;
|
||||
|
||||
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 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;
|
||||
double A = a2/(4.f);
|
||||
|
||||
new.ccof[0*i] = ep * 1.f/A;
|
||||
new.ccof[1*i] = ep * -2.f/A;
|
||||
new.ccof[2*i] = ep * 1.f/A;
|
||||
new.a[0*i] = ep * 1.f/A;
|
||||
new.a[1*i] = ep * -2.f/A;
|
||||
new.a[2*i] = ep * 1.f/A;
|
||||
|
||||
new.dcof[0*i] = ep * 0.f;
|
||||
new.dcof[1*i] = ep * 2.f*(1-a2*c);
|
||||
new.dcof[2*i] = ep * -(a2*c - 2.f*a*b + 1.f);
|
||||
new.b[0*i] = ep * 0.f;
|
||||
new.b[1*i] = ep * 2.f*(1-a2*c);
|
||||
new.b[2*i] = ep * -(a2*c - 2.f*a*b + 1.f);
|
||||
}
|
||||
|
||||
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;
|
||||
double A = 1.f/(4.f);
|
||||
|
||||
new.ccof[0*i] = ep * 1.f/A;
|
||||
new.ccof[1*i] = ep * -2.f/A;
|
||||
new.ccof[2*i] = ep * 1.f/A;
|
||||
new.a[0*i] = ep * 1.f/A;
|
||||
new.a[1*i] = ep * -2.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;
|
||||
|
||||
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 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);
|
||||
s = b2*c + 2.f*b*r + 1.f;
|
||||
|
||||
new.ccof[0*i] = ep * 1.f/A;
|
||||
new.ccof[1*i] = ep * -2.f/A;
|
||||
new.ccof[2*i] = ep * 1.f/A;
|
||||
new.a[0*i] = ep * 1.f/A;
|
||||
new.a[1*i] = ep * -2.f/A;
|
||||
new.a[2*i] = ep * 1.f/A;
|
||||
|
||||
new.dcof[0*i] = 0.f;
|
||||
new.dcof[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.dcof[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[0*i] = 0.f;
|
||||
new.b[1*i] = ep * 4.f*a*(1.f+b*r)/s;
|
||||
new.b[2*i] = ep * 2.f*(b2*c-2.f*a2-1.f)/s;
|
||||
new.b[3*i] = ep * 4.f*a*(1.f-b*r)/s;
|
||||
new.b[4*i] = ep * -(b2*c - 2.f*b*r + 1.f) / s;
|
||||
}
|
||||
|
||||
return new;
|
||||
|
@ -1096,16 +1050,17 @@ double ep = 2.f/e;
|
|||
c = pow(r, 2) + pow(c, 2);
|
||||
s = b2*c + 2.f*b*r + 1.f;
|
||||
|
||||
new.ccof[0*i] = ep * 1.f/A;
|
||||
new.ccof[1*i] = ep * -2.f/A;
|
||||
new.ccof[2*i] = ep * 1.f/A;
|
||||
new.a[0*i] = ep * 1.f/A;
|
||||
new.a[1*i] = ep * -2.f/A;
|
||||
new.a[2*i] = ep * 1.f/A;
|
||||
|
||||
new.dcof[0*i] = 0.f;
|
||||
new.dcof[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.dcof[3*i] = ep * 4.f*a*(c-b*r)/s;
|
||||
new.dcof[4*i] = ep * -(b2 - 2.f*b*r + c) / s;
|
||||
new.b[0*i] = 0.f;
|
||||
new.b[1*i] = ep * 4.f*a*(c+b*r)/s;
|
||||
new.b[2*i] = ep * 2.f*(b2-2.f*a2*c-c)/s;
|
||||
new.b[3*i] = ep * 4.f*a*(c-b*r)/s;
|
||||
new.b[4*i] = ep * -(b2 - 2.f*b*r + c) / s;
|
||||
}
|
||||
|
||||
return new;
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
#ifndef IIR_H
|
||||
#define IIR_H
|
||||
|
||||
#include "dsp.h"
|
||||
#include "sound.h"
|
||||
|
||||
struct dsp_iir {
|
||||
float freq;
|
||||
int n; // Amount of constants
|
||||
int order; // How many times it's applied
|
||||
float *ccof;
|
||||
float *dcof;
|
||||
float *dx;
|
||||
float *dy;
|
||||
|
||||
struct dsp_filter in;
|
||||
float *a;
|
||||
float *b;
|
||||
float *x;
|
||||
float *y;
|
||||
};
|
||||
|
||||
struct dsp_iir make_iir(int cofs, int order);
|
||||
struct dsp_iir make_iir(int order);
|
||||
|
||||
double *binomial_mult( int n, double *p );
|
||||
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 "tsf.h"
|
||||
#include "tml.h"
|
||||
#include "mix.h"
|
||||
#include "sound.h"
|
||||
#include "log.h"
|
||||
#include "resources.h"
|
||||
#include <stdlib.h>
|
||||
#include "stb_ds.h"
|
||||
|
||||
#define TSF_BLOCK 32
|
||||
|
||||
struct dsp_midi_song gsong;
|
||||
struct dsp_filter songfil;
|
||||
|
||||
float music_pan = 0.f;
|
||||
static struct {
|
||||
char *key;
|
||||
tsf **value;
|
||||
} *sf_hash = NULL;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
struct bus *musicbus;
|
||||
tsf *make_soundfont(const char *path)
|
||||
{
|
||||
int idx = shgeti(sf_hash, path);
|
||||
if (idx != -1) return sf_hash[idx].value;
|
||||
|
||||
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;
|
||||
void *raw = slurp_file(midi, &rawlen);
|
||||
struct dsp_midi_song *ms = malloc(sizeof(*ms));
|
||||
ms->time = 0.0;
|
||||
ms->midi = tml_load_memory(raw, rawlen);
|
||||
ms->sf = tsf_copy(sf);
|
||||
return make_node(ms, dsp_midi_fillbuf);
|
||||
}
|
||||
|
||||
void play_song(const char *midi, const char *sf)
|
||||
{
|
||||
long rawlen;
|
||||
void *raw = slurp_file(midi, &rawlen);
|
||||
|
||||
gsong.midi = tml_load_memory(raw, rawlen);
|
||||
if (gsong.midi == NULL) {
|
||||
YughWarn("Midi %s not found.", midi);
|
||||
free(raw);
|
||||
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()
|
||||
{
|
||||
unplug_filter(&songfil);
|
||||
plugin_node(dsp_midi(midi, make_soundfont(sf)), masterbus);
|
||||
}
|
||||
|
|
|
@ -3,16 +3,18 @@
|
|||
|
||||
#include "tsf.h"
|
||||
#include "tml.h"
|
||||
#include "dsp.h"
|
||||
|
||||
struct dsp_midi_song {
|
||||
float bpm;
|
||||
double time;
|
||||
tsf *sf;
|
||||
tml_message *midi;
|
||||
float bpm;
|
||||
double time;
|
||||
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 dsp_midi_fillbuf(struct dsp_midi_song *song, void *out, int n);
|
||||
void music_stop();
|
||||
|
||||
#endif
|
||||
|
|
|
@ -14,7 +14,9 @@
|
|||
#include "stb_ds.h"
|
||||
|
||||
#include "dsp.h"
|
||||
#include "mix.h"
|
||||
|
||||
#define POCKETMOD_IMPLEMENTATION
|
||||
#include "pocketmod.h"
|
||||
|
||||
#include "sokol/sokol_audio.h"
|
||||
|
||||
|
@ -53,82 +55,77 @@ static struct {
|
|||
struct wav *value;
|
||||
} *wavhash = NULL;
|
||||
|
||||
static struct wav change_channels(struct wav w, int ch) {
|
||||
soundbyte *data = w.data;
|
||||
int samples = ch * w.frames;
|
||||
void change_channels(struct wav *w, int ch) {
|
||||
if (w->ch == ch) return;
|
||||
soundbyte *data = w->data;
|
||||
int samples = ch * w->frames;
|
||||
soundbyte *new = malloc(sizeof(soundbyte) * samples);
|
||||
|
||||
if (ch > w.ch) {
|
||||
if (ch > w->ch) {
|
||||
/* 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++)
|
||||
new[i * ch + j] = data[i];
|
||||
}
|
||||
} else {
|
||||
/* 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++)
|
||||
new[i * ch + j] = data[i * ch + j];
|
||||
}
|
||||
|
||||
free(w.data);
|
||||
w.data = new;
|
||||
return w;
|
||||
free(w->data);
|
||||
w->data = new;
|
||||
}
|
||||
|
||||
static struct wav change_samplerate(struct wav w, int rate) {
|
||||
float ratio = (float)rate / w.samplerate;
|
||||
int outframes = w.frames * ratio;
|
||||
void resample(soundbyte *in, soundbyte *out, int in_frames, int out_frames, int channels)
|
||||
{
|
||||
float ratio = (float)in_frames/out_frames;
|
||||
SRC_DATA ssrc;
|
||||
soundbyte *resampled = calloc(w.ch*outframes,sizeof(soundbyte));
|
||||
|
||||
ssrc.data_in = w.data;
|
||||
ssrc.data_out = resampled;
|
||||
ssrc.input_frames = w.frames;
|
||||
ssrc.output_frames = outframes;
|
||||
ssrc.data_in = in;
|
||||
ssrc.data_out = out;
|
||||
ssrc.input_frames = in_frames;
|
||||
ssrc.output_frames = out_frames;
|
||||
ssrc.src_ratio = ratio;
|
||||
|
||||
int err = src_simple(&ssrc, SRC_LINEAR, w.ch);
|
||||
if (err) {
|
||||
int err = src_simple(&ssrc, SRC_LINEAR, channels);
|
||||
if (err)
|
||||
YughError("Resampling error code %d: %s", err, src_strerror(err));
|
||||
free(resampled);
|
||||
return w;
|
||||
}
|
||||
|
||||
free(w.data);
|
||||
w.data = resampled;
|
||||
w.frames = outframes;
|
||||
w.samplerate = rate;
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
void change_samplerate(struct wav *w, int rate) {
|
||||
if (rate == w->samplerate) return;
|
||||
float ratio = (float)rate / w->samplerate;
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
w->data = resampled;
|
||||
w->frames = outframes;
|
||||
w->samplerate = rate;
|
||||
}
|
||||
|
||||
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() {
|
||||
mixer_init();
|
||||
dsp_init();
|
||||
saudio_setup(&(saudio_desc){
|
||||
.stream_cb = push_sound,
|
||||
.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) {
|
||||
int index = shgeti(wavhash, wav);
|
||||
if (index != -1)
|
||||
|
@ -150,7 +166,7 @@ struct wav *make_sound(const char *wav) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
struct wav mwav;
|
||||
struct wav *mwav = malloc(sizeof(*mwav));
|
||||
long rawlen;
|
||||
void *raw = slurp_file(wav, &rawlen);
|
||||
if (!raw) {
|
||||
|
@ -159,11 +175,11 @@ struct wav *make_sound(const char *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")) {
|
||||
#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
|
||||
YughWarn("Could not load %s because Primum was built without FLAC support.", wav);
|
||||
#endif
|
||||
|
@ -171,9 +187,9 @@ struct wav *make_sound(const char *wav) {
|
|||
else if (!strcmp(ext, "mp3")) {
|
||||
#ifndef NMP3
|
||||
drmp3_config cnf;
|
||||
mwav.data = drmp3_open_memory_and_read_pcm_frames_f32(raw, rawlen, &cnf, &mwav.frames, NULL);
|
||||
mwav.ch = cnf.channels;
|
||||
mwav.samplerate = cnf.sampleRate;
|
||||
mwav->data = drmp3_open_memory_and_read_pcm_frames_f32(raw, rawlen, &cnf, &mwav->frames, NULL);
|
||||
mwav->ch = cnf.channels;
|
||||
mwav->samplerate = cnf.sampleRate;
|
||||
#else
|
||||
YughWarn("Could not load %s because Primum was built without MP3 support.", wav);
|
||||
#endif
|
||||
|
@ -182,11 +198,11 @@ struct wav *make_sound(const char *wav) {
|
|||
#ifndef NQOA
|
||||
qoa_desc qoa;
|
||||
short *qoa_data = qoa_decode(raw, rawlen, &qoa);
|
||||
mwav.ch = qoa.channels;
|
||||
mwav.samplerate = qoa.samplerate;
|
||||
mwav.frames = qoa.samples;
|
||||
mwav.data = malloc(sizeof(soundbyte) * mwav.frames * mwav.ch);
|
||||
src_short_to_float_array(qoa_data, mwav.data, mwav.frames*mwav.ch);
|
||||
mwav->ch = qoa.channels;
|
||||
mwav->samplerate = qoa.samplerate;
|
||||
mwav->frames = qoa.samples;
|
||||
mwav->data = malloc(sizeof(soundbyte) * mwav->frames * mwav->ch);
|
||||
src_short_to_float_array(qoa_data, mwav->data, mwav->frames*mwav->ch);
|
||||
free(qoa_data);
|
||||
#else
|
||||
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 {
|
||||
YughWarn("File with unknown type '%s'.", wav);
|
||||
free (raw);
|
||||
free(mwav);
|
||||
return NULL;
|
||||
}
|
||||
free(raw);
|
||||
|
||||
if (mwav.samplerate != SAMPLERATE)
|
||||
mwav = change_samplerate(mwav, SAMPLERATE);
|
||||
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);
|
||||
shput(wavhash, wav, newwav);
|
||||
shput(wavhash, wav, mwav);
|
||||
|
||||
return newwav;
|
||||
return mwav;
|
||||
}
|
||||
|
||||
void free_sound(const char *wav) {
|
||||
|
@ -222,68 +233,52 @@ void free_sound(const char *wav) {
|
|||
shdel(wavhash, wav);
|
||||
}
|
||||
|
||||
struct soundstream *soundstream_make() {
|
||||
struct soundstream *new = malloc(sizeof(*new));
|
||||
new->buf = circbuf_make(sizeof(short), BUF_FRAMES * CHANNELS * 2);
|
||||
return new;
|
||||
void sound_fillbuf(struct sound *s, soundbyte *buf, int n) {
|
||||
int frames = s->data->frames - s->frame;
|
||||
if (frames == 0) return;
|
||||
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++;
|
||||
}
|
||||
|
||||
if(end) {
|
||||
if (s->loop)
|
||||
s->frame = 0;
|
||||
call_env(s->hook, "this.end();");
|
||||
}
|
||||
}
|
||||
|
||||
void kill_oneshot(struct sound *s) {
|
||||
void free_source(struct sound *s)
|
||||
{
|
||||
JS_FreeValue(js, s->hook);
|
||||
free(s);
|
||||
}
|
||||
|
||||
void play_oneshot(struct wav *wav) {
|
||||
struct dsp_node *dsp_source(char *path)
|
||||
{
|
||||
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->endcb = kill_oneshot;
|
||||
}
|
||||
|
||||
struct sound *play_sound(struct wav *wav) {
|
||||
struct sound *self = calloc(1, 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->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;
|
||||
self->data = make_sound(path);
|
||||
self->loop = false;
|
||||
self->hook = JS_UNDEFINED;
|
||||
dsp_node *n = make_node(self, sound_fillbuf);
|
||||
n->data_free = free_source;
|
||||
return n;
|
||||
}
|
||||
|
||||
int sound_finished(const struct sound *s) {
|
||||
return s->frame == s->data->frames;
|
||||
}
|
||||
|
||||
int sound_stopped(const struct sound *s) {
|
||||
return s->bus == NULL;
|
||||
}
|
||||
|
||||
struct mp3 make_music(const char *mp3) {
|
||||
// drmp3 new;
|
||||
// if (!drmp3_init_file(&new, mp3, NULL)) {
|
||||
|
@ -294,41 +289,6 @@ struct mp3 make_music(const char *mp3) {
|
|||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (pct <= 0) return -72.f;
|
||||
|
||||
|
|
|
@ -1,81 +1,59 @@
|
|||
#ifndef SOUND_H
|
||||
#define SOUND_H
|
||||
|
||||
#include "cbuf.h"
|
||||
#include "script.h"
|
||||
|
||||
typedef float soundbyte;
|
||||
|
||||
struct soundstream {
|
||||
struct circbuf *buf;
|
||||
};
|
||||
|
||||
struct soundstream *soundstream_make();
|
||||
struct dsp_node;
|
||||
|
||||
/* A bookmark into a wav, actually playing the sound */
|
||||
struct sound {
|
||||
int loop; /* How many times to loop */
|
||||
typedef struct sound {
|
||||
unsigned int frame; /* Pointing to the current frame on the wav */
|
||||
int playing;
|
||||
float gain;
|
||||
|
||||
struct wav *data;
|
||||
struct bus *bus;
|
||||
|
||||
void (*endcb)(struct sound*);
|
||||
};
|
||||
int loop;
|
||||
JSValue hook;
|
||||
} sound;
|
||||
|
||||
/* Represents a sound file source, fulled loaded*/
|
||||
struct wav {
|
||||
typedef struct wav {
|
||||
unsigned int ch;
|
||||
unsigned int samplerate;
|
||||
unsigned long long frames;
|
||||
float gain; /* In dB */
|
||||
soundbyte *data;
|
||||
};
|
||||
} wav;
|
||||
|
||||
/* Represents a sound file stream */
|
||||
struct mp3 {
|
||||
typedef struct mp3 {
|
||||
|
||||
};
|
||||
} mp3;
|
||||
|
||||
typedef struct qoa {
|
||||
// struct qoa_desc desc;
|
||||
} qoa;
|
||||
|
||||
void sound_init();
|
||||
void audio_open(const char *device);
|
||||
void audio_close();
|
||||
|
||||
void sound_fillbuf(struct sound *s, soundbyte *buf, int n);
|
||||
|
||||
struct wav *make_sound(const char *wav);
|
||||
void free_sound(const char *wav);
|
||||
void wav_norm_gain(struct wav *w, double lv);
|
||||
struct sound *play_sound(struct wav *wav);
|
||||
void play_oneshot(struct wav *wav);
|
||||
struct dsp_node *dsp_source(char *path);
|
||||
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);
|
||||
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);
|
||||
|
||||
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);
|
||||
short db2short(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 pct2mult(float pct);
|
||||
|
||||
void audio_init();
|
||||
|
||||
|
||||
|
||||
#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:)
|
||||
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_others_item];
|
||||
[app_menu addItem:show_all_item];
|
||||
[app_menu addItem:fullscreen_item];
|
||||
[app_menu addItem:[NSMenuItem separatorItem]];
|
||||
[app_menu addItem:quit_menu_item];
|
||||
app_menu_item.submenu = app_menu;
|
||||
|
|
|
@ -77,7 +77,7 @@ void timer_start(struct timer *t) {
|
|||
|
||||
void timer_remove(int id) {
|
||||
struct timer *t = id2timer(id);
|
||||
free_callee(t->data);
|
||||
// free_callee(t->data);
|
||||
t->next = first;
|
||||
t->on = 0;
|
||||
first = id;
|
||||
|
@ -94,7 +94,7 @@ struct timer *id2timer(int id) {
|
|||
|
||||
void timers_free()
|
||||
{
|
||||
for (int i = 0; i < arrlen(timers); i++)
|
||||
if (timers[i].on)
|
||||
free_callee(timers[i].data);
|
||||
// for (int i = 0; i < arrlen(timers); i++)
|
||||
// if (timers[i].on)
|
||||
// free_callee(timers[i].data);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#include "window.h"
|
||||
#include "sound.h"
|
||||
#include "resources.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "datastream.h"
|
||||
|
@ -142,8 +141,10 @@ const char *engine_info()
|
|||
static int argc;
|
||||
static char **args;
|
||||
|
||||
struct datastream *bjork;
|
||||
|
||||
void c_init() {
|
||||
sound_init();
|
||||
|
||||
input_init();
|
||||
script_evalf("world_start();");
|
||||
|
||||
|
@ -151,6 +152,7 @@ void c_init() {
|
|||
window_set_icon("icons/moon.gif");
|
||||
window_resize(sapp_width(), sapp_height());
|
||||
script_evalf("Game.init();");
|
||||
// bjork = ds_openvideo("bjork.mpg");
|
||||
}
|
||||
|
||||
int frame_fps() {
|
||||
|
@ -160,6 +162,7 @@ int frame_fps() {
|
|||
static void process_frame()
|
||||
{
|
||||
double elapsed = stm_sec(stm_laptime(&frame_t));
|
||||
// ds_advance(bjork, elapsed);
|
||||
input_poll(0);
|
||||
/* Timers all update every frame - once per monitor refresh */
|
||||
timer_update(elapsed, timescale);
|
||||
|
@ -374,7 +377,7 @@ dam->update_activity(dam, &da, NULL, NULL);
|
|||
stm_setup(); /* time */
|
||||
start_t = frame_t = stm_now();
|
||||
physlast = updatelast = start_t;
|
||||
|
||||
sound_init();
|
||||
resources_init();
|
||||
phys2d_init();
|
||||
script_startup();
|
||||
|
@ -398,7 +401,6 @@ dam->update_activity(dam, &da, NULL, NULL);
|
|||
start_desc.width = mainwin.width;
|
||||
start_desc.height = mainwin.height;
|
||||
start_desc.fullscreen = 0;
|
||||
|
||||
sapp_run(&start_desc);
|
||||
|
||||
return 0;
|
||||
|
|
Loading…
Reference in a new issue