diff --git a/audioutils/fmsynth/Kconfig b/audioutils/fmsynth/Kconfig new file mode 100644 index 0000000000..81d7977d07 --- /dev/null +++ b/audioutils/fmsynth/Kconfig @@ -0,0 +1,10 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config AUDIOUTILS_FMSYNTH_LIB + bool "FM Synthesizer Library" + default n + ---help--- + Enable support for the FM Synthesizer library. diff --git a/audioutils/fmsynth/Make.defs b/audioutils/fmsynth/Make.defs new file mode 100644 index 0000000000..0350823ed7 --- /dev/null +++ b/audioutils/fmsynth/Make.defs @@ -0,0 +1,23 @@ +############################################################################ +# apps/audioutils/fmsynth/Make.defs +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifeq ($(CONFIG_AUDIOUTILS_FMSYNTH_LIB),y) +CONFIGURED_APPS += $(APPDIR)/audioutils/fmsynth +endif diff --git a/audioutils/fmsynth/Makefile b/audioutils/fmsynth/Makefile new file mode 100644 index 0000000000..f291a74b12 --- /dev/null +++ b/audioutils/fmsynth/Makefile @@ -0,0 +1,25 @@ +############################################################################ +# apps/audioutils/fmsynth/Makefile +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +include $(APPDIR)/Make.defs + +CSRCS = fmsynth.c fmsynth_eg.c fmsynth_op.c + +include $(APPDIR)/Application.mk diff --git a/audioutils/fmsynth/fmsynth.c b/audioutils/fmsynth/fmsynth.c new file mode 100644 index 0000000000..86a791fab2 --- /dev/null +++ b/audioutils/fmsynth/fmsynth.c @@ -0,0 +1,237 @@ +/**************************************************************************** + * apps/audioutils/fmsynth/fmsynth.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define WRAP_ROUND_TIME_SEC (10) + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static int max_phase_time; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * name: fetch_feedback + ****************************************************************************/ + +static void fetch_feedback(FAR fmsynth_op_t *ops) +{ + while (ops != NULL) + { + fmsynthop_update_feedback(ops); + ops = ops->parallelop; + } +} + +/**************************************************************************** + * name: update_phase + ****************************************************************************/ + +static void update_phase(FAR fmsynth_sound_t *snd) +{ + snd->phase_time++; + if (snd->phase_time >= max_phase_time) + { + snd->phase_time = 0; + } +} + +/**************************************************************************** + * name: sound_modulate + ****************************************************************************/ + +static int sound_modulate(FAR fmsynth_sound_t *snd) +{ + int out = 0; + FAR fmsynth_op_t *op; + + if (snd->operators == NULL) + { + return out; + } + + fetch_feedback(snd->operators); + + for (op = snd->operators; op != NULL; op = op->parallelop) + { + out += fmsynthop_operate(op, snd->phase_time); + } + + update_phase(snd); + + return out * snd->volume / FMSYNTH_MAX_VOLUME; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * name: fmsynth_initialize + ****************************************************************************/ + +int fmsynth_initialize(int fs) +{ + max_phase_time = fs * WRAP_ROUND_TIME_SEC; + return fmsynthop_set_samplerate(fs); +} + +/**************************************************************************** + * name: fmsynthsnd_create + ****************************************************************************/ + +FAR fmsynth_sound_t *fmsynthsnd_create(void) +{ + FAR fmsynth_sound_t *ret; + ret = (FAR fmsynth_sound_t *)malloc(sizeof(fmsynth_sound_t)); + if (ret) + { + ret->phase_time = 0; + ret->volume = FMSYNTH_MAX_VOLUME; + ret->operators = NULL; + ret->next_sound = NULL; + } + + return ret; +} + +/**************************************************************************** + * name: fmsynthsnd_delete + ****************************************************************************/ + +void fmsynthsnd_delete(FAR fmsynth_sound_t *snd) +{ + if (snd != NULL) + { + free(snd); + } +} + +/**************************************************************************** + * name: fmsynthsnd_set_operator + ****************************************************************************/ + +int fmsynthsnd_set_operator(FAR fmsynth_sound_t *snd, FAR fmsynth_op_t *op) +{ + snd->operators = op; + + return OK; +} + +/**************************************************************************** + * name: fmsynthsnd_set_soundfreq + ****************************************************************************/ + +void fmsynthsnd_set_soundfreq(FAR fmsynth_sound_t *snd, float freq) +{ + FAR fmsynth_op_t *op; + + for (op = snd->operators; op != NULL; op = op->parallelop) + { + fmsynthop_set_soundfreq(op, freq); + fmsynthop_start(op); + } +} + +/**************************************************************************** + * name: fmsynthsnd_set_volume + ****************************************************************************/ + +void fmsynthsnd_set_volume(FAR fmsynth_sound_t *snd, float vol) +{ + snd->volume = vol * FMSYNTH_MAX_VOLUME; +} + +/**************************************************************************** + * name: fmsynthsnd_add_subsound + ****************************************************************************/ + +int fmsynthsnd_add_subsound(FAR fmsynth_sound_t *top, + FAR fmsynth_sound_t *sub) +{ + FAR fmsynth_sound_t *s = top; + + if (!top || !sub) + { + return ERROR; + } + + for (s = top; s->next_sound; s = s->next_sound); + + s->next_sound = sub; + + return OK; +} + +/**************************************************************************** + * name: fmsynth_rendering + ****************************************************************************/ + +int fmsynth_rendering(FAR fmsynth_sound_t *snd, + FAR int16_t *sample, int sample_num, int chnum, + fmsynth_tickcb_t cb, unsigned long cbarg) +{ + int i; + int ch; + int out; + FAR fmsynth_sound_t *itr; + + for (i = 0; i < sample_num; i += chnum) + { + out = 0; + for (itr = snd; itr != NULL; itr = itr->next_sound) + { + out = out + sound_modulate(itr); + } + + for (ch = 0; ch < chnum; ch++) + { + *sample++ = (int16_t)out; + } + + if (cb != NULL) + { + cb(cbarg); + } + } + + if (i > sample_num) + { + i -= chnum; + } + + /* Return total bytes stored in the buffer */ + + return i * sizeof(int16_t); +} diff --git a/audioutils/fmsynth/fmsynth_eg.c b/audioutils/fmsynth/fmsynth_eg.c new file mode 100644 index 0000000000..6f7ca48523 --- /dev/null +++ b/audioutils/fmsynth/fmsynth_eg.c @@ -0,0 +1,197 @@ +/**************************************************************************** + * apps/audioutils/fmsynth/fmsynth_eg.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define CONVERT_INITVAL(lv) (int)((lv) * FMSYNTH_MAX_EGLEVEL) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * name: set_egparams + ****************************************************************************/ + +static int set_egparams(int fs, + FAR fmsynth_egparam_t *param, + FAR struct fmsynth_eglevel_s *target_level, + FAR struct fmsynth_eglevel_s *last_level) +{ + param->initval = CONVERT_INITVAL(last_level->level); + param->period = fs * target_level->period_ms / 1000; + param->diff2next = CONVERT_INITVAL(target_level->level) + - CONVERT_INITVAL(last_level->level); + + if (param->initval < -FMSYNTH_MAX_EGLEVEL || + param->initval > FMSYNTH_MAX_EGLEVEL || param->period < 0) + { + return -1; + } + + return 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * name: fmsyntheg_create + ****************************************************************************/ + +FAR fmsynth_eg_t *fmsyntheg_create(void) +{ + int i; + FAR fmsynth_eg_t *ret = (FAR fmsynth_eg_t *)malloc(sizeof(fmsynth_eg_t)); + + if (ret) + { + ret->state = EGSTATE_RELEASED; + ret->state_counter = 0; + for (i = 0; i < EGSTATE_MAX; i++) + { + ret->state_params[i].initval = 0; + ret->state_params[i].period = 0; + } + + ret->state_params[EGSTATE_RELEASED].initval = FMSYNTH_MAX_EGLEVEL; + } + + return ret; +} + +/**************************************************************************** + * name: fmsyntheg_delete + ****************************************************************************/ + +void fmsyntheg_delete(FAR fmsynth_eg_t *eg) +{ + if (eg != NULL) + { + free(eg); + } +} + +/**************************************************************************** + * name: fmsyntheg_set_param + ****************************************************************************/ + +int fmsyntheg_set_param(FAR fmsynth_eg_t *eg, + int fs, FAR fmsynth_eglevels_t *levels) +{ + int errcnt = 0; + + if (fs <= 0) + { + return ERROR; + } + + errcnt += set_egparams(fs, &eg->state_params[EGSTATE_ATTACK], + &levels->attack, &levels->release); + + errcnt += set_egparams(fs, &eg->state_params[EGSTATE_DECAYBREAK], + &levels->decaybrk, &levels->attack); + + errcnt += set_egparams(fs, &eg->state_params[EGSTATE_DECAY], + &levels->decay, &levels->decaybrk); + + errcnt += set_egparams(fs, &eg->state_params[EGSTATE_SUSTAIN], + &levels->sustain, &levels->decay); + + errcnt += set_egparams(fs, &eg->state_params[EGSTATE_RELEASE], + &levels->release, &levels->sustain); + + eg->state_params[EGSTATE_RELEASED].initval = + CONVERT_INITVAL(levels->release.level); + + return errcnt ? ERROR : OK; +} + +/**************************************************************************** + * name: fmsyntheg_start + ****************************************************************************/ + +void fmsyntheg_start(FAR fmsynth_eg_t *eg) +{ + eg->state = EGSTATE_ATTACK; + eg->state_counter = 0; +} + +/**************************************************************************** + * name: fmsyntheg_stop + ****************************************************************************/ + +void fmsyntheg_stop(FAR fmsynth_eg_t *eg) +{ + eg->state = EGSTATE_RELEASED; + eg->state_counter = 0; +} + +/**************************************************************************** + * name: fmsyntheg_operate + ****************************************************************************/ + +int fmsyntheg_operate(FAR fmsynth_eg_t *eg) +{ + int val; + FAR fmsynth_egparam_t *param = &eg->state_params[eg->state]; + + val = param->initval; + + if (eg->state != EGSTATE_RELEASED) + { + if (eg->state_counter >= eg->state_params[eg->state].period) + { + /* Reset the counter */ + + eg->state_counter = 0; + + /* Search next available state */ + + do + { + eg->state++; + } + while (eg->state < EGSTATE_RELEASED + && eg->state_params[eg->state].period == 0); + + val = eg->state_params[eg->state].initval; + } + else + { + val = val + param->diff2next * eg->state_counter / param->period; + eg->state_counter++; + } + } + + return val; +} diff --git a/audioutils/fmsynth/fmsynth_op.c b/audioutils/fmsynth/fmsynth_op.c new file mode 100644 index 0000000000..3f5fda5212 --- /dev/null +++ b/audioutils/fmsynth/fmsynth_op.c @@ -0,0 +1,521 @@ +/**************************************************************************** + * apps/audioutils/fmsynth/fmsynth_op.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define PHASE_ADJUST(th) \ + ( ((th) < 0 ? (FMSYNTH_PI) - (th) : (th)) % (FMSYNTH_PI * 2) ) + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const short s_sintbl[] = +{ + 0xff37, /* Extra data for linear completion */ + + /* Actual sin table of half PI [256] */ + + 0x0000, 0x00c9, 0x0192, 0x025b, + 0x0324, 0x03ed, 0x04b6, 0x057e, + 0x0647, 0x0710, 0x07d9, 0x08a1, + 0x096a, 0x0a32, 0x0afb, 0x0bc3, + 0x0c8b, 0x0d53, 0x0e1b, 0x0ee3, + 0x0fab, 0x1072, 0x1139, 0x1200, + 0x12c7, 0x138e, 0x1455, 0x151b, + 0x15e1, 0x16a7, 0x176d, 0x1833, + 0x18f8, 0x19bd, 0x1a82, 0x1b46, + 0x1c0b, 0x1ccf, 0x1d93, 0x1e56, + 0x1f19, 0x1fdc, 0x209f, 0x2161, + 0x2223, 0x22e4, 0x23a6, 0x2467, + 0x2527, 0x25e7, 0x26a7, 0x2767, + 0x2826, 0x28e5, 0x29a3, 0x2a61, + 0x2b1e, 0x2bdb, 0x2c98, 0x2d54, + 0x2e10, 0x2ecc, 0x2f86, 0x3041, + 0x30fb, 0x31b4, 0x326d, 0x3326, + 0x33de, 0x3496, 0x354d, 0x3603, + 0x36b9, 0x376f, 0x3824, 0x38d8, + 0x398c, 0x3a3f, 0x3af2, 0x3ba4, + 0x3c56, 0x3d07, 0x3db7, 0x3e67, + 0x3f16, 0x3fc5, 0x4073, 0x4120, + 0x41cd, 0x4279, 0x4325, 0x43d0, + 0x447a, 0x4523, 0x45cc, 0x4674, + 0x471c, 0x47c3, 0x4869, 0x490e, + 0x49b3, 0x4a57, 0x4afa, 0x4b9d, + 0x4c3f, 0x4ce0, 0x4d80, 0x4e20, + 0x4ebf, 0x4f5d, 0x4ffa, 0x5097, + 0x5133, 0x51ce, 0x5268, 0x5301, + 0x539a, 0x5432, 0x54c9, 0x555f, + 0x55f4, 0x5689, 0x571d, 0x57b0, + 0x5842, 0x58d3, 0x5963, 0x59f3, + 0x5a81, 0x5b0f, 0x5b9c, 0x5c28, + 0x5cb3, 0x5d3d, 0x5dc6, 0x5e4f, + 0x5ed6, 0x5f5d, 0x5fe2, 0x6067, + 0x60eb, 0x616e, 0x61f0, 0x6271, + 0x62f1, 0x6370, 0x63ee, 0x646b, + 0x64e7, 0x6562, 0x65dd, 0x6656, + 0x66ce, 0x6745, 0x67bc, 0x6831, + 0x68a5, 0x6919, 0x698b, 0x69fc, + 0x6a6c, 0x6adb, 0x6b4a, 0x6bb7, + 0x6c23, 0x6c8e, 0x6cf8, 0x6d61, + 0x6dc9, 0x6e30, 0x6e95, 0x6efa, + 0x6f5e, 0x6fc0, 0x7022, 0x7082, + 0x70e1, 0x7140, 0x719d, 0x71f9, + 0x7254, 0x72ae, 0x7306, 0x735e, + 0x73b5, 0x740a, 0x745e, 0x74b1, + 0x7503, 0x7554, 0x75a4, 0x75f3, + 0x7640, 0x768d, 0x76d8, 0x7722, + 0x776b, 0x77b3, 0x77f9, 0x783f, + 0x7883, 0x78c6, 0x7908, 0x7949, + 0x7989, 0x79c7, 0x7a04, 0x7a41, + 0x7a7c, 0x7ab5, 0x7aee, 0x7b25, + 0x7b5c, 0x7b91, 0x7bc4, 0x7bf7, + 0x7c29, 0x7c59, 0x7c88, 0x7cb6, + 0x7ce2, 0x7d0e, 0x7d38, 0x7d61, + 0x7d89, 0x7db0, 0x7dd5, 0x7df9, + 0x7e1c, 0x7e3e, 0x7e5e, 0x7e7e, + 0x7e9c, 0x7eb9, 0x7ed4, 0x7eef, + 0x7f08, 0x7f20, 0x7f37, 0x7f4c, + 0x7f61, 0x7f74, 0x7f86, 0x7f96, + 0x7fa6, 0x7fb4, 0x7fc1, 0x7fcd, + 0x7fd7, 0x7fe0, 0x7fe8, 0x7fef, + 0x7ff5, 0x7ff9, 0x7ffc, 0x7ffe, + + 0x7fff, /* Extra data for linear completion */ +}; + +static int local_fs; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * name: pseudo_sin256 + ****************************************************************************/ + +static int pseudo_sin256(int theta) +{ + int short_sin; + int rest; + int phase; + int tblidx; + + theta = PHASE_ADJUST(theta); + + rest = theta & 0x7f; + phase = theta / (FMSYNTH_PI / 2); + tblidx = (theta % (FMSYNTH_PI / 2)) >> 7; + + if (phase & 0x01) + { + tblidx = 257 - tblidx; + short_sin = s_sintbl[tblidx]; + short_sin = short_sin + + (((s_sintbl[tblidx - 1] - short_sin) * rest) >> 7); + } + else + { + short_sin = s_sintbl[tblidx + 1]; + short_sin = short_sin + + (((s_sintbl[tblidx + 2] - short_sin) * rest) >> 7); + } + + return phase & 0x02 ? -short_sin : short_sin; +} + +/**************************************************************************** + * name: triangle_wave + ****************************************************************************/ + +static int triangle_wave(int theta) +{ + int ret = 0; + int phase; + int offset; + int slope; + + theta = PHASE_ADJUST(theta); + phase = theta / (FMSYNTH_PI / 2); + offset = theta % (FMSYNTH_PI / 2); + + switch (phase) + { + case 0: + ret = 0; + slope = SHRT_MAX; + break; + case 1: + ret = SHRT_MAX; + slope = -SHRT_MAX; + break; + case 2: + ret = 0; + slope = -SHRT_MAX; + break; + case 3: + ret = -SHRT_MAX; + slope = SHRT_MAX; + break; + default: + ret = 0; + slope = SHRT_MAX; + break; + } + + return ret + ((slope * offset) >> 15); +} + +/**************************************************************************** + * name: sawtooth_wave + ****************************************************************************/ + +static int sawtooth_wave(int theta) +{ + theta = PHASE_ADJUST(theta); + return (theta >> 1) - SHRT_MAX; +} + +/**************************************************************************** + * name: square_wave + ****************************************************************************/ + +static int square_wave(int theta) +{ + theta = PHASE_ADJUST(theta); + return theta < FMSYNTH_PI ? SHRT_MAX : -SHRT_MAX; +} + +/**************************************************************************** + * name: update_parameters + ****************************************************************************/ + +static void update_parameters(FAR fmsynth_op_t *op) +{ + if (local_fs != 0) + { + op->delta_phase = 2 * FMSYNTH_PI * op->sound_freq * op->freq_rate + / (float)local_fs; + } + else + { + op->delta_phase = 0.f; + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * name: fmsynthop_set_samplerate + ****************************************************************************/ + +int fmsynthop_set_samplerate(int fs) +{ + if (fs < 0) + { + return ERROR; + } + + local_fs = fs; + return OK; +} + +/**************************************************************************** + * name: fmsynthop_create + ****************************************************************************/ + +FAR fmsynth_op_t *fmsynthop_create(void) +{ + FAR fmsynth_op_t *ret; + + ret = (FAR fmsynth_op_t *)malloc(sizeof(fmsynth_op_t)); + + if (ret) + { + ret->eg = fmsyntheg_create(); + if (!ret->eg) + { + free(ret); + return NULL; + } + + ret->wavegen = NULL; + ret->cascadeop = NULL; + ret->parallelop = NULL; + ret->feedback_ref = NULL; + ret->feedback_val = 0; + ret->feedbackrate = 0; + ret->last_sigval = 0; + ret->freq_rate = 1.f; + ret->sound_freq = 0.f; + ret->delta_phase = 0.f; + ret->current_phase = 0.f; + } + + return ret; +} + +/**************************************************************************** + * name: fmsynthop_delete + ****************************************************************************/ + +void fmsynthop_delete(FAR fmsynth_op_t *op) +{ + if (op != NULL) + { + if (op->eg) + { + free(op->eg); + } + + free(op); + } +} + +/**************************************************************************** + * name: fmsynthop_select_opfunc + ****************************************************************************/ + +int fmsynthop_select_opfunc(FAR fmsynth_op_t *op, int type) +{ + int ret = ERROR; + + if (op != NULL) + { + switch (type) + { + case FMSYNTH_OPFUNC_SIN: + op->wavegen = pseudo_sin256; + ret = OK; + break; + + case FMSYNTH_OPFUNC_TRIANGLE: + op->wavegen = triangle_wave; + ret = OK; + break; + + case FMSYNTH_OPFUNC_SAWTOOTH: + op->wavegen = sawtooth_wave; + ret = OK; + break; + + case FMSYNTH_OPFUNC_SQUARE: + op->wavegen = square_wave; + ret = OK; + break; + } + } + + return ret; +} + +/**************************************************************************** + * name: fmsynthop_set_envelope + ****************************************************************************/ + +int fmsynthop_set_envelope(FAR fmsynth_op_t *op, + FAR fmsynth_eglevels_t *levels) +{ + if (local_fs >= 0 && op && levels) + { + return fmsyntheg_set_param(op->eg, local_fs, levels); + } + + return ERROR; +} + +/**************************************************************************** + * name: fmsynthop_cascade_subop + ****************************************************************************/ + +int fmsynthop_cascade_subop(FAR fmsynth_op_t *op, + FAR fmsynth_op_t *subop) +{ + FAR fmsynth_op_t *tmp; + + if (!op || !subop) + { + return ERROR; + } + + for (tmp = op; tmp->cascadeop; tmp = tmp->cascadeop); + + tmp->cascadeop = subop; + + return OK; +} + +/**************************************************************************** + * name: fmsynthop_parallel_subop + ****************************************************************************/ + +int fmsynthop_parallel_subop(FAR fmsynth_op_t *op, + FAR fmsynth_op_t *subop) +{ + FAR fmsynth_op_t *tmp; + + if (!op || !subop) + { + return ERROR; + } + + for (tmp = op; tmp->parallelop; tmp = tmp->parallelop); + + tmp->parallelop = subop; + + return OK; +} + +/**************************************************************************** + * name: fmsynthop_bind_feedback + ****************************************************************************/ + +int fmsynthop_bind_feedback(FAR fmsynth_op_t *op, + FAR fmsynth_op_t *subop, float ratio) +{ + if (!op || !subop) + { + return ERROR; + } + + op->feedbackrate = (int)((float)FMSYNTH_MAX_EGLEVEL * ratio); + op->feedback_ref = &subop->last_sigval; + + return OK; +} + +/**************************************************************************** + * name: fmsynthop_update_feedback + ****************************************************************************/ + +int fmsynthop_update_feedback(FAR fmsynth_op_t *op) +{ + FAR fmsynth_op_t *tmp; + + for (tmp = op->cascadeop; tmp != NULL; tmp = tmp->parallelop) + { + fmsynthop_update_feedback(tmp); + } + + if (op->feedback_ref) + { + op->feedback_val = *op->feedback_ref * op->feedbackrate + / FMSYNTH_MAX_EGLEVEL; + } + + return OK; +} + +/**************************************************************************** + * name: fmsynthop_set_soundfreq + ****************************************************************************/ + +void fmsynthop_set_soundfreq(FAR fmsynth_op_t *op, float freq) +{ + FAR fmsynth_op_t *tmp; + + op->sound_freq = freq; + update_parameters(op); + + for (tmp = op->cascadeop; tmp != NULL; tmp = tmp->parallelop) + { + fmsynthop_set_soundfreq(tmp, freq); + } +} + +/**************************************************************************** + * name: fmsynthop_set_soundfreqrate + ****************************************************************************/ + +void fmsynthop_set_soundfreqrate(FAR fmsynth_op_t *op, float rate) +{ + op->freq_rate = rate; + update_parameters(op); +} + +/**************************************************************************** + * name: fmsynthop_start + ****************************************************************************/ + +void fmsynthop_start(FAR fmsynth_op_t *op) +{ + FAR fmsynth_op_t *tmp; + + fmsyntheg_start(op->eg); + + for (tmp = op->cascadeop; tmp; tmp = tmp->parallelop) + { + fmsynthop_start(tmp); + } +} + +/**************************************************************************** + * name: fmsynthop_stop + ****************************************************************************/ + +void fmsynthop_stop(FAR fmsynth_op_t *op) +{ + FAR fmsynth_op_t *tmp; + + fmsyntheg_stop(op->eg); + + for (tmp = op->cascadeop; tmp; tmp = tmp->parallelop) + { + fmsynthop_stop(tmp); + } +} + +/**************************************************************************** + * name: fmsynthop_operate + ****************************************************************************/ + +int fmsynthop_operate(FAR fmsynth_op_t *op, int phase_time) +{ + int phase; + FAR fmsynth_op_t *subop; + + op->current_phase = phase_time ? op->current_phase + op->delta_phase : 0.f; + + phase = (int)op->current_phase + op->feedback_val; + + subop = op->cascadeop; + + while (subop) + { + phase += fmsynthop_operate(subop, phase_time); + subop = subop->parallelop; + } + + op->last_sigval = fmsyntheg_operate(op->eg) * op->wavegen(phase) + / FMSYNTH_MAX_EGLEVEL; + + return op->last_sigval; +} diff --git a/audioutils/fmsynth/test/.gitignore b/audioutils/fmsynth/test/.gitignore new file mode 100644 index 0000000000..34211c6ab1 --- /dev/null +++ b/audioutils/fmsynth/test/.gitignore @@ -0,0 +1,5 @@ +/fmsynth_alsa +/fmsynth_test +/fmsyntheg_test +/fmsynthop_test +/opfunctest diff --git a/audioutils/fmsynth/test/Makefile b/audioutils/fmsynth/test/Makefile new file mode 100644 index 0000000000..7031c15e04 --- /dev/null +++ b/audioutils/fmsynth/test/Makefile @@ -0,0 +1,24 @@ +SRCS = ../fmsynth_eg.c ../fmsynth_op.c ../fmsynth.c +CFLAGS = -DFAR= -DCODE= -DOK=0 -DERROR=-1 -I .. -I ../../../include -g + +TARGETS = opfunctest fmsyntheg_test fmsynthop_test fmsynth_test fmsynth_alsa + +all: $(TARGETS) + +opfunctest: $(SRCS) opfunc_test.c + gcc $(CFLAGS) -o $@ $^ -lm + +fmsyntheg_test: $(SRCS) fmsynth_eg_test.c + gcc $(CFLAGS) -o $@ $^ + +fmsynthop_test: $(SRCS) fmsynth_op_test.c + gcc $(CFLAGS) -o $@ $^ + +fmsynth_test: $(SRCS) fmsynth_test.c + gcc $(CFLAGS) -o $@ $^ + +fmsynth_alsa: $(SRCS) fmsynth_alsa_test.c + gcc $(CFLAGS) -o $@ $^ -lasound + +clean: + rm -rf $(TARGETS) diff --git a/audioutils/fmsynth/test/fmsynth_alsa_test.c b/audioutils/fmsynth/test/fmsynth_alsa_test.c new file mode 100644 index 0000000000..1de8fcb3c1 --- /dev/null +++ b/audioutils/fmsynth/test/fmsynth_alsa_test.c @@ -0,0 +1,324 @@ +/**************************************************************************** + * apps/audioutils/fmsynth/test/fmsynth_alsa_test.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include + +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define FS (48000) + +#define CHANNEL_NUM (2) +#define RESAMPLING_ALSA (1) +#define LATENCY_ALSA (10000) + +#define SAMPLE_NUM (FS / 20) +#define BUFF_LENGTH (SAMPLE_NUM * CHANNEL_NUM) + +#define CODE_C_FREQ (261.625565f) +#define CODE_D_FREQ (293.6647674f) +#define CODE_E_FREQ (329.6275561f) +#define CODE_F_FREQ (349.2282305f) +#define CODE_G_FREQ (391.9954347f) +#define CODE_A_FREQ (440.f) +#define CODE_B_FREQ (493.8833009f) + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static int16_t samples[BUFF_LENGTH]; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * name: init_alsa + ****************************************************************************/ + +static snd_pcm_t *init_alsa(int fs) +{ + int ret; + snd_pcm_t *hndl = NULL; + + ret = snd_pcm_open(&hndl, "default", SND_PCM_STREAM_PLAYBACK, 0); + if (ret < 0) + { + printf("sdn_pcm_open error\n"); + return NULL; + } + + ret = snd_pcm_set_params(hndl, + SND_PCM_FORMAT_S16, + SND_PCM_ACCESS_RW_INTERLEAVED, + CHANNEL_NUM, fs, RESAMPLING_ALSA, LATENCY_ALSA); + if (ret != 0) + { + printf("sdn_pcm_set_params error\n"); + snd_pcm_close(hndl); + return NULL; + } + + return hndl; +} + +/**************************************************************************** + * name: set_nonblocking + ****************************************************************************/ + +static int set_nonblocking(struct termios *saved) +{ + struct termios settings; + + tcgetattr(0, saved); + settings = *saved; + + settings.c_lflag &= ~(ECHO | ICANON); + settings.c_cc[VTIME] = 0; + settings.c_cc[VMIN] = 1; + tcsetattr(0, TCSANOW, &settings); + fcntl(0, F_SETFL, O_NONBLOCK); + + return OK; +} + +/**************************************************************************** + * name: store_setting + ****************************************************************************/ + +static void store_setting(struct termios *saved) +{ + tcsetattr(0, TCSANOW, saved); +} + +/**************************************************************************** + * name: set_levels + ****************************************************************************/ + +static fmsynth_eglevels_t *set_levels(fmsynth_eglevels_t *level, + float atk_lvl, int atk_peri, + float decbrk_lvl, int decbrk_peri, + float dec_lvl, int dec_peri, + float sus_lvl, int sus_peri, + float rel_lvl, int rel_peri) +{ + level->attack.level = atk_lvl; + level->attack.period_ms = atk_peri; + level->decaybrk.level = decbrk_lvl; + level->decaybrk.period_ms = decbrk_peri; + level->decay.level = dec_lvl; + level->decay.period_ms = dec_peri; + level->sustain.level = sus_lvl; + level->sustain.period_ms = sus_peri; + level->release.level = rel_lvl; + level->release.period_ms = rel_peri; + + return level; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * name: main + ****************************************************************************/ + +int main(void) +{ + int running; + int dump_count = 0; + int dump_enable = 0; + + fmsynth_eglevels_t levels; + fmsynth_sound_t *snd1; + fmsynth_op_t *envop; + fmsynth_op_t *fbop; + snd_pcm_t *hndl = NULL; + + struct termios save_param; + + hndl = init_alsa(FS); + if (!hndl) + { + printf("Init alsa error\n"); + return -1; + } + + /* Initialize FM synthesizer */ + + fmsynth_initialize(FS); + + /* Operator setup */ + + envop = fmsynthop_create(); + fbop = fmsynthop_create(); + + set_levels(&levels, 0.6f, 100, 0.3f, 300, 0.1f, 500, 0.f, 0, 0.f, 70); + + fmsynthop_set_envelope(envop, &levels); + fmsynthop_select_opfunc(envop, FMSYNTH_OPFUNC_SIN); + + fmsynthop_set_envelope(fbop, &levels); + fmsynthop_select_opfunc(fbop, FMSYNTH_OPFUNC_SIN); + fmsynthop_bind_feedback(fbop, fbop, 0.6f); + + fmsynthop_parallel_subop(envop, fbop); + + /* Sound setup */ + + snd1 = fmsynthsnd_create(); + fmsynthsnd_set_operator(snd1, envop); + fmsynthsnd_set_soundfreq(snd1, CODE_C_FREQ); + + set_nonblocking(&save_param); + running = 1; + while (running) + { + switch (getchar()) + { + case 'c': + fmsynthsnd_set_soundfreq(snd1, CODE_C_FREQ); + if (dump_enable) + { + dump_count = FS; + dump_enable = 0; + printf("DUMP: "); + } + + printf("Do\n"); + break; + case 'd': + fmsynthsnd_set_soundfreq(snd1, CODE_D_FREQ); + if (dump_enable) + { + dump_count = FS; + dump_enable = 0; + printf("DUMP: "); + } + + printf("Le\n"); + break; + case 'e': + fmsynthsnd_set_soundfreq(snd1, CODE_E_FREQ); + if (dump_enable) + { + dump_count = FS; + dump_enable = 0; + printf("DUMP: "); + } + + printf("Mi\n"); + break; + case 'f': + fmsynthsnd_set_soundfreq(snd1, CODE_F_FREQ); + if (dump_enable) + { + dump_count = FS; + dump_enable = 0; + printf("DUMP: "); + } + + printf("Fha\n"); + break; + case 'g': + fmsynthsnd_set_soundfreq(snd1, CODE_G_FREQ); + if (dump_enable) + { + dump_count = FS; + dump_enable = 0; + printf("DUMP: "); + } + + printf("So\n"); + break; + case 'a': + fmsynthsnd_set_soundfreq(snd1, CODE_A_FREQ); + if (dump_enable) + { + dump_count = FS; + dump_enable = 0; + printf("DUMP: "); + } + + printf("Ra\n"); + break; + case 'b': + fmsynthsnd_set_soundfreq(snd1, CODE_B_FREQ); + if (dump_enable) + { + dump_count = FS; + dump_enable = 0; + printf("DUMP: "); + } + + printf("Shi\n"); + break; + case 'z': + dump_enable = 1; + printf("Dump next code\n"); + break; + case 'q': + running = 0; + break; + } + + fmsynth_rendering(snd1, samples, BUFF_LENGTH, CHANNEL_NUM, NULL, 0); + + if (dump_count) + { + for (int i = 0; i < BUFF_LENGTH; i += 2) + { + printf("%d\n", samples[i]); + } + + dump_count -= SAMPLE_NUM; + } + + snd_pcm_writei(hndl, (const void *)samples, SAMPLE_NUM); + } + + snd_pcm_drain(hndl); + snd_pcm_close(hndl); + + fmsynthop_delete(envop); + fmsynthop_delete(fbop); + + fmsynthsnd_delete(snd1); + + store_setting(&save_param); + + return 0; +} diff --git a/audioutils/fmsynth/test/fmsynth_eg_test.c b/audioutils/fmsynth/test/fmsynth_eg_test.c new file mode 100644 index 0000000000..1be811cf25 --- /dev/null +++ b/audioutils/fmsynth/test/fmsynth_eg_test.c @@ -0,0 +1,127 @@ +/**************************************************************************** + * apps/audioutils/fmsynth/test/fmsynth_eg_test.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define FS (48000) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * name: state_name + ****************************************************************************/ + +static const char *state_name(int s) +{ + switch (s) + { + case EGSTATE_ATTACK: + return "Attack "; + case EGSTATE_DECAYBREAK: + return "DecayBreak"; + case EGSTATE_DECAY: + return "Decay "; + case EGSTATE_SUSTAIN: + return "Sustain "; + case EGSTATE_RELEASE: + return "Release "; + case EGSTATE_RELEASED: + case -1: + return "RELEASED.."; + } + + return ""; +} + +/**************************************************************************** + * name: dump_eg + ****************************************************************************/ + +static void dump_eg(fmsynth_eg_t *env) +{ + int i; + fmsynth_egparam_t *last = &env->state_params[EGSTATE_RELEASED]; + + printf("===== STATE : %s =======\n", state_name(env->state)); + for (i = -1; i < EGSTATE_RELEASED; i++) + { + printf(" [%s] %5d <--------------> [%s] %5d\n", + state_name(i), last->initval, + state_name(i + 1), env->state_params[i + 1].initval); + printf(" per %d\n", env->state_params[i + 1].period); + printf(" dlt %d\n", env->state_params[i + 1].diff2next); + last = &env->state_params[i + 1]; + } + + printf("\n"); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * name: main + ****************************************************************************/ + +int main(void) +{ + fmsynth_eg_t *eg; + fmsynth_eglevels_t levels; + + levels.attack.level = 0.6f; + levels.attack.period_ms = 10; + levels.decaybrk.level = 0.3f; + levels.decaybrk.period_ms = 20; + levels.decay.level = 0.5f; + levels.decay.period_ms = 15; + levels.sustain.level = 0.65f; + levels.sustain.period_ms = 5; + levels.release.level = 0.f; + levels.release.period_ms = 70; + + eg = fmsyntheg_create(); + fmsyntheg_set_param(eg, FS, &levels); + dump_eg(eg); + + fmsyntheg_start(eg); + dump_eg(eg); + + while (eg->state != EGSTATE_RELEASED) + { + printf("%d\n", fmsyntheg_operate(eg)); + } + + fmsyntheg_delete(eg); + + return 0; +} diff --git a/audioutils/fmsynth/test/fmsynth_op_test.c b/audioutils/fmsynth/test/fmsynth_op_test.c new file mode 100644 index 0000000000..581eb03a83 --- /dev/null +++ b/audioutils/fmsynth/test/fmsynth_op_test.c @@ -0,0 +1,143 @@ +/**************************************************************************** + * apps/audioutils/fmsynth/test/fmsynth_op_test.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define FS (48000) +#define SOUNDFREQ (261.f) +#define TEST_LOOP (FS * (30 + 10 + 100 + 30 + 1) / 1000) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * name: set_levels + ****************************************************************************/ + +static fmsynth_eglevels_t *set_levels(fmsynth_eglevels_t *level, + float atk_lvl, int atk_peri, + float decbrk_lvl, int decbrk_peri, + float dec_lvl, int dec_peri, + float sus_lvl, int sus_peri, + float rel_lvl, int rel_peri) +{ + level->attack.level = atk_lvl; + level->attack.period_ms = atk_peri; + level->decaybrk.level = decbrk_lvl; + level->decaybrk.period_ms = decbrk_peri; + level->decay.level = dec_lvl; + level->decay.period_ms = dec_peri; + level->sustain.level = sus_lvl; + level->sustain.period_ms = sus_peri; + level->release.level = rel_lvl; + level->release.period_ms = rel_peri; + + return level; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * name: main + ****************************************************************************/ + +int main(void) +{ + int phase_time; + fmsynth_eglevels_t levels; + + fmsynth_op_t *envop; + fmsynth_op_t *fbop; + fmsynth_op_t *triop; + fmsynth_op_t *mainop; + fmsynth_op_t *subop; + + fmsynth_op_t *conv1; + fmsynth_op_t *conv2; + + phase_time = 0; + fmsynthop_set_samplerate(FS); + + set_levels(&levels, 0.12f, 10, 0.06f, 20, 0.1f, 16, 0.1f, 5, 0.f, 70); + + envop = fmsynthop_create(); + fmsynthop_set_envelope(envop, &levels); + fmsynthop_select_opfunc(envop, FMSYNTH_OPFUNC_SIN); + fmsynthop_set_soundfreq(envop, SOUNDFREQ); + fmsynthop_start(envop); + + triop = fmsynthop_create(); + fmsynthop_select_opfunc(triop, FMSYNTH_OPFUNC_TRIANGLE); + fmsynthop_set_soundfreq(triop, SOUNDFREQ); + fmsynthop_start(triop); + + fbop = fmsynthop_create(); + fmsynthop_select_opfunc(fbop, FMSYNTH_OPFUNC_SIN); + fmsynthop_set_soundfreq(fbop, SOUNDFREQ); + fmsynthop_bind_feedback(fbop, fbop, 0.6f); + fmsynthop_start(fbop); + + mainop = fmsynthop_create(); + subop = fmsynthop_create(); + fmsynthop_select_opfunc(mainop, FMSYNTH_OPFUNC_SIN); + fmsynthop_select_opfunc(subop, FMSYNTH_OPFUNC_SIN); + fmsynthop_cascade_subop(mainop, subop); + fmsynthop_set_soundfreq(mainop, SOUNDFREQ); + fmsynthop_set_soundfreqrate(subop, 2.f); + fmsynthop_start(mainop); + + printf("idx,EnvTest,FeedbackTest,CascadeTest,Triangle\n"); + while (phase_time < TEST_LOOP) + { + fmsynthop_update_feedback(envop); + fmsynthop_update_feedback(fbop); + fmsynthop_update_feedback(mainop); + fmsynthop_update_feedback(triop); + + printf("%d,%d,%d,%d,%d\n", + phase_time, + fmsynthop_operate(envop, phase_time), + fmsynthop_operate(fbop, phase_time), + fmsynthop_operate(mainop, phase_time), + fmsynthop_operate(triop, phase_time)); + + phase_time++; + } + + fmsynthop_delete(envop); + fmsynthop_delete(fbop); + fmsynthop_delete(mainop); + fmsynthop_delete(subop); + + return 0; +} diff --git a/audioutils/fmsynth/test/fmsynth_test.c b/audioutils/fmsynth/test/fmsynth_test.c new file mode 100644 index 0000000000..72bb4e9fad --- /dev/null +++ b/audioutils/fmsynth/test/fmsynth_test.c @@ -0,0 +1,130 @@ +/**************************************************************************** + * apps/audioutils/fmsynth/test/fmsynth_test.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define FS (48000) +#define SOUNDFREQ (3000.f) +#define TEST_LENGTH ((FS / 1000) * (10 + 20 + 16 + 5 + 30)) + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static int16_t my_sample[TEST_LENGTH]; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * name: set_levels + ****************************************************************************/ + +static fmsynth_eglevels_t *set_levels(fmsynth_eglevels_t *level, + int atk_lvl, int atk_peri, + int decbrk_lvl, int decbrk_peri, + int dec_lvl, int dec_peri, + int sus_lvl, int sus_peri, + int rel_lvl, int rel_peri) +{ + level->attack.level = atk_lvl; + level->attack.period_ms = atk_peri; + level->decaybrk.level = decbrk_lvl; + level->decaybrk.period_ms = decbrk_peri; + level->decay.level = dec_lvl; + level->decay.period_ms = dec_peri; + level->sustain.level = sus_lvl; + level->sustain.period_ms = sus_peri; + level->release.level = rel_lvl; + level->release.period_ms = rel_peri; + + return level; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * name: main + ****************************************************************************/ + +int main(void) +{ + int phase_time; + + fmsynth_eglevels_t levels; + fmsynth_sound_t *snd1; + fmsynth_op_t *envop; + fmsynth_op_t *fbop; + + /* Initialize FM synthesizer */ + + fmsynth_initialize(FS); + + /* Operator setup */ + + envop = fmsynthop_create(); + fbop = fmsynthop_create(); + + set_levels(&levels, 0.12f, 10, 0.06f, 20, 0.1f, 16, 0.1f, 5, 0.f, 70); + + fmsynthop_set_envelope(envop, &levels); + fmsynthop_select_opfunc(envop, FMSYNTH_OPFUNC_SIN); + + fmsynthop_select_opfunc(fbop, FMSYNTH_OPFUNC_SIN); + fmsynthop_bind_feedback(fbop, fbop, 0.6f); + + fmsynthop_parallel_subop(envop, fbop); + + /* Sound setup */ + + snd1 = fmsynthsnd_create(); + fmsynthsnd_set_operator(snd1, envop); + fmsynthsnd_set_soundfreq(snd1, SOUNDFREQ); + + fmsynth_rendering(snd1, my_sample, TEST_LENGTH, 1, NULL, 0); + + for (int i = 0; i < TEST_LENGTH; i++) + { + printf("%d\n", my_sample[i]); + } + + fmsynthop_delete(envop); + fmsynthop_delete(fbop); + + fmsynthsnd_delete(snd1); + + return 0; +} diff --git a/audioutils/fmsynth/test/opfunc_test.c b/audioutils/fmsynth/test/opfunc_test.c new file mode 100644 index 0000000000..9b928c0544 --- /dev/null +++ b/audioutils/fmsynth/test/opfunc_test.c @@ -0,0 +1,165 @@ +/**************************************************************************** + * apps/audioutils/fmsynth/test/opfunc_test.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define FS (48000) +#define DUMP_PERIOD (FS * 3 / 2000) +#define ACCURACY_TEST_PERIOD (FS * 3) + +#define HZ (4186) + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static opfunc_t func_sin; +static opfunc_t func_tri; +static opfunc_t func_saw; +static opfunc_t func_sqa; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * name: prepare_opfuncs + ****************************************************************************/ + +static void prepare_opfuncs(void) +{ + fmsynth_op_t *op; + + op = fmsynthop_create(); + + fmsynthop_select_opfunc(op, FMSYNTH_OPFUNC_SIN); + func_sin = op->wavegen; + + fmsynthop_select_opfunc(op, FMSYNTH_OPFUNC_TRIANGLE); + func_tri = op->wavegen; + + fmsynthop_select_opfunc(op, FMSYNTH_OPFUNC_SAWTOOTH); + func_saw = op->wavegen; + + fmsynthop_select_opfunc(op, FMSYNTH_OPFUNC_SQUARE); + func_sqa = op->wavegen; + + fmsynthop_delete(op); +} + +/**************************************************************************** + * name: wavegen_dump + ****************************************************************************/ + +static void wavegen_dump(void) +{ + int t; + float deltaact; + + deltaact = (float)FMSYNTH_PI * 2. * (float)HZ / (float)FS; + + printf("===== Wave generator Dump ====\n"); + printf("SIN, TRIANGLE, SAWTOOTH, SQUARE\n"); + for (t = 0; t < DUMP_PERIOD; t++) + { + printf("%d, %d, %d, %d\n", + func_sin((int)(deltaact * t)), + func_tri((int)(deltaact * t)), + func_saw((int)(deltaact * t)), + func_sqa((int)(deltaact * t)) + ); + } + + printf("\n"); +} + +/**************************************************************************** + * name: sin_accuracy_test + ****************************************************************************/ + +static void sin_accuracy_test(void) +{ + int t; + float delta; + float deltaact; + float max_diff = 0.f; + float ref_sin; + int sin_val; + float norm_sin; + float diff; + + delta = M_PI * 2. * (float)HZ / (float)FS; + deltaact = (float)FMSYNTH_PI * 2. * (float)HZ / (float)FS; + + printf("===== Local SIN function ACCURACY TEST ====\n"); + for (t = 0; t < ACCURACY_TEST_PERIOD; t++) + { + sin_val = func_sin((int)(deltaact * t)); + ref_sin = sinf(delta * t); + + norm_sin = (float)sin_val / (float)SHRT_MAX; + printf("t=%d, operator-sin(%d)=%d, norm_sin=%f, sinf(%f)=%f ", + t, (int)(deltaact * t), sin_val, norm_sin, delta * t, ref_sin); + + diff = fabsf(norm_sin - ref_sin); + max_diff = max_diff < diff ? diff : max_diff; + + if (diff >= 0.005) + { + printf(" BIG-DIFF : %f\n", diff); + } + else + { + printf("\n"); + } + } + + printf("\n\nMAX DIFF = %f\n\n", max_diff); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * name: main + ****************************************************************************/ + +int main(void) +{ + prepare_opfuncs(); + sin_accuracy_test(); + wavegen_dump(); + + return 0; +} diff --git a/include/audioutils/fmsynth.h b/include/audioutils/fmsynth.h new file mode 100644 index 0000000000..827be95df7 --- /dev/null +++ b/include/audioutils/fmsynth.h @@ -0,0 +1,81 @@ +/**************************************************************************** + * apps/include/audioutils/fmsynth.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_AUDIOUTILS_FMSYNTH_H +#define __INCLUDE_AUDIOUTILS_FMSYNTH_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define FMSYNTH_MAX_VOLUME (SHRT_MAX) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +typedef struct fmsynth_sound_s +{ + int phase_time; + int max_phase_time; + int volume; + FAR fmsynth_op_t *operators; + + FAR struct fmsynth_sound_s *next_sound; +} fmsynth_sound_t; + +typedef CODE void (*fmsynth_tickcb_t)(unsigned long cbarg); + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +extern "C" +{ +#endif + +int fmsynth_initialize(int fs); +FAR fmsynth_sound_t *fmsynthsnd_create(void); +void fmsynthsnd_delete(FAR fmsynth_sound_t *snd); +int fmsynthsnd_set_operator(FAR fmsynth_sound_t *snd, FAR fmsynth_op_t *op); +void fmsynthsnd_set_soundfreq(FAR fmsynth_sound_t *snd, float freq); +void fmsynthsnd_set_volume(FAR fmsynth_sound_t *snd, float vol); +int fmsynthsnd_add_subsound(FAR fmsynth_sound_t *top, + FAR fmsynth_sound_t *sub); +int fmsynth_rendering(FAR fmsynth_sound_t *snd, + FAR int16_t *sample, int sample_num, int chnum, + fmsynth_tickcb_t cb, unsigned long cbarg); + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_AUDIOUTILS_FMSYNTH_H */ diff --git a/include/audioutils/fmsynth_eg.h b/include/audioutils/fmsynth_eg.h new file mode 100644 index 0000000000..040cc7b98b --- /dev/null +++ b/include/audioutils/fmsynth_eg.h @@ -0,0 +1,100 @@ +/**************************************************************************** + * apps/include/audioutils/fmsynth_eg.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __APPS_INCLUDE_AUDIOUTILS_FMSYNTH_EG_H +#define __APPS_INCLUDE_AUDIOUTILS_FMSYNTH_EG_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define FMSYNTH_MAX_EGLEVEL (SHRT_MAX / 8) + +#define EGSTATE_ATTACK (0) +#define EGSTATE_DECAYBREAK (1) +#define EGSTATE_DECAY (2) +#define EGSTATE_SUSTAIN (3) +#define EGSTATE_RELEASE (4) +#define EGSTATE_RELEASED (5) +#define EGSTATE_MAX (6) + +#define EGSTATE_NUM EGSTATE_RELEASED + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct fmsynth_eglevel_s +{ + float level; + int period_ms; +}; + +typedef struct fmsynth_eglevels_s +{ + struct fmsynth_eglevel_s attack; + struct fmsynth_eglevel_s decaybrk; + struct fmsynth_eglevel_s decay; + struct fmsynth_eglevel_s sustain; + struct fmsynth_eglevel_s release; +} fmsynth_eglevels_t; + +typedef struct fmsynth_egparam_s +{ + int initval; + int period; + int diff2next; +} fmsynth_egparam_t; + +typedef struct fmsynth_eg_s +{ + int state; + int state_counter; + fmsynth_egparam_t state_params[EGSTATE_MAX]; +} fmsynth_eg_t; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +extern "C" +{ +#endif + +FAR fmsynth_eg_t *fmsyntheg_create(void); +void fmsyntheg_delete(FAR fmsynth_eg_t *eg); +int fmsyntheg_set_param(FAR fmsynth_eg_t *eg, + int fs, FAR fmsynth_eglevels_t *levels); +void fmsyntheg_start(FAR fmsynth_eg_t *eg); +void fmsyntheg_stop(FAR fmsynth_eg_t *eg); +int fmsyntheg_operate(FAR fmsynth_eg_t *eg); + +#ifdef __cplusplus +} +#endif + +#endif /* __APPS_INCLUDE_AUDIOUTILS_FMSYNTH_EG_H */ diff --git a/include/audioutils/fmsynth_op.h b/include/audioutils/fmsynth_op.h new file mode 100644 index 0000000000..aad10de169 --- /dev/null +++ b/include/audioutils/fmsynth_op.h @@ -0,0 +1,99 @@ +/**************************************************************************** + * apps/include/audioutils/fmsynth_op.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __APPS_INCLUDE_AUDIOUTILS_FMSYNTH_OP_H +#define __APPS_INCLUDE_AUDIOUTILS_FMSYNTH_OP_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define FMSYNTH_PI (0x10000) + +#define FMSYNTH_OPFUNC_SIN (0) +#define FMSYNTH_OPFUNC_TRIANGLE (1) +#define FMSYNTH_OPFUNC_SAWTOOTH (2) +#define FMSYNTH_OPFUNC_SQUARE (3) +#define FMSYNTH_OPFUNC_NUM (4) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +typedef CODE int (*opfunc_t)(int theta); + +typedef struct fmsynth_op_s +{ + FAR fmsynth_eg_t *eg; + opfunc_t wavegen; + struct fmsynth_op_s *cascadeop; + struct fmsynth_op_s *parallelop; + + FAR int *feedback_ref; + int feedback_val; + int feedbackrate; + int last_sigval; + + float freq_rate; + float sound_freq; + float delta_phase; + float current_phase; +} fmsynth_op_t; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +extern "C" +{ +#endif + +int fmsynthop_set_samplerate(int fs); + +FAR fmsynth_op_t *fmsynthop_create(void); +void fmsynthop_delete(FAR fmsynth_op_t *op); +int fmsynthop_select_opfunc(FAR fmsynth_op_t *op, int type); +int fmsynthop_set_envelope(FAR fmsynth_op_t *op, + FAR fmsynth_eglevels_t *levels); +int fmsynthop_cascade_subop(FAR fmsynth_op_t *op, + FAR fmsynth_op_t *subop); +int fmsynthop_parallel_subop(FAR fmsynth_op_t *op, + FAR fmsynth_op_t *subop); +int fmsynthop_bind_feedback(FAR fmsynth_op_t *op, + FAR fmsynth_op_t *subop, float ratio); +int fmsynthop_update_feedback(FAR fmsynth_op_t *op); +void fmsynthop_set_soundfreq(FAR fmsynth_op_t *op, float freq); +void fmsynthop_set_soundfreqrate(FAR fmsynth_op_t *op, float rate); +void fmsynthop_start(FAR fmsynth_op_t *op); +void fmsynthop_stop(FAR fmsynth_op_t *op); +int fmsynthop_operate(FAR fmsynth_op_t *op, int phase_time); + +#ifdef __cplusplus +} +#endif + +#endif /* __APPS_INCLUDE_AUDIOUTILS_FMSYNTH_OP_H */