|
-
- /*******************************************************************************/
- /* 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"
- #include "Module_Parameter_Editor.H"
-
- static const float max_distance = 15.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;
- _early_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 = 0.0f;
- p.hints.visible = false;
-
- 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.connect_to( new float );
- p.control_value( p.hints.default_value );
-
- add_port( p );
- }
-
- {
- Port p( this, Port::INPUT, Port::CONTROL, "Angle" );
- 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, "Advanced Options" );
- p.hints.type = Port::Hints::BOOLEAN;
- p.hints.ranged = true;
- p.hints.minimum = 0.0f;
- p.hints.maximum = 1.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, "Speed of Sound" );
- p.hints.type = Port::Hints::BOOLEAN;
- p.hints.ranged = true;
- p.hints.minimum = 0.0f;
- p.hints.maximum = 1.0f;
- p.hints.default_value = 1.0f;
- p.hints.visible = false;
- p.connect_to( new float );
- p.control_value( p.hints.default_value );
-
- add_port( p );
- }
-
- {
- Port p( this, Port::INPUT, Port::CONTROL, "Late Gain (dB)" );
- p.hints.type = Port::Hints::LOGARITHMIC;
- p.hints.ranged = true;
- p.hints.minimum = -70.0f;
- p.hints.maximum = 6.0f;
- p.hints.default_value = 0.0f;
- p.hints.visible = false;
- p.connect_to( new float );
- p.control_value( p.hints.default_value );
-
- add_port( p );
- }
-
- {
- Port p( this, Port::INPUT, Port::CONTROL, "Early Gain (dB)" );
- p.hints.type = Port::Hints::LOGARITHMIC;
- p.hints.ranged = true;
- p.hints.minimum = -70.0f;
- p.hints.maximum = 6.0f;
- p.hints.default_value = 0.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();
- _early_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() );
- late_gain_smoothing.sample_rate( sample_rate() );
- early_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 _early_panner;
- delete _panner;
- for ( unsigned int i = 0; i < control_input.size(); i++ )
- delete (float*)control_input[i].buffer();
- }
-
-
-
-
- void
- Spatializer_Module::handle_sample_rate_change ( nframes_t n )
- {
- gain_smoothing.sample_rate( n );
- delay_smoothing.sample_rate( n );
- early_gain_smoothing.sample_rate( n );
- late_gain_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 )
- {
- 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 angle = control_input[5].control_value();
- // bool more_options = control_input[6].control_value();
- bool speed_of_sound = control_input[7].control_value() > 0.5f;
- float late_gain = DB_CO( control_input[8].control_value() );
- float early_gain = DB_CO( control_input[9].control_value() );
-
- control_input[3].hints.visible = highpass_freq != 0.0f;
-
- float delay_seconds = 0.0f;
-
- if ( speed_of_sound && 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 = false;
- 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 );
-
- /* 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 );
-
- }
-
- {
- use_gainbuf = late_gain_smoothing.apply( gainbuf, nframes, late_gain );
-
- /* gain effects */
- if ( unlikely( use_gainbuf ) )
- buffer_apply_gain_buffer( (sample_t*)aux_audio_output[0].jack_port()->buffer(nframes), gainbuf, nframes );
- else
- buffer_apply_gain( (sample_t*)aux_audio_output[0].jack_port()->buffer(nframes), nframes, late_gain );
- }
-
- float early_angle = azimuth - angle;
- if ( early_angle > 180.0f )
- early_angle = -180 - ( early_angle - 180 );
- else if ( early_angle < -180.0f )
- early_angle = 180 - ( early_angle + 180 );
-
- /* send to early reverb */
- if ( audio_input.size() == 1 )
- {
- _early_panner->run_mono( (sample_t*)audio_input[0].buffer(),
- (sample_t*)aux_audio_output[1].jack_port()->buffer(nframes),
- (sample_t*)aux_audio_output[2].jack_port()->buffer(nframes),
- (sample_t*)aux_audio_output[3].jack_port()->buffer(nframes),
- (sample_t*)aux_audio_output[4].jack_port()->buffer(nframes),
- azimuth + angle,
- elevation,
- nframes );
- }
- else
- {
- _early_panner->run_stereo( (sample_t*)audio_input[0].buffer(),
- (sample_t*)audio_input[1].buffer(),
- (sample_t*)aux_audio_output[1].jack_port()->buffer(nframes),
- (sample_t*)aux_audio_output[2].jack_port()->buffer(nframes),
- (sample_t*)aux_audio_output[3].jack_port()->buffer(nframes),
- (sample_t*)aux_audio_output[4].jack_port()->buffer(nframes),
- azimuth + angle,
- elevation,
- width,
- nframes );
- }
-
- {
- use_gainbuf = early_gain_smoothing.apply( gainbuf, nframes, early_gain );
-
- for ( int i = 1; i < 5; i++ )
- {
- /* gain effects */
- if ( unlikely( use_gainbuf ) )
- buffer_apply_gain_buffer( (sample_t*)aux_audio_output[i].jack_port()->buffer(nframes), gainbuf, nframes );
- else
- buffer_apply_gain( (sample_t*)aux_audio_output[i].jack_port()->buffer(nframes), nframes, early_gain );
- }
- }
-
- float corrected_angle = fabs( angle ) - (fabs( width ) * 0.5f);
-
- if ( corrected_angle < 0.0f )
- corrected_angle = 0.0f;
-
- float cutoff_frequency = ( 1.0f / ( 1.0f + corrected_angle ) ) * 300000.0f;
-
- use_gainbuf = gain_smoothing.apply( gainbuf, nframes, gain );
-
- for ( unsigned int i = 0; i < audio_input.size(); i++ )
- {
- /* gain effects */
- if ( unlikely( use_gainbuf ) )
- buffer_apply_gain_buffer( (sample_t*)audio_input[i].buffer(), gainbuf, nframes );
- else
- buffer_apply_gain( (sample_t*)audio_input[i].buffer(), nframes, gain );
-
- /* frequency effects */
- _lowpass[i]->run_lowpass( (sample_t*)audio_input[i].buffer(), cutoff_frequency, nframes );
-
- /* delay effects */
- if ( likely( speed_of_sound ) )
- {
- if ( unlikely( use_delaybuf ) )
- _delay[i]->run( (sample_t*)audio_input[i].buffer(), delaybuf, 0, nframes );
- else
- _delay[i]->run( (sample_t*)audio_input[i].buffer(), 0, delay_seconds, nframes );
- }
- }
-
- /* now do direct outputs */
- 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 );
- }
- }
-
- void
- Spatializer_Module::handle_control_changed ( Port *p )
- {
- if ( p == &control_input[6] )
- {
- bool v = p->control_value();
-
- control_input[7].hints.visible = v;
- control_input[8].hints.visible = v;
- control_input[9].hints.visible = v;
-
- DMESSAGE( "reloading" );
- if ( _editor )
- _editor->reload();
- }
- }
-
- 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;
-
- control_input[4].hints.default_value = audio_input.size() == 2 ? 90.0f : 0.0f;
-
- 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;
- }
|