diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..130f5ef --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +filterd +fctrl +*.o diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..601c7d1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib"] + path = lib + url = git@github.com:tuneraudio/tunerlib.git diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ee1189d --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +OUT = filterd +SRC = ${wildcard *.c} +OBJ = ${SRC:.c=.o} + +CFLAGS:=-std=gnu99 \ + -Wall -Wextra -pedantic \ + -Ilib/include \ + ${CFLAGS} + +LDFLAGS:=-ljack -lm -lpthread \ + -Llib -ltuner \ + ${LDFLAGS} + +${OUT}: lib ${OBJ} + ${CC} -o $@ ${OBJ} ${LDFLAGS} + +client lib: + ${MAKE} -C $@ + +clean: + ${RM} ${OUT} ${OBJ} + +.PHONY: clean lib client install uninstall diff --git a/README b/README deleted file mode 100644 index afe62a3..0000000 --- a/README +++ /dev/null @@ -1 +0,0 @@ -a repo of jack clients for a variety of tasks diff --git a/README.md b/README.md new file mode 100644 index 0000000..941f8fe --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# README + +This code creates a simple jack client that takes a specified biquad +filter and processes audio passed to the client via the jack audio +server. + +## Overview + +To properly use this client you must run two separate processes. The +first is the jack client (`fctrl`) which processes real time audio and +acts as a Unix socket server, and the second is a Unix socket client +(`filterd`) which transmits requests to change the state of the real +time filter. + +## Building + +``` +git submodule update --init +make +make client +``` + +## Usage + +To run the filter: + +``` +./filterd.sh +``` + +Then to run the client: + +``` +./client/fctrl +``` + +``` +command>type={'lpf','bpf','hpf','peq','hsh','lsh'} + + fc=double + g=double + bw=double +``` diff --git a/client/Makefile b/client/Makefile new file mode 100644 index 0000000..9896887 --- /dev/null +++ b/client/Makefile @@ -0,0 +1,15 @@ +OUT = fctrl +SRC = ${wildcard *.c} +OBJ = ${SRC:.c=.o} + +CFLAGS:=-std=gnu99 \ + -Wall -Wextra -pedantic \ + ${CFLAGS} + +${OUT}: ${OBJ} + ${CC} -o $@ ${OBJ} ${LDFLAGS} + +clean: + ${RM} ${OUT} ${OBJ} + +.PHONY: clean install uninstall diff --git a/client/fctrl.c b/client/fctrl.c new file mode 100644 index 0000000..ea5653c --- /dev/null +++ b/client/fctrl.c @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../filterd.h" + +static inline ssize_t +prompt(char *buf, size_t size) +{ + printf("command> "); + fgets(buf, size, stdin); + return buf ? (ssize_t)strlen(buf) : -1; +} + +int +main(void) +{ + struct sockaddr_un remote; + cmd_t command; + int fd; + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + perror("socket"); + exit(EXIT_FAILURE); + } + + /* assign the socket path */ + remote.sun_family = AF_UNIX; + strcpy(remote.sun_path, socket_path); + + if (connect(fd, (struct sockaddr *)&remote, sizeof(remote)) == -1) { + perror("connect"); + exit(EXIT_FAILURE); + } + + ssize_t ret; + while ((ret = prompt(command, BUFF_SIZE))) { + /* remove \n character */ + command[--ret] = '\0'; + + /* send message */ + if (send(fd, command, ret, 0) == -1) { + perror("send"); + exit(EXIT_FAILURE); + } + + ret = recv(fd, command, BUFF_SIZE, 0); + + if (ret == 0) + break; + else if (ret == -1) { + perror("recv"); + exit(EXIT_FAILURE); + } + + command[ret] = '\0'; + printf("status> %s\n", command); + } + + printf("Server closed connection\n"); + close(fd); + return 0; +} + +// vim: et:sts=4:sw=4:cino=(0 diff --git a/filter_client/Makefile b/filter_client/Makefile deleted file mode 100644 index 4f7fef4..0000000 --- a/filter_client/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -######################################## -CC = gcc # compiler -CFLAGS = -Wall -pedantic -std=gnu99 -g # compiler flags -LDFLAGS = -ljack -lm -pthread # linker (ld) flags - -PROG = filter_client # final executable name -PROG2 = commander -OBJ = filter_client.o biquad_df1.o # object/source files to be created/compiled -OBJ2 = commander.o -######################################## - -all: $(PROG) $(PROG2) - -$(PROG): $(OBJ) - @echo CC -o $@ - @$(CC) -o $@ $(OBJ) $(LDFLAGS) - -$(PROG2): $(OBJ2) - @echo CC -o $@ - @$(CC) -o $@ $(OBJ2) $(LDFLAGS) - -%.o: %.c - @echo CC $@ - @$(CC) -o $@ -c $(CFLAGS) $< - -clean: - @echo cleaning up... - @rm *.o $(PROG) $(PROG2) - -run: - @./$(PROG) - -mem: - @valgrind --leak-check=yes ./$(PROG) - - -.PHONY: all clean diff --git a/filter_client/README.md b/filter_client/README.md deleted file mode 100644 index 3f02d69..0000000 --- a/filter_client/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# README - -This code creates a simple jack client that takes a specified biquad -filter and processes audio passed to the client via the jack audio -server. - -## Overview - -To properly use this client you must run two separate processes. The -first is the jack client (`filter_client`) which processes real time -audio and acts as a Unix socket server, and the second is a Unix socket -client (`commander`) which transmits requests to change the state of the -real time filter. - -## Usage - -``` -./filter_client (in shell 1) -``` - -``` -./commander (in shell 2) -``` - -``` -command>type={'lpf','bpf','hpf','peq','hsh','lsh'} - - fc=double - g=double - bw=double -``` diff --git a/filter_client/biquad_df1.c b/filter_client/biquad_df1.c deleted file mode 100644 index a8217cc..0000000 --- a/filter_client/biquad_df1.c +++ /dev/null @@ -1,131 +0,0 @@ -/* Based on the work - -Cookbook formulae for audio EQ biquad filter coefficients ---------------------------------------------------------- -by Robert Bristow-Johnson, pbjrbj@viconet.com a.k.a. robert@audioheads.com - - * Available on the web at -http://www.smartelectronix.com/musicdsp/text/filters005.txt -*/ - -#include "biquad_df1.h" - -/* computes a df1 biquad implementation on a sample */ -smp_type -df1(smp_type sample, biquad * b) -{ - smp_type result; - - /* compute result */ - result = b->b0 * sample + b->b1 * b->x1 + b->b2 * b->x2 - - b->a1 * b->y1 - b->a2 * b->y2; - - /* shift x1 to x2, sample to x1 */ - b->x2 = b->x1; - b->x1 = sample; - - /* shift y1 to y2, result to y1 */ - b->y2 = b->y1; - b->y1 = result; - - return result; -} - -/* sets up a BiQuad Filter */ -biquad * -compute_biquad(int type, smp_type dbGain, smp_type freq, -smp_type srate, smp_type bandwidth) -{ - biquad *b; - smp_type A, omega, sn, cs, alpha, beta; - smp_type a0, a1, a2, b0, b1, b2; - - b = malloc(sizeof(biquad)); - if (b == NULL) - return NULL; - - /* setup variables */ - A = pow(10, dbGain /40); - omega = 2 * M_PI * freq /srate; - sn = sin(omega); - cs = cos(omega); - alpha = sn * sinh(M_LN2 /2 * bandwidth * omega /sn); - beta = sqrt(A + A); - - switch (type) { - case LPF: - b0 = (1 - cs) /2; - b1 = 1 - cs; - b2 = (1 - cs) /2; - a0 = 1 + alpha; - a1 = -2 * cs; - a2 = 1 - alpha; - break; - case HPF: - b0 = (1 + cs) /2; - b1 = -(1 + cs); - b2 = (1 + cs) /2; - a0 = 1 + alpha; - a1 = -2 * cs; - a2 = 1 - alpha; - break; - case BPF: - b0 = alpha; - b1 = 0; - b2 = -alpha; - a0 = 1 + alpha; - a1 = -2 * cs; - a2 = 1 - alpha; - break; - case NOTCH: - b0 = 1; - b1 = -2 * cs; - b2 = 1; - a0 = 1 + alpha; - a1 = -2 * cs; - a2 = 1 - alpha; - break; - case PEQ: - b0 = 1 + (alpha * A); - b1 = -2 * cs; - b2 = 1 - (alpha * A); - a0 = 1 + (alpha /A); - a1 = -2 * cs; - a2 = 1 - (alpha /A); - break; - case LSH: - b0 = A * ((A + 1) - (A - 1) * cs + beta * sn); - b1 = 2 * A * ((A - 1) - (A + 1) * cs); - b2 = A * ((A + 1) - (A - 1) * cs - beta * sn); - a0 = (A + 1) + (A - 1) * cs + beta * sn; - a1 = -2 * ((A - 1) + (A + 1) * cs); - a2 = (A + 1) + (A - 1) * cs - beta * sn; - break; - case HSH: - b0 = A * ((A + 1) + (A - 1) * cs + beta * sn); - b1 = -2 * A * ((A - 1) + (A + 1) * cs); - b2 = A * ((A + 1) + (A - 1) * cs - beta * sn); - a0 = (A + 1) - (A - 1) * cs + beta * sn; - a1 = 2 * ((A - 1) - (A + 1) * cs); - a2 = (A + 1) - (A - 1) * cs - beta * sn; - break; - default: - free(b); - return NULL; - } - - /* precompute the coefficients */ - b->b0 = b0 /a0; - b->b1 = b1 /a0; - b->b2 = b2 /a0; - b->a1 = a1 /a0; - b->a2 = a2 /a0; - - /* zero initial samples */ - b->x1 = b->x2 = 0; - b->y1 = b->y2 = 0; - - return b; -} -/* crc==3062280887, version==4, Sat Jul 7 00:03:23 2001 */ - diff --git a/filter_client/biquad_df1.h b/filter_client/biquad_df1.h deleted file mode 100644 index 7dc7b1b..0000000 --- a/filter_client/biquad_df1.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef BIQUAD_H -#define BIQUAD_H - -/* Simple implementation of Biquad filters -- Tom St Denis - * - * Based on the work - -Cookbook formulae for audio EQ biquad filter coefficients ---------------------------------------------------------- -by Robert Bristow-Johnson, pbjrbj@viconet.com a.k.a. robert@audioheads.com - - * Available on the web at - -http://www.smartelectronix.com/musicdsp/text/filters005.txt -*/ - -#include -#include - -#ifndef M_LN2 -#define M_LN2 0.69314718055994530942 -#endif - -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif - -/* whatever sample type you want */ -typedef double smp_type; - -/* this holds the data required to update samples thru a filter */ -typedef struct { - smp_type b0, b1, b2, a1, a2; - smp_type x1, x2, y1, y2; -}biquad; - -/* this holds the control data instructions to compute a - * biquad filter */ -typedef struct { - int ftype; /* see filter types below */ - smp_type dBgain;/* gain in dB */ - smp_type fc; /* cut off / center frequency */ - smp_type fs; /* sample rate (not actual control data?) */ - smp_type bw; /* bandwidth in octaves */ -}control_list; - -extern smp_type BiQuad(smp_type sample, biquad * b); -extern biquad *BiQuad_new(int type, smp_type dbGain, /* gain of filter */ - smp_type freq, /* center frequency */ - smp_type srate, /* sampling rate */ - smp_type bandwidth); /* bandwidth in octaves */ - -/* filter types */ -enum { - LPF, /* low pass filter */ - HPF, /* High pass filter */ - BPF, /* band pass filter */ - NOTCH, /* Notch Filter */ - PEQ, /* Peaking band EQ filter */ - LSH, /* Low shelf filter */ - HSH /* High shelf filter */ -}; - -/* Filter functions */ -smp_type df1(smp_type sample, biquad *b); - -biquad * -compute_biquad(int type, smp_type dbGain, smp_type freq, - smp_type srate, smp_type bandwidth); - -#endif diff --git a/filter_client/commander.c b/filter_client/commander.c deleted file mode 100644 index b345c48..0000000 --- a/filter_client/commander.c +++ /dev/null @@ -1,67 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#define SOCK_PATH "command_socket" - -int main(void) -{ - int s, len; - socklen_t t; - struct sockaddr_un remote; - char command[64]; - char *nl; - - if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { - perror("socket"); - exit(1); - } - - printf("Trying to connect...\n"); - - /* assign the socket path */ - remote.sun_family = AF_UNIX; - strcpy(remote.sun_path, SOCK_PATH); - len = strlen(remote.sun_path) + sizeof(remote.sun_family); - - if (connect(s, (struct sockaddr *)&remote, len) == -1) { - perror("connect"); - exit(1); - } - - printf("Socket Connected.\n"); - - while(printf("command> "), fgets(command, sizeof(command), stdin)) { - - /* remove NL character */ - nl = strchr(command,'\n'); - if (nl != NULL) - *nl = '\0'; - - /* implement API interface here? */ - - /* send message */ - if (send(s, command, strlen(command), 0) == -1) { - perror("send"); - exit(1); - } - - if ((t=recv(s, command, 100, 0)) > 0) { - command[t] = '\0'; - printf("status> %s\n", command); - } else { - if (t < 0) perror("recv"); - else printf("Server closed connection\n"); - exit(1); - } - } - - close(s); - - return 0; -} diff --git a/filter_client/filter_client.c b/filter_client/filter_client.c deleted file mode 100644 index 37545d9..0000000 --- a/filter_client/filter_client.c +++ /dev/null @@ -1,484 +0,0 @@ -/*This is very simple biquad filter client that processes a single - * channel of audio with a selected filter parameter set. - */ -#include -#include "biquad_df1.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - - -#define SOCK_PATH "command_socket" -#define RX_FLAGS 0 //MSG_WAITALL? -> wtf should this be anyways? -#define NUM_ClIENTS 1 - -/* control parameters */ -#define TYPE ('t' | 'y' << 8 | 'p' << 8 | 'e' << 8 ) -#define CENTER_FREQUENCY ('f' | 'c' << 8) -#define BANDWIDTH ('b' | 'w' << 8) -#define GAIN ('g') - - -/* Globals */ -jack_port_t *input_port; -jack_port_t *output_port; -jack_client_t *client; -typedef jack_default_audio_sample_t sample_t; -biquad *filter; -control_list *ctrls; - -/* passthrough switch state */ -enum { - ON, - OFF, -}; -int STATE = OFF; - -/* Prototypes */ -void * start_jack_client(void *ptr); -void * start_messenger(void *ptr); -int process (jack_nframes_t nframes, void *arg); -void jack_shutdown (void *arg); -int parse_command(char *command, control_list **list, char *statusmessage); - - -int -main (int argc, char *argv[]) -{ - int peng, pmssg; - pthread_t aengine, messg; - - /* create two threads: audio engine, and messenger */ - if((peng = pthread_create(&aengine, NULL, start_jack_client, NULL))) { - printf("thread creation failed: %d\n", peng); - } - - if((pmssg = pthread_create(&messg, NULL, start_messenger, NULL))) { - printf("thread creation failed: %d\n", pmssg); - } - - /* wait until threads complete before main continues */ - pthread_join(aengine,NULL); - //pthread_join(messg,NULL); - - /* shouldn't get here but if we do shut down safely */ - jack_client_close (client); - exit (0); -} - -void * -start_messenger(void *ptr) -{ - - int s, s2, len, i; - socklen_t t; - struct sockaddr_un local, remote; - char command[32]; - char mstatus[64]; - - /* alloc for message status */ - //mstatus = malloc(sizeof(mstatus) - - /* create a unix stream socket */ - if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { - perror("socket"); - exit(1); - } - - /* assign the socket path */ - local.sun_family = AF_UNIX; - strcpy(local.sun_path, SOCK_PATH); - /* unlink if socket already exists */ - unlink(local.sun_path); - len = strlen(local.sun_path) + sizeof(local.sun_family); - if (bind(s, (struct sockaddr *)&local, len) == -1) { - perror("bind"); - pthread_exit((void *)errno); - } - - /* qeue up to 1 connections, then reject */ - if (listen(s, NUM_ClIENTS) == -1) { - perror("listen"); - exit(1); - } - - /* wait for the remote connection(s) */ - for(;;) { - int done, n; - printf("fclient: >> Waiting for a connection... <<\n"); - t = sizeof(remote); - if ((s2 = accept(s, (struct sockaddr *)&remote, &t)) == -1) { - perror("accept"); - pthread_exit((void *)errno); - } - - printf("fclient: >> Socket Connected <<\n"); - - /* loop to receive data/commands */ - done = 0; - do { - /* wait to receive a command */ - n = recv(s2, command, sizeof(command), RX_FLAGS ); - - /* echo back the command */ - if (command != '\0') - printf("COMMAND> %s\n", command); - - if (n <= 0) { - if (n < 0) { - perror("recv"); - } - done = 1; - } - if (!done) { - - /************************************* - process the command - *************************************/ - - /* clear last status */ - strcpy(mstatus, ""); - - /* parse the command */ - if (!parse_command(command, &ctrls, mstatus)) { - printf("command was parsed with status: %s\n", mstatus); - - /* clean up old filter */ - free(filter); - - /* Compute new biquad (callback) */ - filter = compute_biquad(ctrls->ftype, - ctrls->dBgain, - ctrls->fc, - ctrls->fs, - ctrls->bw); - - printf("printing control list\n"); - printf("fc=%f, g=%f, bw=%f \n", ctrls->fc, ctrls->dBgain, ctrls->bw); - printf("printing new coefficients:\n"); - printf("b0=%f, b1=%f, b2=%f, a1=%f, a2=%f \n\n", filter->b0, filter->b1, filter->b2, filter->a1, filter->a2); - } - - /* transmit response */ - if (send(s2, mstatus, n, 0) < 0) { - perror("send"); - done = 1; - } - - /* clear the command */ - for (i = 0; i < n; i++) { - command[i] = '\0'; - } - } - - } while (!done); - - close(s2); - } - pthread_exit(NULL); - //return 0; -} - -/** - * This is the control message parser for this filter client. - * It is called by whenever a valid message requesting filter - * parameter changes is received by the messenger thread - * INPUTS: command string, controls list, status message string - * OUTPUTS: int - */ -int -parse_command(char *command, control_list **list, char *statusmessage) -{ - char *control, *value; - char *saveptr, *endptr; - char *type; - double fpval; - - /* parse the command string into tokens */ - /* control part */ - control = strtok_r(command, "=", &saveptr); - if (control == NULL) { - printf("fclient: invalid format -> control=value\n"); - strcpy(statusmessage, "no control specified\n"); - return -1; - } - - /* value part */ - value = strtok_r(NULL, "\n", &saveptr); - if (value == NULL) { - printf("fclient: no value set!\n"); - strcpy(statusmessage, "no value specified"); - return -1; - } - - /* filter type change request */ - if (strcmp(control, "type") == 0) { - type = value; - strcpy(statusmessage, "filter type change success"); - - /* determine the filter type */ - if(strcmp(type, "lpf") == 0) { - printf("requested an lpf\n"); - (*list)->ftype = LPF; - } - else if(strcmp(type, "hpf") == 0) { - printf("requested an hpf\n"); - (*list)->ftype = HPF; - } - else if(strcmp(type, "bpf") == 0) { - printf("requested an bpf\n"); - (*list)->ftype = BPF; - } - else if(strcmp(type, "notch") == 0) { - printf("requested a notch filter\n"); - (*list)->ftype = NOTCH; - } - else if(strcmp(type, "peq") == 0) { - printf("requested a peaking eq\n"); - (*list)->ftype = PEQ; - } - else if(strcmp(type, "lsh") == 0) { - printf("requested a low shelf filter\n"); - (*list)->ftype = LSH; - } - else if(strcmp(type, "hsh") == 0) { - printf("requested a high shelf filter\n"); - (*list)->ftype = HSH; - - }else{ - printf("requested an unsupported filter type\n"); - strcpy(statusmessage, ""); - strcpy(statusmessage, "filter type change failed"); - - } - }else{ - - /* convert str to double */ - fpval = strtod(value, &endptr); - - if ((errno == ERANGE && (fpval == HUGE_VAL)) - || (errno != 0 && fpval == 0)) { - perror("strtod"); - return -1; - } - - if (endptr == value) { - fprintf(stderr, "No digits were found\n"); - return -1; - } - - printf("(control,value) = (%s,%f)\n",control, fpval); - - if (*endptr != '\0') /* Not necessarily an error... */ - printf("Further characters after number: %s\n", endptr); - - /* check which control */ - if (strcmp(control, "fc") == 0) { - (*list)->fc = (smp_type)fpval; - strcpy(statusmessage, "cut off frequency change success"); - } - else if (strcmp(control, "g") == 0) { - (*list)->dBgain = (smp_type)fpval; - strcpy(statusmessage, "gain change success"); - } - else if (strcmp(control, "bw") == 0) { - (*list)->bw = (smp_type)fpval; - strcpy(statusmessage, "bandwith change success"); - - }else{ - printf("control parameter requested not found in list!\n"); - strcpy(statusmessage, "no control in list"); - return -1; - } - } - return 0; -} - -void * -start_jack_client(void *ptr) -{ - const char *client_name = "filter"; - const char *server_name = NULL; - jack_options_t options = (JackNoStartServer|JackUseExactName|JackSessionID); - jack_status_t status; - - /* open a client connection to the JACK server */ - client = jack_client_open (client_name, options, &status, server_name); - if (client == NULL) { - fprintf (stderr, "jack_client_open() failed, " - "status = 0x%2.0x\n", status); - if (status & JackServerFailed) { - fprintf (stderr, "Unable to connect to JACK server\n"); - } - exit (1); - } - if (status & JackServerStarted) { - fprintf (stderr, "JACK server started\n"); - } - if (status & JackNameNotUnique) { - client_name = jack_get_client_name(client); - fprintf (stderr, "unique name `%s' assigned\n", client_name); - } - - /* tell the JACK server to call `process()' whenever - there is work to be done. */ - jack_set_process_callback (client, process, 0); - - - /* tell the JACK server to call `jack_shutdown()' if - it ever shuts down, either entirely, or if it - just decides to stop calling us. */ - jack_on_shutdown (client, jack_shutdown, 0); - - - /* display the current sample rate. */ - printf ("engine sample rate: %" PRIu32 "\n", - jack_get_sample_rate (client)); - - - /* create two ports */ - - input_port = jack_port_register (client, "input", - JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); - - output_port = jack_port_register (client, "output", - JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); - - if ((input_port == NULL) || (output_port == NULL)) { - fprintf(stderr, "no more JACK ports available\n"); - exit (1); - } - - /* create and initialize the control list */ - ctrls = malloc(sizeof(control_list)); - - /* set with default values */ - ctrls->ftype = LPF; - ctrls->dBgain = 0; // dB value - ctrls->fc = 100; //Hz value - ctrls->fs = (smp_type)jack_get_sample_rate(client); - ctrls->bw = 0.25; //bandwidth (ocataves) - - /* Compute default biquad lpf */ - filter = compute_biquad(ctrls->ftype, - ctrls->dBgain, - ctrls->fc, - ctrls->fs, - ctrls->bw); - - printf("initial coefficients:\n"); - printf("b0=%f, b1=%f, b2=%f, a1=%f, a2=%f \n", filter->b0, filter->b1, filter->b2, filter->a1, filter->a2); - - - /* Tell the JACK server that we are ready to roll. Our - * process() callback will start running now. */ - - if (jack_activate (client)) { - fprintf (stderr, "cannot activate client"); - exit (1); - } - - - /* Connect the ports. You can't do this before the client is - * activated, because we can't make connections to clients - * that aren't running. Note the confusing (but necessary) - * orientation of the driver backend ports: playback ports are - * "input" to the backend, and capture ports are "output" from - * it. */ - - /********************************************************** - Automatic Port Connections! -> Not usually recommended! - *********************************************************** - const char **ports; - - ports = jack_get_ports (client, NULL, NULL, JackPortIsPhysical|JackPortIsOutput); - if (ports == NULL) { - fprintf(stderr, "no physical capture ports\n"); - exit (1); - } - - if (jack_connect (client, ports[0], jack_port_name (input_port))) { - fprintf (stderr, "cannot connect input ports\n"); - } - - free (ports); - - ports = jack_get_ports (client, NULL, NULL, - JackPortIsPhysical|JackPortIsInput); - - if (ports == NULL) { - fprintf(stderr, "no physical playback ports\n"); - exit (1); - } - - if (jack_connect (client, jack_port_name (output_port), ports[0])) { - fprintf (stderr, "cannot connect output ports\n"); - } - - free (ports); -*/ - /* keep running until stopped by the user */ - - sleep (-1); - - /* this is never reached, but if the program - had some other way to exit besides being killed, - they would be important to call. */ - - jack_client_close (client); - exit (0); -} - -/**************************************************** - * The process callback for this JACK application - * (i.e. the audio workhorse function) - * It is called by JACK at the appropriate times - * Optimize this function for time! -****************************************************/ -int -process (jack_nframes_t nframes, void *arg) -{ - /* consider passing the filter struct */ - //biquad *filter = arg[0]; - - sample_t *out = (sample_t *) jack_port_get_buffer (output_port, nframes); - sample_t *in = (sample_t *) jack_port_get_buffer (input_port, nframes); - smp_type outsample; - - for(int i = 0; i < nframes; i++) { - - /* compute the output sample */ - outsample = df1((smp_type)in[i], filter); - out[i] = (sample_t)outsample; - } - - - /* bypass switch */ - if( STATE == ON) { - memcpy (out, in, sizeof (sample_t) * nframes); - } - - //printf("%d = %f \n", i, out[i]); -// printf("%f \n", out[10]); - return 0; -} - -/** - * This is the shutdown callback for this JACK application. - * It is called by JACK if the server ever shuts down or - * decides to disconnect the client. - */ -void -jack_shutdown (void *arg) -{ - exit (1); -} - diff --git a/filterd.c b/filterd.c new file mode 100644 index 0000000..954331c --- /dev/null +++ b/filterd.c @@ -0,0 +1,456 @@ +#include "filterd.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAXEVENTS 10 + +#define CHECK(f,c,action) if((f) == -1) { perror((c)); action; } +#define FAIL(f,c) CHECK(f,c,exit(EXIT_FAILURE)); +#define STREQ(x,y) (strcmp((x),(y)) == 0) +#define UNUSED __attribute__((unused)) +#define LOCAL __thread + +typedef jack_default_audio_sample_t sample_t; + +/* Globals */ +static LOCAL jack_port_t *input_port; +static LOCAL jack_port_t *output_port; +static LOCAL jack_client_t *client; +static LOCAL biquad_t *filter; + +static LOCAL int epollfd; + +static filter_t ctrls = { + .type = FILTER_LOW_PASS, + .gain = 0, // dB value + .fc = 100, // Hz value + .bw = 0.25, // bandwidth (ocataves) +}; + +/* Prototypes */ +static int open_socket(void); +static void accept_conn(int sfd); +static void handle(int fd); +static int set_nonblocking(int fd); +static void listener(void); +static int parse_command(char *command, char *statusmessage); + +static void *jack(void *arg); +static int jack_process(jack_nframes_t nframes, void *); +static void jack_shutdown(void *); + +int +open_socket(void) +{ + struct sockaddr_un local; + int sfd; + + sfd = socket(AF_UNIX, SOCK_STREAM, 0); + FAIL(sfd, "socket"); + + local.sun_family = AF_UNIX; + strcpy(local.sun_path, socket_path); + unlink(local.sun_path); + + int ret = bind(sfd, (struct sockaddr *)&local, sizeof(local)); + FAIL(ret, "bind"); + + return sfd; +} + +void +accept_conn(int sfd) +{ + struct sockaddr_un remote; + socklen_t len; + int cfd = accept(sfd, (struct sockaddr *)&remote, &len); + FAIL(cfd, "accept"); + + printf("fclient: >> Socket Connected <<\n"); + + set_nonblocking(cfd); + struct epoll_event event = { + .data = { .fd = cfd }, + .events = EPOLLIN | EPOLLET + }; + + int ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, cfd, &event); + FAIL(ret, "epoll_ctl"); +} + +void +handle(int fd) +{ + cmd_t command; + + ssize_t ret = recv(fd, command, sizeof(command), 0); + FAIL(ret, "recv"); + + if (ret == 0) { + close(fd); + return; + } + + command[ret] = '\0'; + printf("COMMAND> %s\n", command); + + /* clear last status */ + cmd_t mstatus; + + /* parse the command */ + if (!parse_command(command, mstatus)) { + printf("command was parsed with status: %s\n", mstatus); + + /* Compute new biquad (callback) */ + /* TODO: lock */ + biquad_init(filter, &ctrls); + + printf("printing control list\n"); + printf("fc=%f, g=%f, bw=%f \n", ctrls.fc, ctrls.gain, ctrls.bw); + /* printf("printing new coefficients:\n"); */ + /* printf("b0=%f, b1=%f, b2=%f, a1=%f, a2=%f \n\n", filter->b0, filter->b1, filter->b2, filter->a1, filter->a2); */ + + /* transmit response */ + ret = send(fd, mstatus, ret, 0); + FAIL(ret, "send"); + } +} + +int +set_nonblocking(int fd) +{ + int ret = fcntl(fd, F_GETFL, 0); + CHECK(ret, "fcntl getfl", return -1); + + ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK); + CHECK(ret, "fcntl setfl", return -1); + + return 0; +} + +void +listener() +{ + epollfd = epoll_create1(0); + FAIL(epollfd, "epoll_create"); + + int sfd = open_socket(); + + int ret = listen(sfd, 1); + FAIL(ret, "listen"); + + ret = set_nonblocking(sfd); + + struct epoll_event event = { + .data = { .fd = sfd }, + .events = EPOLLIN | EPOLLET + }; + + ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, sfd, &event); + FAIL(ret, "epoll_ctl"); + + struct epoll_event events[MAXEVENTS]; + while (1) { + int n = epoll_wait(epollfd, events, MAXEVENTS, -1); + + for (int i = 0; i < n; i++) { + struct epoll_event ev = events[i]; + + /* error on the fd */ + if ((ev.events & EPOLLERR) || (ev.events & EPOLLHUP)) { + fprintf(stderr, "epoll error"); + close(ev.data.fd); + continue; + } + + if (ev.data.fd == sfd) { + accept_conn(sfd); + } else { + handle(ev.data.fd); + } + } + } +} + +/** + * This is the control message parser for this filter client. + * It is called by whenever a valid message requesting filter + * parameter changes is received by the messenger thread + * INPUTS: command string, controls list, status message string + * OUTPUTS: int + */ +int +parse_command(char *command, char *statusmessage) +{ + char *control, *value; + char *saveptr, *endptr; + char *type; + smp_t fpval; + + /* parse the command string into tokens */ + /* control part */ + control = strtok_r(command, "=", &saveptr); + if (control == NULL) { + printf("fclient: invalid format -> control=value\n"); + strcpy(statusmessage, "no control specified\n"); + return -1; + } + + /* value part */ + value = strtok_r(NULL, "\n", &saveptr); + if (value == NULL) { + printf("fclient: no value set!\n"); + strcpy(statusmessage, "no value specified"); + return -1; + } + + /* filter type change request */ + if (STREQ(control, "type")) { + type = value; + strcpy(statusmessage, "filter type change success"); + + /* determine the filter type */ + if (STREQ(type, "lpf")) { + printf("requested an lpf\n"); + ctrls.type = FILTER_LOW_PASS; + } else if (STREQ(type, "hpf")) { + printf("requested an hpf\n"); + ctrls.type = FILTER_HIGH_PASS; + } else if (STREQ(type, "bpf")) { + printf("requested an bpf\n"); + ctrls.type = FILTER_BAND_PASS; + } else if (STREQ(type, "notch")) { + printf("requested a notch filter\n"); + ctrls.type = FILTER_NOTCH; + } else if (STREQ(type, "peq")) { + printf("requested a peaking eq\n"); + ctrls.type = FILTER_PEAKING_BAND; + } else if (STREQ(type, "lsh")) { + printf("requested a low shelf filter\n"); + ctrls.type = FILTER_LOW_SHELF; + } else if (STREQ(type, "hsh")) { + printf("requested a high shelf filter\n"); + ctrls.type = FILTER_HIGH_SHELF; + } else { + printf("requested an unsupported filter type\n"); + strcpy(statusmessage, ""); + strcpy(statusmessage, "filter type change failed"); + } + } else { + /* convert str to double */ + fpval = strtod(value, &endptr); + + /* if ((errno == ERANGE && (fpval == HUGE_VAL)) */ + /* || (errno != 0 && fpval == 0)) { */ + /* perror("strtod"); */ + /* return -1; */ + /* } */ + + if (endptr == value) { + fprintf(stderr, "No digits were found\n"); + return -1; + } + + printf("(control,value) = (%s,%f)\n",control, fpval); + + if (*endptr != '\0') /* Not necessarily an error... */ + printf("Further characters after number: %s\n", endptr); + + /* check which control */ + if (STREQ(control, "fc")) { + ctrls.fc = fpval; + strcpy(statusmessage, "cut off frequency change success"); + } else if (STREQ(control, "g")) { + ctrls.gain = fpval; + strcpy(statusmessage, "gain change success"); + } else if (STREQ(control, "bw")) { + ctrls.bw = fpval; + strcpy(statusmessage, "bandwith change success"); + } else { + printf("control parameter requested not found in list!\n"); + strcpy(statusmessage, "no control in list"); + return -1; + } + } + return 0; +} + +/* JACK {{{ */ +void * +jack(void UNUSED *arg) +{ + const char *client_name = "filter"; + const char *server_name = NULL; + jack_options_t options = JackNoStartServer | JackUseExactName | JackSessionID; + jack_status_t status; + + /* open a client connection to the JACK server */ + client = jack_client_open(client_name, options, &status, server_name); + if (client == NULL) { + fprintf(stderr, "jack_client_open() failed, " + "status = 0x%2.0x\n", status); + if (status & JackServerFailed) { + fprintf (stderr, "Unable to connect to JACK server\n"); + } + exit(EXIT_FAILURE); + } + if (status & JackServerStarted) { + fprintf(stderr, "JACK server started\n"); + } + if (status & JackNameNotUnique) { + client_name = jack_get_client_name(client); + fprintf(stderr, "unique name `%s' assigned\n", client_name); + } + + jack_set_process_callback(client, jack_process, 0); + jack_on_shutdown(client, jack_shutdown, 0); + + /* display the current sample rate. */ + printf("engine sample rate: %" PRIu32 "\n", jack_get_sample_rate (client)); + + /* create two ports */ + input_port = jack_port_register(client, "input", + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput, 0); + + output_port = jack_port_register(client, "output", + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + + if ((input_port == NULL) || (output_port == NULL)) { + fprintf(stderr, "no more JACK ports available\n"); + exit (1); + } + + ctrls.fs = jack_get_sample_rate(client), + filter = biquad_new(&ctrls); + + /* printf("initial coefficients:\n"); */ + /* printf("b0=%f, b1=%f, b2=%f, a1=%f, a2=%f \n", filter->b0, filter->b1, filter->b2, filter->a1, filter->a2); */ + + if (jack_activate(client)) { + fprintf(stderr, "cannot activate client"); + exit(EXIT_FAILURE); + } + + + /* Connect the ports. You can't do this before the client is + * activated, because we can't make connections to clients + * that aren't running. Note the confusing (but necessary) + * orientation of the driver backend ports: playback ports are + * "input" to the backend, and capture ports are "output" from + * it. */ + + /********************************************************** + Automatic Port Connections! -> Not usually recommended! + *********************************************************** + const char **ports; + + ports = jack_get_ports (client, NULL, NULL, JackPortIsPhysical|JackPortIsOutput); + if (ports == NULL) { + fprintf(stderr, "no physical capture ports\n"); + exit (1); + } + + if (jack_connect (client, ports[0], jack_port_name (input_port))) { + fprintf (stderr, "cannot connect input ports\n"); + } + + free (ports); + + ports = jack_get_ports (client, NULL, NULL, + JackPortIsPhysical|JackPortIsInput); + + if (ports == NULL) { + fprintf(stderr, "no physical playback ports\n"); + exit (1); + } + + if (jack_connect (client, jack_port_name (output_port), ports[0])) { + fprintf (stderr, "cannot connect output ports\n"); + } + + free (ports); +*/ + /* keep running until stopped by the user */ + + sleep(-1); + + /* This code should never run */ + jack_client_close(client); + exit(EXIT_FAILURE); +} + +/**************************************************** + * The process callback for this JACK application + * (i.e. the audio workhorse function) + * It is called by JACK at the appropriate times + * Optimize this function for time! +****************************************************/ +int +jack_process(jack_nframes_t nframes, void UNUSED *arg) +{ + /* consider passing the filter struct */ + //biquad *filter = arg[0]; + + sample_t *out = (sample_t *)jack_port_get_buffer(output_port, nframes); + sample_t *in = (sample_t *)jack_port_get_buffer(input_port, nframes); + smp_t outsample; + + for (size_t i = 0; i < nframes; i++) { + /* compute the output sample */ + outsample = df1(in[i], filter); + out[i] = (sample_t)outsample; + } + + + /* bypass switch */ + /* if ( STATE == ON) { */ + /* memcpy (out, in, sizeof(sample_t) * nframes); */ + /* } */ + + //printf("%d = %f \n", i, out[i]); +// printf("%f \n", out[10]); + return 0; +} + +/** + * This is the shutdown callback for this JACK application. + * It is called by JACK if the server ever shuts down or + * decides to disconnect the client. + */ +void +jack_shutdown(void UNUSED *arg) +{ + exit(EXIT_FAILURE); +} +/* }}} */ + +int +main(void) +{ + pthread_t audio; + + /* create the audio engine thread */ + if ((pthread_create(&audio, NULL, jack, NULL))) { + perror("pthread_create"); + exit(EXIT_FAILURE); + } + + listener(); + + pthread_join(audio, NULL); + return 0; +} + +// vim: et:sts=4:sw=4:cino=(0 diff --git a/filterd.h b/filterd.h new file mode 100644 index 0000000..369bc09 --- /dev/null +++ b/filterd.h @@ -0,0 +1,9 @@ +#ifndef FILTERD_H +#define FILTERD_H + +const char *socket_path = "filter"; + +#define BUFF_SIZE (1 << 6) +typedef char cmd_t[BUFF_SIZE]; + +#endif diff --git a/filterd.sh b/filterd.sh new file mode 100755 index 0000000..a2bb273 --- /dev/null +++ b/filterd.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +if [[ ! -f ./filterd ]]; then + echo "filterd not found" + exit 1 +fi + +export LD_LIBRARY_PATH="$PWD/lib:$LD_LIBRARY_PATH" +exec ./filterd diff --git a/lib b/lib new file mode 160000 index 0000000..430714c --- /dev/null +++ b/lib @@ -0,0 +1 @@ +Subproject commit 430714c2c5ac3bb1ed9b818b210122b49765de14