diff --git a/makefile b/makefile index ffc0c4b..9400f69 100644 --- a/makefile +++ b/makefile @@ -1,8 +1,8 @@ -SRC = src/fft.c src/utils_curses.c src/music_visualizer.c #src/alsa_fifo.c +SRC = src/fft.c src/utils_curses.c src/music_visualizer.c src/utils_mpd.c src/settings.h#src/alsa_fifo.c ALL = $(SRC) LOCPATH = ./bin INSTALLPATH = /usr/local -LFLAGS = -lm -lcurses -ldl -ltinfo #-lasound +LFLAGS = -lm -lcurses -ldl -lmpdclient #-ltinfo #-lasound DEBFLAGS = -g -Wall NAME = mvc diff --git a/src/fft.c b/src/fft.c index fa7a866..3e63622 100755 --- a/src/fft.c +++ b/src/fft.c @@ -23,18 +23,17 @@ cplx omegaN, omega; cplx *evenA, *oddA, *transformedA; int i; - + /*termination*/ if(len == 1){ return compArray; } - + omega = 1; omegaN = cexp(2*PI*I/len); //the fourier coefficient - - evenA = _fast_ft(split_array(compArray, len, EVEN), len/2); + evenA = _fast_ft(split_array(compArray, len, EVEN), len/2); oddA = _fast_ft(split_array(compArray, len, ODD), len/2); - + /*the final array*/ transformedA = malloc(len*sizeof(cplx)); @@ -65,7 +64,7 @@ { double sq; unsigned int res; - + /*compute amplitude*/ sq = sqrt(pow(creal(c), 2) + pow(cimag(c), 2)); res = round(20*log10(sq)); // dB scale @@ -85,14 +84,14 @@ exit(EXIT_FAILURE); } inputComponents = (cplx*)malloc((inLen)*sizeof(cplx)); - + for(i=1; i +#include // status #include // exit #include // read #include // open #include // open #include // uint16_t #include // strcmp +#include + #include "fft.h" // fast fourier transform #include "utils_curses.h" #include "alsa_fifo.h" +#include "utils_mpd.h" #define MPD_FIFO "/tmp/mpd.fifo" /*FREQ 44100*/ @@ -16,11 +20,26 @@ static int maxR; static int maxC; +static STATUS* status; + +void +get_mpd_status() +{ + signal(SIGALRM, SIG_IGN); + struct mpd_connection* session = open_connection(); + status = get_current_status(session); + close_connection(session); + signal(SIGALRM, get_mpd_status); + alarm(1); +} void process_mpd (int fifo) { uint16_t buf[N_SAMPLES]; unsigned int *fftBuf, *fftAvg; int correction; //curses + struct mpd_connection* session = open_connection(); + get_mpd_status(); + alarm(1); while(read(fifo, (uint16_t*)buf, 2*N_SAMPLES) != 0){ // close on keypress 'q' @@ -40,34 +59,42 @@ erase(); // correction can be used to exclude certain frequencies - // not advised nor customary - correction = 0; + correction = 2; int i; - for(i=correction; i maxR || fftAvg[i] < 0){ - fftAvg[i] = 1; - } - // print the column fftAvg[i] - print_col(i-correction, fftAvg[i], maxR); - } + for(i=correction; i maxR || fftAvg[i] < 0){ + fftAvg[i] = 1; + } + // print the column fftAvg[i] + print_col(i-correction, fftAvg[i], maxR, maxC); + } + + // print current mpd status + // if unable, just print error + if(!(status)){ + mvprintw(0,0, "Unable to retrieve status from mpd."); + } else { + print_mpd_status(status,maxC,maxR/2+maxR/6); + } + // refresh the screen, free the allocated buffer refresh(); free(fftAvg); } + close_connection(session); } void -process_alsa (int fifo) -{} - +process_alsa() {} int main(int argc, char *argv[]) { int fifo; WINDOW *mainwin; + import_var_from_settings(); while((fifo = open(MPD_FIFO, O_RDONLY)) == -1); diff --git a/src/settings.h b/src/settings.h new file mode 100644 index 0000000..b6f5484 --- /dev/null +++ b/src/settings.h @@ -0,0 +1,4 @@ +static char defHost[] = "192.168.1.131"; // the ip of mpd +static unsigned int defPort = 6600;// the port as defined in mpd configs +static char defDBlocation[] = "/home/fra/.mpd/mpd.db"; // the location of the database to be used with the custom search +#define TIMEOUT 1000 // timeout for connection, define at your own risk. Good values range between 1000 and 5000 diff --git a/src/utils_curses.c b/src/utils_curses.c index ee8e2e0..171672b 100755 --- a/src/utils_curses.c +++ b/src/utils_curses.c @@ -1,5 +1,8 @@ #include "utils_curses.h" +#include "utils_mpd.h" +#include "string.h" +#include // initialize color pairs // used in print_col void @@ -36,21 +39,118 @@ // while doing so, perform a rotation of the color // modify here to change color output void -print_col(int col, int l, int maxR) +print_col(int col, int l, const int maxR, const int maxC) { int row, changeCol; int color = 5; for(row=maxR; row>=0; row--){ - color++; - changeCol = color % 6; - if ((row > l && row < maxR-l/3)) { // center of the screen - color_set(changeCol, NULL); - mvaddch(row, col, SHARP); - } else { - color_set(changeCol, NULL); - mvaddch(row, col, HEAVY); - } + if (col < maxC) { + color++; + changeCol = color % 6; + if ((row > l && row < maxR-l/3)) { // center of the screen + color_set(changeCol, NULL); + mvaddch(row, col, SHARP); + } else { + color_set(changeCol, NULL); + mvaddch(row, col, HEAVY); + } + } } } +void +print_row(int row, int l, const int maxR, const int maxC) +{ + int col, changeCol; + int color = 5; + + for(col=maxC; col>=0; col--){ + if (row < maxR) { + color++; + changeCol = color % 6; + if ((col > l && col < maxR-l/3)) { // center of the screen + color_set(changeCol, NULL); + mvaddch(row, col, SHARP); + } else { + color_set(changeCol, NULL); + mvaddch(col, col, HEAVY); + } + } + } +} + +/* prints a STATUS structure to stdout */ +void +print_mpd_status(STATUS* status, const int maxC, const int row) +{ + SONG* song = NULL; + bool CRflag = false, nullFlag = false; + int i,j; + + if(status == NULL){ + return; + } + + song = status->song; + int maxlen; + int center; + if (song->artist == NULL) { + maxlen = strlen(song->uri) + 3; + center = maxC/2 - maxlen/2; + } else { + maxlen = strlen(song->artist) + strlen(song->title) + 5; + if (strlen(status->state)+18 > maxlen){ + maxlen=strlen(status->state)+18; + } + maxlen = strlen(song->artist) + strlen(song->title) + 5; + center = maxC/2 - (maxlen)/2; + } + + color_set(1, NULL); + for (i=0; iartist != NULL){ + mvprintw(row+1, center+1, "%s - ", song->artist); + nullFlag = true; + } + if(song->title != NULL){ + mvprintw(row+1, center+1+strlen(song->artist) + 3, "%s", song->title); + nullFlag = true; + } + if(song->album != NULL){ + mvprintw(row+2, center+1, "%s", song->album); + nullFlag = true; + } else { + nullFlag = false; + // even if only album field is missing, the filesystem name will be printed + } + if (nullFlag == false) { + // title, artist, album fields are missing, will print filesystem name + mvprintw (row+1, center+1, "%s", song->uri); + mvprintw (row+2, center+6, "---- No metadata avaiable ----"); + } + if(status->state != NULL){ + mvprintw(row+3, center+1, "(%s)", status->state); + } + mvprintw(row+3, center+maxlen-10, "%d:%.2d/%d:%.2d ", status->elapsedTime_min, status->elapsedTime_sec, song->duration_min, song->duration_sec); + } + for (i=0; i +#include "utils_mpd.h" + // used while printing #define SHARP 'x' #define HEAVY '.' void init_color_pairs(); WINDOW* curses_init(); -void print_col(int col, int length, int maxR); +void print_col(int col, int length, const int maxR, const int maxC); +void print_row(int row, int length, const int maxR, const int maxC); +void print_mpd_status(STATUS* status, const int maxC, const int row); diff --git a/src/utils_mpd.c b/src/utils_mpd.c new file mode 100644 index 0000000..7c385d9 --- /dev/null +++ b/src/utils_mpd.c @@ -0,0 +1,439 @@ +#include "utils_mpd.h" +#include // libmpdclient +#include // fprintf +#include // true false +#include // strcmp +#include "settings.h" + +/* This macro is a first step towards a sort of try except macro system + * the objective of this macro is to have y = f(x) non NULL + * but if it fails it trashes x and generates a new X using newX + * and starts again + */ +/* it is not used anymore since now open_connection is called every time it is needed */ +#define TRY(x, delX, newX, y, f) do {\ + if ((y = f (x)) == NULL) {\ + delX (x); doNotClose = false;\ + }\ + } while (y == NULL && (x = newX () ) != NULL) + +char *_host; +char *_DBlocation; +unsigned int _port; + +void +import_var_from_settings () +{ + // import variables from settings.h to be passed to other functions + _host = strdup (defHost); + _port = defPort; + _DBlocation = strdup (defDBlocation); +} + +void +free_var_from_settings () +{ + free (_host); + free (_DBlocation); +} + +bool change_host (char **args, int n) +{ + if (n == 0) { + STANDARD_USAGE_ERROR ("port"); + return false; + } + _host = strdup (args[0]); + return true; +} +bool change_port (char **args, int n) +{ + if (n == 0) { + STANDARD_USAGE_ERROR ("port"); + return false; + } + _port = atoi (args[0]); + return true; +} + +/* opens a new connection to the mpd server + * arguments: host, port (timeout defined) + * returns a pointer to the connection structure if successful + * returns a null pointer if error occours + */ +struct mpd_connection* +open_connection() +{ + struct mpd_connection *newConn = NULL; + enum mpd_error connErr; + + newConn = mpd_connection_new(_host, _port, TIMEOUT); + connErr = mpd_connection_get_error(newConn); + + /*if error occours*/ + if(connErr != MPD_ERROR_SUCCESS){ + + fprintf(stderr, "MPD connection error: "); + + /*error codes are defined in libmpdclient, file error.h*/ + switch(connErr){ + case MPD_ERROR_OOM: + fprintf(stderr, "Out of memory\n"); + break; + + case MPD_ERROR_ARGUMENT: + fprintf(stderr, "Unrecognized argument\n"); + break; + + case MPD_ERROR_STATE: + fprintf(stderr, "State\n"); + break; + + case MPD_ERROR_TIMEOUT: + fprintf(stderr, "Timeout was reached\n"); + break; + + case MPD_ERROR_SYSTEM: + fprintf(stderr, "System error\n"); + break; + + case MPD_ERROR_RESOLVER: + fprintf(stderr, "Unknown host\n"); + break; + + case MPD_ERROR_MALFORMED: + fprintf(stderr, "Malformed response received from MPD\n"); + break; + + case MPD_ERROR_CLOSED: + fprintf(stderr, "Connection closed\n"); + break; + + case MPD_ERROR_SERVER: + fprintf(stderr, "Server error\n"); + break; + default: + break; + } + + exit (69); // can't open the connection, mpd is unavailable + } + + return newConn; +} + +/* closes connection and frees all memory + * arguments: connection (struct mpd_connection *) + */ +void +close_connection(struct mpd_connection *mpdConnection) +{ + if (mpdConnection != NULL) { + mpd_connection_free(mpdConnection); + } +} + +/* converts a (struct mpd_song*) defined in libmpdclient + * into a SONG* structure defined in util2.h + * returns NULL if error + * returns a SONG* element if successful + */ +SONG* +parse_mpd_song(struct mpd_song* mpdSong) +{ + SONG *song = NULL; + int duration; + const char *tmp; + + song = (SONG*)malloc(sizeof(SONG)); + if(song == NULL){ + fprintf(stderr, "Memory allocation error.\n"); + return NULL; + } + + // get every field of mpdsong and allocate it on kpd song struct + tmp = mpd_song_get_tag(mpdSong, MPD_TAG_TITLE, 0); + if (tmp != NULL) { + song->title = strdup (tmp); + } else { + song->title = NULL; + } + tmp = mpd_song_get_tag(mpdSong, MPD_TAG_ARTIST, 0); + if (tmp != NULL) { + song->artist = strdup (tmp); + } else { + song->artist = NULL; + } + tmp = mpd_song_get_tag(mpdSong, MPD_TAG_ALBUM, 0); + if (tmp != NULL) { + song->album = strdup (tmp); + } else { + song->album = NULL; + } + tmp = mpd_song_get_uri (mpdSong); + if (tmp != NULL) { + song->uri = strdup (tmp); + } else { + song->uri = NULL; + } + duration = mpd_song_get_duration(mpdSong); + song->duration_min = duration/60; + song->duration_sec = duration%60; + song->position = mpd_song_get_pos(mpdSong); + + mpd_song_free (mpdSong); + return song; +} + +/* function to free song_struct */ +void free_song_st (SONG *s) +{ + if (s!=NULL) { + if (s->title != NULL) { + free (s->title); + } + if (s->artist != NULL) { + free (s->artist); + } + if (s->album != NULL) { + free (s->album); + } + if (s->uri != NULL) { + free (s->uri); + } + free (s); + } +} + +/* function to free status_struct */ +void free_status_st (STATUS *s) +{ + if (s != NULL) { + free (s->state); + free_song_st (s->song); + free (s); + } +} + +/* queries the server for the current song, inserts it into a structure + * arguments: Connection + * returns a NULL structure if no current song / error, + * returns a pointer to a SONG structure if successful + */ + +SONG* +get_current_song() +{ + struct mpd_connection *mpdConnection = NULL; + struct mpd_song* mpdSong = NULL; + + mpdConnection = open_connection (); + mpdSong = mpd_run_current_song(mpdConnection); + close_connection (mpdConnection); + /*TRY (mpdConnection, close_connection, open_connection, mpdSong, mpd_run_current_song);*/ + if(mpdSong == NULL){ + return NULL; + } + return parse_mpd_song(mpdSong); +} + +/* queries the server for the current state (play, pause, stop, unknown) + * arguments: status (struct mpd_status*) + * returns NULL if error, + * returns a pointer to a string with the status if successful + */ +static char* +get_current_state(struct mpd_status* mpdStatus) +{ + int mpdState; + char *state; + mpdState = mpd_status_get_state(mpdStatus); + if(!mpdState){ + return NULL; + } + + switch(mpdState){ + case MPD_STATE_STOP: + state = strdup ("stop"); + break; + case MPD_STATE_PLAY: + state = strdup ("play"); + break; + case MPD_STATE_PAUSE: + state = strdup ("pause"); + break; + default: + state = NULL; + break; + } + return state; +} + +/* queries the server for the current status structure, + * parses it and inserts it into another structure + * arguments: Connection + * returns NULL if error, + * returns a pointer to a STATUS structure if successful + */ +STATUS* +get_current_status() +{ + struct mpd_connection *mpdConnection = NULL; + struct mpd_status* mpdStatus = NULL; + STATUS *status = NULL; + int eltime; + + mpdConnection = open_connection (); + mpdStatus = mpd_run_status(mpdConnection); + close_connection (mpdConnection); + /*TRY (mpdConnection, close_conneccion, open_connection, mpdStatus, mpd_run_status);*/ + + if(mpdStatus == NULL){ + fprintf(stderr, "Unable to retrieve status. Connection error.\n"); + return NULL; + } + status = (STATUS*)malloc(sizeof(STATUS)); + if(status == NULL){ + fprintf(stderr, "Memory allocation error.\n"); + return NULL; + } + status->random = mpd_status_get_random(mpdStatus); + status->repeat = mpd_status_get_repeat(mpdStatus); + status->single = mpd_status_get_single(mpdStatus); + status->consume = mpd_status_get_consume(mpdStatus); + status->update = mpd_status_get_update_id (mpdStatus); + status->crossfade = mpd_status_get_crossfade(mpdStatus); + status->song = get_current_song(); + status->state = get_current_state(mpdStatus); + eltime = (float)mpd_status_get_elapsed_time(mpdStatus); + status->elapsedTime_min = eltime/60; + status->elapsedTime_sec = eltime%60; + status->queueLenght = mpd_status_get_queue_length(mpdStatus); + + mpd_status_free (mpdStatus); + return status; +} + +/* add a new element to the playlist queue + * returns 1 if error + * returns 0 if successful + * */ +bool +enqueue(QUEUE *q, SONG *s) +{ + QUEUE *t; + if((t = (QUEUE*)malloc(sizeof(QUEUE))) == NULL){ + STANDARD_USAGE_ERROR("calloc"); + return false; + } + t->song = s; + if(q->next == NULL){ + q->next = t; + q->next->next = t; + } else { + t->next = q->next->next; + q->next->next = t; + q->next = t; + } + return true; +} + +/* delete an element from the queue and save the linked song + * returns NULL if q empty + * returns SONG* element if successful + */ +SONG* +dequeue(QUEUE* q) +{ + QUEUE *t; + SONG* song = NULL; + if(q->next == NULL){ + return NULL; + } + if(q->next != q->next->next){ + t = q->next->next; + q->next->next = t->next; + song = t->song; + free(t); + } else { + song = q->next->song; + free(q->next); + q->next = NULL; + } + return song; +} + +void destroy_queue (QUEUE *q) +{ + if (q != NULL) { + destroy_queue (q->next); + /*free_song_st (q->song);*/ + free (q); + } +} + +/* receives the songs in the playlist, one by one + * after the get_current_playlist function + * returns NULL if error or empty playlist + * returns a queue of songs if successful + */ +static QUEUE* +retrieve_songs(QUEUE *q, struct mpd_connection *mpdConnection) +{ + SONG* song = NULL; + struct mpd_song* mpdSong = NULL; + + if((mpdSong = mpd_recv_song(mpdConnection)) == NULL){ + return q; + } + + song = parse_mpd_song(mpdSong); + if(enqueue(q, song) == 0){ + return NULL; + } + + retrieve_songs(q, mpdConnection); + return q; +} + +/* sends to the server the request for a playlist queue, + * calls retrieve_songs and builds q + * returns NULL if error + * returns a playlist queue if successful + */ +QUEUE* +get_current_playlist() +{ + QUEUE *q = NULL; + struct mpd_connection *mpdConnection = NULL; + + mpdConnection = open_connection (); + mpd_send_list_queue_meta(mpdConnection); + + if((q = (QUEUE*)malloc(sizeof(QUEUE))) == NULL){ + STANDARD_USAGE_ERROR("calloc"); + return NULL; + } + q->next = NULL; + retrieve_songs(q, mpdConnection); + close_connection (mpdConnection); + + return q; +} + +int +count_playlist_elements (QUEUE *q) +{ + QUEUE *tmp = q; + int i = 0; + + if (q == NULL) { + return 0; + } else { + while (tmp != NULL) { + i++; + tmp = tmp->next; + } + return i; + } +} diff --git a/src/utils_mpd.h b/src/utils_mpd.h new file mode 100644 index 0000000..f88b8b0 --- /dev/null +++ b/src/utils_mpd.h @@ -0,0 +1,99 @@ +#ifndef KPD_UTIL_H +#define KPD_UTIL_H +#include +#include +#include +#include +#include + +#define MAXFEAT 4 +#define STANDARD_USAGE_ERROR(commandname) fprintf (stderr,"kpd: incorrect usage of %s\nTry 'kpd --help' for more information.\n", commandname); + +typedef struct so { + char *title; + char *artist; + char *album; + char *uri; // name on the filesystem + int duration_min; + int duration_sec; + unsigned int position; +} SONG; + +typedef struct st { + char *state; + bool random; + bool consume; + bool repeat; + bool single; + bool crossfade; + bool update; + int elapsedTime_min; + int elapsedTime_sec; + int queueLenght; + SONG* song; +} STATUS; + + +typedef struct q { + SONG* song; + struct q *next; +} QUEUE; + +extern char *_host; +extern char *_DBlocation; +extern unsigned int _port; + +bool change_port (); +bool change_host (); +void import_var_from_settings (); +void free_var_from_settings (); + +struct mpd_connection *open_connection(); +void close_connection(struct mpd_connection *mpdConnection); +SONG* get_current_song(); +void free_song_st (SONG *s); +void free_status_st (STATUS *s); +STATUS* get_current_status(); +void print_current_status(STATUS* status); +QUEUE* get_current_playlist(); +int count_playlist_elements (QUEUE *q); +bool enqueue(QUEUE* q, SONG* s); +SONG* dequeue(QUEUE* q); +void destroy_queue (QUEUE *q); +void print_current_playlist(QUEUE *q); +bool list (char **argv, int n); + +bool play (char **args, int n); +//bool pause (char **args, int n); +bool next ( char **args, int n); +bool previous (char **args, int n); +bool stop(); +//bool clear(); +bool delete_song( int pos); +int convert_to_int(char *arg); +bool delete( char **args, int n); +bool delete_range( char **args, int n); +int compare_pos(const void *pos1, const void *pos2); +bool random_kpd( char **args, int n); +bool consume( char **args, int n); +bool single( char **args, int n); +bool repeat( char **args, int n); +bool seek( char **args, int n); +bool forward(char **args, int n); +bool backward(char **args, int n); +bool swap( char **args, int n); +//bool move( char **args, int n); +bool update( char **args, int n); +bool shuffle( char **args, int n); +bool shuffle_range( char **args, int n); +bool output_enable( char **args, int n); + +bool search_util ( char **args, int n); +void destroy_search_results (); +void print_search_results (char **results, int size); +bool filter_helper (char **args, int n); +bool vfilter_helper (char **args, int n); +bool add( char **args, int n); +bool print_full_names (char **args, int n); + +#endif