2012-03-27 23:52:15 +00:00
|
|
|
/***************************************************************************
|
|
|
|
* __________ __ ___.
|
|
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
|
|
* \/ \/ \/ \/ \/
|
|
|
|
* $Id$
|
|
|
|
*
|
|
|
|
* Copyright (C) 2006 Thom Johansen
|
2012-05-01 07:58:27 +00:00
|
|
|
* Copyright (C) 2010 Bertrik Sikken
|
2012-03-27 23:52:15 +00:00
|
|
|
* Copyright (C) 2012 Michael Sevakis
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
|
|
* KIND, either express or implied.
|
|
|
|
*
|
|
|
|
****************************************************************************/
|
2013-05-27 07:23:33 +00:00
|
|
|
#include "rbcodecconfig.h"
|
2012-03-27 23:52:15 +00:00
|
|
|
#include "fixedpoint.h"
|
|
|
|
#include "fracmul.h"
|
|
|
|
#include "replaygain.h"
|
2013-05-23 17:58:51 +00:00
|
|
|
#include "dsp_misc.h"
|
2012-03-27 23:52:15 +00:00
|
|
|
#include "dsp_proc_entry.h"
|
2012-04-29 21:31:30 +00:00
|
|
|
#include "dsp_filter.h"
|
|
|
|
#include "crossfeed.h"
|
|
|
|
#include <string.h>
|
2012-03-27 23:52:15 +00:00
|
|
|
|
|
|
|
/* Implemented here or in target assembly code */
|
2012-05-01 07:58:27 +00:00
|
|
|
void crossfeed_process(struct dsp_proc_entry *this,
|
|
|
|
struct dsp_buffer **buf_p);
|
|
|
|
void crossfeed_meier_process(struct dsp_proc_entry *this,
|
|
|
|
struct dsp_buffer **buf_p);
|
|
|
|
|
2012-03-27 23:52:15 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Applies crossfeed to the stereo signal.
|
|
|
|
*
|
|
|
|
* Crossfeed is a process where listening over speakers is simulated. This
|
|
|
|
* is good for old hard panned stereo records, which might be quite fatiguing
|
|
|
|
* to listen to on headphones with no crossfeed.
|
|
|
|
*/
|
|
|
|
|
2013-05-23 17:58:51 +00:00
|
|
|
#define DELAY_LEN(fs) ((300*(fs) / 1000000)*2) /* ~300 uS */
|
|
|
|
|
2012-03-27 23:52:15 +00:00
|
|
|
/* Crossfeed */
|
|
|
|
static struct crossfeed_state
|
|
|
|
{
|
2012-05-01 07:58:27 +00:00
|
|
|
union
|
|
|
|
{
|
2013-05-23 17:58:51 +00:00
|
|
|
struct /* Data for meier crossfeed */
|
2012-05-01 07:58:27 +00:00
|
|
|
{
|
2013-05-23 17:58:51 +00:00
|
|
|
int32_t reserved; /* 00h: Reserved: overlaps gain */
|
|
|
|
int32_t vcl; /* 04h: Left filter output */
|
|
|
|
int32_t vcr; /* 08h: Right filter output */
|
|
|
|
int32_t vdiff; /* 0ch: L-R difference signal */
|
|
|
|
int32_t coef1; /* 10h: Left/right filter coef */
|
|
|
|
int32_t coef2; /* 14h: Crossfeed filter coef */
|
2012-05-01 07:58:27 +00:00
|
|
|
};
|
2013-05-23 17:58:51 +00:00
|
|
|
struct /* Data for custom crossfeed */
|
2012-05-01 07:58:27 +00:00
|
|
|
{
|
2013-05-23 17:58:51 +00:00
|
|
|
int32_t gain; /* 00h: Direct path gain */
|
|
|
|
int32_t coefs[3]; /* 04h: Filter coefficients: b0, b1, a1 */
|
2012-05-01 07:58:27 +00:00
|
|
|
int32_t history[4]; /* 10h: Format is x[n - 1], y[n - 1] (L + R) */
|
2013-05-23 17:58:51 +00:00
|
|
|
int32_t *index; /* 20h: Current pointer into the delay line */
|
|
|
|
int32_t *index_max; /* 24h: Current max pointer of delay line */
|
|
|
|
/* 28h: Delay line buffer (L + R interleaved) */
|
|
|
|
int32_t delay[DELAY_LEN(DSP_OUT_MAX_HZ)]; /* Target-dependent size */
|
2012-05-01 07:58:27 +00:00
|
|
|
};
|
|
|
|
};
|
2012-03-27 23:52:15 +00:00
|
|
|
} crossfeed_state IBSS_ATTR;
|
|
|
|
|
2012-05-01 07:58:27 +00:00
|
|
|
static int crossfeed_type = CROSSFEED_TYPE_NONE;
|
2013-05-23 17:58:51 +00:00
|
|
|
/* Cached custom settings */
|
|
|
|
static long crossfeed_lf_gain;
|
|
|
|
static long crossfeed_hf_gain;
|
|
|
|
static long crossfeed_cutoff;
|
2012-05-01 07:58:27 +00:00
|
|
|
|
2012-03-27 23:52:15 +00:00
|
|
|
/* Discard the sample histories */
|
|
|
|
static void crossfeed_flush(struct dsp_proc_entry *this)
|
|
|
|
{
|
|
|
|
struct crossfeed_state *state = (void *)this->data;
|
2012-05-01 07:58:27 +00:00
|
|
|
|
2012-12-19 22:34:57 +00:00
|
|
|
if (crossfeed_type != CROSSFEED_TYPE_CUSTOM)
|
2012-05-01 07:58:27 +00:00
|
|
|
{
|
2012-12-19 22:34:57 +00:00
|
|
|
state->vcl = state->vcr = state->vdiff = 0;
|
2012-05-01 07:58:27 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2013-05-23 17:58:51 +00:00
|
|
|
memset(state->history, 0, sizeof (state->history));
|
|
|
|
memset(state->delay, 0, sizeof (state->delay));
|
2012-12-19 22:34:57 +00:00
|
|
|
state->index = state->delay;
|
2012-05-01 07:58:27 +00:00
|
|
|
}
|
2012-03-27 23:52:15 +00:00
|
|
|
}
|
|
|
|
|
2013-05-23 17:58:51 +00:00
|
|
|
static void crossfeed_meier_update_filter(struct crossfeed_state *state,
|
|
|
|
unsigned int fout)
|
|
|
|
{
|
|
|
|
/* 1 / (F.Rforward.C) */
|
|
|
|
state->coef1 = fp_div(2128, fout, 31);
|
|
|
|
/* 1 / (F.Rcross.C) */
|
|
|
|
state->coef2 = fp_div(1000, fout, 31);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void crossfeed_custom_update_filter(struct crossfeed_state *state,
|
|
|
|
unsigned int fout)
|
|
|
|
{
|
|
|
|
long lf_gain = crossfeed_lf_gain;
|
|
|
|
long hf_gain = crossfeed_hf_gain;
|
|
|
|
long cutoff = crossfeed_cutoff;
|
|
|
|
int32_t *c = state->coefs;
|
|
|
|
|
|
|
|
long scaler = get_replaygain_int(lf_gain * 10) << 7;
|
|
|
|
|
|
|
|
cutoff = fp_div(cutoff, fout, 32);
|
|
|
|
hf_gain -= lf_gain;
|
|
|
|
/* Divide cutoff by sqrt(10^(hf_gain/20)) to place cutoff at the -3 dB
|
|
|
|
* point instead of shelf midpoint. This is for compatibility with the old
|
|
|
|
* crossfeed shelf filter and should be removed if crossfeed settings are
|
|
|
|
* ever made incompatible for any other good reason.
|
|
|
|
*/
|
|
|
|
cutoff = fp_div(cutoff, get_replaygain_int(hf_gain*5), 24);
|
|
|
|
|
|
|
|
filter_shelf_coefs(cutoff, hf_gain, false, c);
|
|
|
|
/* Scale coefs by LF gain and shift them to s0.31 format. We have no gains
|
|
|
|
* over 1 and can do this safely
|
|
|
|
*/
|
|
|
|
c[0] = FRACMUL_SHL(c[0], scaler, 4);
|
|
|
|
c[1] = FRACMUL_SHL(c[1], scaler, 4);
|
|
|
|
c[2] <<= 4;
|
|
|
|
}
|
|
|
|
|
2012-03-27 23:52:15 +00:00
|
|
|
|
|
|
|
/** DSP interface **/
|
|
|
|
|
2012-05-01 07:58:27 +00:00
|
|
|
/* Set the type of crossfeed to use */
|
|
|
|
void dsp_set_crossfeed_type(int type)
|
2012-03-27 23:52:15 +00:00
|
|
|
{
|
2012-05-01 07:58:27 +00:00
|
|
|
if (type == crossfeed_type)
|
|
|
|
return; /* No change */
|
|
|
|
|
|
|
|
crossfeed_type = type;
|
2012-03-27 23:52:15 +00:00
|
|
|
|
|
|
|
struct dsp_config *dsp = dsp_get_config(CODEC_IDX_AUDIO);
|
2012-05-01 07:58:27 +00:00
|
|
|
dsp_proc_enable(dsp, DSP_PROC_CROSSFEED, type != CROSSFEED_TYPE_NONE);
|
2012-03-27 23:52:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Set the gain of the dry mix */
|
|
|
|
void dsp_set_crossfeed_direct_gain(int gain)
|
|
|
|
{
|
|
|
|
uint32_t gain32 = get_replaygain_int(gain * 10);
|
|
|
|
crossfeed_state.gain =
|
|
|
|
gain32 >= (0x80000000ul >> 7) ? 0x7ffffffful: (gain32 << 7);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Both gains should be below 0 dB */
|
|
|
|
void dsp_set_crossfeed_cross_params(long lf_gain, long hf_gain, long cutoff)
|
|
|
|
{
|
2013-05-23 17:58:51 +00:00
|
|
|
crossfeed_lf_gain = lf_gain;
|
|
|
|
crossfeed_hf_gain = hf_gain;
|
|
|
|
crossfeed_cutoff = cutoff;
|
2012-03-27 23:52:15 +00:00
|
|
|
|
2013-08-16 13:28:36 +00:00
|
|
|
if (crossfeed_type != CROSSFEED_TYPE_CUSTOM)
|
|
|
|
return;
|
|
|
|
|
2013-05-23 17:58:51 +00:00
|
|
|
struct dsp_config *dsp = dsp_get_config(CODEC_IDX_AUDIO);
|
|
|
|
crossfeed_custom_update_filter(&crossfeed_state,
|
|
|
|
dsp_get_output_frequency(dsp));
|
2012-03-27 23:52:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#if !defined(CPU_COLDFIRE) && !defined(CPU_ARM)
|
|
|
|
/* Apply the crossfade to the buffer in place */
|
|
|
|
void crossfeed_process(struct dsp_proc_entry *this, struct dsp_buffer **buf_p)
|
|
|
|
{
|
|
|
|
struct crossfeed_state *state = (void *)this->data;
|
|
|
|
struct dsp_buffer *buf = *buf_p;
|
|
|
|
|
|
|
|
int32_t *hist_l = &state->history[0];
|
|
|
|
int32_t *hist_r = &state->history[2];
|
|
|
|
int32_t *delay = state->delay;
|
|
|
|
int32_t *coefs = &state->coefs[0];
|
|
|
|
int32_t gain = state->gain;
|
|
|
|
int32_t *di = state->index;
|
2013-05-23 17:58:51 +00:00
|
|
|
int32_t *di_max = state->index_max;
|
2012-03-27 23:52:15 +00:00
|
|
|
|
|
|
|
int count = buf->remcount;
|
|
|
|
|
|
|
|
for (int i = 0; i < count; i++)
|
|
|
|
{
|
|
|
|
int32_t left = buf->p32[0][i];
|
|
|
|
int32_t right = buf->p32[1][i];
|
|
|
|
|
|
|
|
/* Filter delayed sample from left speaker */
|
|
|
|
int32_t acc = FRACMUL(*di, coefs[0]);
|
|
|
|
acc += FRACMUL(hist_l[0], coefs[1]);
|
|
|
|
acc += FRACMUL(hist_l[1], coefs[2]);
|
|
|
|
/* Save filter history for left speaker */
|
|
|
|
hist_l[1] = acc;
|
|
|
|
hist_l[0] = *di;
|
|
|
|
*di++ = left;
|
|
|
|
/* Filter delayed sample from right speaker */
|
|
|
|
acc = FRACMUL(*di, coefs[0]);
|
|
|
|
acc += FRACMUL(hist_r[0], coefs[1]);
|
|
|
|
acc += FRACMUL(hist_r[1], coefs[2]);
|
|
|
|
/* Save filter history for right speaker */
|
|
|
|
hist_r[1] = acc;
|
|
|
|
hist_r[0] = *di;
|
|
|
|
*di++ = right;
|
|
|
|
/* Now add the attenuated direct sound and write to outputs */
|
|
|
|
buf->p32[0][i] = FRACMUL(left, gain) + hist_r[1];
|
|
|
|
buf->p32[1][i] = FRACMUL(right, gain) + hist_l[1];
|
|
|
|
|
|
|
|
/* Wrap delay line index if bigger than delay line size */
|
2013-05-23 17:58:51 +00:00
|
|
|
if (di >= di_max)
|
2012-03-27 23:52:15 +00:00
|
|
|
di = delay;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Write back local copies of data we've modified */
|
|
|
|
state->index = di;
|
|
|
|
}
|
|
|
|
#endif /* CPU */
|
|
|
|
|
2012-05-01 07:58:27 +00:00
|
|
|
#if !defined(CPU_COLDFIRE) && !defined(CPU_ARM)
|
|
|
|
/**
|
|
|
|
* Implementation of the "simple" passive crossfeed circuit by Jan Meier.
|
|
|
|
* See also: http://www.meier-audio.homepage.t-online.de/passivefilter.htm
|
|
|
|
*/
|
|
|
|
|
|
|
|
void crossfeed_meier_process(struct dsp_proc_entry *this,
|
|
|
|
struct dsp_buffer **buf_p)
|
|
|
|
{
|
|
|
|
struct dsp_buffer *buf = *buf_p;
|
|
|
|
|
|
|
|
/* Get filter state */
|
|
|
|
struct crossfeed_state *state = (struct crossfeed_state *)this->data;
|
|
|
|
int32_t vcl = state->vcl;
|
|
|
|
int32_t vcr = state->vcr;
|
|
|
|
int32_t vdiff = state->vdiff;
|
|
|
|
int32_t coef1 = state->coef1;
|
|
|
|
int32_t coef2 = state->coef2;
|
|
|
|
|
|
|
|
int count = buf->remcount;
|
|
|
|
|
|
|
|
for (int i = 0; i < count; i++)
|
|
|
|
{
|
|
|
|
/* Calculate new output */
|
|
|
|
int32_t lout = buf->p32[0][i] + vcl;
|
|
|
|
int32_t rout = buf->p32[1][i] + vcr;
|
|
|
|
buf->p32[0][i] = lout;
|
|
|
|
buf->p32[1][i] = rout;
|
|
|
|
|
|
|
|
/* Update filter state */
|
|
|
|
int32_t common = FRACMUL(vdiff, coef2);
|
|
|
|
vcl -= FRACMUL(vcl, coef1) + common;
|
|
|
|
vcr -= FRACMUL(vcr, coef1) - common;
|
|
|
|
|
|
|
|
vdiff = lout - rout;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Store filter state */
|
|
|
|
state->vcl = vcl;
|
|
|
|
state->vcr = vcr;
|
|
|
|
state->vdiff = vdiff;
|
|
|
|
}
|
|
|
|
#endif /* CPU */
|
|
|
|
|
2012-12-19 22:34:57 +00:00
|
|
|
/* Update the processing function according to crossfeed type */
|
|
|
|
static void update_process_fn(struct dsp_proc_entry *this,
|
|
|
|
struct dsp_config *dsp)
|
|
|
|
{
|
|
|
|
struct crossfeed_state *state = (struct crossfeed_state *)this->data;
|
2013-05-23 17:58:51 +00:00
|
|
|
dsp_proc_fn_type fn;
|
|
|
|
|
|
|
|
unsigned int fout = dsp_get_output_frequency(dsp);
|
2012-12-19 22:34:57 +00:00
|
|
|
|
|
|
|
if (crossfeed_type != CROSSFEED_TYPE_CUSTOM)
|
|
|
|
{
|
2013-05-23 17:58:51 +00:00
|
|
|
crossfeed_meier_update_filter(state, fout);
|
2012-12-19 22:34:57 +00:00
|
|
|
fn = crossfeed_meier_process;
|
|
|
|
}
|
2013-05-23 17:58:51 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
state->index_max = state->delay + DELAY_LEN(fout);
|
|
|
|
crossfeed_custom_update_filter(state, fout);
|
|
|
|
fn = crossfeed_process;
|
|
|
|
}
|
2012-12-19 22:34:57 +00:00
|
|
|
|
|
|
|
if (this->process != fn)
|
|
|
|
{
|
|
|
|
this->process = fn; /* Set proper function */
|
|
|
|
if (dsp_proc_active(dsp, DSP_PROC_CROSSFEED))
|
|
|
|
crossfeed_flush(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Crossfeed boot/format change function */
|
|
|
|
static intptr_t crossfeed_new_format(struct dsp_proc_entry *this,
|
|
|
|
struct dsp_config *dsp,
|
|
|
|
struct sample_format *format)
|
|
|
|
{
|
|
|
|
DSP_PRINT_FORMAT(DSP_PROC_CROSSFEED, format);
|
|
|
|
|
|
|
|
bool was_active = dsp_proc_active(dsp, DSP_PROC_CROSSFEED);
|
|
|
|
bool active = format->num_channels >= 2;
|
|
|
|
dsp_proc_activate(dsp, DSP_PROC_CROSSFEED, active);
|
|
|
|
|
|
|
|
if (active)
|
|
|
|
{
|
|
|
|
if (!was_active)
|
|
|
|
crossfeed_flush(this); /* Going online */
|
|
|
|
|
|
|
|
return PROC_NEW_FORMAT_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Can't do this. Sleep until next change */
|
|
|
|
DEBUGF(" DSP_PROC_CROSSFEED- deactivated\n");
|
|
|
|
return PROC_NEW_FORMAT_DEACTIVATED;
|
|
|
|
}
|
|
|
|
|
2012-03-27 23:52:15 +00:00
|
|
|
/* DSP message hook */
|
|
|
|
static intptr_t crossfeed_configure(struct dsp_proc_entry *this,
|
|
|
|
struct dsp_config *dsp,
|
|
|
|
unsigned int setting,
|
|
|
|
intptr_t value)
|
|
|
|
{
|
2012-12-19 22:34:57 +00:00
|
|
|
intptr_t retval = 0;
|
|
|
|
|
2012-03-27 23:52:15 +00:00
|
|
|
switch (setting)
|
|
|
|
{
|
|
|
|
case DSP_PROC_INIT:
|
2012-05-01 07:58:27 +00:00
|
|
|
if (value == 0)
|
|
|
|
this->data = (intptr_t)&crossfeed_state;
|
|
|
|
|
2013-05-23 17:58:51 +00:00
|
|
|
case DSP_SET_OUT_FREQUENCY:
|
2012-12-19 22:34:57 +00:00
|
|
|
update_process_fn(this, dsp);
|
2012-05-01 07:58:27 +00:00
|
|
|
break;
|
|
|
|
|
2012-03-27 23:52:15 +00:00
|
|
|
case DSP_FLUSH:
|
|
|
|
crossfeed_flush(this);
|
|
|
|
break;
|
|
|
|
|
2012-12-19 22:34:57 +00:00
|
|
|
case DSP_PROC_NEW_FORMAT:
|
|
|
|
retval = crossfeed_new_format(this, dsp,
|
|
|
|
(struct sample_format *)value);
|
2012-03-27 23:52:15 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2012-12-19 22:34:57 +00:00
|
|
|
return retval;
|
2012-03-27 23:52:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Database entry */
|
|
|
|
DSP_PROC_DB_ENTRY(
|
|
|
|
CROSSFEED,
|
|
|
|
crossfeed_configure);
|