Initial commit.
parent
2bc375a5cb
commit
b94eb57717
|
@ -0,0 +1,46 @@
|
|||
|
||||
#CC = gcc -O0 -march=native -Wall -pedantic -std=gnu11 -DJL_LEAK_CHECK \
|
||||
-Warray-bounds
|
||||
|
||||
CC = gcc -Wall -std=gnu11 -march=native -Ofast -flto -fopenmp -pthread
|
||||
|
||||
LIBS = sndfile jack alsa fftw3
|
||||
|
||||
PKGCONFIG = $(shell which pkg-config)
|
||||
|
||||
CFLAGS = $(shell $(PKGCONFIG) --cflags $(LIBS))
|
||||
|
||||
LDFLAGS = $(shell $(PKGCONFIG) --libs $(LIBS)) \
|
||||
-Wall -march=native -Ofast -lm -lpthread
|
||||
|
||||
SRC = sampler.c sound.c soundconfig.c sample.c soundfile.c fx.c \
|
||||
parse.c jl_ringbuf.c jl_mem.c ini.c init.c main.c
|
||||
|
||||
OBJS = $(SRC:.c=.o)
|
||||
|
||||
all: jlsampler
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -c -o $(@F) $(CFLAGS) $<
|
||||
|
||||
jlsampler: $(OBJS)
|
||||
$(CC) -o $(@F) $(LDFLAGS) $(OBJS)
|
||||
|
||||
SAMPLE_TEST_SRC = sample_test.c sample.c jl_mem.c
|
||||
SAMPLE_TEST_OBJS = $(SAMPLE_TEST_SRC:.c=.o)
|
||||
sample_test: $(SAMPLE_TEST_SRC) $(SAMPLE_TEST_OBJS)
|
||||
$(CC) -o $(@F) $(LDFLAGS) $(SAMPLE_TEST_OBJS)
|
||||
./sample_test
|
||||
|
||||
SOUND_TEST_SRC = sound_test.c sound.c sample.c ini.c jl_mem.c
|
||||
SOUND_TEST_OBJS = $(SOUND_TEST_SRC:.c=.o)
|
||||
sound_test: $(SOUND_TEST_SRC) $(SOUND_TEST_OBJS)
|
||||
$(CC) -o $(@F) $(LDFLAGS) $(SOUND_TEST_OBJS)
|
||||
./sound_test
|
||||
|
||||
test: sample_test sound_test
|
||||
|
||||
clean:
|
||||
rm -f jlsampler $(OBJS) \
|
||||
$(SAMPLE_TEST_OBJS) sample_test \
|
||||
$(SOUND_TEST_OBJS) sound_test
|
|
@ -1,2 +1,8 @@
|
|||
# jlsampler
|
||||
|
||||
TODO:
|
||||
|
||||
* In-memory storage of samples as int16 type.
|
||||
* Tuning via tuning file
|
||||
* Layer mixing
|
||||
* Fake RC Layer for single layered sounds
|
|
@ -0,0 +1,23 @@
|
|||
#ifndef const_HEADER_
|
||||
#define const_HEADER_
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
// Global controls.
|
||||
bool gl_sustain;
|
||||
|
||||
#define MIDI_EVT_NOTE 0
|
||||
#define MIDI_EVT_CTRL 1
|
||||
|
||||
#define SAMPLE_RATE 48000
|
||||
#define MAX_PLAYING 256
|
||||
#define MAX_MIDI_CHANS 128
|
||||
#define MAX_SOUNDS 128
|
||||
#define MAX_LAYERS 128
|
||||
#define MAX_VARS 128
|
||||
#define MIN_AMP 1e-6
|
||||
#define MAX_NAME 128
|
||||
#define MAX_FX 8
|
||||
#define MAX_FX_LINE 256
|
||||
|
||||
#endif
|
|
@ -0,0 +1,381 @@
|
|||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
#include <complex.h>
|
||||
#include <pthread.h>
|
||||
#include <fftw3.h>
|
||||
#include "jl_mem.h"
|
||||
#include "soundfile.h"
|
||||
#include "fx.h"
|
||||
|
||||
void fx_amp(Sample *s, double gain) {
|
||||
for(int i = 0; i < s->len; ++i) {
|
||||
s->L[i] *= gain;
|
||||
s->R[i] *= gain;
|
||||
}
|
||||
}
|
||||
|
||||
void fx_balance(Sample *s, double balance) {
|
||||
if(balance == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(balance < -1) {
|
||||
balance = -1;
|
||||
} else if(balance > 1) {
|
||||
balance = 1;
|
||||
}
|
||||
|
||||
if(balance < 0) {
|
||||
for(int i = 0; i < s->len; ++i) {
|
||||
s->L[i] *= 1 + balance;
|
||||
}
|
||||
} else {
|
||||
for(int i = 0; i < s->len; ++i) {
|
||||
s->R[i] *= 1 - balance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fx_divide_rms(Sample *s, double t_ms) {
|
||||
int di = t_ms * SAMPLE_RATE / 1000.0;
|
||||
if(di > s->len) {
|
||||
di = s->len;
|
||||
}
|
||||
|
||||
double rms = 0;
|
||||
double x;
|
||||
for(int i = 0; i < di; ++i) {
|
||||
x = s->L[i];
|
||||
rms += x*x;
|
||||
x = s->R[i];
|
||||
rms += x*x;
|
||||
}
|
||||
|
||||
rms = sqrt(rms / (2.0 * di));
|
||||
fx_amp(s, 1.0 / rms);
|
||||
}
|
||||
|
||||
void fx_fade_in(Sample *s, double fade_ms) {
|
||||
int n_fade = fade_ms * SAMPLE_RATE / 1000.0;
|
||||
for(int i = 0; i < n_fade && i < s->len; ++i) {
|
||||
double val = (double)i / (double)n_fade;
|
||||
s->L[i] *= val;
|
||||
s->R[i] *= val;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_t fftw_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static fftw_complex *fft(int fft_len, int in_len, float *in_data) {
|
||||
|
||||
double *in = jl_malloc_exit(fft_len * sizeof(double));
|
||||
for(int i = 0; i < in_len; ++i) {
|
||||
in[i] = in_data[i];
|
||||
}
|
||||
for(int i = in_len; i < fft_len; ++i) {
|
||||
in[i] = 0;
|
||||
}
|
||||
|
||||
fftw_complex *out = jl_malloc_exit(fft_len * sizeof(fftw_complex));
|
||||
|
||||
pthread_mutex_lock(&fftw_mutex);
|
||||
fftw_plan p = fftw_plan_dft_r2c_1d(fft_len, in, out, FFTW_ESTIMATE);
|
||||
pthread_mutex_unlock(&fftw_mutex);
|
||||
|
||||
fftw_execute(p);
|
||||
|
||||
|
||||
pthread_mutex_lock(&fftw_mutex);
|
||||
fftw_destroy_plan(p);
|
||||
pthread_mutex_unlock(&fftw_mutex);
|
||||
|
||||
jl_free(in);
|
||||
return out;
|
||||
}
|
||||
|
||||
static float *rfft(int fft_len, fftw_complex *in, int out_len) {
|
||||
|
||||
double *out = jl_malloc_exit(fft_len * sizeof(double));
|
||||
|
||||
pthread_mutex_lock(&fftw_mutex);
|
||||
fftw_plan p = fftw_plan_dft_c2r_1d(fft_len, in, out, FFTW_ESTIMATE);
|
||||
pthread_mutex_unlock(&fftw_mutex);
|
||||
|
||||
fftw_execute(p);
|
||||
|
||||
pthread_mutex_lock(&fftw_mutex);
|
||||
fftw_destroy_plan(p);
|
||||
pthread_mutex_unlock(&fftw_mutex);
|
||||
|
||||
float *f_out = jl_malloc_exit(out_len * sizeof(float));
|
||||
|
||||
for(int i = 0; i < out_len; ++i) {
|
||||
f_out[i] = out[i] / (float)fft_len;
|
||||
}
|
||||
|
||||
jl_free(out);
|
||||
return f_out;
|
||||
}
|
||||
|
||||
void fx_ir(Sample *s, const char *path, double mix) {
|
||||
if(mix <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Sample ir;
|
||||
ir.len = soundfile_load_stereo(path, &ir.L, &ir.R);
|
||||
|
||||
int out_len = ir.len + s->len - 1;
|
||||
|
||||
// Using a power of 2 fft length makes the computations MUCH faster.
|
||||
int fft_len = pow(2, ceil(log2(out_len)));
|
||||
|
||||
fftw_complex *ir_L = fft(fft_len, ir.len, ir.L);
|
||||
fftw_complex *ir_R = fft(fft_len, ir.len, ir.R);
|
||||
|
||||
sample_free(&ir);
|
||||
|
||||
fftw_complex *L = fft(fft_len, s->len, s->L);
|
||||
fftw_complex *R = fft(fft_len, s->len, s->R);
|
||||
|
||||
// Convolve.
|
||||
for(int i = 0; i < fft_len; ++i) {
|
||||
L[i] *= ir_L[i];
|
||||
R[i] *= ir_R[i];
|
||||
}
|
||||
|
||||
// Reverse fft.
|
||||
float *L_wet = rfft(fft_len, L, out_len);
|
||||
float *R_wet = rfft(fft_len, R, out_len);
|
||||
|
||||
if(mix < 1) {
|
||||
for(int i = 0; i < out_len; ++i) {
|
||||
L_wet[i] *= mix;
|
||||
R_wet[i] *= mix;
|
||||
}
|
||||
for(int i = 0; i < s->len; ++i) {
|
||||
L_wet[i] += (1 - mix) * s->L[i];
|
||||
R_wet[i] += (1 - mix) * s->R[i];
|
||||
}
|
||||
}
|
||||
|
||||
jl_free(ir_L);
|
||||
jl_free(ir_R);
|
||||
jl_free(L);
|
||||
jl_free(R);
|
||||
|
||||
sample_set_data(s, L_wet, R_wet, out_len);
|
||||
}
|
||||
|
||||
void fx_mono(Sample *s) {
|
||||
for(int i = 0; i < s->len; ++i) {
|
||||
s->L[i] += s->R[i];
|
||||
s->L[i] /= 2.0;
|
||||
s->R[i] = s->L[i];
|
||||
}
|
||||
}
|
||||
|
||||
void fx_pad_multiple(Sample *s, int n) {
|
||||
if(s->len % n == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute new length.
|
||||
int len = s->len + n;
|
||||
len -= len % n;
|
||||
|
||||
// Allocate arrays.
|
||||
float *L = jl_malloc_exit(len * sizeof(float));
|
||||
float *R = jl_malloc_exit(len * sizeof(float));
|
||||
|
||||
// Zero padding samples.
|
||||
for(int i = s->len; i < len; ++i) {
|
||||
L[i] = 0;
|
||||
R[i] = 0;
|
||||
}
|
||||
|
||||
// Copy data.
|
||||
for(int i = 0; i < s->len; ++i) {
|
||||
L[i] = s->L[i];
|
||||
R[i] = s->R[i];
|
||||
}
|
||||
|
||||
sample_set_data(s, L, R, len);
|
||||
}
|
||||
|
||||
void fx_pan(Sample *s, double pan) {
|
||||
if(pan < 0) {
|
||||
for(int i = 0; i < s->len; ++i) {
|
||||
s->L[i] += s->R[i] * -pan;
|
||||
s->R[i] *= (1 + pan);
|
||||
}
|
||||
} else {
|
||||
for(int i = 0; i < s->len; ++i) {
|
||||
s->R[i] += s->L[i] * pan;
|
||||
s->L[i] *= (1 - pan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static float interp(float *v, double idx) {
|
||||
int i = (int)idx;
|
||||
double mu = idx - i;
|
||||
return mu * v[i+1] + (1 - mu) * v[i];
|
||||
}
|
||||
|
||||
void fx_playback_speed(Sample *s, double speed) {
|
||||
int len = s->len / speed;
|
||||
float *L = jl_malloc_exit(len * sizeof(float));
|
||||
float *R = jl_malloc_exit(len * sizeof(float));
|
||||
|
||||
double idx = 0;
|
||||
for(int i = 0; i < len; ++i) {
|
||||
L[i] = interp(s->L, idx);
|
||||
R[i] = interp(s->R, idx);
|
||||
idx += speed;
|
||||
}
|
||||
|
||||
sample_set_data(s, L, R, len);
|
||||
}
|
||||
|
||||
void fx_pre_cut(Sample *s, double pct, double fade_ms) {
|
||||
float th = pct / 100.0;
|
||||
|
||||
int i_cut = 0;
|
||||
|
||||
for(i_cut = 0; i_cut < s->len; ++i_cut) {
|
||||
if(s->L[i_cut] > th ||
|
||||
s->R[i_cut] > th ||
|
||||
s->L[i_cut] < -th ||
|
||||
s->R[i_cut] < -th) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(i_cut == s->len) {
|
||||
printf("Warning: pre-cut threshold not reached.\n");
|
||||
fx_fade_in(s, fade_ms);
|
||||
return;
|
||||
}
|
||||
|
||||
i_cut -= fade_ms * SAMPLE_RATE / 1000.0;
|
||||
if(i_cut <= 0) {
|
||||
fx_fade_in(s, fade_ms);
|
||||
return;
|
||||
}
|
||||
|
||||
if(i_cut > SAMPLE_RATE * 5 / 1000) {
|
||||
printf("Warning: cutting %i ms from sample.\n",
|
||||
i_cut * 1000 / SAMPLE_RATE);
|
||||
}
|
||||
|
||||
int len = s->len - i_cut;
|
||||
|
||||
// Allocate arrays.
|
||||
float *L = jl_malloc_exit(len * sizeof(float));
|
||||
float *R = jl_malloc_exit(len * sizeof(float));
|
||||
|
||||
for(int i = 0; i < len; ++i) {
|
||||
L[i] = s->L[i_cut + i];
|
||||
R[i] = s->R[i_cut + i];
|
||||
}
|
||||
|
||||
sample_set_data(s, L, R, len);
|
||||
fx_fade_in(s, fade_ms);
|
||||
}
|
||||
|
||||
static float *delay_array(float *in, int in_len, int out_len, int delay) {
|
||||
float *out = jl_malloc_exit(out_len * sizeof(float));
|
||||
for(int i = 0; i < delay; ++i) {
|
||||
out[i] = 0;
|
||||
}
|
||||
for(int i = 0; i < in_len; ++i) {
|
||||
out[delay + i] = in[i];
|
||||
}
|
||||
for(int i = delay + in_len; i < out_len; ++i) {
|
||||
out[i] = 0;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void fx_pre_delay(Sample *s, double left_ms, double right_ms) {
|
||||
int di_L = left_ms * SAMPLE_RATE / 1000;
|
||||
int di_R = right_ms * SAMPLE_RATE / 1000;
|
||||
|
||||
int out_len = s->len;
|
||||
out_len += di_L > di_R ? di_L : di_R;
|
||||
|
||||
float *L = delay_array(s->L, s->len, out_len, di_L);
|
||||
float *R = delay_array(s->R, s->len, out_len, di_R);
|
||||
|
||||
sample_set_data(s, L, R, out_len);
|
||||
}
|
||||
|
||||
void fx_rc_highpass(Sample *s, double freq, int order) {
|
||||
fx_rc_lowshelf(s, freq, order, -1);
|
||||
}
|
||||
|
||||
void fx_rc_lowpass(Sample *s, double freq, int order) {
|
||||
fx_rc_highshelf(s, freq, order, -1);
|
||||
}
|
||||
|
||||
static double freq_3db(double freq, int order) {
|
||||
return freq / sqrt(pow(2.0, 1.0 / ((double)order)) - 1.0);
|
||||
}
|
||||
|
||||
static void rc_highshelf1(float *data, int len, double freq, double gain) {
|
||||
double rc = 1.0 / (freq * 2.0 * M_PI);
|
||||
double dt = 1.0 / SAMPLE_RATE;
|
||||
double alpha = dt / (rc + dt);
|
||||
double prev = 0;
|
||||
|
||||
for (int i = 0; i < len; ++i) {
|
||||
prev *= (1 - alpha);
|
||||
prev += alpha * data[i];
|
||||
data[i] += gain * (data[i] - prev);
|
||||
}
|
||||
}
|
||||
|
||||
static void rc_lowshelf1(float *data, int len, double freq, double gain) {
|
||||
double rc = 1.0 / (freq * 2.0 * M_PI);
|
||||
double dt = 1.0 / SAMPLE_RATE;
|
||||
double alpha = dt / (rc + dt);
|
||||
double prev = 0;
|
||||
|
||||
for (int i = 0; i < len; ++i) {
|
||||
prev *= (1 - alpha);
|
||||
prev += alpha * data[i];
|
||||
data[i] += gain * prev;
|
||||
}
|
||||
}
|
||||
|
||||
void fx_rc_highshelf(Sample *s, double freq, int order, double gain) {
|
||||
freq = freq_3db(freq, order);
|
||||
for(int i = 0; i < order; ++i) {
|
||||
rc_highshelf1(s->L, s->len, freq, gain);
|
||||
rc_highshelf1(s->R, s->len, freq, gain);
|
||||
}
|
||||
}
|
||||
|
||||
void fx_rc_lowshelf(Sample *s, double freq, int order, double gain) {
|
||||
freq = freq_3db(freq, order);
|
||||
for(int i = 0; i < order; ++i) {
|
||||
rc_lowshelf1(s->L, s->len, freq, gain);
|
||||
rc_lowshelf1(s->R, s->len, freq, gain);
|
||||
}
|
||||
}
|
||||
|
||||
// See http://musicdsp.org/showArchiveComment.php?ArchiveID=256
|
||||
void fx_stereo_width(Sample *s, double width) {
|
||||
float w2 = width * 0.5;
|
||||
float m, p;
|
||||
|
||||
for(int i = 0; i < s->len; ++i) {
|
||||
m = (s->L[i] + s->R[i]) * 0.5;
|
||||
p = (s->R[i] - s->L[i]) * w2;
|
||||
s->L[i] = m - p;
|
||||
s->R[i] = m + p;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
#ifndef fx_HEADER_
|
||||
#define fx_HEADER_
|
||||
|
||||
#include "sample.h"
|
||||
|
||||
void fx_amp(Sample *s, double gain);
|
||||
void fx_balance(Sample *s, double balance);
|
||||
void fx_divide_rms(Sample *s, double t_ms);
|
||||
void fx_fade_in(Sample *s, double t_ms);
|
||||
void fx_ir(Sample *s, const char *path, double mix);
|
||||
void fx_mono(Sample *s);
|
||||
void fx_pad_multiple(Sample *s, int n); // INTERNAL ONLY.
|
||||
void fx_pan(Sample *s, double pan);
|
||||
void fx_playback_speed(Sample *s, double speed);
|
||||
void fx_pre_cut(Sample *s, double pct, double fade_ms);
|
||||
void fx_pre_delay(Sample *s, double left_ms, double right_ms);
|
||||
void fx_rc_highpass(Sample *s, double freq, int order);
|
||||
void fx_rc_lowpass(Sample *s, double freq, int order);
|
||||
void fx_rc_highshelf(Sample *s, double freq, int order, double gain);
|
||||
void fx_rc_lowshelf(Sample *s, double freq, int order, double gain);
|
||||
void fx_stereo_width(Sample *s, double width);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,194 @@
|
|||
/* inih -- simple .INI file parser
|
||||
|
||||
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||
home page for more info:
|
||||
|
||||
https://github.com/benhoyt/inih
|
||||
|
||||
*/
|
||||
|
||||
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ini.h"
|
||||
|
||||
#if !INI_USE_STACK
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#define MAX_SECTION 50
|
||||
#define MAX_NAME 50
|
||||
|
||||
/* Strip whitespace chars off end of given string, in place. Return s. */
|
||||
static char* rstrip(char* s)
|
||||
{
|
||||
char* p = s + strlen(s);
|
||||
while (p > s && isspace((unsigned char)(*--p)))
|
||||
*p = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Return pointer to first non-whitespace char in given string. */
|
||||
static char* lskip(const char* s)
|
||||
{
|
||||
while (*s && isspace((unsigned char)(*s)))
|
||||
s++;
|
||||
return (char*)s;
|
||||
}
|
||||
|
||||
/* Return pointer to first char (of chars) or inline comment in given string,
|
||||
or pointer to null at end of string if neither found. Inline comment must
|
||||
be prefixed by a whitespace character to register as a comment. */
|
||||
static char* find_chars_or_comment(const char* s, const char* chars)
|
||||
{
|
||||
#if INI_ALLOW_INLINE_COMMENTS
|
||||
int was_space = 0;
|
||||
while (*s && (!chars || !strchr(chars, *s)) &&
|
||||
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
|
||||
was_space = isspace((unsigned char)(*s));
|
||||
s++;
|
||||
}
|
||||
#else
|
||||
while (*s && (!chars || !strchr(chars, *s))) {
|
||||
s++;
|
||||
}
|
||||
#endif
|
||||
return (char*)s;
|
||||
}
|
||||
|
||||
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
|
||||
static char* strncpy0(char* dest, const char* src, size_t size)
|
||||
{
|
||||
strncpy(dest, src, size);
|
||||
dest[size - 1] = '\0';
|
||||
return dest;
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
|
||||
void* user)
|
||||
{
|
||||
/* Uses a fair bit of stack (use heap instead if you need to) */
|
||||
#if INI_USE_STACK
|
||||
char line[INI_MAX_LINE];
|
||||
#else
|
||||
char* line;
|
||||
#endif
|
||||
char section[MAX_SECTION] = "";
|
||||
char prev_name[MAX_NAME] = "";
|
||||
|
||||
char* start;
|
||||
char* end;
|
||||
char* name;
|
||||
char* value;
|
||||
int lineno = 0;
|
||||
int error = 0;
|
||||
|
||||
#if !INI_USE_STACK
|
||||
line = (char*)malloc(INI_MAX_LINE);
|
||||
if (!line) {
|
||||
return -2;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Scan through stream line by line */
|
||||
while (reader(line, INI_MAX_LINE, stream) != NULL) {
|
||||
lineno++;
|
||||
|
||||
start = line;
|
||||
#if INI_ALLOW_BOM
|
||||
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
|
||||
(unsigned char)start[1] == 0xBB &&
|
||||
(unsigned char)start[2] == 0xBF) {
|
||||
start += 3;
|
||||
}
|
||||
#endif
|
||||
start = lskip(rstrip(start));
|
||||
|
||||
if (*start == ';' || *start == '#') {
|
||||
/* Per Python configparser, allow both ; and # comments at the
|
||||
start of a line */
|
||||
}
|
||||
#if INI_ALLOW_MULTILINE
|
||||
else if (*prev_name && *start && start > line) {
|
||||
/* Non-blank line with leading whitespace, treat as continuation
|
||||
of previous name's value (as per Python configparser). */
|
||||
if (!handler(user, section, prev_name, start) && !error)
|
||||
error = lineno;
|
||||
}
|
||||
#endif
|
||||
else if (*start == '[') {
|
||||
/* A "[section]" line */
|
||||
end = find_chars_or_comment(start + 1, "]");
|
||||
if (*end == ']') {
|
||||
*end = '\0';
|
||||
strncpy0(section, start + 1, sizeof(section));
|
||||
*prev_name = '\0';
|
||||
}
|
||||
else if (!error) {
|
||||
/* No ']' found on section line */
|
||||
error = lineno;
|
||||
}
|
||||
}
|
||||
else if (*start) {
|
||||
/* Not a comment, must be a name[=:]value pair */
|
||||
end = find_chars_or_comment(start, "=:");
|
||||
if (*end == '=' || *end == ':') {
|
||||
*end = '\0';
|
||||
name = rstrip(start);
|
||||
value = lskip(end + 1);
|
||||
#if INI_ALLOW_INLINE_COMMENTS
|
||||
end = find_chars_or_comment(value, NULL);
|
||||
if (*end)
|
||||
*end = '\0';
|
||||
#endif
|
||||
rstrip(value);
|
||||
|
||||
/* Valid name[=:]value pair found, call handler */
|
||||
strncpy0(prev_name, name, sizeof(prev_name));
|
||||
if (!handler(user, section, name, value) && !error)
|
||||
error = lineno;
|
||||
}
|
||||
else if (!error) {
|
||||
/* No '=' or ':' found on name[=:]value line */
|
||||
error = lineno;
|
||||
}
|
||||
}
|
||||
|
||||
#if INI_STOP_ON_FIRST_ERROR
|
||||
if (error)
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !INI_USE_STACK
|
||||
free(line);
|
||||
#endif
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int ini_parse_file(FILE* file, ini_handler handler, void* user)
|
||||
{
|
||||
return ini_parse_stream((ini_reader)fgets, file, handler, user);
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int ini_parse(const char* filename, ini_handler handler, void* user)
|
||||
{
|
||||
FILE* file;
|
||||
int error;
|
||||
|
||||
file = fopen(filename, "r");
|
||||
if (!file)
|
||||
return -1;
|
||||
error = ini_parse_file(file, handler, user);
|
||||
fclose(file);
|
||||
return error;
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/* inih -- simple .INI file parser
|
||||
|
||||
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||
home page for more info:
|
||||
|
||||
https://github.com/benhoyt/inih
|
||||
|
||||
*/
|
||||
|
||||
#ifndef __INI_H__
|
||||
#define __INI_H__
|
||||
|
||||
/* Make this header file easier to include in C++ code */
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/* Typedef for prototype of handler function. */
|
||||
typedef int (*ini_handler)(void* user, const char* section,
|
||||
const char* name, const char* value);
|
||||
|
||||
/* Typedef for prototype of fgets-style reader function. */
|
||||
typedef char* (*ini_reader)(char* str, int num, void* stream);
|
||||
|
||||
/* Parse given INI-style file. May have [section]s, name=value pairs
|
||||
(whitespace stripped), and comments starting with ';' (semicolon). Section
|
||||
is "" if name=value pair parsed before any section heading. name:value
|
||||
pairs are also supported as a concession to Python's configparser.
|
||||
|
||||
For each name=value pair parsed, call handler function with given user
|
||||
pointer as well as section, name, and value (data only valid for duration
|
||||
of handler call). Handler should return nonzero on success, zero on error.
|
||||
|
||||
Returns 0 on success, line number of first error on parse error (doesn't
|
||||
stop on first error), -1 on file open error, or -2 on memory allocation
|
||||
error (only when INI_USE_STACK is zero).
|
||||
*/
|
||||
int ini_parse(const char* filename, ini_handler handler, void* user);
|
||||
|
||||
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
|
||||
close the file when it's finished -- the caller must do that. */
|
||||
int ini_parse_file(FILE* file, ini_handler handler, void* user);
|
||||
|
||||
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
|
||||
filename. Used for implementing custom or string-based I/O. */
|
||||
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
|
||||
void* user);
|
||||
|
||||
/* Nonzero to allow multi-line value parsing, in the style of Python's
|
||||
configparser. If allowed, ini_parse() will call the handler with the same
|
||||
name for each subsequent line parsed. */
|
||||
#ifndef INI_ALLOW_MULTILINE
|
||||
#define INI_ALLOW_MULTILINE 1
|
||||
#endif
|
||||
|
||||
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
|
||||
the file. See http://code.google.com/p/inih/issues/detail?id=21 */
|
||||
#ifndef INI_ALLOW_BOM
|
||||
#define INI_ALLOW_BOM 1
|
||||
#endif
|
||||
|
||||
/* Nonzero to allow inline comments (with valid inline comment characters
|
||||
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
|
||||
Python 3.2+ configparser behaviour. */
|
||||
#ifndef INI_ALLOW_INLINE_COMMENTS
|
||||
#define INI_ALLOW_INLINE_COMMENTS 1
|
||||
#endif
|
||||
#ifndef INI_INLINE_COMMENT_PREFIXES
|
||||
#define INI_INLINE_COMMENT_PREFIXES ";"
|
||||
#endif
|
||||
|
||||
/* Nonzero to use stack, zero to use heap (malloc/free). */
|
||||
#ifndef INI_USE_STACK
|
||||
#define INI_USE_STACK 1
|
||||
#endif
|
||||
|
||||
/* Stop parsing on first error (default is to keep parsing). */
|
||||
#ifndef INI_STOP_ON_FIRST_ERROR
|
||||
#define INI_STOP_ON_FIRST_ERROR 0
|
||||
#endif
|
||||
|
||||
/* Maximum line length for any line in INI file. */
|
||||
#ifndef INI_MAX_LINE
|
||||
#define INI_MAX_LINE 200
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __INI_H__ */
|
|
@ -0,0 +1,260 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include "ini.h"
|
||||
#include "parse.h"
|
||||
#include "init.h"
|
||||
|
||||
static int sound_index(Sampler *s, const char *name) {
|
||||
for(int i = 0; i < s->num_sounds; ++i) {
|
||||
if(strncmp(s->index[i], name, MAX_NAME) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int sound_index_add(Sampler *s, const char *name) {
|
||||
if(s->num_sounds >= MAX_SOUNDS) {
|
||||
printf("Maximum number of sounds exceeded: %s\n", name);
|
||||
exit(1);
|
||||
}
|
||||
strncpy(s->index[s->num_sounds], name, MAX_NAME);
|
||||
s->num_sounds += 1;
|
||||
return s->num_sounds - 1;
|
||||
}
|
||||
|
||||
static int sampler_handler(void *user,
|
||||
const char *section,
|
||||
const char *name,
|
||||
const char *value) {
|
||||
|
||||
Sampler *s = (Sampler*)user;
|
||||
|
||||
if(strcmp(section, "SAMPLER") != 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("%s: %s = %s\n", section, name, value);
|
||||
if(strcmp(name, "base_dir") == 0) {
|
||||
strncpy(s->base_dir, value, PATH_MAX);
|
||||
}
|
||||
else if(strcmp(name, "midi_min") == 0) {
|
||||
s->midi_min_vel = parse_int(value);
|
||||
}
|
||||
else if(strcmp(name, "midi_max") == 0) {
|
||||
s->midi_max_vel = parse_int(value);
|
||||
} else {
|
||||
printf("Unknown setting.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int trigger_handler(void *user,
|
||||
const char *section,
|
||||
const char *name,
|
||||
const char *value) {
|
||||
|
||||
Sampler *s = (Sampler*)user;
|
||||
|
||||
if(strcmp(section, "TRIGGERS") != 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("%s: %s = %s\n", section, name, value);
|
||||
|
||||
int from = parse_int(name);
|
||||
if(from < 0 || from > 127) {
|
||||
printf("Illegal trigger: %i\n", from);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int to = sound_index(s, value);
|
||||
if(to == -1) {
|
||||
to = sound_index_add(s, value);
|
||||
}
|
||||
|
||||
s->trigger_map[from][s->num_triggers[from]] = to;
|
||||
s->num_triggers[from]++;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int cut_handler(void *user,
|
||||
const char *section,
|
||||
const char *name,
|
||||
const char *value) {
|
||||
|
||||
Sampler *s = (Sampler*)user;
|
||||
|
||||
if(strcmp(section, "CUTS") != 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("%s: %s = %s\n", section, name, value);
|
||||
|
||||
int from = sound_index(s, name);
|
||||
if(from == -1) {
|
||||
printf("Sound is not triggered: %s\n", name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int to = sound_index(s, value);
|
||||
if(to == -1) {
|
||||
printf("Sound is not triggered: %s\n", value);
|
||||
return 1;
|
||||
}
|
||||
|
||||
s->cut_map[from][s->num_cuts[from]] = to;
|
||||
s->num_cuts[from]++;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void value_handler(SoundConfig *conf,
|
||||
const char *name,
|
||||
const char *value) {
|
||||
if(strcmp(name, "path") == 0) {
|
||||
strncpy((char*)&conf->path, value, PATH_MAX);
|
||||
}
|
||||
else if(strcmp(name, "gamma_amp") == 0) {
|
||||
conf->gamma_amp = parse_double(value);
|
||||
if(conf->gamma_amp <= 0) {
|
||||
printf("Illegal gamma_amp value: %s\n", value);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else if(strcmp(name, "gamma_layer") == 0) {
|
||||
conf->gamma_layer = parse_double(value);
|
||||
if(conf->gamma_layer <= 0) {
|
||||
printf("Illegal gamma_layer value: %s\n", value);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else if(strcmp(name, "tau_off") == 0) {
|
||||
conf->tau_off = parse_double(value);
|
||||
conf->tau_off = proc_raw_tau_value(conf->tau_off);
|
||||
}
|
||||
else if(strcmp(name, "tau_cut") == 0) {
|
||||
conf->tau_cut = parse_double(value);
|
||||
conf->tau_cut = proc_raw_tau_value(conf->tau_cut);
|
||||
}
|
||||
else if(strcmp(name, "fx_pre") == 0 || strcmp(name, "fx") == 0) {
|
||||
soundconfig_proc_fx(conf, value, true);
|
||||
}
|
||||
else if(strcmp(name, "fx_post") == 0) {
|
||||
soundconfig_proc_fx(conf, value, false);
|
||||
}
|
||||
else {
|
||||
printf("Unknown value.\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static int sound_handler(void *user,
|
||||
const char *section,
|
||||
const char *name,
|
||||
const char *value) {
|
||||
|
||||
Sampler *s = (Sampler*)user;
|
||||
|
||||
// Is this a new section? If so, record the name.
|
||||
bool new_section = strcmp(s->section, section) != 0;
|
||||
if(new_section) {
|
||||
strncpy(s->section, section, MAX_NAME);
|
||||
}
|
||||
|
||||
// Skip non-sound sections.
|
||||
if(strcmp(section, "TRIGGERS") == 0 ||
|
||||
strcmp(section, "CUTS") == 0 ||
|
||||
strcmp(section, "SAMPLER") == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("%s: %s = %s\n", section, name, value);
|
||||
|
||||
// Process a global section.
|
||||
if(strcmp(section, "GLOBAL") == 0) {
|
||||
if(new_section) {
|
||||
soundconfig_load_defaults(&s->global_conf);
|
||||
}
|
||||
value_handler(&s->global_conf, name, value);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Otherwise this is a sound.
|
||||
int i = sound_index(s, section);
|
||||
if(i == -1) {
|
||||
printf("Sound is not triggered: %s\n", section);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Sound *sound = &s->sounds[i];
|
||||
|
||||
// If it's a new sound, apply the globals.
|
||||
if(new_section) {
|
||||
soundconfig_copy_globals(&sound->conf, &s->global_conf);
|
||||
}
|
||||
|
||||
value_handler(&s->sounds[i].conf, name, value);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void sampler_init(Sampler *sampler, char *conf_path) {
|
||||
sampler->section[0] = '\0';
|
||||
sampler->num_sounds = 0;
|
||||
sampler->midi_min_vel = 0;
|
||||
sampler->midi_max_vel = 127;
|
||||
sampler->midi_free = jlrb_new(MAX_PLAYING);
|
||||
sampler->midi_new = jlrb_new(MAX_PLAYING);
|
||||
sampler->sustainControl = 64;
|
||||
|
||||
for(int i = 0; i < MAX_PLAYING; ++i) {
|
||||
jlrb_put(sampler->midi_free, jl_malloc_exit(sizeof(MidiEvt)));
|
||||
}
|
||||
|
||||
for(int i = 0; i < MAX_SOUNDS; ++i) {
|
||||
sampler->index[i][0] = '\0';
|
||||
sound_init(&sampler->sounds[i]);
|
||||
sampler->num_triggers[i] = 0;
|
||||
sampler->num_cuts[i] = 0;
|
||||
}
|
||||
|
||||
// 1) Load sampler settings.
|
||||
if(ini_parse(conf_path, sampler_handler, sampler) < 0) {
|
||||
printf("Failed to parse config file %s (sampler).\n", conf_path);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// 2) Load the triggers.
|
||||
if(ini_parse(conf_path, trigger_handler, sampler) < 0) {
|
||||
printf("Failed to parse config file %s (triggers).\n", conf_path);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// 3) Load cuts.
|
||||
if(ini_parse(conf_path, cut_handler, sampler) < 0) {
|
||||
printf("Failed to parse config file %s (cuts).\n", conf_path);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// 4) Load configuration defaults before parsing sound section. We do this
|
||||
// in case the user hasn't provided a GLOBAL section.
|
||||
soundconfig_load_defaults(&sampler->global_conf);
|
||||
|
||||
// 5) Load sounds from the config file.
|
||||
if(ini_parse(conf_path, sound_handler, sampler) < 0) {
|
||||
printf("Failed to parse config file %s (sounds).\n", conf_path);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Change into base directory.
|
||||
if(chdir(sampler->base_dir) != 0) {
|
||||
printf("Failed to change directory: %s\n", sampler->base_dir);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
printf("Loaded %i sounds.\n", sampler->num_sounds);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef init_HEADER_
|
||||
#define init_HEADER_
|
||||
|
||||
#include "sampler.h"
|
||||
|
||||
void sampler_init(Sampler *sampler, char *conf_path);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,145 @@
|
|||
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include "jl_mem.h"
|
||||
|
||||
#ifdef jl_malloc_exit
|
||||
#undef jl_malloc_exit
|
||||
#undef jl_calloc_exit
|
||||
#undef jl_realloc_exit
|
||||
#endif
|
||||
|
||||
// ALIGN_SIZE should be larger than sizeof(malloc_info), and properly aligned.
|
||||
// On x86_64, memory is generally aligned on 16 byte boundaries.
|
||||
#define ALIGN_SIZE 32
|
||||
|
||||
void *jl_malloc_exit(size_t size)
|
||||
{
|
||||
void *data = aligned_alloc(ALIGN_SIZE, size);
|
||||
if(data == NULL) {
|
||||
printf("Out of memory.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
typedef struct malloc_info malloc_info;
|
||||
|
||||
struct malloc_info
|
||||
{
|
||||
char *file;
|
||||
int line;
|
||||
size_t size;
|
||||
malloc_info *prev, *next;
|
||||
};
|
||||
|
||||
static malloc_info *mi_head = NULL;
|
||||
static pthread_mutex_t lock;
|
||||
|
||||
static size_t alloc_count = 0;
|
||||
static size_t free_count = 0;
|
||||
static size_t alloc_bytes = 0;
|
||||
static size_t free_bytes = 0;
|
||||
|
||||
void jl_mem_leak_check_init()
|
||||
{
|
||||
if(pthread_mutex_init(&lock, NULL) != 0) {
|
||||
printf("Failed to initialize mutex.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
atexit(jl_mem_print_leaks);
|
||||
}
|
||||
|
||||
void jl_free_chk(void *ptr)
|
||||
{
|
||||
if(ptr == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
malloc_info *mi = ptr - ALIGN_SIZE;
|
||||
|
||||
if(mi->prev == NULL) {
|
||||
mi_head = mi->next;
|
||||
}
|
||||
|
||||
if(mi->prev) {
|
||||
mi->prev->next = mi->next;
|
||||
}
|
||||
|
||||
if(mi->next) {
|
||||
mi->next->prev = mi->prev;
|
||||
}
|
||||
|
||||
free_bytes += mi->size;
|
||||
free_count++;
|
||||
|
||||
free(mi);
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
}
|
||||
|
||||
void *jl_malloc_chk(size_t size, char *file, int line)
|
||||
{
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
malloc_info *mi = aligned_alloc(ALIGN_SIZE, size + ALIGN_SIZE);
|
||||
if(mi == NULL) {
|
||||
printf("\nMALLOC FAILED:\n");
|
||||
printf(" File: %s\n", file);
|
||||
printf(" Line: %d\n", line);
|
||||
printf(" Size: %u bytes\n", (unsigned int)size);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
mi->file = file;
|
||||
mi->line = line;
|
||||
mi->size = size;
|
||||
alloc_bytes += size;
|
||||
alloc_count++;
|
||||
|
||||
mi->prev = NULL;
|
||||
mi->next = mi_head;
|
||||
|
||||
if(mi_head) {
|
||||
mi_head->prev = mi;
|
||||
}
|
||||
mi_head = mi;
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
|
||||
return (void*)mi + ALIGN_SIZE;
|
||||
}
|
||||
|
||||
void jl_mem_print_leaks(void)
|
||||
{
|
||||
int count = 0;
|
||||
malloc_info *mi = mi_head;
|
||||
while(mi) {
|
||||
printf("MEMORY LEAK DETECTED\n");
|
||||
printf(" File: %s\n", mi->file);
|
||||
printf(" Line: %d\n", (int)mi->line);
|
||||
printf(" Size: %d bytes\n", (int)mi->size);
|
||||
mi = mi->next;
|
||||
++count;
|
||||
if(count > 10) {
|
||||
printf("ADDITIONAL LEAKS NOT PRINTED.\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
printf("MEMORY SUMMARY\n");
|
||||
printf(" Num allocs: %lu\n", (unsigned long)alloc_count);
|
||||
printf(" Num frees: %lu\n", (unsigned long)free_count);
|
||||
printf("\n");
|
||||
printf(" Bytes alloced: %lu\n", (unsigned long)alloc_bytes);
|
||||
printf(" Bytes freed: %lu\n", (unsigned long)free_bytes);
|
||||
printf(" Bytes leaked: %lu\n",
|
||||
(unsigned long)(alloc_bytes - free_bytes));
|
||||
printf("\n");
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*****************************************************************************
|
||||
* VERSION: 1.05
|
||||
* 2015-08-28
|
||||
*
|
||||
* Memory allocation functions.
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef JLLIB_MEM_HEADER_
|
||||
#define JLLIB_MEM_HEADER_
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
void *jl_malloc_chk(size_t size, char *file, int line);
|
||||
void jl_free_chk(void *ptr);
|
||||
|
||||
void *jl_malloc_exit(size_t size);
|
||||
|
||||
void jl_mem_print_leaks(void);
|
||||
void jl_mem_leak_check_init();
|
||||
|
||||
#ifdef JL_LEAK_CHECK
|
||||
|
||||
#define jl_malloc_exit(s) jl_malloc_chk(s, __FILE__, __LINE__)
|
||||
#define jl_malloc(s) jl_malloc_chk(s, __FILE__, __LINE__)
|
||||
#define jl_free(p) jl_free_chk(p)
|
||||
|
||||
#define JL_LEAK_CHECK_INIT jl_mem_leak_check_init()
|
||||
|
||||
#else // JL_LEAK_CHECK
|
||||
|
||||
#define JL_LEAK_CHECK_INIT
|
||||
|
||||
#define jl_malloc(s) malloc(s)
|
||||
#define jl_calloc(n, s) calloc(n, s)
|
||||
#define jl_realloc(p, s) realloc(p, s)
|
||||
#define jl_free(p) free(p)
|
||||
|
||||
#endif // JL_LEAK_CHECK
|
||||
|
||||
#endif // JLLIB_MEM_HEADER_
|
|
@ -0,0 +1,53 @@
|
|||
#include "jl_ringbuf.h"
|
||||
|
||||
struct RingBuffer {
|
||||
int size;
|
||||
jack_ringbuffer_t *buf;
|
||||
};
|
||||
|
||||
RingBuffer *jlrb_new(int size)
|
||||
{
|
||||
RingBuffer *rb = jl_malloc_exit(sizeof(RingBuffer));
|
||||
rb->size = size;
|
||||
// It appears that we need to add one extra byte to the allocation
|
||||
// in order to get the capacity we desire -jdl 2015-08-29.
|
||||
rb->buf = jack_ringbuffer_create(sizeof(void *) * size + 1);
|
||||
return rb;
|
||||
}
|
||||
|
||||
void jlrb_free(RingBuffer ** rb)
|
||||
{
|
||||
jack_ringbuffer_free((*rb)->buf);
|
||||
jl_free(*rb);
|
||||
*rb = NULL;
|
||||
}
|
||||
|
||||
int jlrb_size(RingBuffer * rb)
|
||||
{
|
||||
return rb->size;
|
||||
}
|
||||
|
||||
inline int jlrb_count(RingBuffer * rb)
|
||||
{
|
||||
return jack_ringbuffer_read_space(rb->buf) / sizeof(void *);
|
||||
}
|
||||
|
||||
inline bool jlrb_put(RingBuffer * rb, void *data)
|
||||
{
|
||||
int writeSpace = jack_ringbuffer_write_space(rb->buf);
|
||||
if (writeSpace < sizeof(void *)) {
|
||||
return false;
|
||||
}
|
||||
jack_ringbuffer_write(rb->buf, (char *)(&data), sizeof(void *));
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void *jlrb_get(RingBuffer * rb)
|
||||
{
|
||||
if (jlrb_count(rb) == 0) {
|
||||
return NULL;
|
||||
}
|
||||
void *data;
|
||||
jack_ringbuffer_read(rb->buf, (char *)(&data), sizeof(void *));
|
||||
return data;
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*****************************************************************************
|
||||
* VERSION: 1.01
|
||||
* 2015-08-29
|
||||
*
|
||||
* Single producer, single consumer ring buffer.
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef JLLIB_RINGBUF_HEADER_
|
||||
#define JLLIB_RINGBUF_HEADER_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <jack/ringbuffer.h>
|
||||
#include "jl_mem.h"
|
||||
|
||||
typedef struct RingBuffer RingBuffer;
|
||||
|
||||
// Create a new RingBuffer with the given capacity.
|
||||
RingBuffer *jlrb_new(int size);
|
||||
|
||||
// Free the buffer. Note that the items in the buffer must be freed by the
|
||||
// caller before the buffer is freed.
|
||||
void jlrb_free(RingBuffer ** rb);
|
||||
|
||||
// Return the total capacity of the buffer.
|
||||
int jlrb_size(RingBuffer * rb);
|
||||
|
||||
// Return the number of items currently in the buffer.
|
||||
int jlrb_count(RingBuffer * rb);
|
||||
|
||||
// Put the given data into the buffer. The return value is true if the put was
|
||||
// successful, and false if there wasn't room.
|
||||
bool jlrb_put(RingBuffer * rb, void *data);
|
||||
|
||||
// Return the next item in the buffer. Returns NULL if there was no item
|
||||
// available.
|
||||
void *jlrb_get(RingBuffer * rb);
|
||||
|
||||
#endif // JLLIB_RINGBUF_HEADER_
|
|
@ -0,0 +1,29 @@
|
|||
/*****************************************************************************
|
||||
* VERSION: 1.00
|
||||
* 2015-08-25
|
||||
*
|
||||
* Testing macros.
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef JLLIB_TEST_HEADER_
|
||||
#define JLLIB_TEST_HEADER_
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#define JL_TEST_FUNC printf("Running: %s\n", __func__)
|
||||
|
||||
#define JL_FAIL_MSG(msg) \
|
||||
do { \
|
||||
printf(" FAILED: %s() line %d\n", __func__, __LINE__); \
|
||||
printf(" %s\n", msg); \
|
||||
exit(EXIT_FAILURE); \
|
||||
} while (0)
|
||||
|
||||
#define JL_FAIL_IF(cond, msg) \
|
||||
do { \
|
||||
if(cond) { \
|
||||
JL_FAIL_MSG(msg); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#endif // JLLIB_TEST_HEADER_
|
|
@ -0,0 +1,18 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "jl_mem.h"
|
||||
#include "sampler.h"
|
||||
#include "init.h"
|
||||
|
||||
Sampler *sampler;
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if(argc != 2) {
|
||||
printf("Please provide a configuration file.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
sampler = jl_malloc_exit(sizeof(Sampler));
|
||||
sampler_init(sampler, argv[1]);
|
||||
sampler_main(sampler);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "parse.h"
|
||||
|
||||
double parse_double(const char *value) {
|
||||
double dest;
|
||||
if(sscanf(value, "%lf", &dest) != 1) {
|
||||
printf("Failed to parse double value: %s\n", value);
|
||||
exit(1);
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
int parse_int(const char *value) {
|
||||
int val;
|
||||
if(sscanf(value, "%d", &val) != 1) {
|
||||
printf("Failed to parse int value: %s\n", value);
|
||||
exit(1);
|
||||
}
|
||||
return val;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
#ifndef parse_HEADER_
|
||||
#define parse_HEADER_
|
||||
|
||||
double parse_double(const char *value);
|
||||
int parse_int(const char *value);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,188 @@
|
|||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include "jl_mem.h"
|
||||
#include "soundfile.h"
|
||||
#include "sample.h"
|
||||
#include "fx.h"
|
||||
|
||||
static bool starts_with(char *s, char *prefix) {
|
||||
int n = strlen(prefix);
|
||||
if(n > strlen(s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int i = 0; i < n; ++i) {
|
||||
if(s[i] != prefix[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void sample_apply_effect(Sample *s, char *txt) {
|
||||
// Attempt to parse each effect until one works.
|
||||
if(starts_with(txt, "amp(")) {
|
||||
double gain;
|
||||
if(sscanf(txt, "amp(%lf)", &gain) != 1) {
|
||||
goto apply_fx_err;
|
||||
}
|
||||
fx_amp(s, gain);
|
||||
}
|
||||
else if(starts_with(txt, "balance(")) {
|
||||
double balance;
|
||||
if(sscanf(txt, "balance(%lf)", &balance) != 1) {
|
||||
goto apply_fx_err;
|
||||
}
|
||||
fx_balance(s, balance);
|
||||
}
|
||||
else if(starts_with(txt, "divide_rms(")) {
|
||||
double t_ms;
|
||||
if(sscanf(txt, "divide_rms(%lf)", &t_ms) != 1) {
|
||||
goto apply_fx_err;
|
||||
}
|
||||
fx_divide_rms(s, t_ms);
|
||||
}
|
||||
else if(starts_with(txt, "fade_in(")) {
|
||||
double t_ms;
|
||||
if(sscanf(txt, "fade_in(%lf)", &t_ms) != 1) {
|
||||
goto apply_fx_err;
|
||||
}
|
||||
fx_fade_in(s, t_ms);
|
||||
}
|
||||
else if(starts_with(txt, "ir(")) {
|
||||
char path[MAX_FX_LINE];
|
||||
double mix;
|
||||
if(sscanf(txt, "ir(%[^,], %lf)", path, &mix) != 2) {
|
||||
goto apply_fx_err;
|
||||
}
|
||||
fx_ir(s, path, mix);
|
||||
}
|
||||
else if(starts_with(txt, "mono()")) {
|
||||
fx_mono(s);
|
||||
}
|
||||
else if(starts_with(txt, "pan(")) {
|
||||
double pan;
|
||||
if(sscanf(txt, "pan(%lf)", &pan) != 1) {
|
||||
goto apply_fx_err;
|
||||
}
|
||||
fx_pan(s, pan);
|
||||
}
|
||||
else if(starts_with(txt, "pre_cut(")) {
|
||||
double pct, fade_ms;
|
||||
if(sscanf(txt, "pre_cut(%lf, %lf)", &pct, &fade_ms) != 2) {
|
||||
goto apply_fx_err;
|
||||
}
|
||||
fx_pre_cut(s, pct, fade_ms);
|
||||
}
|
||||
else if(starts_with(txt, "playback_speed(")) {
|
||||
double speed;
|
||||
if(sscanf(txt, "playback_speed(%lf)", &speed) != 1) {
|
||||
goto apply_fx_err;
|
||||
}
|
||||
fx_playback_speed(s, speed);
|
||||
}
|
||||
else if(starts_with(txt, "pre_delay(")) {
|
||||
double left_ms, right_ms;
|
||||
if(sscanf(txt, "pre_delay(%lf, %lf)", &left_ms, &right_ms) != 2) {
|
||||
goto apply_fx_err;
|
||||
}
|
||||
fx_pre_delay(s, left_ms, right_ms);
|
||||
}
|
||||
else if(starts_with(txt, "rc_highpass(")) {
|
||||
double freq;
|
||||
int order;
|
||||
if(sscanf(txt, "rc_highpass(%lf, %d)", &freq, &order) != 2) {
|
||||
goto apply_fx_err;
|
||||
}
|
||||
fx_rc_highpass(s, freq, order);
|
||||
}
|
||||
else if(starts_with(txt, "rc_lowpass(")) {
|
||||
double freq;
|
||||
int order;
|
||||
if(sscanf(txt, "rc_lowpass(%lf, %d)", &freq, &order) != 2) {
|
||||
goto apply_fx_err;
|
||||
}
|
||||
fx_rc_lowpass(s, freq, order);
|
||||
}
|
||||
else if(starts_with(txt, "rc_highshelf(")) {
|
||||
double freq, gain;
|
||||
int order;
|
||||
if(sscanf(txt, "rc_highshelf(%lf, %d, %lf)", &freq, &order, &gain) != 3) {
|
||||
goto apply_fx_err;
|
||||
}
|
||||
fx_rc_highshelf(s, freq, order, gain);
|
||||
}
|
||||
else if(starts_with(txt, "rc_lowshelf(")) {
|
||||
double freq, gain;
|
||||
int order;
|
||||
if(sscanf(txt, "rc_lowshelf(%lf, %d, %lf)", &freq, &order, &gain) != 3) {
|
||||
goto apply_fx_err;
|
||||
}
|
||||
fx_rc_lowshelf(s, freq, order, gain);
|
||||
}
|
||||
else if(starts_with(txt, "stereo_width(")) {
|
||||
double width;
|
||||
if(sscanf(txt, "stereo_width(%lf)", &width) != 1) {
|
||||
goto apply_fx_err;
|
||||
}
|
||||
fx_stereo_width(s, width);
|
||||
}
|
||||
else {
|
||||
printf("Unknown effect: %s\n", txt);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
apply_fx_err:
|
||||
printf("Parsing error: %s\n", txt);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void sample_load(Sample *s, char *path, SoundConfig *conf, int buf_size)
|
||||
{
|
||||
s->len = soundfile_load_stereo(path, &s->L, &s->R);
|
||||
|
||||
// Apply global pre effects.
|
||||
for(int i = 0; conf->global_fx_pre[i][0] != '\0'; ++i) {
|
||||
sample_apply_effect(s, conf->global_fx_pre[i]);
|
||||
}
|
||||
|
||||
// Apply pre effects.
|
||||
for(int i = 0; conf->fx_pre[i][0] != '\0'; ++i) {
|
||||
sample_apply_effect(s, conf->fx_pre[i]);
|
||||
}
|
||||
|
||||
// Apply post effets.
|
||||
for(int i = 0; conf->fx_post[i][0] != '\0'; ++i) {
|
||||
sample_apply_effect(s, conf->fx_post[i]);
|
||||
}
|
||||
|
||||
// Apply global post effects.
|
||||
for(int i = 0; conf->global_fx_post[i][0] != '\0'; ++i) {
|
||||
sample_apply_effect(s, conf->global_fx_post[i]);
|
||||
}
|
||||
|
||||
// Pad to buffer size.
|
||||
fx_pad_multiple(s, buf_size);
|
||||
}
|
||||
|
||||
void sample_set_data(Sample *s, float *L, float *R, int len) {
|
||||
jl_free(s->L);
|
||||
jl_free(s->R);
|
||||
s->L = L;
|
||||
s->R = R;
|
||||
s->len = len;
|
||||
}
|
||||
|
||||
void sample_free(Sample *s)
|
||||
{
|
||||
jl_free(s->L);
|
||||
jl_free(s->R);
|
||||
s->R = s->L = NULL;
|
||||
s->len = 0;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef sample_HEADER_
|
||||
#define sample_HEADER_
|
||||
|
||||
#include "soundconfig.h"
|
||||
|
||||
typedef struct {
|
||||
int len; // The number of samples in each channel.
|
||||
float *L; // The left channel.
|
||||
float *R; // The right channel.
|
||||
} Sample;
|
||||
|
||||
// Load a sample. Allocates.
|
||||
void sample_load(Sample *s, char *path, SoundConfig *conf, int buf_size);
|
||||
void sample_set_data(Sample *s, float *L, float *R, int len);
|
||||
|
||||
// Free a sample.
|
||||
void sample_free(Sample *s);
|
||||
|
||||
|
||||
#endif
|
|
@ -0,0 +1,57 @@
|
|||
|
||||
#include "jl_mem.h"
|
||||
#include "jl_test.h"
|
||||
#include "sample.h"
|
||||
|
||||
static void test_load_free()
|
||||
{
|
||||
JL_TEST_FUNC;
|
||||
Sample s;
|
||||
sample_load(&s, "test-files/sound.flac", 1.0, 8);
|
||||
|
||||
// Check length.
|
||||
int len = 71750 + 8;
|
||||
len -= len % 8;
|
||||
JL_FAIL_IF(s.len != len, "Incorrect length.");
|
||||
|
||||
// Check that data is normalized.
|
||||
float max = 0;
|
||||
for(int i = 0; i < s.len; ++i) {
|
||||
if(s.L[i] > max) {
|
||||
max = s.L[i];
|
||||
} else if(-s.L[i] > max) {
|
||||
max = -s.L[i];
|
||||
}
|
||||
if(s.R[i] > max) {
|
||||
max = s.R[i];
|
||||
} else if(-s.R[i] > max) {
|
||||
max = -s.R[i];
|
||||
}
|
||||
}
|
||||
JL_FAIL_IF(max != 1.0, "Data not normalized.");
|
||||
sample_free(&s);
|
||||
}
|
||||
|
||||
static void test_playback_speed()
|
||||
{
|
||||
JL_TEST_FUNC;
|
||||
Sample s1;
|
||||
Sample s2;
|
||||
sample_load(&s1, "test-files/sound.flac", 1.0, 0);
|
||||
sample_load(&s2, "test-files/sound.flac", 0.5, 0);
|
||||
JL_FAIL_IF(2 * s1.len != s2.len, "Incorrect lengths.");
|
||||
for(int i = 0; i < s1.len; ++i) {
|
||||
JL_FAIL_IF(s1.L[i] != s2.L[2*i], "Incorrect data.");
|
||||
JL_FAIL_IF(s1.R[i] != s2.R[2*i], "Incorrect data.");
|
||||
}
|
||||
sample_free(&s1);
|
||||
sample_free(&s2);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
JL_LEAK_CHECK_INIT;
|
||||
test_load_free();
|
||||
test_playback_speed();
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
#ifndef sampleinfo_HEADER_
|
||||
#define sampleinfo_HEADER_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
|
||||
typedef struct {
|
||||
char *name; // The sample name - used when opening files.
|
||||
|
||||
uint8_t numLayers; // Number velocity layers.
|
||||
uint8_t numVariations[MAX_LAYERS]; // Number of variations per layer.
|
||||
uint8_t rrIdx[MAX_LAYERS]; // Round-robin index per layer.
|
||||
Sample *sample[MAX_LAYERS][MAX_VARS]; // Samples.
|
||||
|
||||
uint8_t numCut; // Number of samples cut by this one.
|
||||
uint8_t cutInds[MAX_SAMPLES]; // Indices of samples cut by this one.
|
||||
|
||||
// Applied at load time.
|
||||
double rms_time; // In ms.
|
||||
double crop_thresh; // 0-1
|
||||
double playback_speed; // 1 => no change.
|
||||
|
||||
double gain;
|
||||
double pan;
|
||||
double gammaAmp;
|
||||
double gammaLayer;
|
||||
double tauOff; // If non-zero, fade out time-constant when pad release.
|
||||
double tauCut; // Fade out time constant when sample is cut.
|
||||
double tauFadeIn;
|
||||
|
||||
} SampleInfo;
|
||||
|
||||
#endif
|
|
@ -0,0 +1,288 @@
|
|||
#include <alsa/asoundlib.h>
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
#include "jl_mem.h"
|
||||
#include "jl_ringbuf.h"
|
||||
#include "sampler.h"
|
||||
|
||||
/****************
|
||||
* sampler_main *
|
||||
****************/
|
||||
|
||||
static void proc_midi_event(Sampler *s, MidiEvt *evt) {
|
||||
if(evt->type == MIDI_EVT_NOTE) {
|
||||
int num_triggers = s->num_triggers[evt->note];
|
||||
for(int i = 0; i < num_triggers; ++i) {
|
||||
int idx = s->trigger_map[evt->note][i];
|
||||
for(int j = 0; j < s->num_cuts[idx]; ++j) {
|
||||
sound_cut(&s->sounds[s->cut_map[idx][j]]);
|
||||
}
|
||||
sound_trigger(&s->sounds[idx], evt->velocity);
|
||||
}
|
||||
} else if(evt->type == MIDI_EVT_CTRL) {
|
||||
if(evt->control == s->sustainControl) {
|
||||
gl_sustain = evt->value > 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void proc_midi(Sampler *s) {
|
||||
int num_events = jlrb_count(s->midi_new);
|
||||
for(int i = 0; i < num_events; ++i) {
|
||||
MidiEvt *evt = jlrb_get(s->midi_new);
|
||||
proc_midi_event(s, evt);
|
||||
jlrb_put(s->midi_free, evt);
|
||||
}
|
||||
}
|
||||
|
||||
static int jack_process(jack_nframes_t nframes, void *data) {
|
||||
Sampler *s = data;
|
||||
|
||||
if(nframes != s->buf_size) {
|
||||
printf("Buffer size changed.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Processing incoming midi events.
|
||||
proc_midi(s);
|
||||
|
||||
// Get output port arrays.
|
||||
float *L = jack_port_get_buffer(s->jack_port_L, nframes);
|
||||
float *R = jack_port_get_buffer(s->jack_port_R, nframes);
|
||||
|
||||
// Zero the output buffers.
|
||||
memset(L, 0, nframes * sizeof(float));
|
||||
memset(R, 0, nframes * sizeof(float));
|
||||
|
||||
// Write output from each sound.
|
||||
int count = s->num_sounds;
|
||||
for(int i = 0; i < count; ++i) {
|
||||
Sound *sound = &s->sounds[i];
|
||||
if(sound->num_playing == 0) {
|
||||
continue;
|
||||
}
|
||||
sound_write_buffer(sound, L, R);
|
||||
}
|
||||
|
||||
for(int i = 0; i < nframes; ++i) {
|
||||
if(L[i] > s->L_max) {
|
||||
s->L_max = L[i];
|
||||
} else if (-L[i] > s->L_max) {
|
||||
s->L_max = -L[i];
|
||||
}
|
||||
|
||||
if(R[i] > s->R_max) {
|
||||
s->R_max = R[i];
|
||||
} else if (-R[i] > s->R_max) {
|
||||
s->R_max = -R[i];
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static double calc_velocity(Sampler *s, int v_in) {
|
||||
if(v_in <= s->midi_min_vel) {
|
||||
return 0;
|
||||
} else if(v_in >= s->midi_max_vel) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
double vel = v_in;
|
||||
vel -= s->midi_min_vel;
|
||||
vel /= s->midi_max_vel - s->midi_min_vel;
|
||||
return vel;
|
||||
}
|
||||
|
||||
void midi_loop(Sampler *s) {
|
||||
// We need to open the sequencer before doing anything else.
|
||||
snd_seq_t *handle;
|
||||
|
||||
int status = snd_seq_open(&handle, "default", SND_SEQ_OPEN_INPUT, 0);
|
||||
if (status != 0) {
|
||||
printf("Failed to open sequencer.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Give the client a name.
|
||||
status = snd_seq_set_client_name(handle, "JLSampler");
|
||||
if (status != 0) {
|
||||
printf("Failed to set sequencer client name.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Create a port with write capabilities.
|
||||
int caps = SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
|
||||
int portNum = snd_seq_create_simple_port(
|
||||
handle, "MIDI In", caps, SND_SEQ_PORT_TYPE_MIDI_GM);
|
||||
|
||||
if (portNum < 0) {
|
||||
printf("Failed to create sequencer port.\n");
|
||||
exit(1);
|
||||
}
|
||||
// We need a midi event handle to read incoming midi events.
|
||||
snd_seq_event_t *seq_event;
|
||||
|
||||
MidiEvt *event;
|
||||
|
||||
while(1) {
|
||||
status = snd_seq_event_input(handle, &seq_event);
|
||||
if (status < 0) {
|
||||
printf("Sampler: Failed to read MIDI event. Status: %i\n", status);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip events that we won't process.
|
||||
if(seq_event->type != SND_SEQ_EVENT_NOTEON &&
|
||||
seq_event->type != SND_SEQ_EVENT_NOTEOFF &&
|
||||
seq_event->type != SND_SEQ_EVENT_CONTROLLER) {
|
||||
continue;
|
||||
}
|
||||
|
||||
event = jlrb_get(s->midi_free);
|
||||
if(event == NULL) {
|
||||
printf("No free midi events available.\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (seq_event->type) {
|
||||
|
||||
case SND_SEQ_EVENT_NOTEON:
|
||||
event->type = MIDI_EVT_NOTE;
|
||||
event->note = seq_event->data.note.note;
|
||||
event->velocity = calc_velocity(s, seq_event->data.note.velocity);
|
||||
break;
|
||||
|
||||
case SND_SEQ_EVENT_NOTEOFF:
|
||||
event->type = MIDI_EVT_NOTE;
|
||||
event->note = seq_event->data.note.note;
|
||||
event->velocity = 0;
|
||||
break;
|
||||
|
||||
case SND_SEQ_EVENT_CONTROLLER:
|
||||
event->type = MIDI_EVT_CTRL;
|
||||
event->control = seq_event->data.control.param;
|
||||
event->value = (double)(seq_event->data.control.value) / 127.0;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
jlrb_put(s->midi_new, event);
|
||||
}
|
||||
}
|
||||
|
||||
static void print_meter(float val) {
|
||||
val = (log10(val) + 3.0) / 3.0;
|
||||
|
||||
for(int j = 0; j < 64; ++j) {
|
||||
if(val > (float)j/64.0) {
|
||||
printf("-");
|
||||
} else {
|
||||
printf(" ");
|
||||
}
|
||||
}
|
||||
|
||||
printf("|");
|
||||
|
||||
for(int j = 0; j < 8; ++j) {
|
||||
if(val > 1.0 + (float)j/8.0) {
|
||||
printf("=");
|
||||
} else {
|
||||
printf(" ");
|
||||
}
|
||||
}
|
||||
|
||||
printf("|\n");
|
||||
}
|
||||
|
||||
static void meter_loop(void *data) {
|
||||
Sampler *s = data;
|
||||
|
||||
struct timespec sleep_time;
|
||||
sleep_time.tv_sec = 0;
|
||||
sleep_time.tv_nsec = 100000000;
|
||||
|
||||
while(1) {
|
||||
nanosleep(&sleep_time, NULL);
|
||||
printf("\n\n");
|
||||
|
||||
printf("L ");
|
||||
print_meter(s->L_max);
|
||||
s->L_max = 0;
|
||||
|
||||
printf("R ");
|
||||
print_meter(s->R_max);
|
||||
s->R_max = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void sampler_main(Sampler *s) {
|
||||
// Initialize JACK client.
|
||||
printf("Creating JACK client...\n");
|
||||
s->jack_client = jack_client_open("JLSampler", JackNullOption, NULL);
|
||||
|
||||
// Create jack output ports.
|
||||
printf("Creating JACK output ports...\n");
|
||||
s->jack_port_L = jack_port_register(s->jack_client, "Out_1",
|
||||
JACK_DEFAULT_AUDIO_TYPE,
|
||||
JackPortIsOutput, 0);
|
||||
s->jack_port_R = jack_port_register(s->jack_client, "Out_2",
|
||||
JACK_DEFAULT_AUDIO_TYPE,
|
||||
JackPortIsOutput, 0);
|
||||
|
||||
// Get the jack buffer size.
|
||||
printf("Getting JACK buffer size...\n");
|
||||
s->buf_size = (int)jack_get_buffer_size(s->jack_client);
|
||||
printf(" Got %i\n", s->buf_size);
|
||||
|
||||
if(s->buf_size % 8 != 0) {
|
||||
printf("Buffer size must be a multiple of 8.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Load samples.
|
||||
#pragma omp parallel for
|
||||
for(int i = 0; i < s->num_sounds; ++i) {
|
||||
sound_load_samples(&s->sounds[i], s->buf_size);
|
||||
}
|
||||
|
||||
// Register the jack callback function.
|
||||
printf("Setting JACK callback...\n");
|
||||
jack_set_process_callback(s->jack_client, jack_process, (void *)s);
|
||||
|
||||
|
||||
printf("Activating JACK client...\n");
|
||||
jack_activate(s->jack_client);
|
||||
|
||||
// Start the VU meter loop in the background.
|
||||
pthread_t th;
|
||||
pthread_create(&th, NULL, (void *) &meter_loop, s);
|
||||
|
||||
// Collect midi events in the main thread.
|
||||
printf("Starting midi loop...\n");
|
||||
midi_loop(s);
|
||||
}
|
||||
|
||||
/****************
|
||||
* sampler_free *
|
||||
****************/
|
||||
|
||||
void sampler_free(Sampler *sampler) {
|
||||
int n = jlrb_count(sampler->midi_free);
|
||||
for(int i = 0; i < n; ++i) {
|
||||
MidiEvt *evt = jlrb_get(sampler->midi_free);
|
||||
jl_free(evt);
|
||||
}
|
||||
jlrb_free(&sampler->midi_free);
|
||||
|
||||
n = jlrb_count(sampler->midi_new);
|
||||
for(int i = 0; i < n; ++i) {
|
||||
MidiEvt *evt = jlrb_get(sampler->midi_new);
|
||||
jl_free(evt);
|
||||
}
|
||||
jlrb_free(&sampler->midi_new);
|
||||
|
||||
for(int i = 0; i < MAX_SOUNDS; ++i) {
|
||||
sound_free(&sampler->sounds[i]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
#ifndef sampler_HEADER_
|
||||
#define sampler_HEADER_
|
||||
|
||||
#include <limits.h>
|
||||
#include <jack/jack.h>
|
||||
#include "jl_ringbuf.h"
|
||||
#include "sound.h"
|
||||
|
||||
// MidiEvt: A MIDI event. The event has a `type`, which is one of the constants
|
||||
// MIDI_EVT_*. Depending on the event type, it will contain a (control, value)
|
||||
// or a (node, velocity) pair.
|
||||
typedef struct {
|
||||
int type; // One of the MIDI_EVT_* constants.
|
||||
union {
|
||||
int control;
|
||||
int note;
|
||||
};
|
||||
union {
|
||||
double value;
|
||||
double velocity;
|
||||
};
|
||||
} MidiEvt;
|
||||
|
||||
|
||||
typedef struct {
|
||||
// Global sound config.
|
||||
char section[MAX_NAME];
|
||||
SoundConfig global_conf;
|
||||
|
||||
// When loading the configuration file, this is used to map names to
|
||||
// sound indices.
|
||||
int32_t num_sounds;
|
||||
char index[MAX_SOUNDS][MAX_NAME];
|
||||
|
||||
char base_dir[PATH_MAX];
|
||||
|
||||
// Min/max values output by controller.
|
||||
int midi_min_vel;
|
||||
int midi_max_vel;
|
||||
|
||||
// Ring buffers for midi events.
|
||||
RingBuffer *midi_free; // Consumed by the midi thread.
|
||||
RingBuffer *midi_new; // Consumed by the jack thread.
|
||||
|
||||
// Midi map.
|
||||
int num_triggers[MAX_SOUNDS];
|
||||
int trigger_map[MAX_SOUNDS][MAX_SOUNDS];
|
||||
|
||||
// Cut map.
|
||||
int num_cuts[MAX_SOUNDS];
|
||||
int cut_map[MAX_SOUNDS][MAX_SOUNDS];
|
||||
|
||||
// Jack client and ports.
|
||||
jack_client_t *jack_client;
|
||||
jack_port_t *jack_port_L, *jack_port_R;
|
||||
|
||||
// The sounds.
|
||||
int buf_size;
|
||||
Sound sounds[MAX_SOUNDS];
|
||||
|
||||
// Sustain pedal value.
|
||||
int sustainControl;
|
||||
|
||||
// Output amplitudes.
|
||||
float L_max;
|
||||
float R_max;
|
||||
} Sampler;
|
||||
|
||||
void sampler_main(Sampler *sampler);
|
||||
void sampler_free(Sampler *sampler);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,96 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import configparser
|
||||
from glob import glob
|
||||
|
||||
|
||||
class Config:
|
||||
def __init__(self, config_path):
|
||||
cp = configparser.ConfigParser()
|
||||
cp.read(config_path)
|
||||
|
||||
# Print non-keyboard keys.
|
||||
for key in cp.keys():
|
||||
if key in ('KEYBOARD', 'DEFAULT'):
|
||||
continue
|
||||
print('[' + key + ']')
|
||||
conf = cp[key]
|
||||
for k in conf.keys():
|
||||
print('{0} = {1}'.format(k, conf[k]))
|
||||
|
||||
conf = cp['KEYBOARD']
|
||||
|
||||
self.midi_min = conf.getint('midi_min', 21)
|
||||
self.midi_max = conf.getint('midi_max', 109)
|
||||
self.amp_low = conf.getfloat('amp_low', 0.24)
|
||||
self.amp_high = conf.getfloat('amp_high', 0.04)
|
||||
self.rms_time_low = conf.getfloat('rms_time_low', 500)
|
||||
self.rms_time_high = conf.getfloat('rms_time_high', 50)
|
||||
|
||||
|
||||
class Tuning:
|
||||
def __init__(self, path):
|
||||
self.cp = configparser.ConfigParser()
|
||||
self.cp.read(path)
|
||||
self.conf = self.cp['TUNING']
|
||||
|
||||
def get_freq(self, note):
|
||||
# TODO!
|
||||
return None
|
||||
|
||||
|
||||
class Note:
|
||||
def __init__(self, idx, conf):
|
||||
self.conf = conf
|
||||
self.idx = idx
|
||||
self.path = "samples/*{0:03}-*-*.flac".format(idx)
|
||||
|
||||
def name(self):
|
||||
return "note-{0:03}".format(self.idx)
|
||||
|
||||
def _linear(self, low, high):
|
||||
m = (high - low) / 87.0
|
||||
return low + m * (self.idx - 21)
|
||||
|
||||
def amp(self):
|
||||
return self._linear(conf.amp_low, conf.amp_high)
|
||||
|
||||
def rms_time(self):
|
||||
return self._linear(conf.rms_time_low, conf.rms_time_high)
|
||||
|
||||
def conf_path(self):
|
||||
return 'path = ' + self.path
|
||||
|
||||
def conf_fx(self):
|
||||
return 'fx = divide_rms({0}) amp({1})'.format(
|
||||
self.rms_time(), self.amp())
|
||||
|
||||
|
||||
def noteFromFile(fn):
|
||||
base = os.path.splitext(fn)[0]
|
||||
return int(base.split('-')[-3])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
conf = Config(sys.argv[1])
|
||||
|
||||
# Create the notes.
|
||||
notes = []
|
||||
for idx in range(conf.midi_min, conf.midi_max + 1):
|
||||
notes.append(Note(idx, conf))
|
||||
|
||||
# Print triggers.
|
||||
print('\n[TRIGGERS]')
|
||||
for note in notes:
|
||||
print('{0} = {1}'.format(note.idx, note.name()))
|
||||
|
||||
print('\n[GLOBAL]')
|
||||
print('tau_off = 100')
|
||||
|
||||
# Print notes.
|
||||
for note in notes:
|
||||
print('')
|
||||
print('[{0}]'.format(note.name()))
|
||||
print(note.conf_path())
|
||||
print(note.conf_fx())
|
|
@ -0,0 +1,285 @@
|
|||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <glob.h>
|
||||
#include <x86intrin.h>
|
||||
#include "jl_mem.h"
|
||||
#include "sound.h"
|
||||
|
||||
/**************
|
||||
* sound_init *
|
||||
**************/
|
||||
|
||||
void sound_init(Sound *sound) {
|
||||
sound->num_layers = 0;
|
||||
|
||||
for(int i = 0; i < MAX_LAYERS; ++i) {
|
||||
sound->num_vars[i] = 0;
|
||||
sound->rr_idx[i] = 0;
|
||||
}
|
||||
|
||||
sound->key_down = false;
|
||||
|
||||
sound->buf_size = 0;
|
||||
|
||||
sound->num_free = 0;
|
||||
sound->num_playing = 0;
|
||||
|
||||
for(int i = 0; i < MAX_PLAYING; ++i) {
|
||||
sound->ps_free[i] = jl_malloc_exit(sizeof(PlayingSample));
|
||||
sound->num_free++;
|
||||
}
|
||||
}
|
||||
|
||||
/**********************
|
||||
* sound_load_samples *
|
||||
**********************/
|
||||
|
||||
static void load_sound_sample(Sound *sound, char *path, int32_t buf_size) {
|
||||
SoundConfig *conf = &(sound->conf);
|
||||
|
||||
char *path_end = &path[strlen(path) - 12];
|
||||
int layer, var;
|
||||
if(sscanf(path_end, "%d-%d.flac", &layer, &var) != 2) {
|
||||
printf("Error parsing sample filename: %s\n", path);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if(layer >= MAX_LAYERS) {
|
||||
printf("Layer is too large.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if(var >= MAX_VARS) {
|
||||
printf("Variation is too large.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if(sound->num_layers < layer) {
|
||||
sound->num_layers = layer;
|
||||
}
|
||||
|
||||
sound->num_vars[layer-1] += 1;
|
||||
Sample *sample = &sound->samples[layer-1][var-1];
|
||||
sample_load(sample, path, conf, buf_size);
|
||||
|
||||
}
|
||||
|
||||
void sound_load_samples(Sound *sound, int32_t buf_size) {
|
||||
sound->buf_size = buf_size;
|
||||
sound->amp_buf = jl_malloc_exit(buf_size * sizeof(float));
|
||||
|
||||
char *path_glob = sound->conf.path;
|
||||
|
||||
if(strlen(path_glob) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
glob_t results;
|
||||
int ret = glob(path_glob, 0, NULL, &results);
|
||||
if(ret == GLOB_NOMATCH) {
|
||||
printf("No files found: %s\n", path_glob);
|
||||
exit(1);
|
||||
} else if(ret != 0) {
|
||||
printf("Error matching files: %s\n", path_glob);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
for(int i = 0; i < results.gl_pathc; i++) {
|
||||
char *path = results.gl_pathv[i];
|
||||
load_sound_sample(sound, path, buf_size);
|
||||
}
|
||||
|
||||
globfree(&results);
|
||||
}
|
||||
|
||||
/*****************
|
||||
* sound_trigger *
|
||||
*****************/
|
||||
|
||||
void sound_trigger(Sound *sound, double vel) {
|
||||
if(vel == 0) {
|
||||
sound->key_down = false;
|
||||
return;
|
||||
}
|
||||
|
||||
sound->key_down = true;
|
||||
|
||||
if(sound->num_layers == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(sound->num_playing == MAX_PLAYING) {
|
||||
// TODO: Log here.
|
||||
// No open slots left.
|
||||
return;
|
||||
}
|
||||
|
||||
PlayingSample *ps = sound->ps_free[sound->num_free - 1];
|
||||
sound->num_free--;
|
||||
|
||||
// Playback from the start with no fade out.
|
||||
ps->state = PS_ON;
|
||||
ps->idx = 0;
|
||||
ps->tau = 1;
|
||||
|
||||
// Compute the playback amplitude.
|
||||
ps->amp = pow(vel, sound->conf.gamma_amp);
|
||||
|
||||
// Get the appropriate layer.
|
||||
vel = pow(vel, sound->conf.gamma_layer);
|
||||
int layer = (int)((double)sound->num_layers * vel);
|
||||
if(layer == sound->num_layers) {
|
||||
--layer;
|
||||
}
|
||||
|
||||
// Update round-robin index.
|
||||
sound->rr_idx[layer] = (sound->rr_idx[layer] + 1) % sound->num_vars[layer];
|
||||
|
||||
// Chose the appropriate variation.
|
||||
ps->sample = &(sound->samples[layer][sound->rr_idx[layer]]);
|
||||
|
||||
// Put playing sample into ring buffer.
|
||||
sound->ps_playing[sound->num_playing] = ps;
|
||||
sound->num_playing++;
|
||||
}
|
||||
|
||||
/*************
|
||||
* sound_cut *
|
||||
*************/
|
||||
|
||||
void sound_cut(Sound *sound) {
|
||||
double tau = sound->conf.tau_cut;
|
||||
if(tau == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < sound->num_playing; ++i) {
|
||||
PlayingSample *ps = sound->ps_playing[i];
|
||||
ps->state = PS_CUT;
|
||||
ps->tau = tau;
|
||||
}
|
||||
}
|
||||
|
||||
/**********************
|
||||
* sound_write_buffer *
|
||||
**********************/
|
||||
|
||||
static inline bool write_smpl_fast(
|
||||
Sound *sound, PlayingSample *ps,
|
||||
float *L, float *R,
|
||||
float *amp_buf, int count) {
|
||||
|
||||
// Update tau for the playing sample based on key state.
|
||||
switch(ps->state) {
|
||||
case PS_OFF:
|
||||
if(sound->key_down || gl_sustain) {
|
||||
ps->state = PS_ON;
|
||||
ps->tau = 1;
|
||||
}
|
||||
break;
|
||||
case PS_ON:
|
||||
if(!sound->key_down && !gl_sustain) {
|
||||
ps->state = PS_OFF;
|
||||
ps->tau = sound->conf.tau_off;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
float amp = ps->amp;
|
||||
float tau = ps->tau;
|
||||
|
||||
for(int i = 0; i < count; ++i) {
|
||||
amp_buf[i] = amp;
|
||||
amp *= tau;
|
||||
}
|
||||
|
||||
ps->amp = amp;
|
||||
|
||||
__m256 *v_amp = (__m256*)amp_buf;
|
||||
|
||||
__m256 *L_out = (__m256*)L;
|
||||
__m256 *R_out = (__m256*)R;
|
||||
|
||||
__m256 *L_smp = (__m256*)ps->sample->L;
|
||||
__m256 *R_smp = (__m256*)ps->sample->R;
|
||||
|
||||
int v_count = count / 8;
|
||||
int idx = ps->idx / 8;
|
||||
|
||||
for(int i = 0; i < v_count; ++i) {
|
||||
L_out[i] = _mm256_add_ps(
|
||||
L_out[i], _mm256_mul_ps(L_smp[idx], v_amp[i]));
|
||||
R_out[i] = _mm256_add_ps(
|
||||
R_out[i], _mm256_mul_ps(R_smp[idx], v_amp[i]));
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
ps->idx += count;
|
||||
|
||||
return ps->idx < ps->sample->len && ps->amp > MIN_AMP;
|
||||
}
|
||||
|
||||
static inline bool write_smpl(
|
||||
PlayingSample *ps, float *L, float *R, int32_t count) {
|
||||
|
||||
// TODO: We could pre-compute the amplitude array and then use SIMD
|
||||
// instructions to speed this up.
|
||||
float *sL = ps->sample->L;
|
||||
float *sR = ps->sample->R;
|
||||
double amp = ps->amp;
|
||||
double tau = ps->tau;
|
||||
int idx = ps->idx;
|
||||
|
||||
// Note: We've ensured that each sample has a length that is a multiple of
|
||||
// the buffer size and of 8, so this will always succeed.
|
||||
for(int32_t i = 0; i < count; ++i) {
|
||||
L[i] += sL[idx] * amp;
|
||||
R[i] += sR[idx] * amp;
|
||||
amp *= tau;
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
ps->amp = amp;
|
||||
ps->idx = idx;
|
||||
|
||||
return ps->idx < ps->sample->len && ps->amp > MIN_AMP;
|
||||
}
|
||||
|
||||
void sound_write_buffer(Sound *sound, float *L, float *R) {
|
||||
int count = sound->num_playing;
|
||||
sound->num_playing = 0;
|
||||
|
||||
for(int i = 0; i < count; ++i) {
|
||||
PlayingSample *ps = sound->ps_playing[i];
|
||||
if(write_smpl_fast(sound, ps, L, R, sound->amp_buf, sound->buf_size)) {
|
||||
sound->ps_playing[sound->num_playing] = ps;
|
||||
sound->num_playing++;
|
||||
} else {
|
||||
sound->ps_free[sound->num_free] = ps;
|
||||
sound->num_free++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**************
|
||||
* sound_free *
|
||||
**************/
|
||||
|
||||
void sound_free(Sound *sound) {
|
||||
for(int i = 0; i < MAX_PLAYING; ++i) {
|
||||
jl_free(sound->ps_free[i]);
|
||||
}
|
||||
|
||||
for(int i = 0; i < MAX_PLAYING; ++i) {
|
||||
jl_free(sound->ps_playing[i]);
|
||||
}
|
||||
|
||||
for(int i = 0; i < MAX_LAYERS; ++i) {
|
||||
for(int j = 0; j < sound->num_vars[i]; ++j) {
|
||||
sample_free(&sound->samples[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
#ifndef config_HEADER_
|
||||
#define config_HEADER_
|
||||
|
||||
#include <stdint.h>
|
||||
#include "const.h"
|
||||
#include "sample.h"
|
||||
#include "soundconfig.h"
|
||||
|
||||
// Playing sample states.
|
||||
#define PS_ON 0 // Undamped.
|
||||
#define PS_OFF 1 // Damped because key off and sustain off.
|
||||
#define PS_CUT 2 // Damped because cut by other key.
|
||||
|
||||
typedef struct {
|
||||
Sample *sample;
|
||||
int state;
|
||||
int idx;
|
||||
double amp;
|
||||
double tau;
|
||||
} PlayingSample;
|
||||
|
||||
typedef struct {
|
||||
// Configuration
|
||||
SoundConfig conf;
|
||||
|
||||
// Whether or not the key is depressed.
|
||||
bool key_down;
|
||||
|
||||
// Samples
|
||||
uint8_t num_layers;
|
||||
uint8_t num_vars[MAX_LAYERS];
|
||||
uint8_t rr_idx[MAX_LAYERS];
|
||||
Sample samples[MAX_LAYERS][MAX_VARS];
|
||||
|
||||
// Playing samples
|
||||
int32_t buf_size;
|
||||
|
||||
int32_t num_free;
|
||||
PlayingSample *ps_free[MAX_PLAYING];
|
||||
|
||||
float *amp_buf;
|
||||
int32_t num_playing;
|
||||
PlayingSample *ps_playing[MAX_PLAYING];
|
||||
} Sound;
|
||||
|
||||
void sound_init(Sound *sound);
|
||||
void sound_load_samples(Sound *sound, int32_t buf_size);
|
||||
void sound_trigger(Sound *sound, double velocity);
|
||||
void sound_cut(Sound *sound);
|
||||
void sound_write_buffer(Sound *sound, float *L, float *R);
|
||||
void sound_free(Sound *sound);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,18 @@
|
|||
#include "jl_mem.h"
|
||||
#include "jl_test.h"
|
||||
#include "sound.h"
|
||||
|
||||
static void test_load()
|
||||
{
|
||||
load_config_file("test-files/test.ini");
|
||||
load_samples(64);
|
||||
free_samples();
|
||||
dump_config();
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
JL_LEAK_CHECK_INIT;
|
||||
test_load();
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "soundconfig.h"
|
||||
|
||||
/*****************************
|
||||
* soundconfig_load_defaults *
|
||||
*****************************/
|
||||
|
||||
void soundconfig_load_defaults(SoundConfig *conf) {
|
||||
conf->path[0] = '\0';
|
||||
conf->gamma_amp = 2.2;
|
||||
conf->gamma_layer = 1;
|
||||
conf->tau_off = 1;
|
||||
conf->tau_cut = proc_raw_tau_value(100);
|
||||
for(int i = 0; i < MAX_FX; ++i) {
|
||||
conf->fx_pre[i][0] = '\0';
|
||||
conf->fx_post[i][0] = '\0';
|
||||
conf->global_fx_pre[i][0] = '\0';
|
||||
conf->global_fx_post[i][0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
/********************
|
||||
* soundconfig_copy *
|
||||
********************/
|
||||
|
||||
void soundconfig_copy_globals(SoundConfig *to, SoundConfig *from) {
|
||||
to->gamma_amp = from->gamma_amp;
|
||||
to->gamma_layer = from->gamma_layer;
|
||||
to->tau_off = from->tau_off;
|
||||
to->tau_cut = from->tau_cut;
|
||||
for(int i = 0; i < MAX_FX; ++i) {
|
||||
strncpy(to->global_fx_pre[i], from->fx_pre[i], MAX_FX_LINE);
|
||||
strncpy(to->global_fx_post[i], from->fx_post[i], MAX_FX_LINE);
|
||||
}
|
||||
}
|
||||
|
||||
/**********************
|
||||
* proc_fx *
|
||||
**********************/
|
||||
|
||||
// This function parses a config file `fx` value into discrete lines that
|
||||
// can be parsed later.
|
||||
void soundconfig_proc_fx(SoundConfig *conf, const char *line, bool pre) {
|
||||
const char *src = line;
|
||||
int i_src = 0;
|
||||
int i_fx = 0;
|
||||
|
||||
char (*fx)[MAX_FX_LINE] = pre ? conf->fx_pre : conf->fx_post;
|
||||
|
||||
while(1) {
|
||||
// Find the next empty fx slot.
|
||||
for(; i_fx < MAX_FX; ++i_fx) {
|
||||
if(fx[i_fx][0] == '\0') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we're not beyond the end.
|
||||
if(i_fx == MAX_FX) {
|
||||
printf("Maximum number of effects reached.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Skip spaces.
|
||||
while(src[i_src] == ' ') {
|
||||
i_src++;
|
||||
}
|
||||
|
||||
// Check for end of line.
|
||||
if(src[i_src] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
char *dest = fx[i_fx];
|
||||
int i_dest = 0;
|
||||
|
||||
// Copy until closing paren.
|
||||
while(1) {
|
||||
char c = src[i_src];
|
||||
i_src++;
|
||||
|
||||
if(c == '\0') {
|
||||
printf("Failed to parse effects line: %s\n", line);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
dest[i_dest] = c;
|
||||
i_dest++;
|
||||
|
||||
if(i_dest >= MAX_FX_LINE) {
|
||||
printf("FX line was too long: %s\n", line);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if(c == ')') {
|
||||
dest[i_dest] = '\0';
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**********************
|
||||
* proc_raw_tau_value *
|
||||
**********************/
|
||||
|
||||
// Convert a millisecond time-constant value into a per-sample decay.
|
||||
double proc_raw_tau_value(double value)
|
||||
{
|
||||
if (value <= 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return exp(-1000.0 / (SAMPLE_RATE * value));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
#ifndef soundconfig_HEADER_
|
||||
#define soundconfig_HEADER_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <linux/limits.h> // Provides PATH_MAX.
|
||||
#include "const.h"
|
||||
|
||||
// A SoundConfig struct represents the configuration for a single sampled
|
||||
// sound. It contains the path to the files, the sound effects (pre and post),
|
||||
// global sound effects, and standard configuration.
|
||||
typedef struct {
|
||||
// This is a glob spec for the files to use. All files should end in
|
||||
// -XXX-YYY.flac where XXX is the velocity level and YYY is the variation.
|
||||
char path[PATH_MAX];
|
||||
|
||||
char fx_pre[MAX_FX][MAX_FX_LINE];
|
||||
char fx_post[MAX_FX][MAX_FX_LINE];
|
||||
|
||||
char global_fx_pre[MAX_FX][MAX_FX_LINE];
|
||||
char global_fx_post[MAX_FX][MAX_FX_LINE];
|
||||
|
||||
// The amplitude of the sample as a function of velocity. For a normalized
|
||||
// velocity between 0 and 1, the amplitude of the output will scale like
|
||||
// velocity^gamma_amp.
|
||||
double gamma_amp;
|
||||
|
||||
// Similar to gamma_amp, but used when choosing the velocity layer.
|
||||
double gamma_layer;
|
||||
|
||||
// If non-zero, this is the fade out time-constant (e-folding time) when a
|
||||
// sound is released (key-up event).
|
||||
double tau_off;
|
||||
|
||||
// If non-zero, this is the fade-out time-constant (e-folding time) when a
|
||||
// sound is cut by another sound. The cut time constant will override
|
||||
// tau_off.
|
||||
double tau_cut;
|
||||
|
||||
|
||||
} SoundConfig;
|
||||
|
||||
void soundconfig_load_defaults(SoundConfig *conf);
|
||||
void soundconfig_copy_globals(SoundConfig *to, SoundConfig *from);
|
||||
void soundconfig_proc_fx(SoundConfig *conf, const char *line, bool pre);
|
||||
double proc_raw_tau_value(double value);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,48 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <sndfile.h>
|
||||
#include "jl_mem.h"
|
||||
#include "const.h"
|
||||
#include "soundfile.h"
|
||||
|
||||
int soundfile_load_stereo(const char *path, float **Lp, float **Rp) {
|
||||
SF_INFO fileInfo;
|
||||
fileInfo.format = 0;
|
||||
fileInfo.channels = 2;
|
||||
fileInfo.samplerate = 48000;
|
||||
|
||||
// Open the file.
|
||||
SNDFILE *sndFile = sf_open(path, SFM_READ, &fileInfo);
|
||||
if (sndFile == NULL) {
|
||||
printf("Failed to open file: %s\n", path);
|
||||
printf(" Error: %s\n", sf_strerror(sndFile));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Check that the parameters are correct.
|
||||
if (fileInfo.channels != 2 || fileInfo.samplerate != 48000) {
|
||||
printf("File must be 2 channels and 48 kHz.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int len = fileInfo.frames;
|
||||
float *L = jl_malloc_exit(len * sizeof(float));
|
||||
float *R = jl_malloc_exit(len * sizeof(float));
|
||||
|
||||
float buf[2];
|
||||
|
||||
for(int i = 0; i < len; ++i) {
|
||||
if(sf_readf_float(sndFile, buf, 1) != 1) {
|
||||
printf("Failed to read frame from file: %s\n", path);
|
||||
exit(1);
|
||||
}
|
||||
L[i] = buf[0];
|
||||
R[i] = buf[1];
|
||||
}
|
||||
|
||||
sf_close(sndFile);
|
||||
|
||||
*Lp = L;
|
||||
*Rp = R;
|
||||
return len;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
#ifndef soundfile_HEADER_
|
||||
#define soundfile_HEADER_
|
||||
|
||||
int soundfile_load_stereo(const char *path, float **Lp, float **Rp);
|
||||
|
||||
#endif
|
Binary file not shown.
|
@ -0,0 +1,34 @@
|
|||
[triggers]
|
||||
|
||||
16 = 18
|
||||
16 = 19
|
||||
17 = 19
|
||||
19 = 19
|
||||
|
||||
[cuts]
|
||||
|
||||
16 = 18
|
||||
25 = 18
|
||||
|
||||
[global]
|
||||
|
||||
rms_time = 250
|
||||
gamma_amp = 2.2
|
||||
gamma_layer = 1.0
|
||||
|
||||
[0]
|
||||
path = /home/johnl/samples/percussion/kits/natural-mf/samples/snare-*
|
||||
gain = 0.88
|
||||
pan = -0.10
|
||||
|
||||
[1]
|
||||
gain = 0.25
|
||||
pan = -0.50
|
||||
|
||||
[2]
|
||||
gain = 0.25
|
||||
pan = -0.50
|
||||
|
||||
[3]
|
||||
gain = 0.25
|
||||
pan = -0.50
|
|
@ -0,0 +1,24 @@
|
|||
[triggers]
|
||||
|
||||
36 = 0
|
||||
39 = 0
|
||||
|
||||
[cuts]
|
||||
|
||||
37 = 0
|
||||
38 = 0
|
||||
|
||||
[global]
|
||||
|
||||
rms_time = 250
|
||||
gamma_amp = 2.2
|
||||
gamma_layer = 1.0
|
||||
|
||||
[0]
|
||||
path = /home/johnl/samples/percussion/kits/sm-drums/samples/hh-tight/hh-*
|
||||
rms_time = 100
|
||||
gain = 0.45
|
||||
pan = 0
|
||||
gamma_amp = 1
|
||||
gamma_layer = 0.2
|
||||
tau_cut = 1
|
Reference in New Issue