/**
* Relational pipes
* Copyright © 2018 František Kučera (Frantovo.cz, GlobalCode.info)
*
* 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, version 3 of the License.
*
* 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/>.
*/
#pragma once
#include <memory>
#include <atomic>
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
#include <locale>
#include <codecvt>
#include <sys/mman.h>
#include <signal.h>
#include <jack/jack.h>
#include <jack/midiport.h>
#include <jack/ringbuffer.h>
#include <relpipe/common/type/typedefs.h>
#include <relpipe/reader/TypeId.h>
#include <relpipe/reader/handlers/RelationalReaderStringHandler.h>
#include <relpipe/reader/handlers/AttributeMetadata.h>
#include "Configuration.h"
#include "JackException.h"
namespace relpipe {
namespace out {
namespace jack {
int dequeueMessages(jack_nframes_t frames, void* arg);
class JackHandler : public relpipe::reader::handlers::RelationalReaderStringHandler {
private:
Configuration& configuration;
std::wstring_convert<std::codecvt_utf8<wchar_t>> convertor; // TODO: local system encoding
jack_client_t* jackClient = nullptr;
jack_port_t* jackPort = nullptr;
jack_ringbuffer_t* ringBuffer = nullptr;
std::atomic<bool> continueProcessing{true};
const int RING_BUFFER_SIZE = 100;
struct MidiMessage {
uint8_t buffer[4096];
uint32_t size;
uint32_t time;
};
public:
JackHandler(Configuration& configuration) : configuration(configuration) {
// Initialize JACK connection:
std::string clientName = convertor.to_bytes(configuration.jackClientName);
jackClient = jack_client_open(clientName.c_str(), JackNullOption, nullptr);
if (jackClient == nullptr) throw JackException(L"Could not create JACK client.");
ringBuffer = jack_ringbuffer_create(RING_BUFFER_SIZE * sizeof (MidiMessage));
jack_set_process_callback(jackClient, relpipe::out::jack::dequeueMessages, this);
// TODO: report also other events (connections etc.)
jackPort = jack_port_register(jackClient, "output", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0);
if (jackPort == nullptr) throw JackException(L"Could not register port.");
if (mlockall(MCL_CURRENT | MCL_FUTURE)) fwprintf(stderr, L"Warning: Can not lock memory.\n");
int jackError = jack_activate(jackClient);
if (jackError) throw JackException(L"Could not activate client.");
}
void startRelation(const relpipe::common::type::StringX name, std::vector<relpipe::reader::handlers::AttributeMetadata> attributes) override {
// TODO: validate metadata and prepare attribute mappings (names and types are important, order does not matter)
}
void attribute(const relpipe::common::type::StringX& value) override {
// TODO: append to current message + if this is last attribute, put whole message to the ring buffer
// TODO: if (continueProcessing) {} ?
}
void endOfPipe() {
// TODO: send optional (configurable) MIDI events
// TODO: wait until the ring buffer is empty
}
int dequeueMessages(jack_nframes_t frames) {
// TODO: send events from the ring buffer to JACK + correct timing
std::cout << "dequeueMessages(" << frames << ")" << std::endl;
return 0;
}
void finish(int sig) {
continueProcessing = false;
}
virtual ~JackHandler() {
// Close JACK connection:
jack_deactivate(jackClient);
jack_client_close(jackClient);
jack_ringbuffer_free(ringBuffer);
}
};
int dequeueMessages(jack_nframes_t frames, void* arg) {
JackHandler* instance = (JackHandler*) arg;
return instance->dequeueMessages(frames);
}
}
}
}