Index: audio_helper.c =================================================================== --- audio_helper.c (revision 0) +++ audio_helper.c (revision 10) @@ -0,0 +1,43 @@ +#include "audio_helper.h" + +#include "audio.h" + +int sample_size(long sfmt) +{ + long fmt = sfmt & SFMT_MASK_FORMAT; + switch(fmt) + { + case SFMT_U8: + case SFMT_S8: + return 1; + case SFMT_U16: + case SFMT_S16: + return 2; + case SFMT_U32: + case SFMT_S32: + return 4; + case SFMT_FLOAT: + return 2; + default: + return -1; + } +} + +void swap_endianess_32(int32_t *buf, size_t size) +{ + size_t i; + for(i=0; i +#endif + +#include + +#define swap_32bit_endianess(i32) \ + ( ((i32&0x000000FF)<<24) | ((i32&0x0000FF00)<<8)| \ + ((i32&0x00FF0000)>>8) | ((i32&0xFF000000)>>24) ) + +#define swap_16bit_endianess(i16) \ + ( ((i16&0x00FF)<<8) | ((i16&0xFF00)>>8) ) + +void swap_endianess_32(int32_t *buf, size_t size); +void swap_endianess_16(int16_t *buf, size_t size); + +int sample_size(long sfmt); + +#endif Index: README_equalizer =================================================================== --- README_equalizer (revision 0) +++ README_equalizer (revision 10) @@ -0,0 +1,164 @@ +Preamble +--- +This document is meant to give you an overview on the idea of having a +parametric equalizer for sound enhancement and how you can create your +own presets. Also the interaction with the equalizer in MOC is +described. +I would like to improve this document to make it more usable; so if you +have any comments and/or ideas feel free to contact me. + +- Hendrik Iben (hibentzi(dot)de) + +Content +--- +0. Document History +1. Motivation +2. Usage +3. Preset Format +4. Creating Presets +5. TODO +6. References + + +0. Document History +--- +07.09.2008 - Initial version + + +1. Nuts and Bolts / Motivation for implementing the equalizer +--- +The equalizer is an implementation of a biquadratic peaking equalizer +filter looked up from the Audio EQ Cookbook[1]. +It happens to be a parametric equalizer and this means that different to +other equalizer implementations the number of bands* is not fixed. +When I started the idea of implementing the equalizer I looked around in +the source of other audio playback software and found that a lot of them +are recycling the code used by the famous XMMS[2] audio player. I would +have liked to also recycle the code but I decided against it for two +reasons: +The first reason is that there is almost no documentation on the +algorithm used. Maybe the signal processing folks have fun finding out +what makes this thing work but I was totally lost. So I decided that I +wanted to *know* what I am doing if I do it. +As for the second reason, the code used by XMMS is totally optimized to +integer arithmetics. There is no problem with this in general but I had +the goal of implementing something as accurate as I could and I wanted +to use floating point arithmetics. +So I am no signals processing guy but I have - I think - a solid +understanding of the matter. I sat down and started to read about +equalizing, audio processing and signal theory in general. After some +time I found a mathematical description and a C implementation of +biquadratic filters in the Audio Cookbook. I made an implementation of +the XMMS equalizer and the biquadratic filter using Octave[3] to compare +the outcome of both filters. I was a bit surprised how different filters +can be but in the end succeeded (?) in finding a quite good biquadratic +filter set that would produce results not unlike the XMMS equalizer. +Although I did not use the XMMS-code I think that people will be more +happy to accept this equalizer if they can use their presets with it. +There is some conversion needed, but it's a straightforward process. I +converted all presets provided by XMMS into presets for this mixer. They +should be available at [4]. + + +2. Using the equalizer +--- +The default keys for the equalizer are: +'e' - Refresh equalizer +'E' - Toggle equalizer (on/off) +'k' - Select next preset +'K' - Select previous preset + +Each of these actions results in a message displayed in the message area. +This message will be overriden by the next action. + + +3. Preset format +--- +Presets for the equalizer are to be placed in a directory called +'eqsets' in MOCs home directory (e.g. $HOME/.moc/eqsets). There is no +convention for the filename, but the filename will serve as the name in +the selection process. +File format in pseudo EBNF: +EQSET +(( )|(0 ))* + +CF: Center frequency (sane values are from ~20 to ~20000) +BW: Bandwith in Octaves. This defines how fast the bands influence + vanishes over the frequencies +PREAMP: Specifies an amplification factor applied before equalizing + +So a valid equalizer set would be: + # this is a comment + EQSET + # amplify audio by 1.4dB + 0 1.4 + # damp frequencies at 100Hz by -4dB, filter bandwidth 1.5 octaves + 100 1.5 -4 + # amplifiy frequencies at 4000Hz by 2dB, filter bandwidth 1.5 octaves + 4000 1.5 2 + +There is no order to stick to when specifying frequencies. + +* A band is a chosen center frequency where a filter has most impact. + If you look at WinAmp / XMMS / Beep Media Player you will find that + they settled on a common set of 10 bands. + + +4. Creating your own presets +--- +For a start you should have a look at the converted presets[4]. The +bandwidths used in the conversion have been extracted by taking a look +at the filters signal response (implementation and analysis in octave). +I tried to do this as accurate as possible but I don't know if I made a +mistake. They sound correct though... :-) +You might note that there is never a positive amplification factor in +the presets although there are in the original preset. The reason for +this is that I used the maximum amplification in the preset as zero +amplification and adjusted the other values accordingly. +In general, when creating a preset get used to the following idea: +Do not amplify the frequencies you want but damp those that are of no +interest. This has the same effect but avoids clipping and this +equalizer type seems to be very prone to clipping... +Also be very careful with pre-amplifying the audio for the same reason. +With this said, the next confusing thing is the bandwidth definition. +Every band needs a defined bandwidth in octaves where the bandwidth +defines where the filters effect has been reduced by 3dB*. This means +that if you define a band at 1000Hz with a bandwidth of 1.5 octaves and +an amplification of -10dB, at 353.6Hz* and at 2828.4Hz the amplification +will be reduced to -7dB. +If unsure, stay in between 1.0 and 2.0. Just keep in mind that if two +bands overlap you might get an undesired amplification. +When designing presets, just save the preset and select it in MOC. After +each change press the refresh key (default 'e'). This will re-create the +equalizer reflecting your changes. +If your preset is not found, have a look at the output of MOC's server +thread. Parsing errors are emitted there. + +* 3dB is commonly used for bandwidth. -3dB equals about 70.7% of + original amplification. +* 353.6 =~ 1000*(2^-1.5), 2828.4 =~ 1000*(2^1.5) + + +5. TODO +--- +- The equalizer is currently not optimized in any way. +- It converts all sound data into floating point values to perform the + equalization and converts them back afterwards. + A better approach would be to either provide algorithms for integer + equalizing or to leave the audio data in floating point format. +- There is no sorting for the presets, their order is defined by reading + the directory content. +- Maybe it would be nice to add a name to the preset different than the + file. + + +6. References +--- +[1] Cookbook formulae for audio EQ biquad filter coefficients + http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt +[2] X Multimedia System + http://xmms.org +[3] GNU Octave + http://www.gnu.org/software/octave/ +[4] Converted WinAmp / XMMS Equalizer sets + http://www.informatik.uni-bremen.de/~hiben/moc/eqsets.tar.gz Index: equalizer.c =================================================================== --- equalizer.c (revision 0) +++ equalizer.c (revision 10) @@ -0,0 +1,1102 @@ +/* + * MOC - music on console + * Copyright (C) 2004-2008 Damian Pietras + * + * Equalizer-extension Copyright (C) 2008 Hendrik Iben + * Provides a parametric biquadratic equalizer. + * + * This code is based on the 'Cookbook formulae for audio EQ biquad filter + * coefficients' by Robert Bristow-Johnson. + * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif +#define _GNU_SOURCE +#include +#include +#ifdef HAVE_STDINT_H +# include +#endif +#ifdef HAVE_LIMITS_H +# include +#endif +#ifdef HAVE_INTTYPES_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "audio.h" +#include "audio_helper.h" +#include "options.h" +#include "common.h" +#include "log.h" + +#include "equalizer.h" + +/* config processing */ +int read_setup(char *name, char *desc, t_eq_setup **sp); +void equalizer_adjust_preamp(); +void equalizer_read_config(); +void equalizer_write_config(); + +/* biquad application */ +/* inline void biquad(float *src, float *dst, int len, t_biquad *b); */ +inline void apply_biquads(float *src, float *dst, int channels, int len, t_biquad *b, int blen); + +/* biquad filter creation */ +t_biquad *mk_biquad(float dbgain, float cf, float srate, float bw, t_biquad *b); + +/* equalizer list processing */ +t_eq_set_list *append_eq_set(t_eq_set *eqs, t_eq_set_list *l); +void clear_eq_set(t_eq_set_list *l); + +/* sound processing */ +void equ_process_buffer_u8(uint8_t *buf, size_t size); +void equ_process_buffer_s8(int8_t *buf, size_t size); +void equ_process_buffer_u16(uint16_t *buf, size_t size); +void equ_process_buffer_s16(int16_t *buf, size_t size); +void equ_process_buffer_u32(uint32_t *buf, size_t size); +void equ_process_buffer_s32(int32_t *buf, size_t size); +void equ_process_buffer_float(float *buf, size_t size); + +/* static global variables */ +static t_eq_set_list equ_list, *current_equ; + +static int sample_rate, equ_active, equ_channels; + +static float mixin_rate, r_mixin_rate; +static float preamp, preampf; + +static char *eqsetdir; + +static char *config_preset_name; + +/* public functions */ +int equalizer_is_active() +{ + return equ_active?1:0; +} + +int equalizer_set_active(int active) +{ + return equ_active = active?1:0; +} + +char *equalizer_current_eqname() +{ + if(equ_active && current_equ && current_equ->set) + { + return xstrdup(current_equ->set->name); + } + + return xstrdup("off"); +} + +void equalizer_next() +{ + if(current_equ) + { + if(current_equ->next) + { + current_equ = current_equ->next; + } + else + { + current_equ = &equ_list; + } + + if(!current_equ->set && !(current_equ == &equ_list && !current_equ->next)) + equalizer_next(); + } + + equalizer_adjust_preamp(); +} + +void equalizer_prev() +{ + if(current_equ) + { + if(current_equ->prev) + { + current_equ = current_equ->prev; + } + else + { + while(current_equ->next) + current_equ = current_equ->next; + } + + if(!current_equ->set && !(current_equ == &equ_list && !current_equ->next)) + equalizer_prev(); + } + + equalizer_adjust_preamp(); +} + +/* biquad functions */ + +/* Create a Peaking EQ Filter + * See 'Audio EQ Cookbook' for more information + */ +t_biquad *mk_biquad(float dbgain, float cf, float srate, float bw, t_biquad *b) +{ + if(b==NULL) + b = (t_biquad *)xmalloc(sizeof(t_biquad)); + + float A = powf(10.0f, dbgain / 40.0f); + float omega = TWOPI * cf / srate; + float sn = sin(omega); + float cs = cos(omega); + float alpha = sn * sinh(M_LN2 / 2.0f * bw * omega / sn); + + float alpha_m_A = alpha * A; + float alpha_d_A = alpha / A; + + float b0 = 1.0f + alpha_m_A; + float b1 = -2.0f * cs; + float b2 = 1.0f - alpha_m_A; + float a0 = 1.0f + alpha_d_A; + float a1 = b1; + float a2 = 1.0f - alpha_d_A; + + b->a0 = b0 / a0; + b->a1 = b1 / a0; + b->a2 = b2 / a0; + b->a3 = a1 / a0; + b->a4 = a2 / a0; + + b->x1 = 0.0f; + b->x2 = 0.0f; + b->y1 = 0.0f; + b->y2 = 0.0f; + + b->cf = cf; + b->bw = bw; + b->srate = srate; + b->israte = (int)srate; + b->gain = dbgain; + + return b; +}; + +/* + * not used but keep as example use for biquad filter +inline void biquad(float *src, float *dst, int len, t_biquad *b) +{ + while(len-->0) + { + float s = *src++; + float f = s * b->a0 + b->a1 * b->x1 + b->a2 * b->x2 - b->a3 * b->y1 - b->a4 * b->y2; + *dst++=f; + b->x2 = b->x1; + b->x1 = s; + b->y2 = b->y1; + b->y1 = f; + } + +}; +*/ + +/* Applies a set of biquadratic filters to a buffer of floating point + * samples. + * It is safe to have the same input and output buffer. + * + * blen is the sample-count ignoring channels (samples per channel * channels) + */ +inline void apply_biquads(float *src, float *dst, int channels, int len, t_biquad *b, int blen) +{ + int bi, ci, boffs, idx; + while(len>0) + { + boffs = 0; + for(ci=0; ciset) + { + preamp = current_equ->set->preamp; + preampf = powf(10.0f, current_equ->set->preamp / 20.0f); + } +} + +void equalizer_read_config() +{ + char *curloc = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); // posix decimal point + + char *sfile = xstrdup(create_file_name("equalizer")); + + FILE *cf = fopen(sfile, "r"); + + if(cf==NULL) + { + logit("Unable to read equalizer configuration"); + return; + } + + char *linebuffer = NULL; + char presetbuf[128]; + presetbuf[0] = 0; + + size_t buffersize = -1; + ssize_t readbytes=-1; + int tmp; + float ftmp; + + while((readbytes=getline(&linebuffer, &buffersize, cf)>-1)) + { + if( + strncasecmp + ( + linebuffer + , EQUALIZER_CFG_ACTIVE + , strlen(EQUALIZER_CFG_ACTIVE) + ) == 0 + ) + { + if(sscanf(linebuffer, "%*s %i", &tmp)>0) + { + if(tmp>0) + { + equ_active = 1; + } + else + { + equ_active = 0; + } + } + } + if( + strncasecmp + ( + linebuffer + , EQUALIZER_CFG_MIXIN + , strlen(EQUALIZER_CFG_MIXIN) + ) == 0 + ) + { + if(sscanf(linebuffer, "%*s %f", &ftmp)>0) + { + if(ftmp>=0.0f && ftmp<=1.0f) + { + mixin_rate = ftmp; + } + } + } + if( + strncasecmp + ( + linebuffer + , EQUALIZER_CFG_PRESET + , strlen(EQUALIZER_CFG_PRESET) + ) == 0 + ) + { + if(sscanf(linebuffer, "%*s %127s", presetbuf)>0) + { + /* ignore too large strings... */ + if(strlen(presetbuf)<127) + { + if(config_preset_name) + free(config_preset_name); + config_preset_name = xstrdup(presetbuf); + } + } + } + } + + free(linebuffer); + + fclose(cf); + + setlocale(LC_NUMERIC, curloc); +} + +void equalizer_write_config() +{ + char *curloc = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); /* posix decimal point */ + + char *cfname = create_file_name(EQUALIZER_SAVE_FILE); + + FILE *cf = fopen(cfname, "w"); + + if(cf==NULL) + { + logit ("Unable to write equalizer configuration"); + return; + } + + fprintf(cf, "%s %i\n", EQUALIZER_CFG_ACTIVE, equ_active); + if(current_equ && current_equ->set) + fprintf(cf, "%s %s\n", EQUALIZER_CFG_PRESET, current_equ->set->name); + fprintf(cf, "%s %f\n", EQUALIZER_CFG_MIXIN, mixin_rate); + + fclose(cf); + + setlocale(LC_NUMERIC, curloc); + + logit("Equalizer configuration written"); +} + +void equalizer_init() +{ + equ_active = 1; + + equ_list.set = NULL; + equ_list.next = NULL; + equ_list.prev = NULL; + + sample_rate = 44100; + + equ_channels = 2; + + preamp = 0.0f; + + preampf = powf(10.0f, preamp / 20.0f); + + eqsetdir = xstrdup(create_file_name("eqsets")); + + config_preset_name = NULL; + + mixin_rate = 0.25f; + + equalizer_read_config(); + + r_mixin_rate = 1.0f - mixin_rate; + + equalizer_refresh(); + + logit("Equalizer initialized"); +} + +void equalizer_shutdown() +{ + if(options_get_int(EQUALIZER_SAVE_OPTION)) + equalizer_write_config(); + + clear_eq_set(&equ_list); + + logit("Equalizer stopped"); +} + +void equalizer_refresh() +{ + t_eq_setup *eqs = NULL; + char buf[1024]; + + char *current_set_name = NULL; + + if(current_equ && current_equ->set) + { + current_set_name = xstrdup(current_equ->set->name); + } + else + { + if(config_preset_name) + current_set_name = config_preset_name; + } + + clear_eq_set(&equ_list); + + current_equ = NULL; + + DIR *d = opendir(eqsetdir); + + if(!d) + { + return; + } + + struct dirent *de = readdir(d); + struct stat st; + + t_eq_set_list *last_elem; + + last_elem = &equ_list; + + while(de) + { + sprintf(buf, "eqsets/%s", de->d_name); + + char *filename = xstrdup(create_file_name(buf)); + + stat(filename, &st); + + if( S_ISREG(st.st_mode) ) + { + FILE *f = fopen(filename, "r"); + + if(f) + { + char filebuffer[4096]; + + char *fb = filebuffer; + + int maxread = 4095 - (fb - filebuffer); + + // read in whole file + while(!feof(f) && maxread>0) + { + maxread = 4095 - (fb - filebuffer); + int rb = fread(fb, sizeof(char), maxread, f); + fb+=rb; + } + + fclose(f); + + *fb = 0; + int r = read_setup(de->d_name, filebuffer, &eqs); + + if(r==0) + { + int i, channel; + t_eq_set *eqset = (t_eq_set *)xmalloc(sizeof(t_eq_set)); + eqset->b = (t_biquad *)xmalloc(sizeof(t_biquad)*eqs->bcount*equ_channels); + + eqset->name = xstrdup(eqs->name); + eqset->preamp = eqs->preamp; + eqset->bcount = eqs->bcount; + eqset->channels = equ_channels; + + for(i=0; ibcount; i++) + { + mk_biquad(eqs->dg[i], eqs->cf[i], sample_rate, eqs->bw[i], &eqset->b[i]); + + for(channel=1; channelb[channel*eqset->bcount + i] = eqset->b[i]; + } + } + + last_elem = append_eq_set(eqset, last_elem); + + free(eqs->name); + free(eqs->cf); + free(eqs->bw); + free(eqs->dg); + + } + else + { + switch(r) + { + case 0: + logit("This should not happen: No error but no EQSET was parsed: %s", filename); + break; + case -1: + logit("Not an EQSET (empty file): %s", filename); + break; + case -2: + logit("Not an EQSET (invalid header): %s", filename); + break; + case -3: + logit("Error while parsing settings from EQSET: %s", filename); + break; + default: + logit("Unknown error while parsing EQSET: %s", filename); + break; + } + } + + if(eqs) + free(eqs); + + eqs = NULL; + } + } + + free(filename); + + de = readdir(d); + } + + closedir(d); + + current_equ = &equ_list; + + if(current_set_name) + { + current_equ = &equ_list; + + while(current_equ) + { + if(current_equ->set) + { + if(strcmp(current_set_name, current_equ->set->name)==0) + break; + } + current_equ = current_equ->next; + } + + free(current_set_name); + } + + if(current_equ && !current_equ->set) + equalizer_next(); + + equalizer_adjust_preamp(); +} + +/* sound processing code */ +void equalizer_process_buffer(char *buf, size_t size, const struct sound_params *sound_params) +{ +#ifdef DEBUG + logit("EQ Processing %u bytes...", size); +#endif + if(!equ_active || !current_equ || !current_equ->set) + return; + + if(sound_params->rate != current_equ->set->b->israte || sound_params->channels != equ_channels) + { + logit("Recreating filters due to sound parameter changes..."); + sample_rate = sound_params->rate; + equ_channels = sound_params->channels; + + equalizer_refresh(); + } + + long sound_endianess = sound_params->fmt & SFMT_MASK_ENDIANES; + long sound_format = sound_params->fmt & SFMT_MASK_FORMAT; + + int samplesize = sample_size(sound_format); + int is_float = (sound_params->fmt & SFMT_MASK_FORMAT) == SFMT_FLOAT; + + int need_endianess_swap = 0; + + if((sound_endianess != SFMT_NE) && (samplesize > 1) && (!is_float)) + { + need_endianess_swap = 1; + } + + /* setup samples to perform arithmetic */ + if(need_endianess_swap) + { +#ifdef DEBUG + logit("Converting endianess before mixing"); +#endif + if(samplesize == 4) + swap_endianess_32((int32_t *)buf, size>>2); + else + swap_endianess_16((int16_t *)buf, size>>1); + } + + switch(sound_format) + { + case SFMT_U8: + equ_process_buffer_u8((uint8_t *)buf, size); + break; + case SFMT_S8: + equ_process_buffer_s8((int8_t *)buf, size); + break; + case SFMT_U16: + equ_process_buffer_u16((uint16_t *)buf, size >> 1); + break; + case SFMT_S16: + equ_process_buffer_s16((int16_t *)buf, size >> 1); + break; + case SFMT_U32: + equ_process_buffer_u32((uint32_t *)buf, size >> 2); + break; + case SFMT_S32: + equ_process_buffer_s32((int32_t *)buf, size >> 2); + break; + case SFMT_FLOAT: + equ_process_buffer_float((float *)buf, size >> 1); + break; + } + + /* restore sample-endianess */ + if(need_endianess_swap) + { +#ifdef DEBUG + logit("Restoring endianess after mixing"); +#endif + if(samplesize == 4) + swap_endianess_32((int32_t *)buf, size>>2); + else + swap_endianess_16((int16_t *)buf, size>>1); + } +} + +void equ_process_buffer_u8(uint8_t *buf, size_t size) +{ +#ifdef DEBUG + logit("equalizing"); +#endif + float *tmp = (float *)xmalloc(size * sizeof(float)); + + size_t i; + + for(i=0; iset->b, current_equ->set->bcount); + + for(i=0; i UINT8_MAX) + tmp[i] = UINT8_MAX; + else + if(tmp[i] < 0) + tmp[i] = 0; + + buf[i] = (uint8_t)tmp[i]; + } + + free(tmp); +} + +void equ_process_buffer_s8(int8_t *buf, size_t size) +{ +#ifdef DEBUG + logit("equalizing"); +#endif + float *tmp = (float *)xmalloc(size * sizeof(float)); + + size_t i; + + for(i=0; iset->b, current_equ->set->bcount); + + for(i=0; i INT8_MAX) + tmp[i] = INT8_MAX; + else + if(tmp[i] < INT8_MIN) + tmp[i] = INT8_MIN; + + buf[i] = (int8_t)tmp[i]; + } + + free(tmp); +} + +void equ_process_buffer_u16(uint16_t *buf, size_t size) +{ +#ifdef DEBUG + logit("equalizing"); +#endif + float *tmp = (float *)xmalloc(size * sizeof(float)); + + size_t i; + + for(i=0; iset->b, current_equ->set->bcount); + + for(i=0; i UINT16_MAX) + tmp[i] = UINT16_MAX; + else + if(tmp[i] < 0) + tmp[i] = 0; + + buf[i] = (uint16_t)tmp[i]; + } + + free(tmp); +} + +void equ_process_buffer_s16(int16_t *buf, size_t size) +{ +#ifdef DEBUG + logit("equalizing"); +#endif + float *tmp = (float *)xmalloc(size * sizeof(float)); + + size_t i; + + for(i=0; iset->b, current_equ->set->bcount); + + for(i=0; i INT16_MAX) + tmp[i] = INT16_MAX; + else + if(tmp[i] < INT16_MIN) + tmp[i] = INT16_MIN; + + buf[i] = (int16_t)tmp[i]; + } + + free(tmp); +} + +void equ_process_buffer_u32(uint32_t *buf, size_t size) +{ +#ifdef DEBUG + logit("equalizing"); +#endif + float *tmp = (float *)xmalloc(size * sizeof(float)); + + size_t i; + + for(i=0; iset->b, current_equ->set->bcount); + + for(i=0; i UINT32_MAX) + tmp[i] = UINT32_MAX; + else + if(tmp[i] < 0) + tmp[i] = 0; + + buf[i] = (uint32_t)tmp[i]; + } + + free(tmp); +} + +void equ_process_buffer_s32(int32_t *buf, size_t size) +{ +#ifdef DEBUG + logit("equalizing"); +#endif + float *tmp = (float *)xmalloc(size * sizeof(float)); + + size_t i; + + for(i=0; iset->b, current_equ->set->bcount); + + for(i=0; i INT32_MAX) + tmp[i] = INT32_MAX; + else + if(tmp[i] < INT32_MIN) + tmp[i] = INT32_MIN; + + buf[i] = (int32_t)tmp[i]; + } + + free(tmp); +} + +void equ_process_buffer_float(float *buf, size_t size) +{ +#ifdef DEBUG + logit("equalizing"); +#endif + float *tmp = (float *)xmalloc(size * sizeof(float)); + + size_t i; + + for(i=0; iset->b, current_equ->set->bcount); + + + for(i=0; i 1.0f) + tmp[i] = 1.0f; + else + if(tmp[i] < -1.0f) + tmp[i] = -1.0f; + + buf[i] = tmp[i]; + } + + free(tmp); +} + +/* equalizer list maintenance */ +t_eq_set_list *append_eq_set(t_eq_set *eqs, t_eq_set_list *l) +{ + if(l->set == NULL) + { + l->set = eqs; + } + else + { + if(l->next) + { + append_eq_set(eqs, l->next); + } + else + { + l->next = (t_eq_set_list *)xmalloc(sizeof(t_eq_set_list)); + l->next->set = NULL; + l->next->next = NULL; + l->next->prev = l; + l = append_eq_set(eqs, l->next); + } + }; + + return l; +}; + +void clear_eq_set(t_eq_set_list *l) +{ + if(l->set) + { + free(l->set->name); + free(l->set->b); + free(l->set); + l->set = NULL; + } + if(l->next) + { + clear_eq_set(l->next); + free(l->next); + l->next = NULL; + } +}; + +/* parsing stuff */ +int read_setup(char *name, char *desc, t_eq_setup **sp) +{ + char *curloc = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); // posix decimal point + + t_eq_setup *s = *sp; + + desc = skip_whitespace(desc); + + if(!*desc) + { + return -1; + } + + if(strncasecmp(desc, EQSET_HEADER, sizeof(EQSET_HEADER)-1)) + { + return -2; + } + + desc+=5; + + desc = skip_whitespace(skip_line(desc)); + + if(s==NULL) + { + s=(t_eq_setup *)xmalloc(sizeof(t_eq_setup)); + *sp = s; + } + + s->name = xstrdup(name); + s->bcount = 0; + s->preamp = 0.0f; + int max_values = 16; + s->cf = (float *)xmalloc(max_values*sizeof(float)); + s->bw = (float *)xmalloc(max_values*sizeof(float)); + s->dg = (float *)xmalloc(max_values*sizeof(float)); + + int r; + + while(*desc) + { + char *endp; + + float cf = 0.0f; + + r = read_float(desc, &cf, &endp); + + if(r!=0) + { + free(s->name); + free(s->cf); + free(s->bw); + free(s->dg); + return -3; + } + + desc = skip_whitespace(endp); + + float bw = 0.0f; + + r = read_float(desc, &bw, &endp); + + if(r!=0) + { + free(s->name); + free(s->cf); + free(s->bw); + free(s->dg); + return -3; + } + + desc = skip_whitespace(endp); + + float dg = 0.0f; + + /* 0Hz means preamp, only one parameter then */ + if(cf!=0.0f) + { + r = read_float(desc, &dg, &endp); + + if(r!=0) + { + free(s->name); + free(s->cf); + free(s->bw); + free(s->dg); + return -3; + } + + desc = skip_whitespace(endp); + + if(s->bcount>=(max_values-1)) + { + max_values*=2; + s->cf=xrealloc(s->cf, max_values*sizeof(float)); + s->bw=xrealloc(s->bw, max_values*sizeof(float)); + s->dg=xrealloc(s->dg, max_values*sizeof(float)); + } + + s->cf[s->bcount]=cf; + s->bw[s->bcount]=bw; + s->dg[s->bcount]=dg; + + s->bcount++; + } + else + { + s->preamp = bw; + } + } + + setlocale(LC_NUMERIC, curloc); // posix decimal point + + return 0; +} + +char *skip_line(char *s) +{ + int dos_line = 0; + while(*s && (*s!=CRETURN && *s!=NEWLINE) ) + s++; + + if(*s==CRETURN) + dos_line = 1; + + if(*s) + s++; + + if(dos_line && *s==NEWLINE) + s++; + + return s; +} + +char *skip_whitespace(char *s) +{ + while(*s && (*s<=SPACE)) + s++; + + if(!*s) + return s; + + if(*s=='#') + { + s = skip_line(s); + + s = skip_whitespace(s); + } + + return s; +} + +int read_float(char *s, float *f, char **endp) +{ + errno = 0; + + float t = strtof(s, endp); + + if(errno==ERANGE) + return -1; + + if(*endp == s) + return -2; + + *f = t; + + return 0; +}; Index: equalizer.h =================================================================== --- equalizer.h (revision 0) +++ equalizer.h (revision 10) @@ -0,0 +1,101 @@ +#ifndef EQUALIZER_H +#define EQUALIZER_H + +#define TWOPI (2.0 * M_PI) + +#define swap_32bit_endianess(i32) \ + ( ((i32&0x000000FF)<<24) | ((i32&0x0000FF00)<<8)| \ + ((i32&0x00FF0000)>>8) | ((i32&0xFF000000)>>24) ) + +#define swap_16bit_endianess(i16) \ + ( ((i16&0x00FF)<<8) | ((i16&0xFF00)>>8) ) + +#define NEWLINE 0x0A +#define CRETURN 0x0D +#define SPACE 0x20 + +#define EQSET_HEADER "EQSET" + +#define EQUALIZER_CFG_ACTIVE "Active:" +#define EQUALIZER_CFG_PRESET "Preset:" +#define EQUALIZER_CFG_MIXIN "Mixin:" + +#define EQUALIZER_SAVE_FILE "equalizer" +#define EQUALIZER_SAVE_OPTION "Equalizer_SaveState" + + +char *skip_line(char *s); +char *skip_whitespace(char *s); +char *word(char *s, char *buf, int *len, int nmax); +int read_float(char *s, float *f, char **endp); + +typedef struct t_biquad t_biquad; + +struct t_biquad +{ + float a0, a1, a2, a3, a4; + float x1, x2, y1, y2; + float cf, bw, gain, srate; + int israte; +}; + +typedef struct t_eq_setup t_eq_setup; + +struct t_eq_setup +{ + char *name; + float preamp; + int bcount; + float *cf; + float *bw; + float *dg; +}; + +typedef struct t_eq_set t_eq_set; + +struct t_eq_set +{ + char *name; + int channels; + float preamp; + int bcount; + t_biquad *b; +}; + +typedef struct t_eq_set_list t_eq_set_list; + +struct t_eq_set_list +{ + t_eq_set *set; + t_eq_set_list *prev, *next; +}; + +typedef struct t_active_set t_active_set; + +struct t_active_set +{ + int srate; + t_eq_set *set; +}; + +typedef struct t_eq_settings t_eq_settings; + +struct t_eq_settings +{ + char *preset_name; + int bcount; + float *gain; + t_eq_settings *next; +}; + +void equalizer_init(); +void equalizer_shutdown(); +void equalizer_process_buffer(char *buf, size_t size, const struct sound_params *sound_params); +void equalizer_refresh(); +int equalizer_is_active(); +int equalizer_set_active(); +char *equalizer_current_eqname(); +void equalizer_next(); +void equalizer_prev(); + +#endif Index: interface.c =================================================================== --- interface.c (revision 1) +++ interface.c (working copy) @@ -3176,6 +3176,26 @@ debug ("Toggle softmixer."); send_int_to_srv (CMD_TOGGLE_SOFTMIXER); break; + case KEY_CMD_TOGGLE_EQUALIZER: + debug ("Toggle equalizer."); + send_int_to_srv (CMD_TOGGLE_EQUALIZER); + break; + case KEY_CMD_EQUALIZER_REFRESH: + debug ("Equalizer Refresh."); + send_int_to_srv (CMD_EQUALIZER_REFRESH); + break; + case KEY_CMD_EQUALIZER_PREV: + debug ("Equalizer Prev."); + send_int_to_srv (CMD_EQUALIZER_PREV); + break; + case KEY_CMD_EQUALIZER_NEXT: + debug ("Equalizer Next."); + send_int_to_srv (CMD_EQUALIZER_NEXT); + break; + case KEY_CMD_TOGGLE_MAKE_MONO: + debug ("Toggle Mono-Mixing."); + send_int_to_srv (CMD_TOGGLE_MAKE_MONO); + break; case KEY_CMD_TOGGLE_LAYOUT: iface_toggle_layout (); break; Index: keys.c =================================================================== --- keys.c (revision 1) +++ keys.c (working copy) @@ -625,6 +625,46 @@ 1 }, { + KEY_CMD_TOGGLE_EQUALIZER, + "toggle_equalizer", + "Toggles the equalizer", + CON_MENU, + { 'E', -1 }, + 1 + }, + { + KEY_CMD_EQUALIZER_REFRESH, + "equalizer_refresh", + "Reload EQ-presets", + CON_MENU, + { 'e', -1 }, + 1 + }, + { + KEY_CMD_EQUALIZER_PREV, + "equalizer_prev", + "Select previous equalizer-preset", + CON_MENU, + { 'K', -1 }, + 1 + }, + { + KEY_CMD_EQUALIZER_NEXT, + "equalizer_next", + "Select next equalizer-preset", + CON_MENU, + { 'k', -1 }, + 1 + }, + { + KEY_CMD_TOGGLE_MAKE_MONO, + "toggle_make_mono", + "Toggle mono-mixing", + CON_MENU, + { 'J', -1 }, + 1 + }, + { KEY_CMD_PLIST_MOVE_UP, "plist_move_up", "Move playlist item up", Index: keys.h =================================================================== --- keys.h (revision 1) +++ keys.h (working copy) @@ -89,6 +89,11 @@ KEY_CMD_EXEC10, KEY_CMD_TOGGLE_PLAYLIST_FULL_PATHS, KEY_CMD_TOGGLE_SOFTMIXER, + KEY_CMD_TOGGLE_EQUALIZER, + KEY_CMD_EQUALIZER_REFRESH, + KEY_CMD_EQUALIZER_PREV, + KEY_CMD_EQUALIZER_NEXT, + KEY_CMD_TOGGLE_MAKE_MONO, KEY_CMD_LYRICS, KEY_CMD_WRONG }; Index: softmixer.c =================================================================== --- softmixer.c (revision 1) +++ softmixer.c (working copy) @@ -31,23 +31,19 @@ #endif #include "softmixer.h" +#include "audio_helper.h" #include "options.h" #include "common.h" #include "log.h" -#define swap_32bit_endianess(i32) \ - ( ((i32&0x000000FF)<<24) | ((i32&0x0000FF00)<<8)| \ - ((i32&0x00FF0000)>>8) | ((i32&0xFF000000)>>24) ) -#define swap_16bit_endianess(i16) \ - ( ((i16&0x00FF)<<8) | ((i16&0xFF00)>>8) ) - /* #define DEBUG */ /* public code */ - int active; +int mix_mono; + int mixer_val, mixer_amp, mixer_real; float mixer_realf; @@ -61,6 +57,7 @@ void softmixer_init() { active = 0; + mix_mono = 0; mixer_amp = 100; softmixer_set_value(100); softmixer_read_config(); @@ -115,10 +112,20 @@ return active; } -/* private code */ +void softmixer_set_mono(int mono) +{ + if(mono) + mix_mono = 1; + else + mix_mono = 0; +} -int sample_size(long sfmt); +int softmixer_is_mono() +{ + return mix_mono; +} +/* private code */ void process_buffer_u8(uint8_t *buf, size_t size); void process_buffer_s8(int8_t *buf, size_t size); void process_buffer_u16(uint16_t *buf, size_t size); @@ -126,10 +133,14 @@ void process_buffer_u32(uint32_t *buf, size_t size); void process_buffer_s32(int32_t *buf, size_t size); void process_buffer_float(float *buf, size_t size); +void mix_mono_u8(uint8_t *buf, int channels, size_t size); +void mix_mono_s8(int8_t *buf, int channels, size_t size); +void mix_mono_u16(uint16_t *buf, int channels, size_t size); +void mix_mono_s16(int16_t *buf, int channels, size_t size); +void mix_mono_u32(uint32_t *buf, int channels, size_t size); +void mix_mono_s32(int32_t *buf, int channels, size_t size); +void mix_mono_float(float *buf, int channels, size_t size); -void swap_endianess_32(int32_t *buf, size_t size); -void swap_endianess_16(int16_t *buf, size_t size); - void softmixer_read_config() { char *cfname = create_file_name(SOFTMIXER_SAVE_FILE); @@ -144,7 +155,8 @@ char *linebuffer=NULL; - int buffersize=-1, readbytes=-1; + size_t buffersize = -1; + int readbytes=-1; int tmp; while((readbytes=getline(&linebuffer, &buffersize, cf)>-1)) @@ -212,6 +224,27 @@ } } } + if( + strncasecmp + ( + linebuffer + , SOFTMIXER_CFG_MONO + , strlen(SOFTMIXER_CFG_MONO) + ) == 0 + ) + { + if(sscanf(linebuffer, "%*s %i", &tmp)>0) + { + if(tmp>0) + { + mix_mono = 1; + } + else + { + mix_mono = 0; + } + } + } } free(linebuffer); @@ -234,6 +267,7 @@ fprintf(cf, "%s %i\n", SOFTMIXER_CFG_ACTIVE, active); fprintf(cf, "%s %i\n", SOFTMIXER_CFG_AMP, mixer_amp); fprintf(cf, "%s %i\n", SOFTMIXER_CFG_VALUE, mixer_val); + fprintf(cf, "%s %i\n", SOFTMIXER_CFG_MONO, mix_mono); fclose(cf); @@ -245,9 +279,11 @@ #ifdef DEBUG logit("Processing %u bytes...", size); #endif - if(mixer_real==100) + if(mixer_real==100 && !mix_mono) return; + int do_softmix = mixer_real != 100; + long sound_endianess = sound_params->fmt & SFMT_MASK_ENDIANES; long sound_format = sound_params->fmt & SFMT_MASK_FORMAT; @@ -276,25 +312,46 @@ switch(sound_format) { case SFMT_U8: - process_buffer_u8((uint8_t *)buf, size); + if(do_softmix) + process_buffer_u8((uint8_t *)buf, size); + if(mix_mono) + mix_mono_u8((uint8_t *)buf, sound_params->channels, size); break; case SFMT_S8: - process_buffer_s8((int8_t *)buf, size); + if(do_softmix) + process_buffer_s8((int8_t *)buf, size); + if(mix_mono) + mix_mono_s8((int8_t *)buf, sound_params->channels, size); break; case SFMT_U16: - process_buffer_u16((uint16_t *)buf, size >> 1); + if(do_softmix) + process_buffer_u16((uint16_t *)buf, size >> 1); + if(mix_mono) + mix_mono_u16((uint16_t *)buf, sound_params->channels, size >> 1); break; case SFMT_S16: - process_buffer_s16((int16_t *)buf, size >> 1); + if(do_softmix) + process_buffer_s16((int16_t *)buf, size >> 1); + if(mix_mono) + mix_mono_s16((int16_t *)buf, sound_params->channels, size >> 1); break; case SFMT_U32: - process_buffer_u32((uint32_t *)buf, size >> 2); + if(do_softmix) + process_buffer_u32((uint32_t *)buf, size >> 2); + if(mix_mono) + mix_mono_u32((uint32_t *)buf, sound_params->channels, size >> 2); break; case SFMT_S32: - process_buffer_s32((int32_t *)buf, size >> 2); + if(do_softmix) + process_buffer_s32((int32_t *)buf, size >> 2); + if(mix_mono) + mix_mono_s32((int32_t *)buf, sound_params->channels, size >> 2); break; case SFMT_FLOAT: - process_buffer_float((float *)buf, size >> 1); + if(do_softmix) + process_buffer_float((float *)buf, size >> 1); + if(mix_mono) + mix_mono_float((float *)buf, sound_params->channels, size >> 1); break; } @@ -311,28 +368,6 @@ } } -int sample_size(long sfmt) -{ - long fmt = sfmt & SFMT_MASK_FORMAT; - switch(fmt) - { - case SFMT_U8: - case SFMT_S8: - return 1; - case SFMT_U16: - case SFMT_S16: - return 2; - case SFMT_U32: - case SFMT_S32: - return 4; - case SFMT_FLOAT: - return 2; - default: - return -1; - } -} - - void process_buffer_u8(uint8_t *buf, size_t size) { #ifdef DEBUG @@ -476,20 +511,243 @@ } } -void swap_endianess_32(int32_t *buf, size_t size) +// Mono-Mixing +void mix_mono_u8(uint8_t *buf, int channels, size_t size) { - size_t i; - for(i=0; i UINT8_MAX) + mono = UINT8_MAX; + // can't be negative + + for(c=0; c INT8_MAX) + mono = INT8_MAX; + else + if(mono < INT8_MIN) + mono = INT8_MIN; + + for(c=0; c UINT16_MAX) + mono = UINT16_MAX; + // can't be negative + + for(c=0; c INT16_MAX) + mono = INT16_MAX; + else + if(mono < INT16_MIN) + mono = INT16_MIN; + + for(c=0; c UINT32_MAX) + mono = UINT32_MAX; + // can't be negative + + for(c=0; c INT32_MAX) + mono = INT32_MAX; + else + if(mono < INT32_MIN) + mono = INT32_MIN; + + for(c=0; c 1.0f) + mono = 1.0f; + else + if(mono < -1.0f) + mono = -1.0f; + + for(c=0; c #include #include -#include -#include -#include -#include #define DEBUG @@ -35,13 +31,6 @@ #include "log.h" #include "audio.h" -struct cache_record -{ - time_t mod_time; /* last modification time of the file */ - time_t atime; /* Time of last access. */ - struct file_tags *tags; -}; - static void request_queue_init (struct request_queue *q) { assert (q != NULL); @@ -145,268 +134,94 @@ return file; } -static size_t strlen_null (const char *s) +static void cache_list_init (struct cache_list *l) { - return s ? strlen(s) : 0; + assert (l != NULL); + + l->head = NULL; + l->tail = NULL; } -static char *cache_record_serialize (const struct cache_record *rec, int *len) +static int cache_list_empty (const struct cache_list *l) { - char *buf; - char *p; - size_t artist_len; - size_t album_len; - size_t title_len; + assert (l != NULL); - - artist_len = strlen_null (rec->tags->artist); - album_len = strlen_null (rec->tags->album); - title_len = strlen_null (rec->tags->title); - - *len = sizeof(rec->mod_time) - + sizeof(rec->atime) - + sizeof(int) * 3 /* lenghts of title, artist, time. */ - + artist_len - + album_len - + title_len - + sizeof(rec->tags->track) - + sizeof(rec->tags->time); - - buf = p = (char *)xmalloc (*len); - - memcpy (p, &rec->mod_time, sizeof(rec->mod_time)); - p += sizeof(rec->mod_time); - - memcpy (p, &rec->atime, sizeof(rec->atime)); - p += sizeof(rec->atime); - - memcpy (p, &artist_len, sizeof(artist_len)); - p += sizeof(artist_len); - if (artist_len) { - memcpy (p, rec->tags->artist, artist_len); - p += artist_len; - } - - memcpy (p, &album_len, sizeof(album_len)); - p += sizeof(album_len); - if (album_len) { - memcpy (p, rec->tags->album, album_len); - p += album_len; - } - - memcpy (p, &title_len, sizeof(title_len)); - p += sizeof(title_len); - if (title_len) { - memcpy (p, rec->tags->title, title_len); - p += title_len; - } - - memcpy (p, &rec->tags->track, sizeof(rec->tags->track)); - p += sizeof(rec->tags->track); - - memcpy (p, &rec->tags->time, sizeof(rec->tags->time)); - p += sizeof(rec->tags->time); - - return buf; + return l->head == NULL; } -static int cache_record_deserialize (struct cache_record *rec, - const char *serialized, const size_t size, - const int skip_tags) +/* Detach the oldest element from the cache list and return a pointer to it + * or NULL if the list is empty. */ +static struct cache_list_node *cache_list_pop (struct cache_list *l) { - const char *p = serialized; - size_t bytes_left = size; - size_t str_len; - - assert (rec != NULL); - assert (serialized != NULL); - - if (!skip_tags) - rec->tags = tags_new (); - else - rec->tags = NULL; - -#define extract_num(var) \ - if (bytes_left < sizeof(var)) \ - goto err; \ - memcpy (&var, p, sizeof(var)); \ - bytes_left -= sizeof(var); \ - p += sizeof(var); - -#define extract_str(var) \ - if (bytes_left < sizeof(str_len)) \ - goto err; \ - memcpy (&str_len, p, sizeof(str_len)); \ - p += sizeof(str_len); \ - if (bytes_left < str_len) \ - goto err; \ - var = xmalloc (str_len + 1); \ - memcpy (var, p, str_len); \ - var[str_len] = '\0'; \ - p += str_len; - - extract_num (rec->mod_time); - extract_num (rec->atime); - - if (!skip_tags) { - extract_str (rec->tags->artist); - extract_str (rec->tags->album); - extract_str (rec->tags->title); - extract_num (rec->tags->track); - extract_num (rec->tags->time); - - if (rec->tags->title) - rec->tags->filled |= TAGS_COMMENTS; - else { - if (rec->tags->artist) - free (rec->tags->artist); - rec->tags->artist = NULL; - - if (rec->tags->album) - free (rec->tags->album); - rec->tags->album = NULL; - - } - - if (rec->tags->time >= 0) - rec->tags->filled |= TAGS_TIME; - } + struct cache_list_node *n; - return 1; + assert (l != NULL); -err: - logit ("Cahce record deserialization error at %dB", p - serialized); - tags_free (rec->tags); - rec->tags = NULL; - return 0; -} + if (l->head == NULL) + return NULL; -static void tags_cache_remove_rec (struct tags_cache *c, const char *fname) -{ - DBT key; - int ret; + n = l->head; + l->head = n->next; - assert (c != NULL); - assert (c->db != NULL); - assert (fname != NULL); + if (l->tail == n) + l->tail = NULL; /* the queue is empty */ - debug ("Removing %s from the cache...", fname); - - memset (&key, 0, sizeof(key)); - key.data = (void *)fname; - key.size = strlen (fname); - - ret = c->db->del (c->db, NULL, &key, 0); - if (ret) - logit ("Can't remove item for %s from the cache: %s", fname, - db_strerror(ret)); + return n; } -/* Remove the one element of the cache based on it's access time. */ -static void tags_cache_gc (struct tags_cache *c) +/* Remove the oldest element of the cache (if it is not empty). */ +static void tags_cache_remove_oldest (struct tags_cache *c) { - DBC *cur; - DBT key; - DBT serialized_cache_rec; - int ret; - char *last_referenced = NULL; - time_t last_referenced_atime = time (NULL); - int nitems = 0; + struct cache_list_node *n; - assert (c != NULL); - if (!c->db) + if (c->cache.tail && c->cache.tail->during_operation) { + debug ("Not deleting the oldest iten because it's in use."); return; - - c->db->cursor (c->db, NULL, &cur, 0); - - memset (&key, 0, sizeof(key)); - memset (&serialized_cache_rec, 0, sizeof(serialized_cache_rec)); - - key.flags = DB_DBT_MALLOC; - serialized_cache_rec.flags = DB_DBT_MALLOC; - - while ((ret = cur->c_get(cur, &key, &serialized_cache_rec, DB_NEXT)) - == 0) { - struct cache_record rec; - - if (cache_record_deserialize(&rec, serialized_cache_rec.data, - serialized_cache_rec.size, 1) - && rec.atime < last_referenced_atime) { - last_referenced_atime = rec.atime; - - if (last_referenced) - free (last_referenced); - last_referenced = (char *)xmalloc (key.size + 1); - memcpy (last_referenced, key.data, key.size); - last_referenced[key.size] = '\0'; - } - - // TODO: remove objects with serialization error. - - nitems++; - - free (key.data); - free (serialized_cache_rec.data); } - if (ret != DB_NOTFOUND) - logit ("Searching for element to remove failed (coursor): %s", - db_strerror(ret)); - - cur->c_close (cur); - - debug ("Elements in cache: %d (limit %d)", nitems, c->max_items); - - if (last_referenced) { - if (nitems > c->max_items) - tags_cache_remove_rec (c, last_referenced); - free (last_referenced); + if ((n = cache_list_pop(&c->cache))) { + debug ("Removing from cache: %s", n->file); + rb_delete (&c->search_tree, n->file); + c->size -= n->size; + + free (n->file); + tags_free (n->tags); + free (n); } - else - debug ("Cache empty"); - } + /* Add this tags object for the file to the cache. */ static void tags_cache_add (struct tags_cache *c, const char *file, struct file_tags *tags) { - char *serialized_cache_rec; - int serial_len; - struct cache_record rec; - DBT key; - DBT data; - int ret; - assert (c != NULL); assert (tags != NULL); - assert (file != NULL); - - rec.mod_time = get_mtime (file); - rec.atime = time (NULL); - rec.tags = tags; - - serialized_cache_rec = cache_record_serialize (&rec, &serial_len); - if (!serialized_cache_rec) - return; - memset (&key, 0, sizeof(key)); - memset (&data, 0, sizeof(data)); - - key.data = (void *)file; - key.size = strlen(file); - - data.data = serialized_cache_rec; - data.size = serial_len; - - tags_cache_gc (c); - - ret = c->db->put (c->db, NULL, &key, &data, 0); - if (ret) { - logit ("DB put error: %s", db_strerror(ret)); + if (!c->cache.head) { + c->cache.head = (struct cache_list_node *)xmalloc ( + sizeof(struct cache_list_node)); + c->cache.tail = c->cache.head; } + else { + assert (c->cache.tail != NULL); + assert (c->cache.tail->next == NULL); + + c->cache.tail->next = (struct cache_list_node *)xmalloc ( + sizeof(struct cache_list_node)); + c->cache.tail = c->cache.tail->next; + } + + c->cache.tail->file = xstrdup (file); + c->cache.tail->tags = tags; + c->cache.tail->mod_time = get_mtime (file); + c->cache.tail->size = sizeof(struct cache_list_node) + + strlen(file) + 1 + tags_mem (tags); + c->cache.tail->during_operation = 0; + c->cache.tail->next = NULL; - free (serialized_cache_rec); + rb_insert (&c->search_tree, c->cache.tail); + c->size += c->cache.tail->size; } /* Read the selected tags for this file and add it to the cache. @@ -416,74 +231,57 @@ const int client_id, const char *file, int tags_sel) { struct file_tags *tags; - DBT key; - DBT serialized_cache_rec; - DB_LOCK lock; - int got_lock = 0; - int ret; + struct rb_node *x; + struct cache_list_node *node = NULL; assert (c != NULL); - assert (c->db != NULL); assert (file != NULL); debug ("Getting tags for %s", file); - memset (&key, 0, sizeof(key)); - memset (&serialized_cache_rec, 0, sizeof(serialized_cache_rec)); + LOCK (c->mutex); - key.data = (void *)file; - key.size = strlen(file); - serialized_cache_rec.flags = DB_DBT_MALLOC; - - ret = c->db_env->lock_get (c->db_env, c->locker, 0, - &key, DB_LOCK_WRITE, &lock); - if (ret) { - logit ("Can't get DB lock: %s", db_strerror(ret)); - } - else { - got_lock = 1; - ret = c->db->get (c->db, NULL, &key, &serialized_cache_rec, 0); - if (ret && ret != DB_NOTFOUND) - logit ("Cache db get error: %s", db_strerror(ret)); - } - /* If this entry is already presend in the cache, we have 3 options: * we must read different tags (TAGS_*) or the tags are outdated * or this is an immediate tags read (client_id == -1) */ - if (ret == 0) { - struct cache_record rec; + if (!rb_is_null(x = rb_search(&c->search_tree, file))) { + node = (struct cache_list_node *)x->data; + + if (node->during_operation) { + debug ("Cache node is during operation, waiting to " + "finish..."); + while (node->during_operation) + pthread_cond_wait (&c->response_cond, &c->mutex); + } - if (cache_record_deserialize(&rec, serialized_cache_rec.data, - serialized_cache_rec.size, 0)) { - time_t curr_mtime = get_mtime(file); + if (node->mod_time != get_mtime(file)) { - if (rec.mod_time != curr_mtime) { + /* outdated tags - remove them and reread */ + rb_delete (&c->search_tree, file); + tags_free (node->tags); + tags = tags_new (); - /* outdated tags - remove them and reread */ - tags_free (rec.tags); - rec.tags = tags_new (); - rec.mod_time = curr_mtime; + debug ("Tags in the cache are outdated"); + } + else if ((node->tags->filled & tags_sel) == tags_sel + && client_id == -1) { + debug ("Tags are in the cache."); + tags = tags_dup (node->tags); + UNLOCK (c->mutex); - debug ("Tags in the cache are outdated"); - } - else if ((rec.tags->filled & tags_sel) == tags_sel - && client_id == -1) { - debug ("Tags are in the cache."); - tags = rec.tags; - - goto end; - } - else { - tags = rec.tags; /* read tags in addition to already - present tags */ - debug ("Tags in the cache are not what we want."); - } + return tags; } - tags = tags_new (); + else { + tags = node->tags; /* read tags in addition to already + present tags */ + debug ("Tags in the cache are not what we want."); + } + + node->during_operation = 1; } - else { + else tags = tags_new (); - } + UNLOCK (c->mutex); if (tags_sel & TAGS_TIME) { int time; @@ -500,28 +298,32 @@ tags = read_file_tags (file, tags, tags_sel); - debug ("Adding/updating cache obiect"); - tags_cache_add (c, file, tags); + LOCK (c->mutex); + if (!node) { + debug ("Adding to the cache"); + tags_cache_add (c, file, tags); + } + else { + debug ("Tags updated"); + node->during_operation = 0; + pthread_cond_broadcast (&c->response_cond); + } + if (client_id != -1) { tags_response (client_id, file, tags); - tags_free (tags); tags = NULL; } + else + tags = tags_dup (tags); - /* TODO: Removed the oldest items from the cache if we exceeded the maximum + /* Removed the oldest items from the cache if we exceeded the maximum * cache size */ + while (c->size > c->max_size && !cache_list_empty(&c->cache)) + tags_cache_remove_oldest (c); + debug ("Cache is %.2fKB big", c->size / 1024.0); + UNLOCK (c->mutex); -end: - if (got_lock) { - ret = c->db_env->lock_put (c->db_env, &lock); - if (ret) - logit ("Can't release DB lock: %s", db_strerror(ret)); - } - - if (serialized_cache_rec.data) - free (serialized_cache_rec.data); - return tags; } @@ -538,7 +340,7 @@ while (!c->stop_reader_thread) { int i; char *request_file; - int tags_sel = 0; + int tags_sel; /* find the queue with a request waiting, begin searching at * curr_queue: we want to get one request from each queue, @@ -579,25 +381,50 @@ return NULL; } +/* Compare function for RB trees */ +static int compare_cache_items (const void *a, const void *b, + void *adata ATTR_UNUSED) +{ + struct cache_list_node *na = (struct cache_list_node *)a; + struct cache_list_node *nb = (struct cache_list_node *)b; + + return strcmp (na->file, nb->file); +} + +/* Compare function for RB trees */ +static int compare_file_cache_item (const void *key, const void *data, + void *adata ATTR_UNUSED) +{ + struct cache_list_node *node = (struct cache_list_node *)data; + const char *file = (const char *)key; + + return strcmp (file, node->file); +} + void tags_cache_init (struct tags_cache *c, const size_t max_size) { int i; assert (c != NULL); - c->db_env = NULL; - c->db = NULL; + rb_init_tree (&c->search_tree, compare_cache_items, + compare_file_cache_item, NULL); + cache_list_init (&c->cache); for (i = 0; i < CLIENTS_MAX; i++) request_queue_init (&c->queues[i]); - c->max_items = max_size; + c->size = 0; + c->max_size = max_size; c->stop_reader_thread = 0; pthread_mutex_init (&c->mutex, NULL); if (pthread_cond_init(&c->request_cond, NULL)) fatal ("Can't create request_cond"); + if (pthread_cond_init(&c->response_cond, NULL)) + fatal ("Can't create response_cond"); + if (pthread_create(&c->reader_thread, NULL, reader_thread, c)) fatal ("Can't create tags cache thread."); } @@ -605,29 +432,21 @@ void tags_cache_destroy (struct tags_cache *c) { int i; - + assert (c != NULL); - + LOCK (c->mutex); c->stop_reader_thread = 1; pthread_cond_signal (&c->request_cond); + pthread_cond_broadcast (&c->response_cond); UNLOCK (c->mutex); - if (c->db) { - c->db->close (c->db, 0); - c->db = NULL; - } - - if (c->db_env) { - c->db_env->lock_id_free (c->db_env, c->locker); - - c->db_env->close (c->db_env, 0); - c->db_env = NULL; - } - if (pthread_join(c->reader_thread, NULL)) fatal ("pthread_join() on cache reader thread failed."); + while (!cache_list_empty(&c->cache)) + tags_cache_remove_oldest (c); + for (i = 0; i < CLIENTS_MAX; i++) request_queue_clear (&c->queues[i]); @@ -635,16 +454,14 @@ logit ("Can't destroy mutex"); if (pthread_cond_destroy(&c->request_cond)) logit ("Can't destroy request_cond"); + if (pthread_cond_destroy(&c->response_cond)) + logit ("Can't destroy request_cond"); } void tags_cache_add_request (struct tags_cache *c, const char *file, const int tags_sel, const int client_id) { - DBT serialized_cache_rec; - DBT key; - int db_ret; - int got_lock; - DB_LOCK lock; + struct rb_node *x; assert (c != NULL); assert (file != NULL); @@ -652,62 +469,23 @@ debug ("Request for tags for %s from client %d", file, client_id); - memset (&key, 0, sizeof(key)); - key.data = (void *)file; - key.size = strlen(file); + LOCK (c->mutex); + if (!rb_is_null(x = rb_search(&c->search_tree, file))) { + struct cache_list_node *n = (struct cache_list_node *)x->data; - memset (&serialized_cache_rec, 0, sizeof(serialized_cache_rec)); - serialized_cache_rec.flags = DB_DBT_MALLOC; - - db_ret = c->db_env->lock_get (c->db_env, c->locker, 0, - &key, DB_LOCK_WRITE, &lock); - if (db_ret) { - logit ("Can't get DB lock: %s", db_strerror(db_ret)); - } - else { - got_lock = 1; - } - - if (c->db) { - db_ret = c->db->get(c->db, NULL, &key, &serialized_cache_rec, 0); - - if (db_ret && db_ret != DB_NOTFOUND) - error ("Cache DB search error: %s", db_strerror(db_ret)); - } - else - db_ret = DB_NOTFOUND; - - if (db_ret == 0) { - struct cache_record rec; - - if (cache_record_deserialize(&rec, serialized_cache_rec.data, - serialized_cache_rec.size, 0)) { - if (rec.mod_time == get_mtime(file) - && (rec.tags->filled & tags_sel) == tags_sel) { - tags_response (client_id, file, rec.tags); - tags_free (rec.tags); - debug ("Tags are present in the cache"); - goto end; - } - - debug ("Found outdated or not complete tags in the cache"); + if (n->mod_time == get_mtime(file) + && (n->tags->filled & tags_sel) == tags_sel) { + tags_response (client_id, file, n->tags); + debug ("Tags are present in the cache"); + UNLOCK (c->mutex); + return; } - } - LOCK (c->mutex); + debug ("Found outdated or not complete tags in the cache"); + } request_queue_add (&c->queues[client_id], file, tags_sel); pthread_cond_signal (&c->request_cond); UNLOCK (c->mutex); - -end: - if (got_lock) { - db_ret = c->db_env->lock_put (c->db_env, &lock); - if (db_ret) - logit ("Can't release DB lock: %s", db_strerror(db_ret)); - } - - if (serialized_cache_rec.data) - free (serialized_cache_rec.data); } void tags_cache_clear_queue (struct tags_cache *c, const int client_id) @@ -739,77 +517,186 @@ void tags_cache_save (struct tags_cache *c, const char *file_name) { - //TODO: to remove + struct cache_list_node *n; + FILE *file; - assert (c != NULL); - assert (file_name != NULL); + if (!(file = fopen(file_name, "w"))) { + logit ("Can't write tags cache to %s: %s", file_name, + strerror(errno)); + return; + } + + LOCK (c->mutex); + logit ("Saving tags cache to %s...", file_name); + n = c->cache.head; + + while (n) { + fprintf (file, "%s\n", n->file); + fprintf (file, "%ld\n", (long)n->mod_time); + fprintf (file, "%s\n", n->tags->title ? n->tags->title : ""); + fprintf (file, "%s\n", n->tags->artist ? n->tags->artist : ""); + fprintf (file, "%s\n", n->tags->album ? n->tags->album : ""); + fprintf (file, "%d\n", n->tags->track); + fprintf (file, "%d\n", n->tags->time); + + n = n->next; + } + + UNLOCK (c->mutex); + + fclose (file); } +/* Read a line from a file, convert it to a number. Return 0 on error. */ +static int read_num (FILE *file, long *num) +{ + char *tmp; + + if ((tmp = read_line(file))) { + char *num_err; + + *num = strtol (tmp, &num_err, 10); + if (*num_err) { + free (tmp); + return 0; + } + + free (tmp); + return 1; + } + + return 0; +} + void tags_cache_load (struct tags_cache *c, const char *file_name) { - int ret; + FILE *file; + int count = 0; - if (mkdir(file_name, 0700) && errno != EEXIST) { - error ("Failed to create directory for tags cache: %s", + if (!(file = fopen(file_name, "r"))) { + logit ("Can't read tags cache from %s: %s", file_name, strerror(errno)); return; } + + LOCK (c->mutex); + logit ("Loading tags cache from %s...", file_name); - ret = db_env_create (&c->db_env, 0); - if (ret) { - error ("Can't create DB environment: %s", db_strerror(ret)); - return; - } + while (!feof(file)) { + char *node_file_name; + struct file_tags *tags; + long mod_time; + long tmp; + node_file_name = read_line (file); + if (!node_file_name) + break; - ret = c->db_env->open (c->db_env, file_name, - DB_CREATE | DB_INIT_MPOOL | DB_THREAD | DB_INIT_LOCK, - 0); - if (ret) { - logit ("Can't open DB environment (%s): %s", - file_name, db_strerror(ret)); - c->db_env->close (c->db_env, 0); - c->db_env = NULL; - return; - } + if (!read_num(file, &mod_time)) { + free (node_file_name); + logit ("File broken, no modification time"); + break; + } + tags = tags_new (); + + /* read the title */ + if (!(tags->title = read_line(file))) { + free (node_file_name); + tags_free (tags); + logit ("File broken, no title"); + break; + } + if (!tags->title[0]) { + free (tags->title); + tags->title = NULL; + } - ret = c->db_env->lock_id (c->db_env, &c->locker); - if (ret) { - error ("Failed to get DB locker: %s", db_strerror(ret)); - c->db = NULL; + /* read the artist */ + if (!(tags->artist = read_line(file))) { + free (node_file_name); + tags_free (tags); + logit ("File broken, no artist"); + break; + } + if (!tags->artist[0]) { + free (tags->artist); + tags->artist = NULL; + } - c->db_env->close (c->db_env, 0); - c->db_env = NULL; + /* read the album */ + if (!(tags->album = read_line(file))) { + free (node_file_name); + tags_free (tags); + logit ("File broken, no artist"); + break; + } + if (!tags->album[0]) { + free (tags->album); + tags->album = NULL; + } - return; - } + if (!read_num(file, &tmp)) { + free (node_file_name); + tags_free (tags); + logit ("File broken, no track"); + break; + } + tags->track = tmp; - ret = db_create (&c->db, c->db_env, 0); - if (ret) { - error ("Failed to create cache db: %s", db_strerror(ret)); - c->db = NULL; + if (!read_num(file, &tmp)) { + free (node_file_name); + tags_free (tags); + logit ("File broken, no time"); + break; + } + tags->time = tmp; - c->db_env->close (c->db_env, 0); - c->db_env = NULL; + if (tags->time < 0) { + free (node_file_name); + tags_free (tags); + logit ("File broken, invalid time"); + break; + } - return; - } + if (tags->title) + tags->filled |= TAGS_COMMENTS; + else { + if (tags->artist) { + free (tags->artist); + tags->artist = NULL; + } + if (tags->album) { + free (tags->album); + tags->album = NULL; + } + + } + if (tags->time != -1) + tags->filled |= TAGS_TIME; - ret = c->db->open (c->db, NULL, "tags.db", NULL, DB_BTREE, - DB_CREATE, 0); - if (ret) { - error ("Failed to open (or create) tags cache db: %s", - db_strerror(ret)); - abort (); - c->db->close (c->db, 0); - c->db = NULL; + + /* check if the file was changed */ + if (get_mtime(node_file_name) == mod_time) { + debug ("Adding file %s", node_file_name); + tags_cache_add (c, node_file_name, tags); + count++; + } + else + debug ("File %s was modified", node_file_name); - c->db_env->close (c->db_env, 0); - c->db_env = NULL; + free (node_file_name); - return; + if (c->size > c->max_size) { + logit ("Maximum tags cache size exceeded"); + break; + } } + + UNLOCK (c->mutex); + + logit ("Loaded %d items to the cache", count); + fclose (file); } /* Immediatelly read tags for a file bypassing the request queue. */ Index: tags_cache.h =================================================================== --- tags_cache.h (revision 1) +++ tags_cache.h (working copy) @@ -2,7 +2,6 @@ #define TAGS_CACHE_H #include -#include #include "playlist.h" @@ -21,22 +20,42 @@ struct request_queue_node *tail; }; +/* Element of the cache pool. */ +struct cache_list_node +{ + struct cache_list_node *next; + char *file; + time_t mod_time; /* last modification time of the file */ + struct file_tags *tags; + size_t size; /* number of bytes allocated for this node (file name and + tags) */ + int during_operation; /* If set to != 0 there is operation pending on + this node (reading tags). */ +}; + +/* List of items in the cache - olders are first. */ +struct cache_list +{ + struct cache_list_node *head; + struct cache_list_node *tail; +}; + struct tags_cache { - /* BerkeleyDB's stuff for storing cache. */ - DB_ENV *db_env; - DB *db; - u_int32_t locker; - - int max_items; /* maximum number of items in the cache. */ + struct rb_tree search_tree; /* search tree used for fast searching + by file name */ + struct cache_list cache; struct request_queue queues[CLIENTS_MAX]; /* requests queues for each client */ + size_t size; /* number of bytes allocated for the cache */ + size_t max_size; /* maximum allowed cache size */ int stop_reader_thread; /* request for stopping read thread (if non-zero) */ pthread_cond_t request_cond; /* condition for signalizing new requests */ - pthread_mutex_t mutex; /* mutex for all above data (except db because - it's thread-safe) */ + pthread_cond_t response_cond; /* condition for signalizing a cache + node read */ + pthread_mutex_t mutex; /* mutex for all above data */ pthread_t reader_thread; /* tid of the reading thread */ }; Index: audio.c =================================================================== --- audio.c (revision 1) +++ audio.c (working copy) @@ -46,6 +46,7 @@ #endif #include "softmixer.h" +#include "equalizer.h" #include "out_buf.h" #include "protocol.h" @@ -755,11 +756,29 @@ int audio_send_pcm (const char *buf, const size_t size) { char *softmixed = NULL; + char *equalized = NULL; + + if(equalizer_is_active()) + { + equalized = xmalloc(size); + memcpy(equalized, buf, size); + + equalizer_process_buffer(equalized, size, &driver_sound_params); + + buf = equalized; + } if(softmixer_is_active()) { - softmixed = xmalloc(size); - memcpy(softmixed, buf, size); + if(equalized) + { + softmixed = equalized; + } + else + { + softmixed = xmalloc(size); + memcpy(softmixed, buf, size); + } softmixer_process_buffer ( @@ -778,9 +797,12 @@ if (played == 0) fatal ("Audio output error."); - if(softmixed!=NULL) + if(softmixed && !equalized) free(softmixed); + if(equalized) + free(equalized); + return played; } @@ -892,6 +914,7 @@ out_buf_init (&out_buf, options_get_int("OutputBuffer") * 1024); softmixer_init(); + equalizer_init(); plist_init (&playlist); plist_init (&shuffled_plist); @@ -918,6 +941,7 @@ free (last_stream_url); softmixer_shutdown(); + equalizer_shutdown(); } void audio_seek (const int sec) Index: protocol.h =================================================================== --- protocol.h (revision 1) +++ protocol.h (working copy) @@ -122,7 +122,13 @@ #define CMD_GET_AVG_BITRATE 0x33 /* get the average bitrate */ #define CMD_TOGGLE_SOFTMIXER 0x34 /* toggle use of softmixer */ +#define CMD_TOGGLE_EQUALIZER 0x35 /* toggle use of equalizer */ +#define CMD_EQUALIZER_REFRESH 0x36 /* refresh EQ-presets */ +#define CMD_EQUALIZER_PREV 0x37 /* select previous eq-preset */ +#define CMD_EQUALIZER_NEXT 0x38 /* select next eq-preset */ +#define CMD_TOGGLE_MAKE_MONO 0x39 /* toggle mono mixing */ + char *socket_name (); int get_int (int sock, int *i); enum noblock_io_status get_int_noblock (int sock, int *i); Index: server.c =================================================================== --- server.c (revision 1) +++ server.c (working copy) @@ -47,6 +47,7 @@ #include "tags_cache.h" #include "files.h" #include "softmixer.h" +#include "equalizer.h" #define SERVER_LOG "mocp_server_log" #define PID_FILE "pid" @@ -1064,6 +1065,76 @@ add_event_all (EV_MIXER_CHANGE, NULL); } +void update_eq_name() +{ + char buffer[27]; + + char *n = equalizer_current_eqname(); + + int l = strlen(n); + + /* status message can only take strings up to 25 chars + * (without terminating zero) + * The message header has 11 chars (EQ set to...) + */ + if(l>14) + { + n[14] = 0; + n[13] = '.'; + n[12] = '.'; + n[11] = '.'; + } + + sprintf(buffer, "EQ set to: %s", n); + + logit("%s", buffer); + + free(n); + + status_msg(buffer); +} + +void req_toggle_equalizer () +{ + equalizer_set_active(!equalizer_is_active()); + + update_eq_name(); +} + +void req_equalizer_refresh() +{ + equalizer_refresh(); + + status_msg("Equalizer refreshed"); + + logit("Equalizer refreshed"); +} + +void req_equalizer_prev() +{ + equalizer_prev(); + + update_eq_name(); +} + +void req_equalizer_next() +{ + equalizer_next(); + + update_eq_name(); +} + +void req_toggle_make_mono() +{ + char buffer[128]; + + softmixer_set_mono(!softmixer_is_mono()); + + sprintf(buffer, "Mono-Mixing set to: %s", softmixer_is_mono()?"on":"off"); + + status_msg(buffer); +} + /* Handle CMD_GET_FILE_TAGS. Return 0 on error. */ static int get_file_tags (const int cli_id) { @@ -1296,6 +1367,21 @@ if (!req_list_move(cli)) err = 1; break; + case CMD_TOGGLE_EQUALIZER: + req_toggle_equalizer(); + break; + case CMD_EQUALIZER_REFRESH: + req_equalizer_refresh(); + break; + case CMD_EQUALIZER_PREV: + req_equalizer_prev(); + break; + case CMD_EQUALIZER_NEXT: + req_equalizer_next(); + break; + case CMD_TOGGLE_MAKE_MONO: + req_toggle_make_mono(); + break; default: logit ("Bad command (0x%x) from the client.", cmd); err = 1; Index: Makefile.am =================================================================== --- Makefile.am (revision 1) +++ Makefile.am (working copy) @@ -41,8 +41,6 @@ themes.h \ keys.c \ keys.h \ - ltdl.c \ - ltdl.h \ io.c \ io.h \ compat.c \ @@ -55,10 +53,14 @@ tags_cache.h \ utf8.c \ utf8.h \ + audio_helper.h \ + audio_helper.c \ softmixer.c \ softmixer.h \ - lyrics.h \ - lyrics.c + lyrics.h \ + lyrics.c \ + equalizer.h \ + equalizer.c EXTRA_mocp_SOURCES = gnugetopt.h \ getopt.c \ getopt1.c \ @@ -74,7 +76,7 @@ jack.h \ gettext.h man_MANS = mocp.1 -mocp_LDADD = @EXTRA_OBJS@ @LIBINTL@ +mocp_LDADD = @EXTRA_OBJS@ @LIBINTL@ -lltdl mocp_DEPENDENCIES = @EXTRA_OBJS@ mocp_LDFLAGS = @EXTRA_LIBS@ $(RCC_LIBS) EXTRA_DIST = config.example mocp.1 THANKS keymap.example Doxyfile \ Index: options.c =================================================================== --- options.c (revision 1) +++ options.c (working copy) @@ -260,6 +260,8 @@ option_add_str ("OnStop", NULL); option_add_int ("Softmixer_SaveState", 1); + + option_add_int ("Equalizer_SaveState", 1); } /* Return 1 if a parameter to an integer option is valid. */ @@ -291,6 +293,7 @@ || !strcasecmp(name, "SidPlay2_StartAtStart") || !strcasecmp(name, "SidPlay2_PlaySubTunes") || !strcasecmp(name, "Softmixer_SaveState") + || !strcasecmp(name, "Equalizer_SaveState") ) { if (!(val == 1 || val == 0)) return 0;