From e8f47bcb3832c772fe324a5a5ed8a970a84de493 Mon Sep 17 00:00:00 2001 From: Joshua Phillips Date: Thu, 1 Jan 2009 16:09:18 +0000 Subject: [PATCH] Added automatic format conversion. Added a ring modulator that only works with floating-point audio. graph_connect inserts the required format conversion modules. --- SConscript | 3 +- SConstruct | 1 + convert.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++++ errors.h | 1 + graph.c | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---- graph.h | 8 ++--- main.c | 45 +++++++++++++++++------ modules.h | 2 ++ playsink.c | 9 +++++ ringmod.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++ wavesource.c | 1 + 11 files changed, 362 insertions(+), 21 deletions(-) create mode 100644 convert.c create mode 100644 ringmod.c diff --git a/SConscript b/SConscript index 440c7f6..64644b7 100644 --- a/SConscript +++ b/SConscript @@ -3,7 +3,8 @@ Import(['env']) programs = { 'main': [ 'main.c', 'wavefile.c', 'soundout.c', 'aformat.c', 'sound-ioctl.c', - 'graph.c', 'errors.c', 'wavesource.c', 'playsink.c', 'buffer.c'], + 'graph.c', 'errors.c', 'wavesource.c', 'playsink.c', 'buffer.c', + 'ringmod.c', 'convert.c'], } objects = {} diff --git a/SConstruct b/SConstruct index 2183fff..d052160 100644 --- a/SConstruct +++ b/SConstruct @@ -17,6 +17,7 @@ Help(opts.GenerateHelpText(env)) opts.Save('scache.conf', env) env['LIBS'] = ['m'] +env['CPPDEFINES'] = {'_POSIX_SOURCE': None} # Pretty coloured output if not env['verbose']: diff --git a/convert.c b/convert.c new file mode 100644 index 0000000..c4b8998 --- /dev/null +++ b/convert.c @@ -0,0 +1,104 @@ +#include "modules.h" +#include "graph.h" +#include "assert.h" + +struct converter { + struct graphpin *in_pin, *out_pin; + struct aformat src_af, dest_af; +}; + +static err_t get_output_format(struct graphnode *node, struct graphpin *pin, struct aformat *af) +{ + struct converter *c = node->extra; + assert(pin == c->out_pin); + *af = c->dest_af; + return EOK; +} + +static err_t run(struct graphnode *node) +{ +#define DO_CONVERT(s_T, d_T, SCALE) { \ + s_T *restrict s_ptr = s_buf->data; \ + d_T *restrict d_ptr = d_buf->data; \ + int i; \ + for (i=s_buf->n_samples * s_buf->format.channels; i; --i){ \ + (*(d_ptr++)) = (d_T) ((*(s_ptr++)) SCALE); \ + } \ + return EOK; } + + struct converter *c = node->extra; + struct buffer *s_buf = &c->in_pin->edge->buf, + *d_buf = &c->out_pin->edge->buf; + err_t err; + + err = buffer_alloc(d_buf, s_buf->n_samples); + if (err != EOK){ + return err; + } + + switch (c->src_af.media){ + case MT_AUDIO_32F: + switch (c->dest_af.media){ + case MT_AUDIO_16I: + DO_CONVERT(float, short, * 32768.0) + case MT_AUDIO_32I: + DO_CONVERT(float, int, * 2147483648.0) + } + case MT_AUDIO_16I: + switch (c->dest_af.media){ + case MT_AUDIO_32F: + DO_CONVERT(short, float, / 32768.0) + case MT_AUDIO_32I: + DO_CONVERT(short, int, >> 16) + } + case MT_AUDIO_32I: + switch (c->dest_af.media){ + case MT_AUDIO_32F: + DO_CONVERT(int, float, / 2147483648.0) + case MT_AUDIO_16I: + DO_CONVERT(int, short, << 16) + } + } + return make_error(ENOTIMPL, node, "conversion between these media types is not implemented"); +} + +static const struct graphnode_functab functab = { + NULL, // is_acceptable_input_format + NULL, // get_ideal_input_format + get_output_format, + NULL, // set_buffer + run, +}; + +err_t audio_converter_create(struct graphnode **node_out, struct aformat *src_af, struct aformat *dest_af) +{ + struct graphnode *node; + struct converter *restrict c; + err_t err; + + err = graphnode_create(&node, &functab, sizeof *c); + if (err != EOK){ + return err; + } + c = node->extra; + + c->src_af = *src_af; + c->dest_af = *dest_af; + + err = graphnode_add_pin(node, &c->in_pin); + if (err != EOK){ + return err; + } + c->in_pin->dir = DIR_IN; + c->in_pin->name = "in"; + + err = graphnode_add_pin(node, &c->out_pin); + if (err != EOK){ + return err; + } + c->out_pin->dir = DIR_OUT; + c->out_pin->name = "out"; + + *node_out = node; + return EOK; +} diff --git a/errors.h b/errors.h index 3375ae6..c82d481 100644 --- a/errors.h +++ b/errors.h @@ -19,6 +19,7 @@ const char *get_last_error_message(void); enum { EOK = 0, ENOMEM, // memory full! + ENOTIMPL, // not implemented }; #endif diff --git a/graph.c b/graph.c index 83f9956..1a7da8f 100644 --- a/graph.c +++ b/graph.c @@ -2,6 +2,8 @@ #include "errors.h" #include "stdlib.h" #include "string.h" +#include "modules.h" +#include "assert.h" static struct graphpin *get_last_pin(struct graphnode *node) { @@ -10,6 +12,17 @@ static struct graphpin *get_last_pin(struct graphnode *node) return pin; } +struct graphpin *graphnode_get_pin(struct graphnode *node, enum direction dir) +{ + struct graphpin *pin; + for (pin=node->pins; pin; pin=pin->next){ + if (pin->dir == dir){ + break; + } + } + return pin; +} + err_t graphnode_add_pin(struct graphnode *node, struct graphpin **pin_out) { // TODO: allocate node + pins in one block? @@ -126,6 +139,73 @@ err_t graph_remove_node(struct graph *graph, struct graphnode *node) return EOK; } +// guess the format of output pin 'pin' - i.e. same as input format +static err_t guess_output_format(struct graphpin *pin, struct aformat *af) +{ + struct graphnode *node; + struct graphpin *pin2; + bool found_input = false; + + node = pin->node; + for (pin2=node->pins; pin2; pin2=pin2->next){ + if (pin2->dir == DIR_IN){ + if (found_input){ + return make_error(ECANNOT_GUESS_FORMAT, node, + "cannot guess the output format of node \"%s\", because it has more than one input", + node->name); + } + found_input = true; + if (!pin2->edge){ + // the input pin is not connected, so it won't have + // a format, yet + return make_error(ECANNOT_GUESS_FORMAT, node, + "cannot guess the output format of node \"%s\", because its input is not connected", + node->name); + } + *af = pin2->edge->buf.format; + } + } + return EOK; +} + +// create a converter node to convert between formats +static err_t create_converter_node(struct graph *graph, struct aformat *src_af, struct aformat *dest_af, + struct graphpin *a, struct graphpin *b, struct graphnode **node_out) +{ + struct graphnode *node; + struct graphpin *out_pin, *in_pin; + err_t err; + // create the new node + err = audio_converter_create(&node, src_af, dest_af); + if (err != EOK){ + return err; + } + + err = graph_add_node(graph, node); + if (err != EOK){ + return err; + } + + // connect it + out_pin = graphnode_get_pin(node, DIR_OUT); + in_pin = graphnode_get_pin(node, DIR_IN); + assert(out_pin); + assert(in_pin); + + err = graph_connect(graph, a, in_pin); + if (err != EOK){ + return err; + } + + err = graph_connect(graph, out_pin, b); + if (err != EOK){ + return err; + } + // done + *node_out = node; + return EOK; +} + static err_t do_connect(struct graph *graph, struct graphpin *a, struct graphpin *b, const struct aformat *af) { struct graphedge *edge = malloc(sizeof *edge); @@ -163,7 +243,9 @@ static err_t do_connect(struct graph *graph, struct graphpin *a, struct graphpin err_t graph_connect(struct graph *graph, struct graphpin *a, struct graphpin *b) { - struct aformat af; + struct aformat af, af2; + struct graphnode *cvt_node; + struct graphpin *cvt_input; err_t err; // check a and b direction @@ -177,14 +259,35 @@ err_t graph_connect(struct graph *graph, struct graphpin *a, struct graphpin *b) // otherwise we won't know what output format to use. if (graphnode_all_inputs_connected(a->node)){ // get media type from source - err = a->node->functab->get_output_format(a->node, a, &af); - if (err != EOK){ - return err; + if (a->node->functab->get_output_format){ + err = a->node->functab->get_output_format(a->node, a, &af); + if (err != EOK){ + return err; + } + } else { + // assume output format is same as input format + err = guess_output_format(a, &af); + if (err != EOK){ + return err; + } } // check media type with b node if (b->node->functab->is_acceptable_input_format && !b->node->functab->is_acceptable_input_format(b->node, b, &af)){ - return make_error(ENO_AGREEABLE_FORMAT, graph, "cannot agree on a common media format"); + // it doesn't like that input format + // we'll have to add a converter node + if (b->node->functab->get_ideal_input_format){ + err = b->node->functab->get_ideal_input_format(b->node, b, &af2); + if (err != EOK){ + return err; + } + cvt_input = NULL; + return create_converter_node(graph, &af, &af2, + a, b, &cvt_node); + } else { + return make_error(ENO_AGREEABLE_FORMAT, graph, "cannot agree on a common media format (\"%s\" won't specify an ideal format)", + b->node->name); + } } // actually do the connection return do_connect(graph, a, b, &af); @@ -193,7 +296,8 @@ err_t graph_connect(struct graph *graph, struct graphpin *a, struct graphpin *b) // (because we don't know what output format to use // without an input format) return make_error(EUNCONNECTED, graph, - "unconnected input pins: cannot determine media type"); + "unconnected input pins: cannot determine media type (\"%s\")", + a->node->name); } } } diff --git a/graph.h b/graph.h index f383fb1..3a60fa2 100644 --- a/graph.h +++ b/graph.h @@ -9,6 +9,7 @@ enum { EPINDIR = ERR_GROUP(7), // incorrect pin directions EUNCONNECTED, // there are unconnected pins ENO_AGREEABLE_FORMAT, // no format that both nodes will agree on + ECANNOT_GUESS_FORMAT, // cannot guess a node's output format }; enum direction { @@ -21,10 +22,9 @@ struct graphpin; struct graphedge; struct graphnode_functab { - bool (* is_acceptable_input_format)(struct graphnode *node, struct graphpin *pin, - const struct aformat *af); - err_t (* get_output_format)(struct graphnode *node, struct graphpin *pin, - struct aformat *af); + bool (* is_acceptable_input_format)(struct graphnode *node, struct graphpin *pin, const struct aformat *af); + err_t (* get_ideal_input_format)(struct graphnode *node, struct graphpin *pin, struct aformat *af); + err_t (* get_output_format)(struct graphnode *node, struct graphpin *pin, struct aformat *af); err_t (* set_buffer)(struct graphnode *node, struct graphpin *pin, struct buffer *buf); err_t (* run)(struct graphnode *node); }; diff --git a/main.c b/main.c index 4132b1a..45e1fe7 100644 --- a/main.c +++ b/main.c @@ -3,34 +3,58 @@ #include "wavefile.h" #include "modules.h" #include "graph.h" -#include "assert.h" #include "errors.h" +#include "assert.h" + +#include "unistd.h" +#include "sys/types.h" +#include "signal.h" + +void moan(err_t err) +{ + if (err != EOK){ + fprintf(stderr, "%s:%d: in function %s: %s\n", + get_last_error_file(), + get_last_error_line(), + get_last_error_func(), + get_last_error_message()); + raise(SIGABRT); + } +} int main(int argc, char **argv) { struct graph _graph, *graph = &_graph; - struct graphnode *node, *node_1, *node_2; + struct graphnode *node, *node_1, *node_2, *node_3; + err_t err; if (graph_create(graph)){ fprintf(stderr, "cannot create graph\n"); return 1; } - assert(wavesource_create(&node, "/home/aoe/reflections.wav") == EOK); + moan(wavesource_create(&node, "/home/aoe/reflections.wav")); + //moan(wavesource_create(&node, "/home/aoe/horizon.wav")); assert(node); - assert(graph_add_node(graph, node) == EOK); + moan(graph_add_node(graph, node)); node->name = "source"; node_1 = node; - assert(playsink_create(&node) == EOK); + moan(playsink_create(&node)); assert(node); - assert(graph_add_node(graph, node) == EOK); + moan(graph_add_node(graph, node)); node->name = "sink"; node_2 = node; - - assert(graph_connect(graph, node_1->pins, node_2->pins) == EOK); - assert(graph_sort(graph) == EOK); + moan(ringmod_create(&node)); + assert(node); + moan(graph_add_node(graph, node)); + node->name = "ringmod"; + node_3 = node; + + moan(graph_connect(graph, node_1->pins, node_3->pins)); + moan(graph_connect(graph, node_3->pins->next, node_2->pins)); + moan(graph_sort(graph)); { struct graphnode *node; @@ -39,7 +63,8 @@ int main(int argc, char **argv) } } - while (graph_run(graph) == EOK); + while ((err = graph_run(graph)) == EOK); + moan(err); graph_destroy(graph); diff --git a/modules.h b/modules.h index 7fc753d..82518c2 100644 --- a/modules.h +++ b/modules.h @@ -6,5 +6,7 @@ err_t wavesource_create(struct graphnode **node_out, const char *filename); err_t playsink_create(struct graphnode **node_out); +err_t ringmod_create(struct graphnode **node_out); +err_t audio_converter_create(struct graphnode **node_out, struct aformat *src_af, struct aformat *dest_af); #endif diff --git a/playsink.c b/playsink.c index 1bee6b9..937b7a2 100644 --- a/playsink.c +++ b/playsink.c @@ -13,6 +13,14 @@ static bool is_acceptable_input_format(struct graphnode *node, struct graphpin * return af->media == MT_AUDIO_16I; } +static err_t get_ideal_input_format(struct graphnode *node, struct graphpin *pin, struct aformat *af) +{ + af->media = MT_AUDIO_16I; + af->srate = 44100; + af->channels = 2; + return EOK; +} + static err_t set_buffer(struct graphnode *node, struct graphpin *pin, struct buffer *buf) { struct playsink *ps = node->extra; @@ -38,6 +46,7 @@ static err_t run(struct graphnode *node) static const struct graphnode_functab functab = { is_acceptable_input_format, + get_ideal_input_format, NULL, // get_output_format set_buffer, run, diff --git a/ringmod.c b/ringmod.c new file mode 100644 index 0000000..8f48b98 --- /dev/null +++ b/ringmod.c @@ -0,0 +1,93 @@ +#include "modules.h" +#include "graph.h" +#include "errors.h" +#include "math.h" + +#define M_PI 3.1415926535897931 + +struct ringmod { + struct graphpin *in_pin, *out_pin; + float pos; +}; + +static bool is_acceptable_input_format(struct graphnode *node, struct graphpin *pin, const struct aformat *af) +{ + return af->media == MT_AUDIO_32F; +} + +static err_t get_ideal_input_format(struct graphnode *node, struct graphpin *pin, struct aformat *af) +{ + af->media = MT_AUDIO_32F; + af->srate = 44100; + af->channels = 2; + return EOK; +} + +static err_t run(struct graphnode *node) +{ + struct ringmod *rm = node->extra; + float *src, *dest, pos; + err_t err; + int i; + + err = buffer_alloc(&rm->out_pin->edge->buf, rm->in_pin->edge->buf.n_samples); + if (err != EOK){ + return err; + } + + src = rm->in_pin->edge->buf.data; + dest = rm->out_pin->edge->buf.data; + pos = rm->pos; + + for (i = rm->in_pin->edge->buf.n_samples * rm->in_pin->edge->buf.format.channels; + i; --i){ + *dest = *src * cos((pos * M_PI * 2) / 441.0); + ++dest, ++src, ++pos; + } + + rm->pos = pos; + + return EOK; +} + +static const struct graphnode_functab functab = { + is_acceptable_input_format, + get_ideal_input_format, + NULL, // set_output_format + NULL, // set_buffer + run, +}; + +err_t ringmod_create(struct graphnode **node_out) +{ + struct graphnode *node; + struct ringmod *restrict rm; + err_t err; + + err = graphnode_create(&node, &functab, sizeof *rm); + if (err != EOK){ + return err; + } + + rm = node->extra; + rm->pos = 0.0; + + err = graphnode_add_pin(node, &rm->in_pin); + if (err != EOK){ + graphnode_free(node); + return err; + } + rm->in_pin->dir = DIR_IN; + rm->in_pin->name = "in"; + + err = graphnode_add_pin(node, &rm->out_pin); + if (err != EOK){ + graphnode_free(node); + return err; + } + rm->out_pin->dir = DIR_OUT; + rm->out_pin->name = "out"; + + *node_out = node; + return EOK; +} diff --git a/wavesource.c b/wavesource.c index abf442c..0304302 100644 --- a/wavesource.c +++ b/wavesource.c @@ -46,6 +46,7 @@ static err_t run(struct graphnode *node) static const struct graphnode_functab functab = { NULL, // is_acceptable_input_format + NULL, // get_ideal_input_format get_output_format, set_buffer, run, -- 2.11.4.GIT