You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
792 lines
27 KiB
C
792 lines
27 KiB
C
/*
|
|
|
|
FreeSurround Output Plugin
|
|
|
|
Copyright (c) 2009 Michel Cailhol
|
|
Based on foo_dsp_freesurround (c) 2007 Christian Kothe
|
|
|
|
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 program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#define __USE_XOPEN
|
|
#include <unistd.h>
|
|
#include <alsa/asoundlib.h>
|
|
#include <alsa/pcm_external.h>
|
|
#include <alsa/pcm_plugin.h>
|
|
#include <fftw3.h>
|
|
#include <complex.h>
|
|
#include <math.h>
|
|
|
|
const float PI = 3.141592654;
|
|
const float epsilon = 0.000001;
|
|
const float center_level = 0.5*sqrt(0.5); // gain of center channel
|
|
#define BLOCK_SIZE 8192
|
|
#define DBL_BLOCK_SIZE (2 * BLOCK_SIZE)
|
|
#define DBL_BLOCK_SIZE_MIN_ONE ((2 * BLOCK_SIZE) - 1)
|
|
const int INPUT_CHANNELS = 2;
|
|
const int OUTPUT_CHANNELS = 6;
|
|
|
|
|
|
typedef float complex cfloat;
|
|
typedef float farraybs[BLOCK_SIZE];
|
|
typedef float farraybse[BLOCK_SIZE + BLOCK_SIZE/2];
|
|
typedef short sarraydbs[DBL_BLOCK_SIZE];
|
|
typedef cfloat cfarraybs[BLOCK_SIZE];
|
|
|
|
typedef struct snd_pcm_fsupmix snd_pcm_fsupmix_t;
|
|
|
|
typedef void (*fsupmixer_t)(snd_pcm_fsupmix_t *fsmix,
|
|
const snd_pcm_channel_area_t *dst_areas,
|
|
snd_pcm_uframes_t dst_offset,
|
|
const snd_pcm_channel_area_t *src_areas,
|
|
snd_pcm_uframes_t src_offset,
|
|
snd_pcm_uframes_t size);
|
|
|
|
|
|
struct snd_pcm_fsupmix {
|
|
snd_pcm_extplug_t ext;
|
|
snd_pcm_format_t format;
|
|
unsigned int channels;
|
|
unsigned int rate;
|
|
unsigned int bitrate;
|
|
int outbuf_size;
|
|
snd_pcm_uframes_t transfer;
|
|
int remain;
|
|
unsigned int slave_period_size;
|
|
unsigned int slave_buffer_size;
|
|
snd_pcm_hw_params_t *hw_params;
|
|
farraybs myinput[2];
|
|
sarraydbs myoutput[6];
|
|
unsigned int myout_count;
|
|
unsigned int myout_write_offset;
|
|
unsigned int myout_read_offset;
|
|
|
|
int preupmixed;
|
|
// FFTW data structures
|
|
float *lt,*rt,*dst; // left total, right total (source arrays), destination array
|
|
fftwf_complex *dftL,*dftR,*src; // intermediate arrays (FFTs of lt & rt, processing source)
|
|
fftwf_plan loadL,loadR,store; // plans for loading the data into the intermediate format and back
|
|
|
|
// buffers
|
|
cfarraybs frontL; // the signal (phase-corrected) in the frequency domain
|
|
cfarraybs frontR; // the signal (phase-corrected) in the frequency domain
|
|
cfarraybs avg; // the signal (phase-corrected) in the frequency domain
|
|
cfarraybs surL; // the signal (phase-corrected) in the frequency domain
|
|
cfarraybs surR; // the signal (phase-corrected) in the frequency domain
|
|
cfarraybs trueavg; // for lfe generation
|
|
|
|
farraybs xfs ; // the feature space positions for each frequency bin
|
|
farraybs yfs ; // the feature space positions for each frequency bin
|
|
farraybs wnd ; // the window function, precalculated
|
|
farraybs filter[6] ; // a frequency filter for each output channel
|
|
|
|
farraybse fsinbuf[2]; // the sliding input buffers
|
|
farraybse fsoutbuf[6]; // the sliding output buffers
|
|
|
|
// coefficients
|
|
float surround_high,surround_low; // high and low surround mixing coefficient (e.g. 0.8165/0.5774)
|
|
float surround_balance; // the xfs balance that follows from the coeffs
|
|
float surround_level; // gain for the surround channels (follows from the coeffs
|
|
float master_gain; // gain for all channels
|
|
float phase_offsetL, phase_offsetR;// phase shifts to be applied to the rear channels
|
|
float front_separation; // front stereo separation
|
|
float rear_separation; // rear stereo separation
|
|
int linear_steering; // whether the steering should be linear or not
|
|
float center_width; // distribution of the center information towards the front left/right channels
|
|
float dimension;
|
|
float adaption_rate;
|
|
unsigned int phase_mode;
|
|
|
|
};
|
|
|
|
|
|
|
|
static inline void *area_addr(const snd_pcm_channel_area_t *area, snd_pcm_uframes_t offset) {
|
|
unsigned int bitofs = area->first + area->step * offset;
|
|
return (char *) area->addr + bitofs / 8;
|
|
}
|
|
|
|
static inline unsigned int area_step(const snd_pcm_channel_area_t *area) {
|
|
return area->step / 8;
|
|
}
|
|
|
|
|
|
|
|
|
|
// set the assumed surround mixing coefficients
|
|
static void surround_coefficients(snd_pcm_fsupmix_t *fsmix, float a, float b) {
|
|
fsmix->master_gain = 1.0;
|
|
// calc the simple coefficients
|
|
fsmix->surround_high = a;
|
|
fsmix->surround_low = b;
|
|
fsmix->surround_balance = (a-b)/(a+b);
|
|
// increase the rear volume
|
|
//fsmix->surround_level = 1/(a+b);
|
|
fsmix->surround_level = 0.85;
|
|
}
|
|
|
|
|
|
// set the phase shifting mode for decoding
|
|
// 0 = (+0°,+0°) - music mode
|
|
// 1 = (+0°,+180°) - PowerDVD compatibility
|
|
// 2 = (+180°,+0°) - BeSweet compatibility
|
|
// 3 = (-90°,+90°) - experimental exact reconstuction; not clear whether this is entirely correct
|
|
static void set_phase_mode(snd_pcm_fsupmix_t *fsmix) {
|
|
const float modes[4][2] = {{0,0},{0,PI},{PI,0},{-PI/2,PI/2}};
|
|
fsmix->phase_offsetL = modes[fsmix->phase_mode][0];
|
|
fsmix->phase_offsetR = modes[fsmix->phase_mode][1];
|
|
}
|
|
|
|
// what steering mode should be chosen
|
|
static void steering_mode(snd_pcm_fsupmix_t *fsmix, int mode) {
|
|
fsmix->linear_steering = mode;
|
|
}
|
|
|
|
// set front & rear separation controls
|
|
static void separation(snd_pcm_fsupmix_t *fsmix, float front, float rear) {
|
|
fsmix->front_separation = front;
|
|
fsmix->rear_separation = rear;
|
|
}
|
|
|
|
// set lfe filter params
|
|
static void sample_rate(snd_pcm_fsupmix_t *fsmix, unsigned int srate) {
|
|
// lfe filter is just straight through band limited
|
|
unsigned int cutoff = (250*BLOCK_SIZE)/srate;
|
|
unsigned f;
|
|
for (f=0;f<=BLOCK_SIZE/2;f++) {
|
|
if ((f>=2) && (f<cutoff))
|
|
//fsmix->filter[5][f] = 0.5*sqrt(0.5);
|
|
fsmix->filter[5][f] = 1.0;
|
|
else
|
|
fsmix->filter[5][f] = 0.0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
// polar <-> cartesian coodinates conversion
|
|
static inline float amplitude(const float cf[2]) { return sqrt(cf[0]*cf[0] + cf[1]*cf[1]); }
|
|
static inline float phase(const float cf[2]) { return atan2(cf[1],cf[0]); }
|
|
static cfloat polar(float a, float p) {
|
|
cfloat cpf = a*cos(p) + I*(a*sin(p));
|
|
return cpf;
|
|
//return cfloat (a*cos(p),a*sin(p));
|
|
}
|
|
static inline float sqr(float x) { return x*x; }
|
|
// the dreaded min/max
|
|
static inline float min(float a, float b) { return a<b?a:b; }
|
|
static inline float max(float a, float b) { return a>b?a:b; }
|
|
static inline float clamp(float x) { return max(-1,min(1,x)); }
|
|
|
|
|
|
|
|
// map from amplitude difference and phase difference to yfs
|
|
static inline double get_yfs(double ampDiff, double phaseDiff) {
|
|
double x = 1-(((1-sqr(ampDiff))*phaseDiff)/PI*2);
|
|
return 0.16468622925824683 + 0.5009268347818189*x - 0.06462757726992101*x*x
|
|
+ 0.09170680403453149*x*x*x + 0.2617754892323973*tan(x) - 0.04180413533856156*sqr(tan(x));
|
|
}
|
|
|
|
// map from amplitude difference and yfs to xfs
|
|
static inline double get_xfs(double ampDiff, double yfs) {
|
|
double x=ampDiff,y=yfs;
|
|
return 2.464833559224702*x - 423.52131153259404*x*y +
|
|
67.8557858606918*x*x*x*y + 788.2429425544392*x*y*y -
|
|
79.97650354902909*x*x*x*y*y - 513.8966153850349*x*y*y*y +
|
|
35.68117670186306*x*x*x*y*y*y + 13867.406173420834*y*asin(x) -
|
|
2075.8237075786396*y*y*asin(x) - 908.2722068360281*y*y*y*asin(x) -
|
|
12934.654772878019*asin(x)*sin(y) - 13216.736529661162*y*tan(x) +
|
|
1288.6463247741938*y*y*tan(x) + 1384.372969378453*y*y*y*tan(x) +
|
|
12699.231471126128*sin(y)*tan(x) + 95.37131275594336*sin(x)*tan(y) -
|
|
91.21223198407546*tan(x)*tan(y);
|
|
}
|
|
|
|
// filter the complex source signal and add it to target
|
|
static void apply_filter(snd_pcm_fsupmix_t *fsmix, cfarraybs signal, int nfilter, int nchannel ) {
|
|
// filter the signal
|
|
unsigned f;
|
|
for (f=0; f<=BLOCK_SIZE/2; f++) {
|
|
fsmix->src[f][0] = crealf(signal[f]) * fsmix->filter[nfilter][f];
|
|
fsmix->src[f][1] = cimagf(signal[f]) * fsmix->filter[nfilter][f];
|
|
}
|
|
// transform into time domain
|
|
fftwf_execute(fsmix->store);
|
|
// add the result to target, windowed
|
|
unsigned k;
|
|
for ( k=0; k<BLOCK_SIZE; k++) {
|
|
fsmix->fsoutbuf[nchannel][k + BLOCK_SIZE/2] += fsmix->wnd[k]*fsmix->dst[k];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
// CORE FUNCTION: decode a block of data
|
|
static void block_decode(snd_pcm_fsupmix_t *fsmix, int buffoffset) {
|
|
|
|
|
|
// 1. scale the input by the window function; this serves a dual purpose:
|
|
// - first it improves the FFT resolution b/c boundary discontinuities (and their frequencies) get removed
|
|
// - second it allows for smooth blending of varying filters between the blocks
|
|
unsigned k;
|
|
for (k=0; k<BLOCK_SIZE; k++) {
|
|
fsmix->lt[k] = fsmix->fsinbuf[0][k + buffoffset] * fsmix->wnd[k] * fsmix->master_gain;
|
|
fsmix->rt[k] = fsmix->fsinbuf[1][k + buffoffset] * fsmix->wnd[k] * fsmix->master_gain;
|
|
}
|
|
|
|
// ... and tranform it into the frequency domain
|
|
fftwf_execute(fsmix->loadL);
|
|
fftwf_execute(fsmix->loadR);
|
|
|
|
// 2. compare amplitude and phase of each DFT bin and produce the X/Y coordinates in the sound field
|
|
unsigned f;
|
|
for (f=0; f<=BLOCK_SIZE/2; f++) {
|
|
// get left/right amplitudes/phases
|
|
float ampL = amplitude(fsmix->dftL[f]), ampR = amplitude(fsmix->dftR[f]);
|
|
float phaseL = phase(fsmix->dftL[f]), phaseR = phase(fsmix->dftR[f]);
|
|
|
|
// calculate the amplitude/phase difference
|
|
float ampDiff = clamp((ampL+ampR < epsilon) ? 0 : (ampR-ampL) / (ampR+ampL));
|
|
float phaseDiff = phaseL - phaseR;
|
|
if (phaseDiff < -PI) phaseDiff += 2*PI;
|
|
if (phaseDiff > PI) phaseDiff -= 2*PI;
|
|
phaseDiff = fabs(phaseDiff);
|
|
|
|
if (fsmix->linear_steering) {
|
|
// --- the new linear mode ---
|
|
|
|
// get sound field x/y position
|
|
fsmix->yfs[f] = get_yfs(ampDiff,phaseDiff);
|
|
fsmix->xfs[f] = get_xfs(ampDiff, fsmix->yfs[f]);
|
|
|
|
// add dimension control
|
|
fsmix->yfs[f] = clamp(fsmix->yfs[f] - fsmix->dimension);
|
|
|
|
// add crossfeed control
|
|
fsmix->xfs[f] = clamp(fsmix->xfs[f] * (fsmix->front_separation*(1+fsmix->yfs[f])/2 + fsmix->rear_separation*(1-fsmix->yfs[f])/2));
|
|
|
|
// 3. generate frequency filters for each output channel
|
|
float left = (1-fsmix->xfs[f])/2, right = (1+fsmix->xfs[f])/2;
|
|
float front = (1+fsmix->yfs[f])/2, back = (1-fsmix->yfs[f])/2;
|
|
float volume[5] = {
|
|
front * (left * fsmix->center_width + max(0,-fsmix->xfs[f]) * (1-fsmix->center_width)), // left
|
|
front * center_level*((1-fabs(fsmix->xfs[f])) * (1-fsmix->center_width)), // center
|
|
front * (right * fsmix->center_width + max(0, fsmix->xfs[f]) * (1-fsmix->center_width)), // right
|
|
back * fsmix->surround_level * left, // left surround
|
|
back * fsmix->surround_level * right // right surround
|
|
};
|
|
|
|
// adapt the prior filter
|
|
unsigned c;
|
|
for (c=0;c<5;c++)
|
|
fsmix->filter[c][f] = (1-fsmix->adaption_rate)*fsmix->filter[c][f] + fsmix->adaption_rate*volume[c]/BLOCK_SIZE;
|
|
|
|
} else {
|
|
// --- the old & simple steering mode ---
|
|
// calculate the amplitude/phase difference
|
|
float ampDiff = clamp((ampL+ampR < epsilon) ? 0 : (ampR-ampL) / (ampR+ampL));
|
|
float phaseDiff = phaseL - phaseR;
|
|
if (phaseDiff < -PI) phaseDiff += 2*PI;
|
|
if (phaseDiff > PI) phaseDiff -= 2*PI;
|
|
phaseDiff = fabs(phaseDiff);
|
|
|
|
// determine sound field x-position
|
|
fsmix->xfs[f] = ampDiff;
|
|
|
|
// determine preliminary sound field y-position from phase difference
|
|
fsmix->yfs[f] = 1 - (phaseDiff/PI)*2;
|
|
|
|
if (fabs(fsmix->xfs[f]) > fsmix->surround_balance) {
|
|
// blend linearly between the surrounds and the fronts if the balance exceeds the surround encoding balance
|
|
// this is necessary because the sound field is trapezoidal and will be stretched behind the listener
|
|
float frontness = (fabs(fsmix->xfs[f]) - fsmix->surround_balance)/(1-fsmix->surround_balance);
|
|
fsmix->yfs[f] = (1-frontness) * fsmix->yfs[f] + frontness * 1;
|
|
}
|
|
|
|
// add dimension control
|
|
fsmix->yfs[f] = clamp(fsmix->yfs[f] - fsmix->dimension);
|
|
|
|
// add crossfeed control
|
|
fsmix->xfs[f] = clamp(fsmix->xfs[f] * (fsmix->front_separation*(1+fsmix->yfs[f])/2 + fsmix->rear_separation*(1-fsmix->yfs[f])/2));
|
|
|
|
// 3. generate frequency filters for each output channel, according to the signal position
|
|
// the sum of all channel volumes must be 1.0
|
|
float left = (1-fsmix->xfs[f])/2, right = (1+fsmix->xfs[f])/2;
|
|
float front = (1+fsmix->yfs[f])/2, back = (1-fsmix->yfs[f])/2;
|
|
float volume[5] = {
|
|
front * (left * fsmix->center_width + max(0,-fsmix->xfs[f]) * (1-fsmix->center_width)), // left
|
|
front * center_level*((1-fabs(fsmix->xfs[f])) * (1-fsmix->center_width)), // center
|
|
front * (right * fsmix->center_width + max(0, fsmix->xfs[f]) * (1-fsmix->center_width)), // right
|
|
back * fsmix->surround_level*max(0,min(1,((1-(fsmix->xfs[f]/fsmix->surround_balance))/2))), // left surround
|
|
back * fsmix->surround_level*max(0,min(1,((1+(fsmix->xfs[f]/fsmix->surround_balance))/2))) // right surround
|
|
};
|
|
|
|
// adapt the prior filter
|
|
unsigned c;
|
|
for (c=0;c<5;c++)
|
|
fsmix->filter[c][f] = (1-fsmix->adaption_rate)*fsmix->filter[c][f] + fsmix->adaption_rate*volume[c]/BLOCK_SIZE;
|
|
}
|
|
|
|
// ... and build the signal which we want to position
|
|
fsmix->frontL[f] = polar(ampL+ampR,phaseL);
|
|
fsmix->frontR[f] = polar(ampL+ampR,phaseR);
|
|
fsmix->avg[f] = fsmix->frontL[f] + fsmix->frontR[f];
|
|
fsmix->surL[f] = polar(ampL+ampR,phaseL+fsmix->phase_offsetL);
|
|
fsmix->surR[f] = polar(ampL+ampR,phaseR+fsmix->phase_offsetR);
|
|
fsmix->trueavg[f] = (fsmix->dftL[f][0] + fsmix->dftR[f][0]) + I*(fsmix->dftL[f][1] + fsmix->dftR[f][1]);
|
|
}
|
|
|
|
// 4. distribute the unfiltered reference signals over the channels
|
|
apply_filter(fsmix, fsmix->frontL, 0, 0); // front left
|
|
apply_filter(fsmix, fsmix->avg, 1, 4); // front center
|
|
apply_filter(fsmix, fsmix->frontR, 2, 1); // front right
|
|
apply_filter(fsmix, fsmix->surL, 3, 2); // surround left
|
|
apply_filter(fsmix, fsmix->surR, 4, 3); // surround right
|
|
//apply_filter(fsmix, fsmix->trueavg, 5, 5); // lfe
|
|
}
|
|
|
|
|
|
|
|
|
|
// handle the output buffering for overlapped calls of block_decode
|
|
static void add_output(snd_pcm_fsupmix_t *fsmix, int buffoffset, int result) {
|
|
|
|
// add the windowed data to the last 2/3 of the output buffer
|
|
block_decode(fsmix, buffoffset);
|
|
unsigned c, k;
|
|
unsigned int ooffset;
|
|
|
|
for (c=0;c<6;c++) {
|
|
if (result)
|
|
// return the first 2/3 of the ouput buffer
|
|
for (k=0;k<BLOCK_SIZE;k++) {
|
|
ooffset = (fsmix->myout_write_offset + k) & DBL_BLOCK_SIZE_MIN_ONE;
|
|
if (c != 5)
|
|
fsmix->myoutput[c][ooffset] = (short)(fsmix->fsoutbuf[c][k] + 0.5);
|
|
else // lfe channel
|
|
fsmix->myoutput[5][ooffset] = (fsmix->myoutput[0][ooffset] >> 1) + (fsmix->myoutput[1][ooffset] >> 1);
|
|
}
|
|
|
|
for ( k=0;k<BLOCK_SIZE;k++)
|
|
// shift the last 2/3 to the first 2/3 of the output buffer
|
|
fsmix->fsoutbuf[c][k] = fsmix->fsoutbuf[c][k+BLOCK_SIZE/2];
|
|
// and clear the rest
|
|
for (k=BLOCK_SIZE; k<BLOCK_SIZE+BLOCK_SIZE/2; k++)
|
|
fsmix->fsoutbuf[c][k] = 0;
|
|
}
|
|
if (result) {
|
|
fsmix->myout_count += BLOCK_SIZE;
|
|
fsmix->myout_write_offset += BLOCK_SIZE;
|
|
fsmix->myout_write_offset &= DBL_BLOCK_SIZE_MIN_ONE;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
// decode a chunk of stereo sound, has to contain exactly blocksize samples
|
|
static void decode(snd_pcm_fsupmix_t *fsmix) {
|
|
// append incoming data to the end of the input buffer
|
|
unsigned k;
|
|
for (k=0; k<BLOCK_SIZE; k++) {
|
|
fsmix->fsinbuf[0][k+BLOCK_SIZE/2] = fsmix->myinput[0][k];
|
|
fsmix->fsinbuf[1][k+BLOCK_SIZE/2] = fsmix->myinput[1][k];
|
|
}
|
|
// process first part
|
|
add_output(fsmix, 0, 0);
|
|
// process second part (overlapped) and return result
|
|
add_output(fsmix, BLOCK_SIZE/2, 1);
|
|
// shift last third of input buffer to the beginning
|
|
for (k=0; k<BLOCK_SIZE/2; k++) {
|
|
fsmix->fsinbuf[0][k] = fsmix->fsinbuf[0][k+BLOCK_SIZE];
|
|
fsmix->fsinbuf[1][k] = fsmix->fsinbuf[1][k+BLOCK_SIZE];
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* transfer callback
|
|
*/
|
|
static snd_pcm_sframes_t fs_transfer(snd_pcm_extplug_t *ext,
|
|
const snd_pcm_channel_area_t *dst_areas,
|
|
snd_pcm_uframes_t dst_offset,
|
|
const snd_pcm_channel_area_t *src_areas,
|
|
snd_pcm_uframes_t src_offset,
|
|
snd_pcm_uframes_t size)
|
|
{
|
|
snd_pcm_fsupmix_t *fsmix = (snd_pcm_fsupmix_t *)ext;
|
|
|
|
unsigned int len2 = BLOCK_SIZE - fsmix->preupmixed;
|
|
short *src[2], *dst[6];
|
|
unsigned int src_step[2], dst_step[6], i, k;
|
|
|
|
if (size > len2) {
|
|
size = len2;
|
|
// printf("on en a trop : size2=%u, len2=%u\n",size2,len2);
|
|
}
|
|
//printf("size1=%u\n",size);
|
|
for (i = 0; i < INPUT_CHANNELS; i++) {
|
|
src[i] = (short *)area_addr(src_areas + i, src_offset);
|
|
src_step[i] = area_step(src_areas + i) / 2;
|
|
}
|
|
for (i = 0; i < OUTPUT_CHANNELS; i++) {
|
|
dst[i] = (short *)area_addr(dst_areas + i, dst_offset);
|
|
// printf("dst[%u]=%p\n",i,dst[i]);
|
|
dst_step[i] = area_step(dst_areas + i) / 2;
|
|
}
|
|
|
|
for (k=0; k<size; k++) {
|
|
fsmix->myinput[0][fsmix->preupmixed + k] = (float)*src[0];
|
|
fsmix->myinput[1][fsmix->preupmixed + k] = (float)*src[1];
|
|
src[0] += src_step[0];
|
|
src[1] += src_step[1];
|
|
}
|
|
fsmix->preupmixed += size;
|
|
|
|
if (fsmix->preupmixed == BLOCK_SIZE && fsmix->myout_count > BLOCK_SIZE) {
|
|
fsmix->preupmixed -= size;
|
|
size = 0;
|
|
}
|
|
else
|
|
if (fsmix->preupmixed == BLOCK_SIZE) {
|
|
// printf("on a rempli un bloc : on ecrit a partir de %u\n",fsmix->myout_write_offset);
|
|
decode(fsmix); // adaption_rate
|
|
/* unsigned int ooffset;
|
|
for (k=0; k<BLOCK_SIZE; k++) {
|
|
ooffset = (rec->myout_write_offset + k) & DBL_BLOCK_SIZE_MIN_ONE;
|
|
rec->myoutput[0][ooffset] = (short)(rec->myinput[0][k] + 0.5);
|
|
rec->myoutput[1][ooffset] = (short)(rec->myinput[1][k] + 0.5);
|
|
rec->myoutput[2][ooffset] = (short)(rec->myinput[0][k] + 0.5);
|
|
rec->myoutput[3][ooffset] = (short)(rec->myinput[1][k] + 0.5);
|
|
rec->myoutput[4][ooffset] = 0;
|
|
rec->myoutput[5][ooffset] = 0;
|
|
}
|
|
rec->myout_count += BLOCK_SIZE;
|
|
rec->myout_write_offset += BLOCK_SIZE;
|
|
rec->myout_write_offset &= DBL_BLOCK_SIZE_MIN_ONE; */
|
|
fsmix->preupmixed = 0;
|
|
}
|
|
// printf("buffer contient %i\n", fsmix->myout_count);
|
|
|
|
unsigned int ooffset;
|
|
/* flatten copy to n-channel interleaved */
|
|
for (k = 0; k < size; k++) {
|
|
for (i = 0; i < OUTPUT_CHANNELS; i++) {
|
|
// printf("k=%u, i=%u\n",k,i);
|
|
ooffset = (fsmix->myout_read_offset + k) & DBL_BLOCK_SIZE_MIN_ONE;
|
|
*dst[i] = fsmix->myoutput[i][ooffset];
|
|
dst[i] += dst_step[i];
|
|
}
|
|
}
|
|
|
|
fsmix->myout_count -= size;
|
|
fsmix->myout_read_offset += size;
|
|
fsmix->myout_read_offset &= DBL_BLOCK_SIZE_MIN_ONE;
|
|
|
|
return size;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* prepare callback
|
|
*
|
|
* Allocate internal buffers
|
|
*/
|
|
static int fs_prepare(snd_pcm_extplug_t *ext)
|
|
{
|
|
snd_pcm_fsupmix_t *fsmix = (snd_pcm_fsupmix_t *)ext;
|
|
|
|
|
|
fsmix->myout_count = BLOCK_SIZE;
|
|
fsmix->myout_write_offset = BLOCK_SIZE;
|
|
fsmix->myout_read_offset = 0;
|
|
|
|
// create FFTW buffers
|
|
fsmix->lt = (float*)fftwf_malloc(sizeof(float)*BLOCK_SIZE);
|
|
fsmix->rt = (float*)fftwf_malloc(sizeof(float)*BLOCK_SIZE);
|
|
fsmix->dst = (float*)fftwf_malloc(sizeof(float)*BLOCK_SIZE);
|
|
fsmix->dftL = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex)*BLOCK_SIZE);
|
|
fsmix->dftR = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex)*BLOCK_SIZE);
|
|
fsmix->src = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex)*BLOCK_SIZE);
|
|
fsmix->loadL = fftwf_plan_dft_r2c_1d(BLOCK_SIZE, fsmix->lt, fsmix->dftL,FFTW_MEASURE);
|
|
fsmix->loadR = fftwf_plan_dft_r2c_1d(BLOCK_SIZE, fsmix->rt, fsmix->dftR,FFTW_MEASURE);
|
|
fsmix->store = fftwf_plan_dft_c2r_1d(BLOCK_SIZE, fsmix->src, fsmix->dst,FFTW_MEASURE);
|
|
|
|
|
|
fsmix->transfer = 0;
|
|
fsmix->remain = 0;
|
|
fsmix->preupmixed = 0;
|
|
|
|
unsigned k;
|
|
for (k=0;k<BLOCK_SIZE;k++)
|
|
fsmix->wnd[k] = sqrt(0.5*(1-cos(2*PI*k/BLOCK_SIZE))); // square root of hann
|
|
|
|
surround_coefficients(fsmix, 0.8165,0.5774);
|
|
set_phase_mode(fsmix);
|
|
//separation(fsmix, 1.0,1.0);
|
|
//steering_mode(rec, 1);
|
|
sample_rate(fsmix, 48000);
|
|
|
|
// printf("prepare : center_width=%f dimension=%f adaption_rate=%f phase_mode=%u linear_steering=%i\n",
|
|
// rec->center_width, rec->dimension, rec->adaption_rate, rec->phase_mode, rec->linear_steering);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* close callback
|
|
*/
|
|
static int fs_close(snd_pcm_extplug_t *ext)
|
|
{
|
|
snd_pcm_fsupmix_t *fsmix = (snd_pcm_fsupmix_t *)ext;
|
|
|
|
// clean up the FFTW stuff
|
|
fftwf_destroy_plan(fsmix->store);
|
|
fftwf_destroy_plan(fsmix->loadR);
|
|
fftwf_destroy_plan(fsmix->loadL);
|
|
fftwf_free(fsmix->src);
|
|
fftwf_free(fsmix->dftR);
|
|
fftwf_free(fsmix->dftL);
|
|
fftwf_free(fsmix->dst);
|
|
fftwf_free(fsmix->rt);
|
|
fftwf_free(fsmix->lt);;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* callback table
|
|
*/
|
|
static snd_pcm_extplug_callback_t fs_callback = {
|
|
.transfer = fs_transfer,
|
|
.close = fs_close,
|
|
.init = fs_prepare,
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
* Main entry point
|
|
*/
|
|
SND_PCM_PLUGIN_DEFINE_FUNC(freesurround)
|
|
{
|
|
snd_config_iterator_t i, next;
|
|
snd_pcm_fsupmix_t *fsmix;
|
|
snd_config_t *sconf = NULL;
|
|
static const unsigned int chlist[2] = {4, 6};
|
|
int err;
|
|
unsigned int rate = 48000;
|
|
unsigned int bitrate = 448;
|
|
unsigned int channels = 6;
|
|
snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;
|
|
float center_width = 0.50;
|
|
float dimension = 0.25;
|
|
float adaption_rate = 0.9;
|
|
unsigned int phase_mode = 0;
|
|
int linear_steering = 1;
|
|
float front_separation = 1.0;
|
|
float rear_separation = 1.0;
|
|
|
|
if (stream != SND_PCM_STREAM_PLAYBACK) {
|
|
SNDERR("freesurround is only for playback");
|
|
return -EINVAL;
|
|
}
|
|
|
|
snd_config_for_each(i, next, conf) {
|
|
snd_config_t *n = snd_config_iterator_entry(i);
|
|
const char *id;
|
|
if (snd_config_get_id(n, &id) < 0)
|
|
continue;
|
|
if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0 || strcmp(id, "hint") == 0)
|
|
continue;
|
|
if (strcmp(id, "slave") == 0) {
|
|
sconf = n;
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(id, "channels") == 0) {
|
|
long val;
|
|
if (snd_config_get_integer(n, &val) < 0) {
|
|
SNDERR("Invalid type for %s", id);
|
|
return -EINVAL;
|
|
}
|
|
channels = val;
|
|
if (channels != 2 && channels != 4 && channels != 6) {
|
|
SNDERR("channels must be 2, 4 or 6");
|
|
return -EINVAL;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(id, "center_width") == 0) {
|
|
double val;
|
|
if (snd_config_get_real(n, &val) < 0) {
|
|
SNDERR("Invalid type for %s", id);
|
|
return -EINVAL;
|
|
}
|
|
center_width = (float)val; // center_width [0..1] distributes the center information towards the front left/right channels, 1=full distribution, 0=no distribution
|
|
if (center_width < 0.0 || center_width > 1.0) {
|
|
SNDERR("center_width must be between 0.0 and 1.0");
|
|
return -EINVAL;
|
|
}
|
|
continue;
|
|
}
|
|
if (strcmp(id, "dimension") == 0) {
|
|
double val;
|
|
if (snd_config_get_real(n, &val) < 0) {
|
|
SNDERR("Invalid type for %s", id);
|
|
return -EINVAL;
|
|
}
|
|
dimension = (float)val; // dimension [0..1] moves the soundfield backwards, 0=front, 1=side
|
|
if (dimension < -0.5 || dimension > 1.0) {
|
|
SNDERR("dimension must be between -0.5 and 1.0");
|
|
return -EINVAL;
|
|
}
|
|
continue;
|
|
}
|
|
if (strcmp(id, "adaption_rate") == 0) {
|
|
double val;
|
|
if (snd_config_get_real(n, &val) < 0) {
|
|
SNDERR("Invalid type for %s", id);
|
|
return -EINVAL;
|
|
}
|
|
adaption_rate = (float)val; // adaption_rate [0..1] determines how fast the steering gets adapted, 1=instantaneous, 0.1 = very slow adaption
|
|
if (adaption_rate < 0.0 || adaption_rate > 1.0) {
|
|
SNDERR("adaption_rate must be between 0.0 and 1.0");
|
|
return -EINVAL;
|
|
}
|
|
continue;
|
|
}
|
|
if (strcmp(id, "phase_mode") == 0) {
|
|
long val;
|
|
if (snd_config_get_integer(n, &val) < 0) {
|
|
SNDERR("Invalid type for %s", id);
|
|
return -EINVAL;
|
|
}
|
|
phase_mode = (unsigned int)val;
|
|
if (phase_mode < 0 || phase_mode > 3) {
|
|
SNDERR("phase_mode must be between 0 and 3");
|
|
return -EINVAL;
|
|
}
|
|
continue;
|
|
}
|
|
if (strcmp(id, "linear_steering") == 0) {
|
|
long val;
|
|
if (snd_config_get_integer(n, &val) < 0) {
|
|
SNDERR("Invalid type for %s", id);
|
|
return -EINVAL;
|
|
}
|
|
linear_steering = (unsigned int)val; //
|
|
if (linear_steering != 0 && linear_steering != 1) {
|
|
SNDERR("linear_steering must be 0 or 1");
|
|
return -EINVAL;
|
|
}
|
|
continue;
|
|
}
|
|
if (strcmp(id, "front_separation") == 0) {
|
|
double val;
|
|
if (snd_config_get_real(n, &val) < 0) {
|
|
SNDERR("Invalid type for %s", id);
|
|
return -EINVAL;
|
|
}
|
|
front_separation = (float)val; // front_separation [0..1.5]
|
|
if (front_separation < 0.0 || front_separation > 1.5) {
|
|
SNDERR("front_separation must be between 0.0 and 1.5");
|
|
return -EINVAL;
|
|
}
|
|
continue;
|
|
}
|
|
if (strcmp(id, "rear_separation") == 0) {
|
|
double val;
|
|
if (snd_config_get_real(n, &val) < 0) {
|
|
SNDERR("Invalid type for %s", id);
|
|
return -EINVAL;
|
|
}
|
|
rear_separation = (float)val; // rear_separation [0..1.5]
|
|
if (rear_separation < 0.0 || rear_separation > 1.5) {
|
|
SNDERR("rear_separation must be between 0.0 and 1.5");
|
|
return -EINVAL;
|
|
}
|
|
continue;
|
|
}
|
|
SNDERR("Unknown field %s", id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (! sconf) {
|
|
SNDERR("No slave configuration for freesurround pcm");
|
|
return -EINVAL;
|
|
}
|
|
|
|
fsmix = calloc(1, sizeof(*fsmix));
|
|
if (! fsmix) {
|
|
SNDERR("cannot allocate");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
fsmix->rate = rate;
|
|
fsmix->bitrate = bitrate;
|
|
fsmix->channels = channels;
|
|
fsmix->format = format;
|
|
|
|
fsmix->center_width = center_width;
|
|
fsmix->dimension = dimension;
|
|
fsmix->adaption_rate = adaption_rate;
|
|
fsmix->phase_mode = phase_mode;
|
|
fsmix->linear_steering = linear_steering;
|
|
fsmix->front_separation = front_separation;
|
|
fsmix->rear_separation = rear_separation;
|
|
|
|
|
|
fsmix->ext.version = SND_PCM_IOPLUG_VERSION;
|
|
fsmix->ext.name = "FreeSurround upmix plugin";
|
|
fsmix->ext.callback = &fs_callback;
|
|
fsmix->ext.private_data = fsmix;
|
|
|
|
err = snd_pcm_extplug_create(&fsmix->ext, name, root, sconf, stream, mode);
|
|
if (err < 0) {
|
|
free(fsmix);
|
|
return err;
|
|
}
|
|
|
|
snd_pcm_extplug_set_param_minmax(&fsmix->ext,
|
|
SND_PCM_EXTPLUG_HW_CHANNELS,
|
|
1, 6);
|
|
if (channels)
|
|
snd_pcm_extplug_set_slave_param_minmax(&fsmix->ext,
|
|
SND_PCM_EXTPLUG_HW_CHANNELS,
|
|
channels, channels);
|
|
else
|
|
snd_pcm_extplug_set_slave_param_list(&fsmix->ext,
|
|
SND_PCM_EXTPLUG_HW_CHANNELS,
|
|
2, chlist);
|
|
snd_pcm_extplug_set_param(&fsmix->ext, SND_PCM_EXTPLUG_HW_FORMAT,
|
|
SND_PCM_FORMAT_S16);
|
|
snd_pcm_extplug_set_slave_param(&fsmix->ext, SND_PCM_EXTPLUG_HW_FORMAT,
|
|
SND_PCM_FORMAT_S16);
|
|
|
|
*pcmp = fsmix->ext.pcm;
|
|
return 0;
|
|
|
|
}
|
|
|
|
SND_PCM_PLUGIN_SYMBOL(freesurround);
|