diff --git a/Makefile b/Makefile index cfb137a..99fc279 100644 --- a/Makefile +++ b/Makefile @@ -31,11 +31,11 @@ default: $(TARGET) all: default SOURCES = src/algorithms.c src/amy.c src/envelope.c src/examples.c \ - src/filters.c src/oscillators.c src/pcm.c src/partials.c \ + src/filters.c src/oscillators.c src/pcm.c src/partials.c src/custom.c \ src/delay.c src/log2_exp2.c src/patches.c OBJECTS = $(patsubst %.c, %.o, src/algorithms.c src/amy.c src/envelope.c \ - src/delay.c src/partials.c src/patches.c \ + src/delay.c src/partials.c src/custom.c src/patches.c \ src/examples.c src/filters.c src/oscillators.c src/pcm.c src/log2_exp2.c \ src/libminiaudio-audio.c) diff --git a/src/amy-example.c b/src/amy-example.c index 8b186f2..4814e45 100644 --- a/src/amy-example.c +++ b/src/amy-example.c @@ -49,6 +49,10 @@ int main(int argc, char ** argv) { } uint32_t start = amy_sysclock(); + #if AMY_HAS_CUSTOM == 1 + example_init_custom(); + #endif + amy_start(/* cores= */ 1, /* reverb= */ 1, /* chorus= */ 1); ma_encoder_config config = ma_encoder_config_init(ma_encoding_format_wav, ma_format_s16, AMY_NCHANS, AMY_SAMPLE_RATE); @@ -66,7 +70,6 @@ int main(int argc, char ** argv) { amy_reset_oscs(); - example_voice_chord(0); // Now just spin for 10s @@ -74,7 +77,7 @@ int main(int argc, char ** argv) { if (output_filename) { int16_t * frames = amy_simple_fill_buffer(); int num_frames = AMY_BLOCK_SIZE; - result = ma_encoder_write_pcm_frames(&encoder, frames, num_frames, NULL); + ma_encoder_write_pcm_frames(&encoder, frames, num_frames, NULL); } usleep(THREAD_USLEEP); } diff --git a/src/amy.c b/src/amy.c index 88cb6c6..e8c517d 100644 --- a/src/amy.c +++ b/src/amy.c @@ -175,7 +175,7 @@ void config_chorus(float level, int max_delay, float lfo_freq, float depth) { alloc_chorus_delay_lines(); } // if we're turning on for the first time, start the oscillator. - if (synth[CHORUS_MOD_SOURCE].status == OFF) { //chorus.level == 0) { + if (synth[CHORUS_MOD_SOURCE].status == STATUS_OFF) { //chorus.level == 0) { // Setup chorus oscillator. synth[CHORUS_MOD_SOURCE].logfreq_coefs[COEF_CONST] = logfreq_of_freq(lfo_freq); synth[CHORUS_MOD_SOURCE].logfreq_coefs[COEF_NOTE] = 0; // Turn off default. @@ -592,7 +592,7 @@ void reset_osc(uint16_t i ) { synth[i].sample = F2S(0); synth[i].mod_value = F2S(0); synth[i].substep = 0; - synth[i].status = OFF; + synth[i].status = STATUS_OFF; AMY_UNSET(synth[i].chained_osc); AMY_UNSET(synth[i].mod_source); synth[i].mod_target = 0; @@ -649,6 +649,9 @@ int8_t oscs_init() { if(pcm_samples) { pcm_init(); } + if(AMY_HAS_CUSTOM == 1) { + custom_init(); + } events = (struct delta*)malloc_caps(sizeof(struct delta) * AMY_EVENT_FIFO_LEN, EVENTS_RAM_CAPS); synth = (struct synthinfo*) malloc_caps(sizeof(struct synthinfo) * (AMY_OSCS+1), SYNTH_RAM_CAPS); msynth = (struct mod_synthinfo*) malloc_caps(sizeof(struct mod_synthinfo) * (AMY_OSCS+1), SYNTH_RAM_CAPS); @@ -787,6 +790,9 @@ void osc_note_on(uint16_t osc, float initial_freq) { if(synth[osc].wave==PARTIAL) partial_note_on(osc); if(synth[osc].wave==PARTIALS) partials_note_on(osc); } + if(AMY_HAS_CUSTOM == 1) { + if(synth[osc].wave==CUSTOM) custom_note_on(osc, initial_freq); + } } void apply_target_to_coefs(uint16_t osc, int target_val, int which_coef) { @@ -954,6 +960,7 @@ void play_event(struct delta d) { if(synth[synth[d.osc].mod_source].wave==TRIANGLE) triangle_mod_trigger(synth[d.osc].mod_source); if(synth[synth[d.osc].mod_source].wave==PULSE) pulse_mod_trigger(synth[d.osc].mod_source); if(synth[synth[d.osc].mod_source].wave==PCM) pcm_mod_trigger(synth[d.osc].mod_source); + if(synth[synth[d.osc].mod_source].wave==CUSTOM) custom_mod_trigger(synth[d.osc].mod_source); } } @@ -977,6 +984,11 @@ void play_event(struct delta d) { #endif } else if(synth[d.osc].wave==PCM) { pcm_note_off(d.osc); } + else if(synth[d.osc].wave==CUSTOM) { + #if AMY_HAS_CUSTOM == 1 + custom_note_off(d.osc); + #endif + } else { // osc note off, start release AMY_UNSET(synth[d.osc].note_on_clock); @@ -1051,7 +1063,7 @@ void hold_and_modify(uint16_t osc) { } else { if ( (total_samples - synth[osc].zero_amp_clock) >= MIN_ZERO_AMP_TIME_SAMPS) { //printf("h&m: time %f osc %d OFF\n", total_samples/(float)AMY_SAMPLE_RATE, osc); - synth[osc].status = OFF; + synth[osc].status = STATUS_OFF; AMY_UNSET(synth[osc].note_off_clock); AMY_UNSET(synth[osc].zero_amp_clock); } @@ -1125,6 +1137,9 @@ SAMPLE render_osc_wave(uint16_t osc, uint8_t core, SAMPLE* buf) { if(synth[osc].wave == PARTIALS) max_val = render_partials(buf, osc); } } + if(AMY_HAS_CUSTOM == 1) { + if(synth[osc].wave == CUSTOM) max_val = render_custom(buf, osc); + } AMY_PROFILE_STOP(RENDER_OSC_WAVE) return max_val; } @@ -1140,7 +1155,7 @@ void amy_render(uint16_t start, uint16_t end, uint8_t core) { SAMPLE max_val = render_osc_wave(osc, core, per_osc_fb[core]); if (max_val > max_max) max_max = max_val; // check it's not off, just in case. todo, why do i care? - if(synth[osc].wave != OFF) { + if(synth[osc].wave != WAVE_OFF) { // apply filter to osc if set if(synth[osc].filter_type != FILTER_NONE) filter_process(per_osc_fb[core], osc, max_val); mix_with_pan(fbl[core], per_osc_fb[core], msynth[osc].last_pan, msynth[osc].pan); diff --git a/src/amy.h b/src/amy.h index 8392875..b985487 100644 --- a/src/amy.h +++ b/src/amy.h @@ -23,8 +23,14 @@ typedef struct { uint8_t midinote; } pcm_map_t; +#ifndef AMY_CONFIG_H +#define AMY_CONFIG_H amy_config.h +#endif + +#define QUOTED(x) #x +#define INCLUDE(x) QUOTED(x) -#include "amy_config.h" +#include INCLUDE(AMY_CONFIG_H) // Rest of amy setup #define SAMPLE_MAX 32767 @@ -94,6 +100,8 @@ enum coefs{ #define ALGO 8 #define PARTIAL 9 #define PARTIALS 10 +#define CUSTOM 11 +#define WAVE_OFF 12 // synth[].status values #define EMPTY 0 #define SCHEDULED 1 @@ -101,8 +109,7 @@ enum coefs{ #define AUDIBLE 3 #define IS_MOD_SOURCE 4 #define IS_ALGO_SOURCE 5 -// Is this for .wave or .status? -#define OFF 11 +#define STATUS_OFF 6 #define true 1 #define false 0 @@ -412,6 +419,16 @@ struct state { int16_t latency_ms; }; +// custom oscillator +struct custom_oscillator { + void (*init)(void); + void (*note_on)(struct synthinfo* osc, float freq); + void (*note_off)(struct synthinfo* osc); + void (*mod_trigger)(struct synthinfo* osc); + SAMPLE (*render)(SAMPLE* buf, struct synthinfo* osc); + SAMPLE (*compute_mod)(struct synthinfo* osc); +}; + // Shared structures extern uint32_t total_samples; extern struct synthinfo *synth; @@ -441,8 +458,8 @@ void amy_stop(); void amy_live_start(); void amy_live_stop(); void amy_reset_oscs(); -void amy_print_devices(); - +void amy_print_devices(); +void amy_set_custom(struct custom_oscillator* custom); extern void reset_osc(uint16_t i ); extern float render_am_lut(float * buf, float step, float skip, float incoming_amp, float ending_amp, const float* lut, int16_t lut_size, float *mod, float bandwidth); @@ -451,6 +468,7 @@ extern void ks_deinit(); extern void algo_init(); extern void algo_deinit(); extern void pcm_init(); +extern void custom_init(); extern SAMPLE render_ks(SAMPLE * buf, uint16_t osc); extern SAMPLE render_sine(SAMPLE * buf, uint16_t osc); extern SAMPLE render_fm_sine(SAMPLE *buf, uint16_t osc, SAMPLE *mod, SAMPLE feedback_level, uint16_t algo_osc, SAMPLE mod_amp); @@ -469,6 +487,7 @@ extern void patches_event_has_voices(struct event e); extern void patches_reset(); extern SAMPLE render_partials(SAMPLE *buf, uint16_t osc); +extern SAMPLE render_custom(SAMPLE *buf, uint16_t osc) ; extern SAMPLE compute_mod_pulse(uint16_t osc); extern SAMPLE compute_mod_noise(uint16_t osc); @@ -477,6 +496,7 @@ extern SAMPLE compute_mod_saw_up(uint16_t osc); extern SAMPLE compute_mod_saw_down(uint16_t osc); extern SAMPLE compute_mod_triangle(uint16_t osc); extern SAMPLE compute_mod_pcm(uint16_t osc); +extern SAMPLE compute_mod_custom(uint16_t osc); extern void sine_note_on(uint16_t osc, float initial_freq); extern void fm_sine_note_on(uint16_t osc, uint16_t algo_osc); @@ -486,6 +506,8 @@ extern void triangle_note_on(uint16_t osc, float initial_freq); extern void pulse_note_on(uint16_t osc, float initial_freq); extern void pcm_note_on(uint16_t osc); extern void pcm_note_off(uint16_t osc); +extern void custom_note_on(uint16_t osc, float freq); +extern void custom_note_off(uint16_t osc); extern void partial_note_on(uint16_t osc); extern void partial_note_off(uint16_t osc); extern void algo_note_on(uint16_t osc); @@ -498,6 +520,7 @@ extern void saw_up_mod_trigger(uint16_t osc); extern void triangle_mod_trigger(uint16_t osc); extern void pulse_mod_trigger(uint16_t osc); extern void pcm_mod_trigger(uint16_t osc); +extern void custom_mod_trigger(uint16_t osc); extern SAMPLE amy_get_random(); //extern void algo_custom_setup_patch(uint16_t osc, uint16_t * target_oscs); diff --git a/src/amy_config.h b/src/amy_config.h index e0f6e1c..c75c9cc 100644 --- a/src/amy_config.h +++ b/src/amy_config.h @@ -9,6 +9,7 @@ #define AMY_OSCS 120 #define AMY_SAMPLE_RATE 44100 #define AMY_NCHANS 2 +#define AMY_USE_FIXEDPOINT // These are overriden for you if you include pcm_X.h {tiny, small, large} @@ -22,6 +23,7 @@ extern const uint16_t pcm_samples; #define AMY_MAX_DRIFT_MS 20000 // ms of time you can schedule ahead before synth recomputes time base #define AMY_KS_OSCS 1 // How many karplus-strong oscillators to keep track of (0 disables KS) #define AMY_HAS_PARTIALS 1 // 1 = Make partials available +#define AMY_HAS_CUSTOM 0 // 1 = Make custom oscillators available #define PCM_AMY_SAMPLE_RATE 22050 #define AMY_EVENT_FIFO_LEN 2000 diff --git a/src/amy_fixedpoint.h b/src/amy_fixedpoint.h index aa764a9..75da1d5 100644 --- a/src/amy_fixedpoint.h +++ b/src/amy_fixedpoint.h @@ -51,7 +51,6 @@ // #define MUL_KK mul4_k12k11(a, b) // #define MUL_KKS mul4_k16k7(a, b) // the second arg is "sensitive". -#define AMY_USE_FIXEDPOINT #ifndef AMY_USE_FIXEDPOINT diff --git a/src/custom.c b/src/custom.c new file mode 100644 index 0000000..35ab475 --- /dev/null +++ b/src/custom.c @@ -0,0 +1,42 @@ +// custom.c + +#include "amy.h" +#include + +struct custom_oscillator* custom_osc; + +void amy_set_custom(struct custom_oscillator* custom) { + assert(custom_osc == NULL); + custom_osc = custom; +} + +void custom_init() { + if (custom_osc != NULL) { + custom_osc->init(); + } +} + +void custom_note_on(uint16_t osc, float freq) { + assert(custom_osc != NULL); + custom_osc->note_on(&synth[osc], freq); +} + +void custom_note_off(uint16_t osc) { + assert(custom_osc != NULL); + custom_osc->note_off(&synth[osc]); +} + +void custom_mod_trigger(uint16_t osc) { + assert(custom_osc != NULL); + custom_osc->mod_trigger(&synth[osc]); +} + +SAMPLE render_custom(SAMPLE* buf, uint16_t osc) { + assert(custom_osc != NULL); + return custom_osc->render(buf, &synth[osc]); +} + +SAMPLE compute_mod_custom(uint16_t osc) { + assert(custom_osc != NULL); + return custom_osc->compute_mod(&synth[osc]); +} diff --git a/src/envelope.c b/src/envelope.c index 3d45508..de97b01 100644 --- a/src/envelope.c +++ b/src/envelope.c @@ -25,6 +25,9 @@ SAMPLE compute_mod_value(uint16_t mod_osc) { if(synth[mod_osc].wave == SINE) value = compute_mod_sine(mod_osc); if(pcm_samples) if(synth[mod_osc].wave == PCM) value = compute_mod_pcm(mod_osc); + if(AMY_HAS_CUSTOM == 1) { + if(synth[mod_osc].wave == CUSTOM) value = compute_mod_custom(mod_osc); + } synth[mod_osc].mod_value = value; return value; } diff --git a/src/examples.c b/src/examples.c index f2c6921..c0e915d 100644 --- a/src/examples.c +++ b/src/examples.c @@ -272,4 +272,61 @@ void example_drums(uint32_t start, int loops) { AMY_UNSET(e.midi_note); } } -} \ No newline at end of file +} + +// Minimal custom oscillator + +#if AMY_HAS_CUSTOM == 1 + +void beeper_init(void) { + printf("Beeper init\n"); +} + +void beeper_note_on(struct synthinfo* osc, float freq) { + saw_down_note_on(osc->osc, freq); +} + +void beeper_note_off(struct synthinfo* osc) { + osc->note_off_clock = total_samples; +} + +void beeper_mod_trigger(struct synthinfo* osc) { + saw_down_mod_trigger(osc->osc); +} + +SAMPLE beeper_render(SAMPLE* buf, struct synthinfo* osc) { + return render_saw_down(buf, osc->osc); +} + +SAMPLE beeper_compute_mod(struct synthinfo* osc) { + return compute_mod_saw_down(osc->osc); +} + +struct custom_oscillator beeper = { + beeper_init, + beeper_note_on, + beeper_note_off, + beeper_mod_trigger, + beeper_render, + beeper_compute_mod +}; + +void example_init_custom() { + amy_set_custom(&beeper); +} + +void example_custom_beep() { + struct event e = amy_default_event(); + e.osc = 50; + e.time = amy_sysclock(); + e.freq_coefs[0] = 880; + e.wave = CUSTOM; + e.velocity = 1; + amy_add_event(e); + + e.velocity = 0; + e.time += 500; + amy_add_event(e); +} + +#endif diff --git a/src/examples.h b/src/examples.h index 5ffebee..a920c07 100644 --- a/src/examples.h +++ b/src/examples.h @@ -13,6 +13,8 @@ void example_fm(uint32_t start) ; void example_multimbral_fm(); void example_drums(uint32_t start, int loops); void example_sine(uint32_t start); +void example_init_custom(); +void example_custom_beep(); void example_patches(); void example_voice_chord(uint16_t patch); void example_voice_alloc(); diff --git a/src/oscillators.c b/src/oscillators.c index d38f817..0f460ce 100644 --- a/src/oscillators.c +++ b/src/oscillators.c @@ -536,7 +536,7 @@ void partial_note_off(uint16_t osc) { AMY_UNSET(synth[osc].note_on_clock); synth[osc].note_off_clock = total_samples; synth[osc].last_amp = 0; - synth[osc].status = OFF; + synth[osc].status = STATUS_OFF; } diff --git a/src/pcm.c b/src/pcm.c index 6b8fbc4..c84dd9f 100644 --- a/src/pcm.c +++ b/src/pcm.c @@ -64,7 +64,7 @@ SAMPLE render_pcm(SAMPLE* buf, uint16_t osc) { synth[osc].phase = P_WRAPPED_SUM(synth[osc].phase, step); base_index = INT_OF_P(synth[osc].phase, PCM_INDEX_BITS); if(base_index >= patch->length) { // end - synth[osc].status = OFF;// is this right? + synth[osc].status = STATUS_OFF;// is this right? sample = 0; } else { if(msynth[osc].feedback > 0) { // loop @@ -94,7 +94,7 @@ SAMPLE compute_mod_pcm(uint16_t osc) { uint32_t base_index = INT_OF_P(synth[osc].phase, PCM_INDEX_BITS); SAMPLE sample; if(base_index >= patch->length) { // end - synth[osc].status = OFF;// is this right? + synth[osc].status = STATUS_OFF;// is this right? sample = 0; } else { sample = L2S(table[base_index]); diff --git a/src/setup.py b/src/setup.py index b5276d7..a221fe9 100644 --- a/src/setup.py +++ b/src/setup.py @@ -2,7 +2,7 @@ import glob import os # the c++ extension module -sources = ['algorithms.c', 'amy.c', 'delay.c', 'envelope.c', 'filters.c', 'patches.c', 'libminiaudio-audio.c', 'oscillators.c', 'partials.c', 'pcm.c', 'pyamy.c', 'log2_exp2.c'] +sources = ['algorithms.c', 'amy.c', 'delay.c', 'envelope.c', 'filters.c', 'custom.c', 'patches.c', 'libminiaudio-audio.c', 'oscillators.c', 'partials.c', 'pcm.c', 'pyamy.c', 'log2_exp2.c'] os.environ["CC"] = "gcc" os.environ["CXX"] = "g++"