/* * 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 . */ #include "jack.hxx" #include #include #include #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(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(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(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(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(instance)->timebase(state,nframes, pos, newPos ); }