Loopp/src/jack.cxx

579 lines
17 KiB
C++

/*
* Author: Harry van Haaren 2013
* harryhaaren@gmail.com
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "jack.hxx"
#include <sstream>
#include <cstring>
#include <iostream>
#include "state/state.hxx"
#include "logic.hxx"
#include "config.hxx"
#include "looper.hxx"
#include "metronome.hxx"
#include "gridlogic.hxx"
#include "trackoutput.hxx"
#include "timemanager.hxx"
#include "controllerupdater.hxx"
#include "dsp/dsp_reverb.hxx"
#include "dsp/dsp_dbmeter.hxx"
#include "audiobuffer.hxx"
#include "eventhandler.hxx"
#include "controller/genericmidi.hxx"
#include "controller/guicontroller.hxx"
using namespace std;
int AudioBuffer::privateID = 0;
// static pointer from main.
extern Jack* jack;
void Jack::setup(std::string name)
{
if ( jack == 0 )
{
jack = new Jack( name );
jack->activate();
return;
}
else
{
LUPPP_WARN("JACK instance already exists!");
}
}
void Jack::resetMidiBindingState()
{
// MIDI binding stuff
bindingEventRecordEnable = false;
bindingEventType = EVENT_NULL;
bindingTrack = -2;
bindingScene = -1;
bindingSend = -1;
bindingActive= -1;
}
Jack::Jack( std::string name ) :
client( jack_client_open ( name.c_str(), JackNullOption , 0 , 0 ) ),
state( new State() ),
controllerUpdater( new ControllerUpdater() ),
clientActive(false)
{
jack = this;
// construct Observer classes here, not in the initializer list as the Jack*
// will be 0x0 until then.
timeManager = new TimeManager(),
metronome = new Metronome();
logic = new Logic();
gridLogic = new GridLogic();
// CAREFUL: setup the size by default: otherwise malloc() is called on push_back
midiIO.reserve( CONTROLLERS_PREALLOC );
resetMidiBindingState();
//GenericMIDI* tmp = new GenericMIDI("akai_apc.ctlr");
//tmp->registerComponents();
//controllerUpdater->registerController( static_cast<Controller*>(tmp) );
buffers.nframes = jack_get_buffer_size( client );
buffers.samplerate = jack_get_sample_rate( client );
EventSamplerate e(buffers.samplerate);
writeToGuiRingbuffer( &e );
uiUpdateCounter = buffers.samplerate / 30;
uiUpdateConstant = buffers.samplerate / 30;
masterInput = jack_port_register( client,
"master_in",
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsInput,
0 );
masterReturnL = jack_port_register( client,
"master_return_left",
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsInput,
0 );
masterReturnR = jack_port_register( client,
"master_return_right",
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsInput,
0 );
masterOutputL = jack_port_register( client,
"master_left",
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput,
0 );
masterOutputR = jack_port_register( client,
"master_right",
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput,
0 );
sendOutput = jack_port_register( client,
"send_out",
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput,
0 );
sidechainKeyOutput= jack_port_register( client,
"sidechain_key",
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput,
0 );
sidechainSignalOutput= jack_port_register( client,
"sidechain_signal",
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput,
0 );
/*
masterMidiInput = jack_port_register( client,
"midi_in",
JACK_DEFAULT_MIDI_TYPE,
JackPortIsInput,
0 );
*/
masterVol = 0.0f;
returnVol = 1.0f;
inputToMixEnable = false;
inputToSendEnable = false;
inputToKeyEnable = false;
inputToMixVol = 0.f;
inputToSendVol = 0.f;
inputToXSideVol = 0.f;
/// prepare internal buffers
buffers.audio[Buffers::SEND] = new float[ buffers.nframes ];
buffers.audio[Buffers::SIDECHAIN_KEY] = new float[ buffers.nframes ];
buffers.audio[Buffers::SIDECHAIN_SIGNAL]=new float[ buffers.nframes ];
buffers.audio[Buffers::MASTER_OUT_L] = new float[ buffers.nframes ];
buffers.audio[Buffers::MASTER_OUT_R] = new float[ buffers.nframes ];
for(int i = 0; i < NTRACKS; i++)
{
/** Setup the tracks:
* The TrackOutput gets a pointer to the next AudioProcessor to call:
* In this case, the track's Looper instance.
**/
loopers.push_back( new Looper(i) );
trackOutputs.push_back( new TrackOutput(i, loopers.back() ) );
buffers.audio[Buffers::TRACK_0 + i] = new float[ buffers.nframes ];
timeManager->registerObserver( loopers.back() );
}
/// setup DSP instances
inputVol = 1.0f;
masterVol = 0.75f;
masterMeter = new DBMeter( buffers.samplerate );
inputMeter = new DBMeter( buffers.samplerate );
buffers.transportPosition = 0;
/// setup JACK callbacks
if ( jack_set_process_callback( client,
static_process,
static_cast<void*>(this)) )
{
LUPPP_ERROR("%s","Error setting process callback");
}
if ( jack_set_timebase_callback(client,
0, //0, 0 == must be master, 1 == conditional
(JackTimebaseCallback)static_timebase,
static_cast<void*>(this)) )
{
LUPPP_ERROR("%s","Error setting timebase callback");
}
//Controller* m = new AkaiAPC();
// TODO: Add GUI dialog to add controllers, and insert them into the controller map.
// Watch out for RT stuff, loading file, registering ports etc: before activate?!
//Controller* m = new GenericMIDI("akai_apc.ctlr","apc");
Controller* gui = new LupppGUI();
controllerUpdater->registerController( gui );
if ( !gui )
{
LUPPP_ERROR("%s","Error creating LupppGUI Controller instance");
}
}
Jack::~Jack()
{
delete timeManager;
delete metronome;
delete state;
delete logic;
delete gridLogic;
delete controllerUpdater;
delete inputMeter;
delete masterMeter;
}
void Jack::activate()
{
jack_activate( client );
jack_transport_start(client);
}
void Jack::quit()
{
//jack_deactivate( client );
jack_client_close( client );
LUPPP_NOTE("%s","Quit JACK.");
}
TrackOutput* Jack::getTrackOutput(int t)
{
if ( t >= 0 && t < NTRACKS )
return trackOutputs.at(t);
#ifdef DEBUG_TRACKS
else
{
printf( "Jack::getTrackOutput() returning 0x0: invalid track requested!\n" );
}
#endif
return 0;
}
Looper* Jack::getLooper(int t)
{
if ( t >= 0 && t < NTRACKS )
return loopers.at(t);
#ifdef DEBUG_TRACKS
else
{
printf( "Jack::getLooper() returning 0x0: invalid track requested!\n" );
}
#endif
return 0;
}
void Jack::registerMidiIO( MidiIO* mo )
{
//LUPPP_NOTE("Jack::registerMidiIO()" );
// CAREFUL : this could need to resize and cause malloc() in RT thread
midiIO.push_back( mo );
}
void Jack::unregisterMidiIO( MidiIO* mo )
{
LUPPP_NOTE("Jack::unregisterMidiIO()");
// unregister the observer
for(unsigned int i = 0; i < midiIO.size(); i++)
{
if ( midiIO.at(i) == mo )
{
cout << "removing mo at " << i << endl;
midiIO.erase( midiIO.begin() + i );
return;
}
}
}
int Jack::process (jack_nframes_t nframes)
{
/// get buffers
buffers.audio[Buffers::MASTER_INPUT] = (float*)jack_port_get_buffer( masterInput , nframes );
buffers.audio[Buffers::MASTER_RETURN_L] = (float*)jack_port_get_buffer( masterReturnL , nframes );
buffers.audio[Buffers::MASTER_RETURN_R] = (float*)jack_port_get_buffer( masterReturnR , nframes );
buffers.audio[Buffers::JACK_SEND_OUT] = (float*)jack_port_get_buffer( sendOutput , nframes );
buffers.audio[Buffers::JACK_MASTER_OUT_L] = (float*)jack_port_get_buffer( masterOutputL , nframes );
buffers.audio[Buffers::JACK_MASTER_OUT_R] = (float*)jack_port_get_buffer( masterOutputR , nframes );
buffers.audio[Buffers::JACK_SIDECHAIN_KEY] = (float*)jack_port_get_buffer(sidechainKeyOutput,nframes);
buffers.audio[Buffers::JACK_SIDECHAIN_SIGNAL]=(float*)jack_port_get_buffer(sidechainSignalOutput,nframes);
//buffers.midi [Buffers::MASTER_MIDI_INPUT] = (void*) jack_port_get_buffer( masterMidiInput, nframes );
/// init buffers for each MidiIO
for(unsigned int i = 0; i < midiIO.size(); i++ )
{
midiIO.at(i)->initBuffers( nframes );
}
/// do events from the ringbuffer
handleDspEvents();
/// process incoming MIDI
/*
jack_midi_event_t in_event;
int masterMidiInputIndex = 0;
int event_count = (int) jack_midi_get_event_count( buffers.midi[Buffers::MASTER_MIDI_INPUT] );
while ( masterMidiInputIndex < event_count )
{
jack_midi_event_get(&in_event, buffers.midi[Buffers::MASTER_MIDI_INPUT], masterMidiInputIndex);
char buffer [50];
sprintf (buffer, "MIDI %i %i %i", int(in_event.buffer[0]), int(in_event.buffer[1]), int(in_event.buffer[2]) );
EventGuiPrint e( buffer );
writeToGuiRingbuffer( &e );
masterMidiInputIndex++;
}
*/
/// update "time" from JACK master, or write master?
buffers.transportFrame = jack_get_current_transport_frame(client);
// time manager deals with detecting bar() / beat() events, and calls
// processFrames() with the appropriate nframes
timeManager->process( &buffers );
return 0;
}
void Jack::processFrames(int nframes)
{
if ( nframes < 0 )
{
LUPPP_WARN("Jack processFrames got nframes < 0");
return;
}
// clear the buffers
memset( buffers.audio[Buffers::JACK_MASTER_OUT_L] , 0, sizeof(float) * nframes );
memset( buffers.audio[Buffers::JACK_MASTER_OUT_R] , 0, sizeof(float) * nframes );
memset( buffers.audio[Buffers::MASTER_OUT_L] , 0, sizeof(float) * nframes );
memset( buffers.audio[Buffers::MASTER_OUT_R] , 0, sizeof(float) * nframes );
memset( buffers.audio[Buffers::SEND] , 0, sizeof(float) * nframes );
memset( buffers.audio[Buffers::SIDECHAIN_KEY] , 0, sizeof(float) * nframes );
memset( buffers.audio[Buffers::SIDECHAIN_SIGNAL] , 0, sizeof(float) * nframes );
/// process each MidiIO registered MIDI port
for(unsigned int i = 0; i < midiIO.size(); i++ )
{
midiIO.at(i)->process( nframes );
}
/// process each track, starting at output and working up signal path
for(unsigned int i = 0; i < NTRACKS; i++)
{
trackOutputs.at(i)->process( nframes, &buffers );
}
/// metro signal
metronome->process( nframes, &buffers );
/// mix input, reverb & post-sidechain in
for(unsigned int i = 0; i < buffers.nframes; i++)
{
float input= buffers.audio[Buffers::MASTER_INPUT][i] * inputVol;
float L = buffers.audio[Buffers::MASTER_OUT_L][i];
float R = buffers.audio[Buffers::MASTER_OUT_R][i];
float returnL = buffers.audio[Buffers::MASTER_RETURN_L][i];
float returnR = buffers.audio[Buffers::MASTER_RETURN_R][i];
if ( inputToMixEnable )
{
// if sending to mix, scale by volume *and* by XSide send
float tmp = input * inputToMixVol * (1-inputToXSideVol);
L += tmp;
R += tmp;
}
if ( inputToSendEnable )
{
// post-mix-send amount: hence * inputToMixVol
buffers.audio[Buffers::SEND][i] += input * inputToSendVol * inputToMixVol;
}
if ( inputToKeyEnable )
{
buffers.audio[Buffers::SIDECHAIN_KEY][i] += input;
}
buffers.audio[Buffers::SIDECHAIN_SIGNAL][i] += input * inputToXSideVol;
/// mixdown returns into master buffers
buffers.audio[Buffers::MASTER_OUT_L][i] = (L + returnL*returnVol) * masterVol;
buffers.audio[Buffers::MASTER_OUT_R][i] = (R + returnR*returnVol) * masterVol;
/// write SEND content to JACK port
buffers.audio[Buffers::JACK_SEND_OUT][i] = buffers.audio[Buffers::SEND][i];
buffers.audio[Buffers::JACK_SIDECHAIN_KEY][i] = buffers.audio[Buffers::SIDECHAIN_KEY][i];
buffers.audio[Buffers::JACK_SIDECHAIN_SIGNAL][i] = buffers.audio[Buffers::SIDECHAIN_SIGNAL][i];
}
/// db meter on master input & output
inputMeter->process( nframes, buffers.audio[Buffers::MASTER_INPUT], buffers.audio[Buffers::MASTER_INPUT]);
masterMeter->process(nframes, buffers.audio[Buffers::MASTER_OUT_L], buffers.audio[Buffers::MASTER_OUT_R] );
if ( uiUpdateCounter > uiUpdateConstant )
{
// instead of scaling whole buffer, just scale output by vol
EventTrackSignalLevel e(-1, masterMeter->getLeftDB(), masterMeter->getRightDB() );
writeToGuiRingbuffer( &e );
EventTrackSignalLevel e2(-2, inputMeter->getLeftDB() * inputVol, inputMeter->getRightDB() * inputVol );
writeToGuiRingbuffer( &e2 );
uiUpdateCounter = 0;
}
uiUpdateCounter += nframes;
// memcpy the internal MASTER_OUTPUT buffer to the JACK_MASTER_OUTPUT
memcpy( buffers.audio[Buffers::JACK_MASTER_OUT_L],
buffers.audio[Buffers::MASTER_OUT_L],
sizeof(float)*nframes);
memcpy( buffers.audio[Buffers::JACK_MASTER_OUT_R],
buffers.audio[Buffers::MASTER_OUT_R],
//buffers.audio[Buffers::POST_SIDECHAIN],
//buffers.audio[Buffers::SEND], // uncomment to listen to reverb send only
sizeof(float)*nframes);
// move buffer pointers up nframes: allows processing of one "nframes" from
// JACK in multiple parts internally in Luppp: used for processing bar() / beat()
// if a full JACK nframes has been processed, this is extra work: its not that expensive
/// update buffers by nframes
buffers.audio[Buffers::MASTER_INPUT] = &buffers.audio[Buffers::MASTER_INPUT][nframes];
buffers.audio[Buffers::MASTER_RETURN_L] = &buffers.audio[Buffers::MASTER_RETURN_L][nframes];
buffers.audio[Buffers::MASTER_RETURN_R] = &buffers.audio[Buffers::MASTER_RETURN_R][nframes];
buffers.audio[Buffers::JACK_SEND_OUT] = &buffers.audio[Buffers::JACK_SEND_OUT][nframes];
buffers.audio[Buffers::JACK_MASTER_OUT_L] = &buffers.audio[Buffers::JACK_MASTER_OUT_L][nframes];
buffers.audio[Buffers::JACK_MASTER_OUT_R] = &buffers.audio[Buffers::JACK_MASTER_OUT_R][nframes];
buffers.audio[Buffers::JACK_SIDECHAIN_KEY] = &buffers.audio[Buffers::JACK_SIDECHAIN_KEY][nframes];
buffers.audio[Buffers::JACK_SIDECHAIN_SIGNAL]=&buffers.audio[Buffers::JACK_SIDECHAIN_SIGNAL][nframes];
return;
}
void Jack::masterVolume(float vol)
{
masterVol = vol;
}
void Jack::returnVolume(float vol)
{
returnVol = vol;
}
void Jack::inputVolume(float v)
{
inputVol = v * 2;
}
void Jack::inputTo(INPUT_TO to, float v)
{
switch ( to )
{
case INPUT_TO_MIX:
inputToMixVol = v;
break;
case INPUT_TO_SEND:
inputToSendVol = v;
break;
case INPUT_TO_XSIDE:
inputToXSideVol = v;
break;
default:
break;
}
}
void Jack::inputToActive(INPUT_TO to, bool a)
{
switch ( to )
{
case INPUT_TO_MIX:
inputToMixEnable = a;
break;
case INPUT_TO_SEND:
inputToSendEnable = a;
break;
case INPUT_TO_SIDE_KEY:
inputToKeyEnable = a;
break;
default:
break;
}
}
int Jack::getBuffersize()
{
return jack_get_buffer_size( client );
}
int Jack::getSamplerate()
{
return jack_get_sample_rate( client );
}
int Jack::timebase(jack_transport_state_t state,
jack_nframes_t nframes,
jack_position_t* pos,
int newPos)
{
// fill buffers with data, then pass to timeManager
buffers.transportPosition = pos;
buffers.transportState =&state;
return 0;
}
int Jack::static_process(jack_nframes_t nframes, void *instance)
{
return static_cast<Jack*>(instance)->process(nframes);
}
int Jack::static_timebase(jack_transport_state_t state,
jack_nframes_t nframes,
jack_position_t* pos,
int newPos,
void* instance)
{
return static_cast<Jack*>(instance)->timebase(state,nframes, pos, newPos );
}