├── lexer.h ├── sc_common.h ├── sc_generic.h ├── Makefile ├── parser.h ├── sc_mul.c ├── sc_add.c ├── sc_sin.c ├── sc_timer.c ├── sc_quant.c ├── LICENSE ├── sc_pulse.c ├── sc_nmc.c ├── README.md ├── sc_dump.c ├── sc_chopsin.c ├── sc_swnoise.c ├── engine.h ├── sc_pink.c ├── sc_alpha.c ├── instr.c ├── lexer.c ├── sc_hardsync.c ├── sc_adsr.c ├── sc_wav.c ├── cli.c ├── test.c ├── sc_nmc_data.h ├── engine.c └── parser.c /lexer.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #ifndef VV_LEXER_H 3 | #define VV_LEXER_H 4 | 5 | typedef unsigned int uint; 6 | 7 | typedef struct { 8 | uint lineno; 9 | uint byteno; 10 | uint slen; 11 | char *str; 12 | float f; 13 | char tok_type; 14 | } tok_t; 15 | int get_token(char **p, tok_t *tok); 16 | 17 | #endif // VV_LEXER_H 18 | -------------------------------------------------------------------------------- /sc_common.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #ifndef SC_COMMON_H 3 | #define SC_COMMON_H 4 | 5 | // this file should ONLY be included in sc_*.c 6 | 7 | #define Y(X) #X, offsetof(sc_type, X) 8 | #define DF(X) printf("D %-20.20s %-10.10s: %f\n", __FILE__, #X, s->X); 9 | #define DI(X) printf("D %-20.20s %-10.10s: %d\n", __FILE__, #X, s->X); 10 | #define PI 3.14159 11 | #define TWOPI (2.0 * PI) 12 | 13 | 14 | #endif // SC_COMMON_H 15 | -------------------------------------------------------------------------------- /sc_generic.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #ifndef SC_GENERIC_H 3 | #define SC_GENERIC_H 4 | 5 | #define BLOCK_MAX_INS 4 6 | 7 | #include "parser.h" 8 | 9 | typedef struct { 10 | sc_class_t *cl; 11 | apb_sound_instr_t *instr; // to kill sound 12 | uint nins; // # of inputs 13 | uint src_blockno[BLOCK_MAX_INS]; // which block's output to take 14 | float *inptr[BLOCK_MAX_INS]; // where to write a previous input to 15 | } sc_gen_t; 16 | 17 | #endif // SC_GENERIC_H 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017 Asher Blum 2 | 3 | CFLAGS=-g -O1 4 | all: lexer.o parser.o sc_sin.o test synthem80 5 | 6 | test: test.o lexer.o parser.o sc_sin.o sc_wav.o sc_add.o sc_nmc.o instr.o engine.o sc_timer.o sc_quant.o sc_pink.o sc_dump.o sc_adsr.o sc_chopsin.o sc_hardsync.o sc_alpha.o sc_swnoise.o sc_mul.o sc_pulse.o 7 | 8 | gcc $(CFLAGS) $^ -lm -o $@ 9 | 10 | synthem80: cli.o lexer.o parser.o sc_sin.o sc_wav.o sc_add.o sc_nmc.o instr.o engine.o sc_timer.o sc_quant.o sc_pink.o sc_dump.o sc_adsr.o sc_chopsin.o sc_hardsync.o sc_alpha.o sc_swnoise.o sc_mul.o sc_pulse.o 11 | gcc $(CFLAGS) $^ -lm -o $@ 12 | 13 | %.o: %.c 14 | gcc $(CFLAGS) -c $^ -o $@ 15 | 16 | 17 | clean: 18 | rm -f *.o test synthem80 19 | -------------------------------------------------------------------------------- /parser.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #ifndef VV_PARSER_H 3 | #define VV_PARSER_H 4 | 5 | #include "lexer.h" 6 | #include "engine.h" 7 | 8 | //typedef unsigned int uint; 9 | 10 | typedef float (*tic_func_t)(void *v); 11 | typedef void (*dump_func_t)(void *v); 12 | typedef void (*init_func_t)(void *v); 13 | 14 | typedef struct { 15 | char *name; 16 | uint offset; 17 | char *def; // if default is NULL, param is mand 18 | } sc_param_t; 19 | 20 | typedef struct { 21 | char *name; // sin ramp 22 | uint size; 23 | sc_param_t *params; 24 | tic_func_t tic_func; 25 | dump_func_t dump_func; 26 | init_func_t init_func; 27 | } sc_class_t; 28 | 29 | int parse_pgm(apb_sound_engine_t *eng, char *pgm, uint user_tag); 30 | #endif // VV_PARSER_H 31 | -------------------------------------------------------------------------------- /sc_mul.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #include 3 | #include 4 | #include 5 | #include "parser.h" 6 | #include "sc_generic.h" 7 | 8 | #define sc_type sc_mul_t 9 | #include "sc_common.h" 10 | 11 | typedef struct { 12 | sc_gen_t gen; 13 | float a; 14 | float b; 15 | } sc_type; 16 | 17 | static sc_param_t params[] = { 18 | { Y(a), "f1" }, 19 | { Y(b), "f1" }, 20 | { 0 } 21 | }; 22 | 23 | 24 | static float tic(void *v) { 25 | sc_type *p = (sc_type*)v; 26 | return p->a * p->b; 27 | } 28 | 29 | static void dump(void *v) { 30 | sc_type *s; 31 | 32 | s=v; 33 | DF(a); 34 | DF(b); 35 | } 36 | 37 | sc_class_t mul_class = { "mul", sizeof(sc_type), params, tic, dump }; 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /sc_add.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #include 3 | #include 4 | #include 5 | #include "parser.h" 6 | #include "sc_generic.h" 7 | 8 | #define sc_type sc_add_t 9 | #include "sc_common.h" 10 | 11 | typedef struct { 12 | sc_gen_t gen; 13 | float a; 14 | float b; 15 | float c; 16 | float d; 17 | } sc_type; 18 | 19 | static sc_param_t params[] = { 20 | { Y(a), "f1" }, 21 | { Y(b), "f0" }, 22 | { Y(c), "f0" }, 23 | { Y(d), "f0" }, 24 | { 0 } 25 | }; 26 | 27 | 28 | static float tic(void *v) { 29 | sc_type *p = (sc_add_t*)v; 30 | return p->a + p->b + p->c + p->d; 31 | } 32 | 33 | static void dump(void *v) { 34 | sc_type *s; 35 | 36 | s=v; 37 | DF(a); 38 | DF(b); 39 | DF(c); 40 | DF(d); 41 | } 42 | 43 | sc_class_t add_class = { "add", sizeof(sc_type), params, tic, dump }; 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /sc_sin.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #include 3 | #include 4 | #include 5 | #include "parser.h" 6 | #define sc_type sc_sin_t 7 | #include "sc_generic.h" 8 | #include "sc_common.h" 9 | 10 | typedef struct { 11 | sc_gen_t gen; 12 | float f; 13 | float amp; 14 | float ph; 15 | float phv; 16 | } sc_type; 17 | 18 | static sc_param_t params[] = { 19 | { Y(f), "f100" }, 20 | { Y(amp), "f0.9" }, 21 | { Y(ph), "f0" }, 22 | { 0 } 23 | }; 24 | 25 | 26 | static void dump(void *v); 27 | 28 | static float tic(void *v) { 29 | sc_type *s; 30 | s=v; 31 | s->ph += s->f * 2.0 * 3.14 / 44100; // fixme 32 | //dump(v); 33 | return sin(s->ph) * s->amp; 34 | } 35 | 36 | static void dump(void *v) { 37 | sc_type *s; 38 | 39 | s=v; 40 | DF(f); 41 | DF(amp); 42 | DF(ph); 43 | DF(phv); 44 | } 45 | 46 | sc_class_t sin_class = { "sin", sizeof(sc_type), params, tic, dump }; 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /sc_timer.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #include 3 | #include 4 | #include 5 | #include "parser.h" 6 | #include "sc_generic.h" 7 | 8 | #define sc_type sc_timer_t 9 | #include "sc_common.h" 10 | 11 | typedef struct { 12 | sc_gen_t gen; 13 | float t; // time, seconds 14 | float _rt; // remaining time in seconds 15 | } sc_type; 16 | 17 | static sc_param_t params[] = { 18 | { Y(t), "f1" }, 19 | { 0 } 20 | }; 21 | 22 | static void init(void *v) { 23 | sc_type *p = (sc_type*)v; 24 | p->_rt = p->t; 25 | } 26 | 27 | static float tic(void *v) { 28 | sc_type *p = (sc_type*)v; 29 | 30 | p->_rt -= (1.0/44100.0); // fixme 31 | if(p->_rt < 0) { 32 | apb_sound_instr_kill(p->gen.instr); 33 | } 34 | return 0; // I am silent 35 | } 36 | 37 | static void dump(void *v) { 38 | sc_type *s; 39 | 40 | s=v; 41 | DF(t); 42 | DF(_rt); 43 | } 44 | 45 | sc_class_t timer_class = { "timer", sizeof(sc_type), params, tic, dump, init }; 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /sc_quant.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #include 3 | #include 4 | #include 5 | #include "parser.h" 6 | #include "sc_generic.h" 7 | 8 | #define sc_type sc_quant_t 9 | #include "sc_common.h" 10 | 11 | typedef struct { 12 | sc_gen_t gen; 13 | float in; 14 | int steps; 15 | float range; 16 | } sc_type; 17 | 18 | static sc_param_t params[] = { 19 | { Y(in), "f1" }, 20 | { Y(steps), "i16" }, 21 | { Y(range), "f1" }, 22 | { 0 } 23 | }; 24 | 25 | 26 | static float tic(void *v) { 27 | sc_type *p = (sc_type*)v; 28 | int i; 29 | float f; 30 | 31 | i = p->in / p->range * (float)(p->steps); 32 | f = p->range * (float)i / (float)(p->steps); 33 | //printf("quant: in=%f i=%d f=%f\n", p->in, i, f); 34 | return f; 35 | } 36 | 37 | static void dump(void *v) { 38 | sc_type *s; 39 | 40 | s=v; 41 | DF(in); 42 | DF(range); 43 | DI(steps); 44 | } 45 | 46 | sc_class_t quant_class = { "quant", sizeof(sc_type), params, tic, dump }; 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Asher Blum 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /sc_pulse.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #include 3 | #include 4 | #include 5 | #include "parser.h" 6 | #include "sc_generic.h" 7 | #define sc_type sc_pulse_t 8 | #include "sc_common.h" 9 | 10 | #define NSLOTS 32.0 11 | 12 | typedef struct { 13 | sc_gen_t gen; 14 | float f; 15 | float amp; 16 | float ph; 17 | float dc; 18 | } sc_type; 19 | 20 | static sc_param_t params[] = { 21 | { Y(f), "f100" }, 22 | { Y(amp), "f1" }, 23 | { Y(ph), "f0" }, 24 | { Y(dc), "f0.5" }, 25 | { 0 } 26 | }; 27 | 28 | static float tic(void *v) { 29 | sc_type *s; 30 | s=v; 31 | float f; 32 | uint slot, u; 33 | 34 | s->ph += s->f * TWOPI / 44100; // fixme 35 | while(s->ph > TWOPI) { 36 | s->ph -= TWOPI; 37 | } 38 | if(s->ph < s->dc * TWOPI) { 39 | return s->amp; 40 | } 41 | return -s->amp; 42 | } 43 | 44 | static void dump(void *v) { 45 | sc_type *s; 46 | 47 | s=v; 48 | DF(f); 49 | DF(amp); 50 | DF(ph); 51 | DF(dc); 52 | } 53 | 54 | sc_class_t pulse_class = { "pulse", sizeof(sc_type), params, tic, dump }; 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /sc_nmc.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #include 3 | #include 4 | #include 5 | #include "parser.h" 6 | #include "sc_nmc_data.h" 7 | #include "sc_generic.h" 8 | #define sc_type sc_nmc_t 9 | #include "sc_common.h" 10 | 11 | typedef struct { 12 | sc_gen_t gen; 13 | float f; 14 | float amp; 15 | float ph; 16 | int wf; 17 | } sc_type; 18 | 19 | #define NSLOTS 32 20 | #define NWAVEFORMS 8 21 | 22 | static sc_param_t params[] = { 23 | { Y(f), "f100" }, 24 | { Y(amp), "f0.9" }, 25 | { Y(ph), "f0" }, 26 | { Y(wf), "i0" }, 27 | { 0 } 28 | }; 29 | 30 | 31 | static void dump(void *v); 32 | 33 | static float tic(void *v) { 34 | sc_type *s; 35 | uint slot; 36 | 37 | s=v; 38 | s->ph += s->f * 2.0 * 3.14 / 44100; // fixme 39 | while(s->ph > TWOPI) { 40 | s->ph -= TWOPI; 41 | } 42 | slot = s->ph * (float)NSLOTS / TWOPI; 43 | return s->amp * data[(s->wf % NWAVEFORMS) * NSLOTS + slot]; 44 | } 45 | 46 | static void dump(void *v) { 47 | sc_type *s; 48 | 49 | s=v; 50 | DF(f); 51 | DF(amp); 52 | DF(ph); 53 | DI(wf); 54 | } 55 | 56 | sc_class_t nmc_class = { "nmc", sizeof(sc_type), params, tic, dump }; 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # synthem80 2 | 3 | Game sound synthesizer using 1980 tech, for embedding or pre-generating sounds. 4 | 5 | Copyright (C) 2018 Asher Blum; http://wildsparx.com 6 | 7 | * Produces classic early-80s arcade sounds 8 | * No UI; command driven 9 | * Can be embedded in a game that has PCM access to sound output 10 | * Can be run during the build phase to produce PCM files 11 | * Uses coarse wavetables to provide fat sounds 12 | * Uses a simple language to describe virtual circuits 13 | 14 | ## Examples 15 | 16 | Some sounds from an 80s arcade classic. 17 | * .sw files are 16 bit PCM, no header 18 | * the play command is from sox 19 | * sox can convert sw to wav 20 | 21 | ``` 22 | ./synthem80 -o coin.sw 'timer(t=.192) wav(wf=4 f=4.46 ph=4.037 amp=198) add(a=< b=222) alpha(f=<)' 23 | ./synthem80 -t3000 -o normal-siren.sw 'wav(wf=4 f=2.6 a=275) add(a=< b=620) nmc(wf=6 f=<)' 24 | ./synthem80 -o eat-monster.sw 'timer(t=0.5) wav(wf=1 f=2 a=45) add(a=< b=48) nmc(wf=5 f=<)' 25 | 26 | play -r 44100 coin.sw 27 | 28 | ``` 29 | The '<' is the output of the previous step in the circuit. 30 | 31 | ## Waveforms of wav() 32 | 33 | Chosen by the wf parameter above. 34 | 35 | 0. Square 36 | 1. Ramp up 37 | 2. Ramp down 38 | 3. Narrow pulse 39 | 4. Triangle 40 | 5. Diamond 41 | 6. Christmas Tree 42 | 7. Special 43 | -------------------------------------------------------------------------------- /sc_dump.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #include 3 | #include 4 | #include 5 | #include "parser.h" 6 | #include 7 | #include "sc_generic.h" 8 | 9 | // dump the input for debugging 10 | 11 | #define sc_type sc_dump_t 12 | #include "sc_common.h" 13 | 14 | typedef struct { 15 | sc_gen_t gen; 16 | float in; 17 | int graph; 18 | } sc_type; 19 | 20 | static sc_param_t params[] = { 21 | { Y(in), "f0" }, 22 | { Y(graph), "i0" }, 23 | { 0 } 24 | }; 25 | 26 | static void draw_graph(float x) { 27 | char buf[40]; 28 | memset(buf, ' ', sizeof(buf)); 29 | buf[0] = '.'; 30 | buf[sizeof(buf)-1] = '.'; 31 | int ind; 32 | 33 | if(x > 1.0) 34 | x = 1.0; 35 | if(x < -1.0) 36 | x = -1.0; 37 | 38 | ind = (int)(20.0 * (x+1.0)); 39 | if(ind >= 40) { 40 | ind = 39; 41 | } 42 | buf[ind] = '*'; 43 | printf("dump: %6.3f %.*s\n", x, sizeof(buf), buf); 44 | } 45 | 46 | static float tic(void *v) { 47 | sc_type *p = (sc_dump_t*)v; 48 | if(p->graph) { 49 | draw_graph(p->in); 50 | } 51 | else { 52 | printf("dump: %f\n", p->in); 53 | } 54 | return p->in; 55 | } 56 | 57 | static void dump(void *v) { 58 | sc_type *s; 59 | 60 | s=v; 61 | DF(in); 62 | } 63 | 64 | sc_class_t dump_class = { "dump", sizeof(sc_type), params, tic, dump }; 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /sc_chopsin.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #include 3 | #include 4 | #include 5 | #include "parser.h" 6 | #define sc_type sc_chopsin_t 7 | #include "sc_generic.h" 8 | #include "sc_common.h" 9 | 10 | #define NSLOTS 32 11 | 12 | typedef struct { 13 | sc_gen_t gen; 14 | float f; 15 | float amp; 16 | float ph; 17 | float phv; 18 | float f7; 19 | int mask; 20 | float g; 21 | } sc_type; 22 | 23 | static sc_param_t params[] = { 24 | { Y(f), "f100" }, 25 | { Y(amp), "f0.9" }, 26 | { Y(ph), "f0" }, 27 | { Y(mask), "i0" }, 28 | { Y(g), "f-1" }, 29 | { Y(f7), "f0" }, // unused 30 | { 0 } 31 | }; 32 | 33 | 34 | static void dump(void *v); 35 | 36 | static float tic(void *v) { 37 | float f; 38 | sc_type *s; 39 | uint n, slot; 40 | 41 | s=v; 42 | s->ph += s->f * 2.0 * 3.14 / 44100; // fixme 43 | //dump(v); 44 | f = sin(s->ph) * s->amp; 45 | slot = (uint)(s->ph * NSLOTS / TWOPI); 46 | n = 1 << slot; 47 | if(s->mask & n) { 48 | f *= s->g; 49 | } 50 | return f; 51 | } 52 | 53 | static void dump(void *v) { 54 | sc_type *s; 55 | 56 | s=v; 57 | DF(f); 58 | DF(amp); 59 | DF(ph); 60 | DF(phv); 61 | DI(mask); 62 | DF(g); 63 | DF(f7); 64 | } 65 | 66 | sc_class_t chopsin_class = { "chopsin", sizeof(sc_type), params, tic, dump }; 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /sc_swnoise.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #include 3 | #include 4 | #include 5 | #include "parser.h" 6 | #include "sc_generic.h" 7 | 8 | #define sc_type sc_swnoise_t 9 | #include "sc_common.h" 10 | 11 | static float C0[] = { 0.99886, 0.99332, 0.96900, 0.86650, 0.55000, -0.7616, 0.0 }; 12 | static float C1[] = { 0.0555179, 0.0750759, 0.1538520, 0.3104856, 0.5329522, 0.0168980, 0.115926 }; 13 | 14 | 15 | typedef struct { 16 | sc_gen_t gen; 17 | float amp; 18 | float f; 19 | float ph; 20 | uint _seed; 21 | uint _white; 22 | float _x; // last sample 23 | } sc_type; 24 | 25 | static sc_param_t params[] = { 26 | { Y(amp), "f1" }, 27 | { Y(f), "f100" }, 28 | { 0 } 29 | }; 30 | 31 | static void init(void *v) { 32 | sc_type *p = (sc_type*)v; 33 | 34 | p->_seed = 1773; 35 | } 36 | 37 | // get a sample of white noise 38 | 39 | static inline float get_white(sc_type *p) { 40 | p->_seed = (p->_seed * 196314165) + 907633515; 41 | p->_white = p->_seed >> 9; 42 | p->_white |= 0x40000000; 43 | return ((*(float*)&(p->_white))-3.0f); 44 | } 45 | 46 | static float tic(void *v) { 47 | sc_type *s = (sc_swnoise_t*)v; 48 | 49 | s->ph += s->f * TWOPI / 44100; // fixme 50 | if(s->ph > TWOPI) { 51 | s->ph = 0; 52 | s->_x = get_white(s); 53 | } 54 | 55 | return s->_x * s->amp; 56 | } 57 | 58 | static void dump(void *v) { 59 | sc_type *s; 60 | 61 | s=v; 62 | DF(amp); 63 | DI(_seed); 64 | DI(_white); 65 | DF(ph); 66 | } 67 | 68 | sc_class_t swnoise_class = { "swnoise", sizeof(sc_type), params, tic, dump, init }; 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /engine.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #ifndef ENGINE_H 3 | #define ENGINE_H 4 | 5 | #define MAX_INSTRS 20 6 | #define MAX_BINDADDRS 10 7 | #define MAX_BLOCKS 20 8 | #include 9 | 10 | typedef struct { 11 | uint active_p; 12 | uint user_tag; 13 | uint nblocks; 14 | uint nbindparams; 15 | void *bindaddrs[MAX_BINDADDRS]; 16 | void *blocks[MAX_BLOCKS]; 17 | } apb_sound_instr_t; 18 | 19 | typedef struct { 20 | char err[0x100]; 21 | uint sample_rate; // samples per second 22 | int err_byte; 23 | int dsp; // fd 24 | uint ninstrs; 25 | apb_sound_instr_t instrs[MAX_INSTRS]; 26 | float old_sample; 27 | FILE *ofh; 28 | uint nsamp; // how many samples written 29 | } apb_sound_engine_t; 30 | 31 | 32 | 33 | float apb_sound_engine_tic(apb_sound_engine_t *inst); 34 | void apb_sound_engine_dump(apb_sound_engine_t *eng); 35 | void apb_sound_instr_dump(apb_sound_instr_t *inst); 36 | void apb_sound_instr_init(apb_sound_instr_t *inst); 37 | float apb_sound_instr_tic(apb_sound_instr_t *inst); 38 | void apb_sound_engine_init(apb_sound_engine_t *eng); 39 | void apb_sound_engine_clear_instrs(apb_sound_engine_t *eng); 40 | void apb_sound_instr_free(apb_sound_instr_t *inst); 41 | void apb_sound_engine_trigger(apb_sound_engine_t *eng, uint user_tag); 42 | void apb_sound_instr_kill(apb_sound_instr_t *inst); 43 | void apb_sound_engine_open_outfile(apb_sound_engine_t *eng, char *filename, uint max_samples); 44 | void apb_sound_engine_close_outfile(apb_sound_engine_t *eng); 45 | uint apb_sound_engine_get_sample_rate(apb_sound_engine_t *eng); 46 | void apb_sound_engine_write_file(apb_sound_engine_t *eng, uint nsamples); 47 | void apb_sound_engine_write(apb_sound_engine_t *eng); 48 | 49 | #endif // ENGINE_H 50 | -------------------------------------------------------------------------------- /sc_pink.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #include 3 | #include 4 | #include 5 | #include "parser.h" 6 | #include "sc_generic.h" 7 | 8 | /* based on Paul Kellet's algorithm http://www.firstpr.com.au/dsp/pink-noise/ */ 9 | 10 | #define sc_type sc_pink_t 11 | #include "sc_common.h" 12 | 13 | static float C0[] = { 0.99886, 0.99332, 0.96900, 0.86650, 0.55000, -0.7616, 0.0 }; 14 | static float C1[] = { 0.0555179, 0.0750759, 0.1538520, 0.3104856, 0.5329522, 0.0168980, 0.115926 }; 15 | 16 | 17 | typedef struct { 18 | sc_gen_t gen; 19 | float amp; 20 | float _b[7]; 21 | uint _seed; 22 | uint _white; 23 | } sc_type; 24 | 25 | static sc_param_t params[] = { 26 | { Y(amp), "f1" }, 27 | { 0 } 28 | }; 29 | 30 | static void init(void *v) { 31 | int i; 32 | sc_type *p = (sc_type*)v; 33 | 34 | p->_seed = 1773; 35 | for(i=0; i<7; i++) { 36 | p->_b[i] = 0; 37 | } 38 | } 39 | 40 | // get a sample of white noise 41 | 42 | static inline float get_white(sc_type *p) { 43 | p->_seed = (p->_seed * 196314165) + 907633515; 44 | p->_white = p->_seed >> 9; 45 | p->_white |= 0x40000000; 46 | return ((*(float*)&(p->_white))-3.0f); 47 | } 48 | 49 | static float tic(void *v) { 50 | sc_type *p = (sc_pink_t*)v; 51 | float white, pink; 52 | int i; 53 | 54 | white = get_white(p); 55 | //return white; 56 | pink = white * 0.5362 + p->_b[6]; 57 | 58 | for(i=0; i<6; i++) { 59 | p->_b[i] = C0[i] * p->_b[i] + C1[i] * white; 60 | pink += p->_b[i]; 61 | } 62 | 63 | p->_b[6] = C1[6] * white; 64 | 65 | return pink * p->amp; 66 | } 67 | 68 | static void dump(void *v) { 69 | sc_type *s; 70 | 71 | s=v; 72 | DF(amp); 73 | DI(_seed); 74 | DI(_white); 75 | } 76 | 77 | sc_class_t pink_class = { "pink", sizeof(sc_type), params, tic, dump, init }; 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /sc_alpha.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #include 3 | #include 4 | #include 5 | #include "parser.h" 6 | #include "sc_generic.h" 7 | #define sc_type sc_alpha_t 8 | #include "sc_common.h" 9 | 10 | /* similar to nmc #2. if sharp==0; sin. */ 11 | 12 | #define NSLOTS 32.0 13 | 14 | typedef struct { 15 | sc_gen_t gen; 16 | float f; 17 | float amp; 18 | float ph; 19 | float sharp; 20 | int tslices; 21 | } sc_type; 22 | 23 | static sc_param_t params[] = { 24 | { Y(f), "f100" }, 25 | { Y(amp), "f1" }, 26 | { Y(ph), "f0" }, 27 | { Y(sharp), "f0.1875" }, 28 | { Y(tslices), "i32" }, 29 | { 0 } 30 | }; 31 | 32 | // 0 < f < 1 33 | static float quantize(int slices, float f) { 34 | float g; 35 | g = floorf(f * slices) / slices; 36 | return g; 37 | } 38 | 39 | static float tic(void *v) { 40 | sc_type *s; 41 | s=v; 42 | float ph, f; 43 | int slice; 44 | 45 | s->ph += s->f * TWOPI / 44100; // fixme 46 | while(s->ph > TWOPI) { 47 | s->ph -= TWOPI; 48 | } 49 | 50 | ph = s->ph; 51 | 52 | if(s->tslices) { // time quantization requested 53 | ph = TWOPI * quantize(s->tslices, s->ph/TWOPI); 54 | } 55 | 56 | // first half: 57 | 58 | if(ph < PI) { 59 | return s->amp * sin(2.0 * ph); 60 | } 61 | 62 | // second, short sine wave: 63 | 64 | if(ph < (2.0 - s->sharp) * PI) { 65 | return s->amp * sin(2.0 * (ph - PI) / (1.0 - s->sharp)); 66 | } 67 | 68 | // linear ramp portion: 69 | 70 | return 1.0 - 2.0 * (ph - (2.0 - s->sharp) * PI) / (s->sharp * PI); 71 | } 72 | 73 | static void dump(void *v) { 74 | sc_type *s; 75 | 76 | s=v; 77 | DF(f); 78 | DF(amp); 79 | DF(ph); 80 | DF(sharp); 81 | DI(tslices); 82 | } 83 | 84 | sc_class_t alpha_class = { "alpha", sizeof(sc_type), params, tic, dump }; 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /instr.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #include 3 | #include 4 | #include 5 | #include "lexer.h" 6 | #include "parser.h" 7 | #include "engine.h" 8 | #include "sc_generic.h" 9 | 10 | #define DEBUG(M, ...) fprintf(stderr, "%-24.24s %5d: ", __FILE__, __LINE__); fprintf(stderr, M, __VA_ARGS__); 11 | 12 | void apb_sound_instr_dump(apb_sound_instr_t *inst) { 13 | uint i; 14 | sc_gen_t *gen; 15 | 16 | printf("Instrument: nblocks=%u active=%u user_tag=%u\n", 17 | inst->nblocks, inst->active_p, inst->user_tag); 18 | for(i=0; inblocks; i++) { 19 | gen = (sc_gen_t*)(inst->blocks[i]); 20 | printf("### classname='%s' addr=%p inptr-=%x\n", gen->cl->name, gen, gen->inptr[0]); 21 | gen->cl->dump_func(inst->blocks[i]); 22 | printf("..\n"); 23 | } 24 | } 25 | 26 | float apb_sound_instr_tic(apb_sound_instr_t *inst) { 27 | uint i, j; 28 | sc_gen_t *gen; 29 | float x, output[MAX_BLOCKS]; 30 | 31 | bzero(output, sizeof(output)); 32 | if(!(inst->active_p)) { 33 | return 0; 34 | } 35 | for(i=0; inblocks; i++) { 36 | gen = (sc_gen_t*)(inst->blocks[i]); 37 | for(j=0; jnins; j++) { 38 | if(gen->inptr[j]) { 39 | //*((float*)(gen->inptr[j])) = x; // fixme - use gen->src_blockno[j] 40 | *((float*)(gen->inptr[j])) = output[gen->src_blockno[j]]; 41 | } 42 | } 43 | x = gen->cl->tic_func(inst->blocks[i]); 44 | output[i] = x; 45 | //DEBUG("block %u: out=%f\n", i, x); 46 | } 47 | return x; 48 | } 49 | 50 | void apb_sound_instr_free(apb_sound_instr_t *inst) { 51 | uint i; 52 | 53 | for(i=0; inblocks; i++) { 54 | free(inst->blocks[i]); 55 | } 56 | inst->nblocks = 0; 57 | } 58 | 59 | void apb_sound_instr_init(apb_sound_instr_t *inst) { 60 | uint i; 61 | sc_gen_t *gen; 62 | 63 | for(i=0; inblocks; i++) { 64 | //DEBUG("checking block %u\n", i); 65 | gen = (sc_gen_t*)(inst->blocks[i]); 66 | if(gen->cl->init_func) { 67 | //DEBUG("initing block %u\n", i); 68 | gen->cl->init_func(inst->blocks[i]); 69 | } 70 | } 71 | } 72 | 73 | void apb_sound_instr_kill(apb_sound_instr_t *inst) { 74 | inst->active_p=0; 75 | } 76 | 77 | -------------------------------------------------------------------------------- /lexer.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | char t1[] = "sin(f=1 ph=3.14 amp=200) add(a1=< a2=300) wav(wf=3 amp=.3 f=<)"; 9 | 10 | #include "lexer.h" 11 | 12 | int is_special_punc(char c) { 13 | if(index("()<=", c)) { 14 | return 1; 15 | } 16 | return 0; 17 | } 18 | 19 | char char2class(char c) { 20 | if(is_special_punc(c)) { 21 | return c; 22 | } 23 | if(c == '$') { 24 | return c; 25 | } 26 | if(isalpha(c) || c == '_') { 27 | return 'w'; 28 | } 29 | if(isdigit(c) || c == '-' || c == '.') { 30 | return 'd'; 31 | } 32 | if(isspace(c)) { 33 | return 's'; 34 | } 35 | return '?'; 36 | } 37 | 38 | static float my_atof(char *s, uint len) { 39 | char buf[0x100]; 40 | double d; 41 | 42 | if(len < 1) { 43 | return 0; 44 | } 45 | 46 | if(len >= sizeof(buf)) { 47 | return -1.0; 48 | } 49 | strncpy(buf, s, len); 50 | buf[len]=0; 51 | d = atof(buf); 52 | //printf("my_atof: '%s' -> %f\n", buf, d); 53 | return (float)d; 54 | } 55 | 56 | int get_token(char **p, tok_t *tok) { 57 | int len = 0; // accumulated sequence 58 | char ocl = 0; 59 | char cl; 60 | 61 | while(**p) { 62 | cl = char2class(**p); 63 | tok->byteno ++; 64 | if(**p == '\n') { 65 | tok->lineno ++; 66 | } 67 | 68 | /* a digit appended to alphas is treated as alpha */ 69 | 70 | if(cl == 'd' && (ocl == 'w' || ocl == '$')) { 71 | cl = ocl; 72 | } 73 | if(len && (ocl != cl || is_special_punc(cl))) { 74 | tok->tok_type = ocl; 75 | tok->slen = len; 76 | tok->str = (*p)-len; 77 | if(ocl == 'd') { 78 | tok->f = my_atof(tok->str, tok->slen); 79 | //printf("parsed '%.*s' to %f\n", tok->slen, tok->str, tok->f); 80 | } 81 | else if(ocl == '$') { 82 | tok->f = my_atof(tok->str+1, tok->slen-1); 83 | } 84 | //printf("tok: %c '%.*s'\n", ocl, len, (*p)-len); 85 | return 1; 86 | len = 0; 87 | } 88 | (*p)++; 89 | len ++; // accumulate 90 | ocl = cl; 91 | } 92 | return 0; 93 | } 94 | 95 | 96 | -------------------------------------------------------------------------------- /sc_hardsync.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #include 3 | #include 4 | #include 5 | #include "parser.h" 6 | #include "sc_generic.h" 7 | 8 | #define sc_type sc_hardsync_t 9 | #include "sc_common.h" 10 | 11 | typedef struct { 12 | sc_gen_t gen; 13 | float f0; 14 | float f1; 15 | float amp; 16 | float ph0; 17 | float ph1; 18 | float af; // accel factor - very high (1000) = harder sync 19 | int _catchup; 20 | } sc_type; 21 | 22 | static sc_param_t params[] = { 23 | { Y(f0), "f100" }, 24 | { Y(f1), "f497.97" }, 25 | { Y(amp), "f1" }, 26 | { Y(ph0), "f0" }, 27 | { Y(ph1), "f0" }, 28 | { Y(af), "f5" }, 29 | { 0 } 30 | }; 31 | 32 | 33 | static float oldtic(void *v) { 34 | sc_type *s = (sc_type*)v; 35 | s=v; 36 | float f; 37 | uint slot, u; 38 | 39 | s->ph0 += s->f0 * TWOPI / 44100; // fixme 40 | while(s->ph0 > TWOPI) { 41 | s->ph0 -= TWOPI; 42 | s->ph1 = 0; // hard sync 43 | } 44 | 45 | s->ph1 += s->f1 * TWOPI / 44100; // fixme 46 | while(s->ph1 > TWOPI) { 47 | s->ph1 -= TWOPI; 48 | } 49 | //f = s->amp * (s->ph1 / PI - 1.0); 50 | f = s->amp * (s->ph1 > PI) ? 1.0 : -1.0; // square 51 | f = s->amp * sin(s->ph1); 52 | return f; 53 | } 54 | 55 | static float tic(void *v) { 56 | sc_type *s = (sc_type*)v; 57 | s=v; 58 | float f; 59 | uint slot, u; 60 | 61 | s->ph0 += s->f0 * TWOPI / 44100; // fixme 62 | while(s->ph0 > TWOPI) { 63 | s->ph0 -= TWOPI; 64 | s->_catchup = 1; 65 | } 66 | 67 | if(s->_catchup) { 68 | f = s->af * s->f1 * TWOPI / 44100; // fixme; 69 | if(f > (TWOPI - s->ph1)) { 70 | f = TWOPI - s->ph1; 71 | s->_catchup = 0; 72 | } 73 | s->ph1 += f; 74 | } 75 | else { 76 | s->ph1 += s->f1 * TWOPI / 44100; // fixme 77 | } 78 | while(s->ph1 > TWOPI) { 79 | s->ph1 -= TWOPI; 80 | } 81 | //f = s->amp * (s->ph1 / PI - 1.0); 82 | f = s->amp * (s->ph1 > PI) ? 1.0 : -1.0; // square 83 | f = s->amp * sin(s->ph1); 84 | return f; 85 | } 86 | 87 | static void dump(void *v) { 88 | sc_type *s; 89 | 90 | s=v; 91 | DF(f0); 92 | DF(f1); 93 | DF(amp); 94 | DF(ph0); 95 | DF(ph1); 96 | } 97 | 98 | sc_class_t hardsync_class = { "hardsync", sizeof(sc_type), params, tic, dump }; 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /sc_adsr.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #include 3 | #include 4 | #include 5 | #include "parser.h" 6 | #include "sc_generic.h" 7 | 8 | #define sc_type sc_adsr_t 9 | #include "sc_common.h" 10 | 11 | #define M_ATTACK 0 12 | #define M_DECAY 1 13 | #define M_SUSTAIN 2 14 | #define M_RELEASE 3 15 | #define M_DONE 4 16 | 17 | typedef struct { 18 | sc_gen_t gen; 19 | float in; 20 | float at; 21 | float dt; 22 | float st; 23 | float sv; 24 | float rt; 25 | float _amp; 26 | float _t; 27 | float _dy; 28 | int _mode; 29 | } sc_type; 30 | 31 | static sc_param_t params[] = { 32 | { Y(in), "f1" }, 33 | { Y(at), "f0.05" }, 34 | { Y(dt), "f0.1" }, 35 | { Y(st), "f0.2" }, 36 | { Y(sv), "f0.5" }, 37 | { Y(rt), "f0.3" }, 38 | { 0 } 39 | }; 40 | 41 | static float calc_dy(float dv, float seconds) { 42 | if(seconds == 0.0) { 43 | return dv/44100.0; // fixme 44 | } 45 | return dv/(seconds * 44100.0); 46 | } 47 | 48 | static void init(void *v) { 49 | sc_type *p = (sc_type*)v; 50 | p->_amp = 0; // start at 0 volume 51 | p->_t = p->at; // remaining time 52 | p->_dy = calc_dy(1.0, p->at); 53 | p->_mode = M_ATTACK; 54 | printf("adsr: start mode=%d _amp=%f _t=%f dy=%f\n", p->_mode, p->_amp, p->_t, p->_dy); 55 | } 56 | 57 | static float tic(void *v) { 58 | sc_type *p = (sc_type*)v; 59 | 60 | if(p->_mode == M_DONE) { 61 | return 0.0; 62 | } 63 | p->_t -= 1.0/44100.0; // fixme 64 | 65 | if(p->_t < 0.0) { 66 | p->_mode ++; 67 | switch(p->_mode) { 68 | case M_DECAY: 69 | p->_t = p->dt; 70 | p->_dy = calc_dy(p->sv - p->_amp, p->dt); // down to sustain 71 | break; 72 | case M_SUSTAIN: 73 | p->_t = p->st; 74 | p->_dy = 0.0; // no vol change during sustain 75 | break; 76 | case M_RELEASE: 77 | p->_t = p->rt; 78 | p->_dy = calc_dy(-p->_amp, p->rt); // down to 0 79 | break; 80 | } 81 | printf("adsr: new mode=%d _amp=%f _t=%f dy=%f\n", p->_mode, p->_amp, p->_t, p->_dy); 82 | } 83 | p->_amp += p->_dy; 84 | return p->in * p->_amp; 85 | } 86 | 87 | static void dump(void *v) { 88 | sc_type *s; 89 | 90 | s=v; 91 | DF(in); 92 | DF(at); 93 | DF(dt); 94 | DF(st); 95 | DF(sv); 96 | DF(rt); 97 | DI(_mode); 98 | DF(_amp); 99 | } 100 | 101 | sc_class_t adsr_class = { "adsr", sizeof(sc_type), params, tic, dump, init }; 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /sc_wav.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #include 3 | #include 4 | #include 5 | #include "parser.h" 6 | #include "sc_generic.h" 7 | #define sc_type sc_wav_t 8 | #include "sc_common.h" 9 | 10 | #define NSLOTS 32.0 11 | 12 | typedef struct { 13 | sc_gen_t gen; 14 | float f; 15 | float amp; 16 | float ph; 17 | int wf; 18 | } sc_type; 19 | 20 | static sc_param_t params[] = { 21 | { Y(f), "f100" }, 22 | { Y(amp), "f1" }, 23 | { Y(ph), "f0" }, 24 | { Y(wf), "i9" }, 25 | { 0 } 26 | }; 27 | 28 | static float tic(void *v) { 29 | sc_type *s; 30 | s=v; 31 | float f; 32 | uint slot, u; 33 | 34 | s->ph += s->f * TWOPI / 44100; // fixme 35 | while(s->ph > TWOPI) { 36 | s->ph -= TWOPI; 37 | } 38 | switch(s->wf) { 39 | case 0 : // square 40 | return s->ph < PI ? s->amp : -s->amp; 41 | case 1 : // +ramp 42 | return s->amp * (s->ph / PI - 1.0); 43 | case 2 : // -ramp 44 | return s->amp * (1.0 - s->ph / PI); 45 | case 3 : // 10% pulse 46 | return s->ph < PI * 0.1 ? s->amp : 47 | s->ph < PI ? 0 : 48 | s->ph < PI * 1.1 ? -s->amp : 49 | 0; 50 | case 4 : // triangle 51 | return (s->ph < PI) ? s->amp * (2.0 * (s->ph / PI) - 1.0) : 52 | s->amp * (-2.0 * (s->ph / PI) + 3.0); 53 | case 5 : // diamond <> 54 | f = s->ph < PI ? s->ph / PI : 1.0-((s->ph-PI)/PI); 55 | slot = (uint)(s->ph * NSLOTS / TWOPI); 56 | if(slot % 2) { 57 | f *= -1; 58 | } 59 | return f; 60 | case 6 : // christmas tree 61 | slot = (uint)(s->ph * NSLOTS / TWOPI); 62 | u = slot % 3; 63 | if(u == 1) { 64 | return 0; // center line of xmas tree 65 | } 66 | 67 | f = (s->ph / TWOPI)-1.0; 68 | if(u == 2) { 69 | f *= -1; 70 | } 71 | return f; 72 | case 7 : // ??? // somewhat successful imitation of nmc #3 73 | slot = (uint)(s->ph * NSLOTS / TWOPI); 74 | //f = 0.2 + 0.4 * (s->ph / TWOPI); 75 | f = 0.3 * sin(s->ph); 76 | f += 0.2 * sin(1.1 * s->ph); 77 | switch(slot % 4) { 78 | case 1: 79 | f += 0.3; 80 | break; 81 | case 3: 82 | f -= 0.3; 83 | break; 84 | } 85 | return f; 86 | } 87 | return sin(s->ph) * s->amp; 88 | } 89 | 90 | static void dump(void *v) { 91 | sc_type *s; 92 | 93 | s=v; 94 | DF(f); 95 | DF(amp); 96 | DF(ph); 97 | DI(wf); 98 | } 99 | 100 | sc_class_t wav_class = { "wav", sizeof(sc_type), params, tic, dump }; 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /cli.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | 3 | /* command line interface for soundling */ 4 | 5 | #include "parser.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | typedef char BOOL; 14 | 15 | /* command object, from user */ 16 | 17 | typedef struct { 18 | char *out_fn; 19 | uint ms; 20 | char *sound_cmd; 21 | BOOL verbose; 22 | } cmd_t; 23 | 24 | /* execute the given command */ 25 | 26 | void imain(cmd_t *cmd) { 27 | char buf[0x400]; 28 | apb_sound_engine_t eng; 29 | //int i, samples; 30 | uint i, samples, rate; 31 | 32 | apb_sound_engine_init(&eng); 33 | rate = apb_sound_engine_get_sample_rate(&eng); 34 | samples = cmd->ms * rate / 1000; 35 | if(cmd->out_fn) { 36 | apb_sound_engine_open_outfile(&eng, cmd->out_fn, samples); 37 | } 38 | 39 | if(parse_pgm(&eng, cmd->sound_cmd, 'a')) { /* error */ 40 | fprintf(stderr, "%s\nat %u\n", eng.err, eng.err_byte); 41 | } 42 | else { /* ok */ 43 | if(cmd->verbose) { 44 | apb_sound_engine_dump(&eng); 45 | printf("samples=%d\n", samples); 46 | printf("OK\n"); 47 | } 48 | apb_sound_engine_trigger(&eng, 'a'); 49 | 50 | if(cmd->out_fn) { 51 | apb_sound_engine_write_file(&eng, samples); 52 | } 53 | else { /* to dsp */ 54 | while(eng.nsamp < samples) { 55 | //printf("%d < %d\n", eng.nsamp, samples); 56 | apb_sound_engine_write(&eng); 57 | } 58 | } 59 | } 60 | 61 | if(cmd->out_fn) { 62 | apb_sound_engine_close_outfile(&eng); 63 | } 64 | apb_sound_engine_clear_instrs(&eng); 65 | } 66 | 67 | void usage(char * prog_name) { 68 | fprintf(stderr, "Usage: %s [-t milliseconds] [-o outfile] 'SOUND_CMD'\n", prog_name); 69 | fprintf(stderr, " -t N Duration of output in ms; default to 1 second\n"); 70 | fprintf(stderr, " -o FILE Write a .sw file\n"); 71 | fprintf(stderr, " -v Verbose\n"); 72 | fprintf(stderr, "\nExample SOUND_CMD:\n"); 73 | fprintf(stderr, "wav(f=4 amp=100 wf=9) add(a=< b=120) sin(f=< a=200) add(a=< b=150) sin(f=< amp=1)\n"); 74 | exit(-1); 75 | } 76 | 77 | void parse_options(int argc, char **argv, cmd_t *cmd) { 78 | int opt; 79 | 80 | cmd->out_fn = 0; // default = no file 81 | cmd->ms = 1000; 82 | cmd->verbose = 0; 83 | 84 | while ((opt = getopt(argc, argv, "o:t:v")) != -1) { 85 | switch (opt) { 86 | case 'o': 87 | cmd->out_fn = optarg; 88 | break; 89 | case 't': 90 | cmd->ms = atoi(optarg); 91 | if(!cmd->ms) { 92 | usage(argv[0]); 93 | } 94 | break; 95 | case 'v': 96 | cmd->verbose = 1; 97 | break; 98 | default: 99 | usage(argv[0]); 100 | break; 101 | } 102 | } 103 | if(optind >= argc) { 104 | usage(argv[0]); 105 | } 106 | cmd->sound_cmd = argv[optind]; 107 | } 108 | 109 | int main(int argc, char **argv) { 110 | cmd_t cmd; 111 | parse_options(argc, argv, &cmd); 112 | imain(&cmd); 113 | } 114 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #include "parser.h" 3 | #include "engine.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define POD(KEY, PGM) if(parse_pgm(&eng, PGM, KEY)) { fprintf(stderr, "%s\nat %u\n", eng.err, eng.err_byte); exit(-1); } 12 | 13 | //char t2[] = "sin(f=27 ph=3.14 amp=1.7) add(a=2 b=300) wav(wf=3 amp=.3 f=<)"; 14 | //char t2[] = "sin(f=200 amp=1.0)"; 15 | //char t2[] = "sin(f=3 amp=100) add(a=< b=200) sin(f=< amp=1)"; 16 | //char t2[] = "wav(f=200 amp=1 wf=2)"; // tri alone 17 | //char t2[] = "wav(f=2 amp=100 wf=2) add(a=< b=200) sin(f=< amp=1)"; 18 | //char t2[] = "sin(f=300 a=200) add(a=< b=150) sin(f=< amp=1)"; // fm 19 | //char t2[] = "wav(f=4 amp=100 wf=9) add(a=< b=120) sin(f=< a=200) add(a=< b=150) sin(f=< amp=1)"; // fm growl 20 | //char t2[] = "wav(f=30 amp=1 wf=6)"; 21 | 22 | char t2[] = "wav(f=2 amp=100 wf=2) add(a=< b=120) nmc(f=< amp=1 wf=2)"; 23 | 24 | int input_waiting() { 25 | int nread; 26 | ioctl(0, FIONREAD, &nread); 27 | return nread ? 1 : 0; 28 | } 29 | 30 | void test1() { // REP loop 31 | char buf[0x400]; 32 | apb_sound_engine_t eng; 33 | int i; 34 | 35 | apb_sound_engine_init(&eng); 36 | printf("wav(f=100 amp=1 wf=5)\n"); 37 | printf("wav(f=4 amp=100 wf=9) add(a=< b=120) sin(f=< a=200) add(a=< b=150) sin(f=< amp=1)\n"); 38 | 39 | while(fgets(buf, sizeof(buf), stdin)) { 40 | if(parse_pgm(&eng, buf, 'a')) { 41 | fprintf(stderr, "%s\nat %u\n", eng.err, eng.err_byte); 42 | } 43 | else { 44 | apb_sound_engine_dump(&eng); 45 | printf("OK\n"); 46 | apb_sound_engine_trigger(&eng, 'a'); 47 | for(i=0; i<300000; i++) { 48 | apb_sound_engine_write(&eng); 49 | } 50 | } 51 | apb_sound_engine_clear_instrs(&eng); 52 | } 53 | } 54 | 55 | void test2() { // choose preset sounds with a, b, c, etc 56 | char buf[0x400]; 57 | apb_sound_engine_t eng; 58 | int i; 59 | 60 | apb_sound_engine_init(&eng); 61 | 62 | POD('a', "timer(t=0.3) sin(f=300 a=200) add(a=< b=150) sin(f=< amp=0.3)") 63 | POD('b', "timer(t=0.8) wav(f=4 amp=100 wf=9) add(a=< b=120) sin(f=< a=200) add(a=< b=150) sin(f=< amp=0.3)") 64 | POD('c', "timer(t=12.0) sin(f=440 amp=0.3)") 65 | POD('c', "timer(t=12.0) sin(f=440 amp=0.3)") 66 | 67 | while(fgets(buf, sizeof(buf), stdin)) { 68 | apb_sound_engine_trigger(&eng, buf[0]); 69 | while(!input_waiting()) { 70 | apb_sound_engine_write(&eng); 71 | } 72 | } 73 | apb_sound_engine_clear_instrs(&eng); 74 | } 75 | 76 | int oldmain(int argc, char **argv) { 77 | apb_sound_engine_t eng; 78 | int i; 79 | 80 | apb_sound_engine_init(&eng); 81 | if(parse_pgm(&eng, t2, 'a')) { 82 | fprintf(stderr, "%s\nat %u\n", eng.err, eng.err_byte); 83 | } 84 | else { 85 | apb_sound_engine_dump(&eng); 86 | printf("OK\n"); 87 | while(!input_waiting()) { 88 | apb_sound_engine_write(&eng); 89 | //printf("tic=%f\n", apb_sound_engine_tic(&eng)); 90 | } 91 | } 92 | } 93 | 94 | void test3(int argc, char **argv) { // Run one sound from cmd line 95 | char buf[0x400]; 96 | apb_sound_engine_t eng; 97 | int i; 98 | 99 | if(argc < 2) { 100 | fprintf(stderr, "Invoke with sound argument\n"); 101 | fprintf(stderr, "Example: wav(f=4 amp=100 wf=9) add(a=< b=120) sin(f=< a=200) add(a=< b=150) sin(f=< amp=1)\n"); 102 | exit(-1); 103 | } 104 | apb_sound_engine_init(&eng); 105 | apb_sound_engine_open_outfile(&eng, "output.sw", 44100); 106 | 107 | if(parse_pgm(&eng, argv[1], 'a')) { 108 | fprintf(stderr, "%s\nat %u\n", eng.err, eng.err_byte); 109 | } 110 | else { 111 | apb_sound_engine_dump(&eng); 112 | printf("OK\n"); 113 | apb_sound_engine_trigger(&eng, 'a'); 114 | for(i=0; i<300000; i++) { 115 | apb_sound_engine_write(&eng); 116 | } 117 | } 118 | apb_sound_engine_close_outfile(&eng); 119 | apb_sound_engine_clear_instrs(&eng); 120 | } 121 | 122 | void sound_sleep(apb_sound_engine_t *eng, int ms) { 123 | while(ms > 0) { 124 | //printf("ssleep: %d\n", ms); 125 | apb_sound_engine_write(eng); 126 | //printf("ssleep sleeping 1000\n"); 127 | usleep(1000); 128 | ms --; 129 | } 130 | } 131 | 132 | void test4(int argc, char **argv) { 133 | apb_sound_engine_t eng; 134 | int i; 135 | 136 | apb_sound_engine_init(&eng); 137 | for(i=0; i<3; i++) { 138 | //if(parse_pgm(&eng, "timer(t=0.01) wav(wf=0 f=300) adsr(in=< at=0.01 dt=0.03 st=.1 sv=0.1 rt=.02)", 'a')) { 139 | if(parse_pgm(&eng, "timer(t=0.09) wav(wf=0 f=300) adsr(in=< at=0.01 dt=0.03 st=.1 sv=0.1 rt=.02)", 'a')) { 140 | fprintf(stderr, "error parsing\n"); 141 | exit(-1); 142 | } 143 | } 144 | apb_sound_engine_trigger(&eng, 'a'); 145 | for(i=0; i<10; i++) { 146 | sound_sleep(&eng, 50); 147 | apb_sound_engine_trigger(&eng, 'a'); 148 | } 149 | apb_sound_engine_clear_instrs(&eng); 150 | } 151 | 152 | int main(int argc, char **argv) { 153 | test3(argc, argv); 154 | //test1(); 155 | } 156 | -------------------------------------------------------------------------------- /sc_nmc_data.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | static float data[] = { 3 | /* --- waveform 0 --- */ 4 | -0.066667, 5 | 0.200000, 6 | 0.333333, 7 | 0.466667, 8 | 0.600000, 9 | 0.733333, 10 | 0.733333, 11 | 0.866667, 12 | 0.866667, 13 | 0.866667, 14 | 0.733333, 15 | 0.733333, 16 | 0.600000, 17 | 0.466667, 18 | 0.333333, 19 | 0.200000, 20 | -0.066667, 21 | -0.333333, 22 | -0.466667, 23 | -0.600000, 24 | -0.733333, 25 | -0.866667, 26 | -0.866667, 27 | -1.000000, 28 | -1.000000, 29 | -1.000000, 30 | -0.866667, 31 | -0.866667, 32 | -0.733333, 33 | -0.600000, 34 | -0.466667, 35 | -0.333333, 36 | /* --- waveform 1 --- */ 37 | -0.066667, 38 | 0.600000, 39 | 0.866667, 40 | 0.866667, 41 | 0.733333, 42 | 0.466667, 43 | 0.200000, 44 | 0.333333, 45 | 0.466667, 46 | 0.466667, 47 | 0.333333, 48 | 0.200000, 49 | -0.200000, 50 | -0.466667, 51 | -0.600000, 52 | -0.333333, 53 | -0.066667, 54 | 0.200000, 55 | 0.466667, 56 | 0.333333, 57 | 0.066667, 58 | -0.333333, 59 | -0.466667, 60 | -0.600000, 61 | -0.600000, 62 | -0.466667, 63 | -0.333333, 64 | -0.600000, 65 | -0.866667, 66 | -1.000000, 67 | -1.000000, 68 | -0.733333, 69 | /* --- waveform 2 --- */ 70 | -0.066667, 71 | 0.333333, 72 | 0.600000, 73 | 0.733333, 74 | 0.866667, 75 | 0.733333, 76 | 0.600000, 77 | 0.333333, 78 | -0.066667, 79 | -0.466667, 80 | -0.733333, 81 | -0.866667, 82 | -1.000000, 83 | -0.866667, 84 | -0.733333, 85 | -0.466667, 86 | -0.066667, 87 | 0.466667, 88 | 0.733333, 89 | 0.866667, 90 | 0.733333, 91 | 0.466667, 92 | -0.066667, 93 | -0.600000, 94 | -0.866667, 95 | -1.000000, 96 | -0.866667, 97 | -0.600000, 98 | -0.066667, 99 | 0.866667, 100 | -0.066667, 101 | -1.000000, 102 | /* --- waveform 3 --- */ 103 | -0.066667, 104 | 0.733333, 105 | 0.466667, 106 | 0.066667, 107 | 0.466667, 108 | 0.733333, 109 | 0.200000, 110 | -0.200000, 111 | 0.466667, 112 | 0.866667, 113 | 0.600000, 114 | -0.066667, 115 | 0.200000, 116 | 0.333333, 117 | -0.200000, 118 | -0.733333, 119 | -0.066667, 120 | 0.600000, 121 | 0.066667, 122 | -0.466667, 123 | -0.333333, 124 | -0.066667, 125 | -0.733333, 126 | -1.000000, 127 | -0.600000, 128 | 0.066667, 129 | -0.333333, 130 | -0.866667, 131 | -0.600000, 132 | -0.200000, 133 | -0.600000, 134 | -0.866667, 135 | /* --- waveform 4 --- */ 136 | -1.000000, 137 | 0.066667, 138 | 1.000000, 139 | -0.066667, 140 | -0.866667, 141 | 0.066667, 142 | 0.866667, 143 | -0.066667, 144 | -0.733333, 145 | 0.066667, 146 | 0.733333, 147 | -0.066667, 148 | -0.600000, 149 | 0.066667, 150 | 0.600000, 151 | -0.066667, 152 | -0.466667, 153 | 0.066667, 154 | 0.466667, 155 | -0.066667, 156 | -0.333333, 157 | 0.066667, 158 | 0.333333, 159 | -0.066667, 160 | -0.200000, 161 | 0.066667, 162 | 0.200000, 163 | -0.066667, 164 | -0.066667, 165 | 0.066667, 166 | 0.066667, 167 | -0.066667, 168 | /* --- waveform 5 --- */ 169 | -0.066667, 170 | 0.066667, 171 | -0.200000, 172 | 0.200000, 173 | -0.333333, 174 | 0.333333, 175 | -0.466667, 176 | 0.466667, 177 | -0.600000, 178 | 0.600000, 179 | -0.733333, 180 | 0.733333, 181 | -0.866667, 182 | 0.866667, 183 | -1.000000, 184 | 1.000000, 185 | -1.000000, 186 | 1.000000, 187 | -0.866667, 188 | 0.866667, 189 | -0.733333, 190 | 0.733333, 191 | -0.600000, 192 | 0.600000, 193 | -0.466667, 194 | 0.466667, 195 | -0.333333, 196 | 0.333333, 197 | -0.200000, 198 | 0.200000, 199 | -0.066667, 200 | 0.066667, 201 | /* --- waveform 6 --- */ 202 | -1.000000, 203 | -0.866667, 204 | -0.733333, 205 | -0.600000, 206 | -0.466667, 207 | -0.333333, 208 | -0.200000, 209 | -0.066667, 210 | 0.066667, 211 | 0.200000, 212 | 0.333333, 213 | 0.466667, 214 | 0.600000, 215 | 0.733333, 216 | 0.866667, 217 | 1.000000, 218 | 1.000000, 219 | 0.866667, 220 | 0.733333, 221 | 0.600000, 222 | 0.466667, 223 | 0.333333, 224 | 0.200000, 225 | 0.066667, 226 | -0.066667, 227 | -0.200000, 228 | -0.333333, 229 | -0.466667, 230 | -0.600000, 231 | -0.733333, 232 | -0.866667, 233 | -1.000000, 234 | /* --- waveform 7 --- */ 235 | -1.000000, 236 | -0.866667, 237 | -0.733333, 238 | -0.600000, 239 | -0.466667, 240 | -0.333333, 241 | -0.200000, 242 | -0.066667, 243 | 0.066667, 244 | 0.200000, 245 | 0.333333, 246 | 0.466667, 247 | 0.600000, 248 | 0.733333, 249 | 0.866667, 250 | 1.000000, 251 | -1.000000, 252 | -0.866667, 253 | -0.733333, 254 | -0.600000, 255 | -0.466667, 256 | -0.333333, 257 | -0.200000, 258 | -0.066667, 259 | 0.066667, 260 | 0.200000, 261 | 0.333333, 262 | 0.466667, 263 | 0.600000, 264 | 0.733333, 265 | 0.866667, 266 | 1.000000 267 | }; 268 | -------------------------------------------------------------------------------- /engine.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "engine.h" 16 | 17 | //#define DEBUG(M, ...) fprintf(stderr, "%-24.24s %5d: ", __FILE__, __LINE__); fprintf(stderr, M, __VA_ARGS__); 18 | #define DEBUG(M, ...) 19 | 20 | #define RATE 44100 21 | 22 | void apb_sound_engine_init(apb_sound_engine_t *eng) { 23 | bzero(eng, sizeof(apb_sound_engine_t)); 24 | eng->sample_rate = RATE; 25 | eng->dsp = 0; // output to sound card not initialized 26 | eng->ofh = NULL; 27 | eng->nsamp = 0; 28 | } 29 | 30 | void apb_sound_engine_init_soundcard(apb_sound_engine_t *eng) { 31 | int format = AFMT_S16_LE; // 16bit little endian 32 | int nchannels=2; 33 | int samplerate=RATE; 34 | int setting = 0x0002000d; 35 | 36 | eng->dsp = open("/dev/dsp", O_WRONLY | O_NONBLOCK); 37 | 38 | if(!(eng->dsp)) { 39 | perror("open dsp"); 40 | exit(1); 41 | } 42 | 43 | if (ioctl (eng->dsp, SNDCTL_DSP_RESET, NULL) == -1) { 44 | perror ("ioctl reset"); // usually means dev is busy 45 | exit(1); 46 | } 47 | 48 | // Set the buffer sizes and number of buffers 49 | if (ioctl (eng->dsp, SNDCTL_DSP_SETFRAGMENT, &setting) == -1) { 50 | perror ("ioctl set fragment"); 51 | exit(1); 52 | } 53 | 54 | // Make it stereo or mono 55 | if (ioctl (eng->dsp, SNDCTL_DSP_STEREO, &nchannels) == -1) { 56 | perror ("ioctl channels"); 57 | exit(1); 58 | } 59 | 60 | // Make it 16 bit little Endian 61 | if (ioctl (eng->dsp, SNDCTL_DSP_SETFMT, &format) == -1) { 62 | perror ("ioctl format"); 63 | exit(1); 64 | } 65 | 66 | // Set the rate 67 | if (ioctl (eng->dsp, SNDCTL_DSP_SPEED, &samplerate) == -1) { 68 | perror ("ioctl sample rate"); 69 | exit(1); 70 | } 71 | } 72 | 73 | uint apb_sound_engine_get_sample_rate(apb_sound_engine_t *eng) { 74 | return eng->sample_rate; 75 | } 76 | 77 | /* If we have an outfile open, write the sample to it */ 78 | 79 | static void apb_sound_engine_write_outfile(apb_sound_engine_t *eng, float fx) { 80 | short x; 81 | if(!eng->ofh) { 82 | return; 83 | } 84 | x = (short)(fx * 32767.0); 85 | fwrite(&x, sizeof(x), 1, eng->ofh); 86 | //printf("wrote; nsamp=%d\n", eng->nsamp); 87 | } 88 | 89 | /* this func is too ambiguous. are we writing once or many times? */ 90 | 91 | void apb_sound_engine_write(apb_sound_engine_t *eng) { 92 | int buf[4]; 93 | int x; 94 | float fx; 95 | int rc; 96 | 97 | fx = eng->old_sample; 98 | 99 | // open the soundcard if we haven't already 100 | if(!eng->dsp) { 101 | apb_sound_engine_init_soundcard(eng); 102 | } 103 | 104 | while(1) { 105 | x = (int)(fx * 32767.0); 106 | buf[0] = x; 107 | buf[1] = x; 108 | buf[2] = x; 109 | buf[3] = x; 110 | rc = write(eng->dsp, buf, 4); 111 | if(rc != 4) { // EAGAIN 112 | eng->old_sample = fx; 113 | return; 114 | } 115 | fx = apb_sound_engine_tic(eng); 116 | // do not write outfile here; choose dsp or file 117 | //apb_sound_engine_write_outfile(eng, fx); 118 | eng->nsamp ++; 119 | } 120 | } 121 | 122 | /* write samples to outfile, which must already be open */ 123 | 124 | void apb_sound_engine_write_file(apb_sound_engine_t *eng, uint nsamples) { 125 | uint i; 126 | float fx; 127 | 128 | for(i=0; insamp ++; 132 | } 133 | } 134 | 135 | void apb_sound_engine_dump(apb_sound_engine_t *eng) { 136 | uint i; 137 | for(i=0; ininstrs; i++) { 138 | printf("** Instrument %u:\n", i); 139 | apb_sound_instr_dump(&(eng->instrs[i])); 140 | } 141 | } 142 | 143 | float apb_sound_engine_tic(apb_sound_engine_t *eng) { 144 | uint i; 145 | float x, y; 146 | 147 | x = 0; 148 | for(i=0; ininstrs; i++) { 149 | y = apb_sound_instr_tic(&(eng->instrs[i])); 150 | //DEBUG("instr %u: %f\n", i, y); 151 | x += y; 152 | } 153 | return x; 154 | } 155 | 156 | // blow away everything; typically before shutting down 157 | 158 | void apb_sound_engine_clear_instrs(apb_sound_engine_t *eng) { 159 | uint i; 160 | 161 | for(i=0; ininstrs; i++) { 162 | apb_sound_instr_free(&(eng->instrs[i])); 163 | } 164 | eng->ninstrs = 0; 165 | } 166 | 167 | void apb_sound_engine_trigger(apb_sound_engine_t *eng, uint user_tag) { 168 | uint i; 169 | 170 | DEBUG("ninstrs=%u\n", eng->ninstrs); 171 | for(i=0; ininstrs; i++) { 172 | DEBUG("Examining instr %u @ %p: active=%u tag=%u\n", 173 | i, &(eng->instrs[i]), eng->instrs[i].active_p, eng->instrs[i].user_tag); 174 | if(!(eng->instrs[i].active_p) && eng->instrs[i].user_tag == user_tag) { 175 | eng->instrs[i].active_p = 1; 176 | apb_sound_instr_init(&(eng->instrs[i])); 177 | return; 178 | } 179 | } 180 | DEBUG("Did not find an idle instr for tag %u\n", user_tag); 181 | // failed 182 | } 183 | 184 | void apb_sound_engine_open_outfile(apb_sound_engine_t *eng, char *filename, uint max_samples) { 185 | if(eng->ofh) { 186 | fclose(eng->ofh); 187 | } 188 | eng->ofh = fopen(filename, "wb"); 189 | if(!eng->ofh) { 190 | perror("Opening outfile"); 191 | exit(-1); 192 | } 193 | } 194 | 195 | void apb_sound_engine_close_outfile(apb_sound_engine_t *eng) { 196 | if(eng->ofh) { 197 | fclose(eng->ofh); 198 | } 199 | eng->ofh = NULL; 200 | } 201 | 202 | 203 | -------------------------------------------------------------------------------- /parser.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2017 Asher Blum */ 2 | #include "lexer.h" 3 | #include "parser.h" 4 | #include "engine.h" 5 | #include "sc_generic.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | //#define DEBUG(M, ...) fprintf(stderr, "%-24.24s %5d: ", __FILE__, __LINE__); fprintf(stderr, M, __VA_ARGS__); 12 | #define DEBUG(M, ...) 13 | #define SET_ERR(...) snprintf(eng->err, sizeof(eng->err), __VA_ARGS__) 14 | #define XRET_ERR(...) SET_ERR(__VA_ARGS__); eng->err_byte = t.byteno; return -1 15 | 16 | extern sc_class_t sin_class; 17 | extern sc_class_t add_class; 18 | extern sc_class_t wav_class; 19 | extern sc_class_t nmc_class; 20 | extern sc_class_t timer_class; 21 | extern sc_class_t quant_class; 22 | extern sc_class_t pink_class; 23 | extern sc_class_t dump_class; 24 | extern sc_class_t adsr_class; 25 | extern sc_class_t chopsin_class; 26 | extern sc_class_t hardsync_class; 27 | extern sc_class_t alpha_class; 28 | extern sc_class_t swnoise_class; 29 | extern sc_class_t mul_class; 30 | extern sc_class_t pulse_class; 31 | 32 | static sc_class_t *classes[] = { 33 | &sin_class, 34 | &add_class, 35 | &wav_class, 36 | &nmc_class, 37 | &timer_class, 38 | &quant_class, 39 | &pink_class, 40 | &dump_class, 41 | &adsr_class, 42 | &chopsin_class, 43 | &hardsync_class, 44 | &alpha_class, 45 | &swnoise_class, 46 | &mul_class, 47 | &pulse_class, 48 | 0 49 | }; 50 | 51 | sc_class_t *find_class(char *name, uint len) { 52 | sc_class_t **p; 53 | for(p=classes; *p; p++) { 54 | if(!strncmp((*p)->name, name, len)) { 55 | return *p; 56 | } 57 | } 58 | return 0; 59 | } 60 | 61 | sc_param_t *find_param(sc_class_t *cl, char *name, uint len) { 62 | sc_param_t *p; 63 | for(p=cl->params; p->name; p++) { 64 | if(!strncmp(p->name, name, len)) { 65 | return p; 66 | } 67 | } 68 | return 0; 69 | } 70 | 71 | static void set_params_to_default(void *v) { 72 | sc_class_t *cl; 73 | sc_param_t *p; 74 | sc_gen_t *gen; 75 | 76 | gen = (sc_gen_t*)v; 77 | cl = gen->cl; 78 | DEBUG("cl=%p\n", cl); 79 | for(p=cl->params; p->name; p++) { 80 | DEBUG("name='%s' def='%s'\n", p->name, p->def); 81 | if(p->def[1] == 0) { // no default 82 | continue; 83 | } 84 | switch(p->def[0]) { 85 | case 'f' : // float 86 | *((float*)(v + p->offset)) = atof(p->def+1); 87 | break; 88 | case 'i' : 89 | *((int*)(v + p->offset)) = atoi(p->def+1); 90 | break; 91 | } 92 | } 93 | } 94 | 95 | // set the block's input pointer. 96 | 97 | static void set_inptr(void *v, uint src_blockno, sc_param_t *param) { 98 | sc_gen_t *gen; 99 | 100 | DEBUG("Setting inptr; from block %u to param '%s' = %f\n", 101 | src_blockno, param->name, *((float*)(v + param->offset))); 102 | gen = (sc_gen_t*)v; 103 | gen->src_blockno[gen->nins] = src_blockno; 104 | gen->inptr[gen->nins++] = (float*)(v + param->offset); 105 | } 106 | 107 | // 0=good -1=err 108 | 109 | int parse_pgm(apb_sound_engine_t *eng, char *pgm, uint user_tag) { 110 | char *p; 111 | p = pgm; 112 | tok_t t, keytok, classtok; 113 | sc_class_t *class; 114 | sc_param_t *param; 115 | uint size; 116 | char otype; 117 | char want; // c=class ( k=key | ')', '=', v=val 118 | void *v; 119 | sc_gen_t *gen; 120 | apb_sound_instr_t *inst; 121 | uint src_blockno; 122 | 123 | if(eng->ninstrs >= MAX_INSTRS) { 124 | return -1; 125 | } 126 | inst = &(eng->instrs[eng->ninstrs]); 127 | eng->ninstrs ++; 128 | 129 | t.lineno=1; 130 | t.byteno=0; 131 | size = 0; 132 | want = 'c'; 133 | eng->err_byte = 0; 134 | bzero(inst, sizeof(apb_sound_instr_t)); 135 | DEBUG("Setting user_tag=%u for instr %p\n", user_tag, inst); 136 | inst->user_tag = user_tag; 137 | 138 | while(get_token(&p, &t)) { 139 | //printf("type=%c\n", t.tok_type); 140 | switch(t.tok_type) { 141 | case 's' : // space 142 | break; 143 | case '(' : 144 | if(want != '(') { 145 | XRET_ERR("Unexpected '('"); 146 | } 147 | want = 'k'; 148 | break; 149 | case ')' : 150 | if(want != ')' && want != 'k') { 151 | XRET_ERR("Unexpected ')'"); 152 | } 153 | want = 'c'; 154 | break; 155 | case '=' : 156 | if(want != '=') { 157 | XRET_ERR("Unexpected '='"); 158 | } 159 | want = 'v'; 160 | break; 161 | case '<' : 162 | if(want != 'v') { 163 | XRET_ERR("Unexpected '<'"); 164 | } 165 | set_inptr(v, inst->nblocks-2, param); // refer to prev block 166 | want = 'k'; 167 | break; 168 | case 'w' : 169 | if(want == 'c') { // want class 170 | class = find_class(t.str, t.slen); 171 | if(!class) { 172 | XRET_ERR("Unknown class: '%.*s'", t.slen, t.str); 173 | } 174 | //printf("cmd: %.*s class: %p\n", t.slen, t.str, class); 175 | //size += class->size; 176 | if(inst->nblocks >= MAX_BLOCKS) { 177 | XRET_ERR("Too many blocks"); 178 | } 179 | v = malloc(class->size); 180 | bzero(v, class->size); 181 | gen = (sc_gen_t*)v; 182 | //*((sc_class_t**)v) = class; 183 | gen->cl = class; 184 | gen->instr = inst; 185 | set_params_to_default(v); 186 | inst->blocks[inst->nblocks] = v; 187 | inst->nblocks ++; 188 | classtok = t; 189 | want = '('; 190 | } 191 | else if(want == 'k') { // want key 192 | param = find_param(class, t.str, t.slen); 193 | if(!param) { 194 | XRET_ERR("Unknown param: '%.*s' in %.*s", 195 | t.slen, t.str, classtok.slen, classtok.str); 196 | } 197 | keytok = t; 198 | want = '='; 199 | } 200 | else { 201 | XRET_ERR("Unexpected word: '%.*s'", t.slen, t.str); 202 | } 203 | break; 204 | case 'd' : 205 | if(want != 'v') { 206 | XRET_ERR("Unexpected number: '%.*s'", t.slen, t.str); 207 | } 208 | DEBUG("setting %s (%c) = %f\n", param->name, param->def[0], t.f); 209 | 210 | switch(param->def[0]) { 211 | case 'f': 212 | *((float*)(v + param->offset)) = t.f; 213 | break; 214 | case 'i': 215 | *((int*)(v + param->offset)) = t.f; 216 | break; 217 | } 218 | want = 'k'; 219 | break; 220 | case '?' : 221 | if(want != 'v') { 222 | XRET_ERR("Unexpected '?'"); 223 | } 224 | if(inst->nbindparams >= MAX_BINDADDRS) { 225 | XRET_ERR("Too many bind parameters"); 226 | } 227 | DEBUG("setting bindparam %u to %p\n", inst->nbindparams, (float*)(v + param->offset)); 228 | inst->bindaddrs[inst->nbindparams] = (float*)(v + param->offset); 229 | inst->nbindparams ++; 230 | want = 'k'; 231 | break; 232 | case '$' : 233 | if(want != 'v') { 234 | XRET_ERR("Unexpected '?'"); 235 | } 236 | src_blockno = (int)(t.f); 237 | DEBUG("setting param at %p to %d...\n", (float*)(v + param->offset), src_blockno); 238 | set_inptr(v, src_blockno, param); // refer to prev block 239 | want = 'k'; 240 | break; 241 | default: 242 | XRET_ERR("Unexpected token type '%c'", t.tok_type); 243 | } 244 | } 245 | //printf("size=%u\n", size); 246 | return 0; 247 | } 248 | 249 | --------------------------------------------------------------------------------