non/mixer/src/Spatializer_Module.C

712 lines
21 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*******************************************************************************/
/* Copyright (C) 2013 Jonathan Moore Liles */
/* */
/* 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; see the file COPYING. If not,write to the Free Software */
/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
/*******************************************************************************/
#include <FL/fl_draw.H>
#include <FL/Fl_Box.H>
#include "Spatializer_Module.H"
#include "dsp.h"
static const float max_distance = 15.0f;
static const float HIGHPASS_FREQ = 200.0f;
//static const float LOWPASS_FREQ = 70000.0f;
static const float LOWPASS_FREQ = 22000.0f;
#include <math.h>
class filter
{
protected:
float _sample_rate;
float _w;
float _last_output;
float _last_cutoff;
float _amount_of_current;
float _amount_of_last;
bool _bypass;
void recalculate ( float cutoff )
{
_last_cutoff = cutoff;
if (_last_cutoff <= 10 )
{
_bypass = true;
}
else if (_last_cutoff > _sample_rate * 0.5f )
{
_bypass = true;
}
else
{
const float c = 2.0f - cosf(_w * _last_cutoff);
_amount_of_last = c - sqrtf(c * c - 1.0f);
_amount_of_current = 1 - _amount_of_last;
_bypass = false;
}
}
public:
void sample_rate ( nframes_t srate )
{
_sample_rate = srate;
_w = (2 * M_PI) / (float)srate;
}
filter ()
{
_last_output = 0;
_last_cutoff = 0;
_w = 0;
_sample_rate = 0;
_amount_of_current = 0;
_amount_of_last = 0;
_bypass = false;
}
void
run_lowpass ( float *buf, float cutoff, nframes_t nframes )
{
if (cutoff != _last_cutoff)
{
recalculate( cutoff );
}
if ( !_bypass )
{
while ( nframes-- )
{
*buf = _last_output = (_amount_of_current * *buf + _amount_of_last * _last_output);
buf++;
}
}
}
void
run_highpass ( float *buf, float cutoff, nframes_t nframes )
{
if (cutoff != _last_cutoff)
{
recalculate( cutoff );
}
if ( !_bypass )
{
while ( nframes-- )
{
_last_output = ((_amount_of_current * *buf) + (_amount_of_last * _last_output));
*buf = *buf - _last_output;
buf++;
}
}
}
};
class delay
{
unsigned int _sample_rate;
float *_buffer;
long _write_index;
unsigned int _buffer_mask;
float _max_delay;
nframes_t _samples_since_motion;
nframes_t _interpolation_delay_samples;
float _interpolation_delay_coeff;
public:
void sample_rate ( float srate )
{
if ( _buffer )
free( _buffer );
unsigned int size, minsize;
minsize = (unsigned long)(srate * _max_delay);
size = 1;
while (size < minsize)
size <<= 1;
_buffer = (float *)calloc(size, sizeof(float));
_buffer_mask = size - 1;
_sample_rate = srate;
_write_index = 0;
_interpolation_delay_samples = 0.2f * srate;
_interpolation_delay_coeff = 1.0f / (float)_interpolation_delay_samples;
}
delay ( float max_delay )
{
_interpolation_delay_samples = 0;
_interpolation_delay_coeff = 0;
_samples_since_motion = 0;
_max_delay = max_delay;
_write_index = 0;
_sample_rate = 0;
_buffer = 0;
_buffer_mask =0;
}
~delay ( )
{
if ( _buffer )
free( _buffer );
}
void run ( float *buf, float *delaybuf, float delay, nframes_t nframes )
{
const nframes_t min_delay_samples = 4;
if ( delaybuf )
{
for (nframes_t i = 0; i < nframes; i++ )
{
float delay_samples = delaybuf[i] * _sample_rate;
if ( delay_samples > _buffer_mask + 1 )
delay_samples = _buffer_mask;
else if ( delay_samples < min_delay_samples )
delay_samples = min_delay_samples;
long idelay_samples = (long)delay_samples;
const float frac = delay_samples - idelay_samples;
const long read_index = _write_index - idelay_samples;
_buffer[_write_index++ & _buffer_mask] = buf[i];
const float read = interpolate_cubic (frac,
_buffer[(read_index-1) & _buffer_mask],
_buffer[read_index & _buffer_mask],
_buffer[(read_index+1) & _buffer_mask],
_buffer[(read_index+2) & _buffer_mask]);
buf[i] = read;
}
_samples_since_motion = 0;
}
else
{
float delay_samples = delay * _sample_rate;
if ( delay_samples > _buffer_mask + 1 )
delay_samples = _buffer_mask;
else if ( delay_samples < min_delay_samples )
delay_samples = min_delay_samples;
long idelay_samples = (long)delay_samples;
if ( _samples_since_motion >= _interpolation_delay_samples )
{
/* switch to non-interpolating mode */
for (nframes_t i = 0; i < nframes; i++ )
{
const long read_index = _write_index - idelay_samples;
_buffer[_write_index++ & _buffer_mask] = buf[i];
const float read = _buffer[read_index & _buffer_mask];
buf[i] = read;
}
}
else
{
/* linearly interpolate our way to an integer sample delay */
float frac = delay_samples - idelay_samples;
const float scale = 1.0f - (_samples_since_motion * _interpolation_delay_coeff);
for (nframes_t i = 0; i < nframes; i++ )
{
const long read_index = _write_index - idelay_samples;
_buffer[_write_index++ & _buffer_mask] = buf[i];
frac *= scale;
const float read = interpolate_cubic (frac,
_buffer[(read_index-1) & _buffer_mask],
_buffer[read_index & _buffer_mask],
_buffer[(read_index+1) & _buffer_mask],
_buffer[(read_index+2) & _buffer_mask]);
buf[i] = read;
}
_samples_since_motion += nframes;
}
}
}
};
class ambisonic_panner
{
/* last values */
float _x, _y, _z;
/* for stereo */
float _xr, _yr;
static inline void spherical_to_cartesian (float a, float e, float &x, float &y, float &z )
{
a *= DEG2RAD;
e *= DEG2RAD;
z = sinf(e);
const float ce = cosf(e);
x = ce * cosf(-a);
y = ce * sinf(-a);
}
public:
ambisonic_panner ( )
{
_x = _y = _z = _xr = _yr = 1.0f;
}
void
run_mono ( float *in,
float *out_w, float *out_x, float *out_y, float *out_z,
float a, float e,
nframes_t nframes )
{
float x = _x;
float y = _y;
float z = _z;
spherical_to_cartesian( a, e, _x, _y, _z );
const float c = 1.0f / (float)nframes;
/* calculate increment for linear interpolation */
const float dx = (_x - x) * c;
const float dy = (_y - y) * c;
const float dz = (_z - z) * c;
while ( nframes-- )
{
x += dx;
y += dy;
z += dz;
const float t = *in++;
*out_w++ = ONEOVERSQRT2 * t;
*out_x++ = x * t;
*out_y++ = y * t;
*out_z++ = z * t;
}
}
void
run_stereo ( float *in_l, float *in_r,
float *out_w, float *out_x, float *out_y, float *out_z,
float a, float e, float w,
nframes_t nframes )
{
float x = _x;
float y = _y;
float z = _z;
float xr = _xr;
float yr = _yr;
w *= 0.5f;
spherical_to_cartesian( a - w, e, _x, _y, _z );
spherical_to_cartesian( a + w, e, _xr, _yr, _z );
const float c = 1.0f / (float)nframes;
/* calculate increment for linear interpolation */
const float dx = (_x - x) * c;
const float dy = (_y - y) * c;
const float dz = (_z - z) * c;
const float dxr = (_xr - xr) * c;
const float dyr = (_yr - yr) * c;
while ( nframes-- )
{
x += dx;
y += dy;
z += dz;
xr += dxr;
yr += dyr;
const float L = *in_l++;
const float R = *in_r++;
const float LR = L + R;
*out_w++ = ONEOVERSQRT2 * LR;
*out_x++ = x * L + xr * R;
*out_y++ = y * L + yr * R;
*out_z++ = z * LR;
}
}
};
Spatializer_Module::Spatializer_Module ( ) : JACK_Module ( false )
{
is_default( false );
_panner = 0;
{
Port p( this, Port::INPUT, Port::CONTROL, "Azimuth" );
p.hints.type = Port::Hints::LINEAR;
p.hints.ranged = true;
p.hints.minimum = -180.0f;
p.hints.maximum = 180.0f;
p.hints.default_value = 0.0f;
p.connect_to( new float );
p.control_value( p.hints.default_value );
add_port( p );
}
{
Port p( this, Port::INPUT, Port::CONTROL, "Elevation" );
p.hints.type = Port::Hints::LINEAR;
p.hints.ranged = true;
p.hints.minimum = -90.0f;
p.hints.maximum = 90.0f;
p.hints.default_value = 0.0f;
p.connect_to( new float );
p.control_value( p.hints.default_value );
add_port( p );
}
{
Port p( this, Port::INPUT, Port::CONTROL, "Radius" );
p.hints.type = Port::Hints::LINEAR;
p.hints.ranged = true;
p.hints.minimum = 0.0f;
p.hints.maximum = max_distance;
p.hints.default_value = 1.0f;
p.connect_to( new float );
p.control_value( p.hints.default_value );
add_port( p );
}
{
Port p( this, Port::INPUT, Port::CONTROL, "Highpass (Hz)" );
p.hints.type = Port::Hints::LINEAR;
p.hints.ranged = true;
p.hints.minimum = 0.0f;
p.hints.maximum = 600.0f;
p.hints.default_value = 200.0f;
p.connect_to( new float );
p.control_value( p.hints.default_value );
add_port( p );
}
{
Port p( this, Port::INPUT, Port::CONTROL, "Width" );
p.hints.type = Port::Hints::LINEAR;
p.hints.ranged = true;
p.hints.minimum = -90.0f;
p.hints.maximum = 90.0f;
p.hints.default_value = 90.0f;
p.hints.visible = false;
p.connect_to( new float );
p.control_value( p.hints.default_value );
add_port( p );
}
log_create();
_panner = new ambisonic_panner();
labelsize(9);
color( FL_DARK1 );
copy_label( "Spatializer" );
align(FL_ALIGN_LEFT|FL_ALIGN_TOP|FL_ALIGN_INSIDE);
gain_smoothing.sample_rate( sample_rate() );
delay_smoothing.cutoff( 0.5f );
delay_smoothing.sample_rate( sample_rate() );
}
Spatializer_Module::~Spatializer_Module ( )
{
configure_inputs(0);
delete _panner;
delete (float*)control_input[0].buffer();
delete (float*)control_input[1].buffer();
delete (float*)control_input[2].buffer();
delete (float*)control_input[3].buffer();
delete (float*)control_input[4].buffer();
}
void
Spatializer_Module::handle_sample_rate_change ( nframes_t n )
{
gain_smoothing.sample_rate( n );
delay_smoothing.sample_rate( n );
for ( unsigned int i = 0; i < audio_input.size(); i++ )
{
_lowpass[i]->sample_rate( n );
_highpass[i]->sample_rate( n );
_delay[i]->sample_rate( n );
}
}
void
Spatializer_Module::draw ( void )
{
int W = 5;
child(0)->size( w() - W, h() );
Module::draw_box(x(),y(),w() - W,h());
Module::draw_label(x() + 4,y(),w() - W,h());
Module *m = this;
fl_color( fl_darker( FL_FOREGROUND_COLOR ) );
int spacing, offset;
int ni = aux_audio_output.size();
spacing = h() / ni;
offset = spacing / 2;
for ( int i = ni; i--; )
{
int xi = offset + ( spacing * i );
fl_rectf( m->x() + m->w() - W, m->y() + xi, W, 2 );
}
}
void
Spatializer_Module::process ( nframes_t nframes )
{
if ( !bypass() )
{
float azimuth = control_input[0].control_value();
float elevation = control_input[1].control_value();
float radius = control_input[2].control_value();
float highpass_freq = control_input[3].control_value();
float width = control_input[4].control_value();
float delay_seconds = 0.0f;
if ( radius > 1.0f )
delay_seconds = ( radius - 1.0f ) / 340.29f;
/* direct sound follows inverse square law */
/* but it's just the inverse as far as SPL goes */
/* let's not go nuts... */
if ( radius < 0.01f )
radius = 0.01f;
float gain = 1.0f / radius;
float cutoff_frequency = gain * LOWPASS_FREQ;
sample_t gainbuf[nframes];
sample_t delaybuf[nframes];
bool use_gainbuf = gain_smoothing.apply( gainbuf, nframes, gain );
bool use_delaybuf = delay_smoothing.apply( delaybuf, nframes, delay_seconds );
for ( unsigned int i = 0; i < audio_input.size(); i++ )
{
sample_t *buf = (sample_t*) audio_input[i].buffer();
/* frequency effects */
_highpass[i]->run_highpass( buf, highpass_freq, nframes );
_lowpass[i]->run_lowpass( buf, cutoff_frequency, nframes );
/* send to late reverb */
if ( i == 0 )
buffer_copy( (sample_t*)aux_audio_output[0].jack_port()->buffer(nframes), buf, nframes );
else
buffer_mix( (sample_t*)aux_audio_output[0].jack_port()->buffer(nframes), buf, nframes );
/* /\* FIXME: use smoothed value... *\/ */
/* buffer_apply_gain( (sample_t*)jack_output[0].buffer(nframes), nframes, 1.0f / sqrt(D) ); */
if ( use_delaybuf )
_delay[i]->run( buf, delaybuf, 0, nframes );
else
_delay[i]->run( buf, 0, delay_seconds, nframes );
}
if ( audio_input.size() == 1 )
{
_panner->run_mono( (sample_t*)audio_input[0].buffer(),
(sample_t*)audio_output[0].buffer(),
(sample_t*)audio_output[1].buffer(),
(sample_t*)audio_output[2].buffer(),
(sample_t*)audio_output[3].buffer(),
azimuth,
elevation,
nframes );
}
else
{
_panner->run_stereo( (sample_t*)audio_input[0].buffer(),
(sample_t*)audio_input[1].buffer(),
(sample_t*)audio_output[0].buffer(),
(sample_t*)audio_output[1].buffer(),
(sample_t*)audio_output[2].buffer(),
(sample_t*)audio_output[3].buffer(),
azimuth,
elevation,
width,
nframes );
}
/* send to early reverb */
for ( int i = 4; i--; )
buffer_copy( (sample_t*)aux_audio_output[1 + i].jack_port()->buffer(nframes),
(sample_t*)audio_output[0 + i].buffer(),
nframes );
/* gain effects */
if ( use_gainbuf )
{
for ( int i = 4; i--; )
buffer_apply_gain_buffer( (sample_t*)audio_output[i].buffer(), gainbuf, nframes );
}
else
{
for ( int i = 4; i--; )
buffer_apply_gain( (sample_t*)audio_output[i].buffer(), nframes, gain );
}
}
}
bool
Spatializer_Module::configure_inputs ( int n )
{
output_connection_handle->show();
output_connection_handle->tooltip( "Late Reverb" );
output_connection2_handle->show();
output_connection2_handle->tooltip( "Early Reverb" );
int on = audio_input.size();
if ( n > on )
{
for ( int i = n - on; i--; )
{
{ filter *o = new filter();
o->sample_rate( sample_rate() );
_lowpass.push_back( o );
}
{
filter *o = new filter();
o->sample_rate( sample_rate() );
_highpass.push_back( o );
}
{
delay *o = new delay( max_distance / 340.29f );
o->sample_rate( sample_rate() );
_delay.push_back( o );
}
add_port( Port( this, Port::INPUT, Port::AUDIO ) );
}
}
else if ( n < on )
{
for ( int i = on - n; i--; )
{
delete _lowpass.back();
_lowpass.pop_back();
delete _highpass.back();
_highpass.pop_back();
delete _delay.back();
_delay.pop_back();
audio_input.pop_back();
}
}
control_input[4].hints.visible = audio_input.size() == 2;
if ( n == 0 )
{
remove_aux_audio_outputs();
audio_output.clear();
audio_input.clear();
}
else
{
if ( audio_output.size() != 4 )
{
for ( int i = 0; i < 4; i++ )
{
add_port( Port( this, Port::OUTPUT, Port::AUDIO ) );
}
}
if ( aux_audio_output.size() != 5 )
{
add_aux_audio_output( "late reverb", 0 );
add_aux_audio_output( "early reverb", 0 );
add_aux_audio_output( "early reverb", 1 );
add_aux_audio_output( "early reverb", 2 );
add_aux_audio_output( "early reverb", 3 );
}
}
_connection_handle_outputs[0][0] = 0;
_connection_handle_outputs[0][1] = 1;
_connection_handle_outputs[1][0] = 1;
_connection_handle_outputs[1][1] = aux_audio_output.size();
return true;
}