554 lines
13 KiB
C++
554 lines
13 KiB
C++
// ------------------------------------------------------------------------
|
|
// midi-server.cpp: MIDI i/o engine serving generic clients.
|
|
// Copyright (C) 2001-2002,2005,2007 Kai Vehmanen
|
|
//
|
|
// Attributes:
|
|
// eca-style-version: 3
|
|
//
|
|
// 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; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
// ------------------------------------------------------------------------
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <cstdlib>
|
|
#include <iostream>
|
|
|
|
#include <unistd.h>
|
|
#include <signal.h>
|
|
#include <sys/time.h>
|
|
|
|
#include <kvu_numtostr.h>
|
|
#include <kvu_dbc.h>
|
|
#include <kvu_rtcaps.h>
|
|
|
|
#include "midi-parser.h"
|
|
#include "midi-server.h"
|
|
#include "eca-logger.h"
|
|
|
|
const unsigned int MIDI_SERVER::max_queue_size_rep = 32768;
|
|
|
|
/**
|
|
* Helper function for starting the slave thread.
|
|
*/
|
|
void* start_midi_server_io_thread(void *ptr)
|
|
{
|
|
sigset_t sigset;
|
|
sigemptyset(&sigset);
|
|
sigaddset(&sigset, SIGINT);
|
|
sigprocmask(SIG_BLOCK, &sigset, 0);
|
|
|
|
MIDI_SERVER* mserver =
|
|
static_cast<MIDI_SERVER*>(ptr);
|
|
|
|
if (mserver->schedrealtime_rep == true) {
|
|
if (kvu_set_thread_scheduling(SCHED_FIFO, mserver->schedpriority_rep) != 0)
|
|
ECA_LOG_MSG(ECA_LOGGER::system_objects, "Unable to change scheduling policy!");
|
|
else
|
|
ECA_LOG_MSG(ECA_LOGGER::info,
|
|
std::string("Using realtime-scheduling (SCHED_FIFO:") + kvu_numtostr(mserver->schedpriority_rep) + ").");
|
|
}
|
|
|
|
/* launch the worker thread */
|
|
mserver->io_thread();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Slave thread.
|
|
*/
|
|
void MIDI_SERVER::io_thread(void)
|
|
{
|
|
fd_set fds;
|
|
unsigned char buf[16];
|
|
struct timeval tv;
|
|
|
|
ECA_LOG_MSG(ECA_LOGGER::user_objects, "Hey, in the I/O loop!");
|
|
while(true) {
|
|
if (running_rep.get() == 0 ||
|
|
clients_rep[0]->is_open() != true) {
|
|
usleep(50000);
|
|
if (exit_request_rep.get() == 1) break;
|
|
continue;
|
|
}
|
|
|
|
DBC_CHECK(clients_rep.size() > 0);
|
|
DBC_CHECK(clients_rep[0]->supports_nonblocking_mode() == true);
|
|
|
|
// FIXME: add support for multiple clients; gather poll
|
|
// descriptors from all clients and create the 'fds' set
|
|
|
|
int fd = clients_rep[0]->poll_descriptor();
|
|
|
|
FD_ZERO(&fds);
|
|
FD_SET(fd, &fds);
|
|
|
|
tv.tv_sec = 1;
|
|
tv.tv_usec = 0;
|
|
int retval = select(fd + 1 , &fds, NULL, NULL, &tv);
|
|
|
|
// FIXME: add multiple client support, go through
|
|
// all the fds
|
|
|
|
int read_bytes = 0;
|
|
if (retval) {
|
|
if (FD_ISSET(fd, &fds) == true) {
|
|
read_bytes = clients_rep[0]->read_bytes(buf, 16);
|
|
//std::cerr << "TRACE: Read from MIDI-device (bytes): " << read_bytes << "." << std::endl;
|
|
}
|
|
}
|
|
|
|
if (read_bytes < 0) {
|
|
std::cerr << "ERROR: Can't read from MIDI-device: "
|
|
<< clients_rep[0]->label() << "." << std::endl;
|
|
break;
|
|
}
|
|
else {
|
|
// cerr << "(midi-server) read bytes: " << read_bytes << endl;
|
|
for(int n = 0; n < read_bytes; n++) {
|
|
buffer_rep.push_back(buf[n]);
|
|
while(buffer_rep.size() > max_queue_size_rep) {
|
|
std::cerr << "(eca-midi) dropping midi bytes" << std::endl;
|
|
buffer_rep.pop_front();
|
|
}
|
|
for(unsigned int m = 0; m < handlers_rep.size(); m++) {
|
|
MIDI_HANDLER* p = handlers_rep[m];
|
|
if (p != 0) p->insert(buf[n]);
|
|
}
|
|
}
|
|
parse_receive_queue();
|
|
}
|
|
|
|
if (stop_request_rep.get() == 1) {
|
|
stop_request_rep.set(0);
|
|
running_rep.set(0);
|
|
}
|
|
}
|
|
ECA_LOG_MSG(ECA_LOGGER::system_objects, "exiting MIDI-server thread");
|
|
}
|
|
|
|
/**
|
|
* Constructor.
|
|
*/
|
|
MIDI_SERVER::MIDI_SERVER (void)
|
|
{
|
|
running_status_rep = 0;
|
|
current_ctrl_channel_rep = -1;
|
|
current_ctrl_number = -1;
|
|
thread_running_rep = false;
|
|
running_rep.set(0);
|
|
stop_request_rep.set(0);
|
|
exit_request_rep.set(0);
|
|
}
|
|
|
|
/**
|
|
* Destructor. Doesn't delete any client objects.
|
|
*/
|
|
MIDI_SERVER::~MIDI_SERVER(void)
|
|
{
|
|
if (is_enabled() == true) disable();
|
|
}
|
|
|
|
/**
|
|
* Starts the MIDI server.
|
|
*
|
|
* ensure
|
|
* is_running() == true
|
|
*/
|
|
void MIDI_SERVER::start(void)
|
|
{
|
|
stop_request_rep.set(0);
|
|
running_rep.set(1);
|
|
ECA_LOG_MSG(ECA_LOGGER::user_objects, "starting processing");
|
|
send_mmc_start();
|
|
if (is_midi_sync_send_enabled() == true) send_midi_start();
|
|
|
|
// --------
|
|
DBC_ENSURE(is_running() == true);
|
|
// --------
|
|
}
|
|
|
|
/**
|
|
* Stops the MIDI-server. Note that this routine only
|
|
* initializes the stop procedure. Processing will
|
|
* stop once the i/o-thread acknowledges the stop request.
|
|
*/
|
|
void MIDI_SERVER::stop(void)
|
|
{
|
|
stop_request_rep.set(1);
|
|
ECA_LOG_MSG(ECA_LOGGER::user_objects, "stopping processing");
|
|
send_mmc_stop();
|
|
if (is_midi_sync_send_enabled() == true) send_midi_stop();
|
|
}
|
|
|
|
/**
|
|
* Initializes the MIDI-server by resetting
|
|
* all MIDI-related state info.
|
|
*/
|
|
void MIDI_SERVER::init(void)
|
|
{
|
|
running_status_rep = 0;
|
|
current_ctrl_channel_rep = -1;
|
|
current_ctrl_number = -1;
|
|
}
|
|
|
|
/**
|
|
* Enables the MIDI-server subsystems and prepared them for
|
|
* processing.
|
|
*
|
|
* Use the set_schedrealtime() and set_schedpriority() functions
|
|
* to set the MIDI subsystem scheduling priority. These settings
|
|
* are set at enable().
|
|
*
|
|
* ensure:
|
|
* is_enabled() == true
|
|
*/
|
|
void MIDI_SERVER::enable(void)
|
|
{
|
|
init();
|
|
running_rep.set(0);
|
|
stop_request_rep.set(0);
|
|
exit_request_rep.set(0);
|
|
if (thread_running_rep != true) {
|
|
ECA_LOG_MSG(ECA_LOGGER::user_objects, "enabling");
|
|
int ret = pthread_create(&io_thread_rep,
|
|
0,
|
|
start_midi_server_io_thread,
|
|
static_cast<void *>(this));
|
|
if (ret != 0) {
|
|
ECA_LOG_MSG(ECA_LOGGER::info, "pthread_create failed, exiting");
|
|
exit(1);
|
|
}
|
|
thread_running_rep = true;
|
|
}
|
|
|
|
// --------
|
|
DBC_ENSURE(is_enabled() == true);
|
|
// --------
|
|
}
|
|
|
|
/**
|
|
* Whether MIDI-server is enabled?
|
|
*/
|
|
bool MIDI_SERVER::is_enabled(void) const { return thread_running_rep; }
|
|
|
|
/**
|
|
* Disables the MIDI-server subsystems.
|
|
*
|
|
* require:
|
|
* is_enabled() == true
|
|
*
|
|
* ensure:
|
|
* is_running() != true
|
|
* is_enabled() != true
|
|
*/
|
|
void MIDI_SERVER::disable(void)
|
|
{
|
|
// --------
|
|
DBC_REQUIRE(is_enabled() == true);
|
|
// --------
|
|
|
|
ECA_LOG_MSG(ECA_LOGGER::user_objects, "disabling");
|
|
stop_request_rep.set(1);
|
|
exit_request_rep.set(1);
|
|
if (thread_running_rep == true) {
|
|
::pthread_join(io_thread_rep, 0);
|
|
}
|
|
thread_running_rep = false;
|
|
|
|
// --------
|
|
DBC_ENSURE(is_running() != true);
|
|
DBC_ENSURE(is_enabled() != true);
|
|
// --------
|
|
}
|
|
|
|
/**
|
|
* Whether the MIDI server has been started?
|
|
*/
|
|
bool MIDI_SERVER::is_running(void) const
|
|
{
|
|
if (running_rep.get() == 0) return false;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Registers a new client object. Midi server doesn't
|
|
* handle initializing and opening of client objects.
|
|
*/
|
|
void MIDI_SERVER::register_client(MIDI_IO* mobject)
|
|
{
|
|
clients_rep.push_back(mobject);
|
|
ECA_LOG_MSG(ECA_LOGGER::user_objects,
|
|
"Registering client " +
|
|
kvu_numtostr(clients_rep.size() - 1) +
|
|
".");
|
|
}
|
|
|
|
/**
|
|
* Unregisters the client object given as the argument. No
|
|
* resources are freed during this call.
|
|
*/
|
|
void MIDI_SERVER::unregister_client(MIDI_IO* mobject)
|
|
{
|
|
for(unsigned int n = 0; n < clients_rep.size(); n++) {
|
|
if (clients_rep[n] == mobject) {
|
|
clients_rep[n] = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Registers a new MIDI-handler. The server will send
|
|
* all received MIDI-data to the handler.
|
|
*/
|
|
void MIDI_SERVER::register_handler(MIDI_HANDLER* object)
|
|
{
|
|
handlers_rep.push_back(object);
|
|
ECA_LOG_MSG(ECA_LOGGER::user_objects,
|
|
"Registering handler " +
|
|
kvu_numtostr(handlers_rep.size() - 1) +
|
|
".");
|
|
}
|
|
|
|
/**
|
|
* Unregisters the handler object given as the argument. No
|
|
* resources are freed during this call.
|
|
*/
|
|
void MIDI_SERVER::unregister_handler(MIDI_HANDLER* object)
|
|
{
|
|
for(unsigned int n = 0; n < handlers_rep.size(); n++) {
|
|
if (handlers_rep[n] == object) {
|
|
handlers_rep[n] = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a new client to which MMC-messages are sent
|
|
* during processing.
|
|
*
|
|
* Note! Id '127' is specified as the all-device
|
|
* id-number in the MMC-spec.
|
|
*/
|
|
void MIDI_SERVER::add_mmc_send_id(int id)
|
|
{
|
|
mmc_send_ids_rep.push_back(id);
|
|
}
|
|
|
|
/**
|
|
* Removes a MMC-message client.
|
|
*/
|
|
void MIDI_SERVER::remove_mmc_send_id(int id)
|
|
{
|
|
mmc_send_ids_rep.remove(id);
|
|
}
|
|
|
|
|
|
/**
|
|
* Sends MMC-start to all MMC-send client device ids.
|
|
*
|
|
* require:
|
|
* is_enabled() == true
|
|
*/
|
|
void MIDI_SERVER::send_midi_bytes(int dev_id, unsigned char* buf, int bytes) {
|
|
// --------
|
|
DBC_REQUIRE(is_enabled() == true);
|
|
// --------
|
|
|
|
if (clients_rep[dev_id - 1]->is_open() == true) {
|
|
DBC_CHECK(static_cast<int>(clients_rep.size()) >= dev_id);
|
|
DBC_CHECK(clients_rep[dev_id - 1]->supports_nonblocking_mode() == true);
|
|
|
|
int err = clients_rep[dev_id - 1]->write_bytes(buf, bytes);
|
|
|
|
DBC_CHECK(err == bytes);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sends an MMC-command to all MMC-send client device ids.
|
|
*/
|
|
void MIDI_SERVER::send_mmc_command(unsigned int cmd)
|
|
{
|
|
unsigned char buf[6];
|
|
buf[0] = 0xf0;
|
|
buf[1] = 0x7f;
|
|
buf[2] = 0x00; /* dev-id */
|
|
buf[3] = 0x06;
|
|
buf[4] = cmd;
|
|
buf[5] = 0xf7;
|
|
std::list<int>::const_iterator p = mmc_send_ids_rep.begin();
|
|
while(p != mmc_send_ids_rep.end()) {
|
|
ECA_LOG_MSG(ECA_LOGGER::system_objects,
|
|
"Sending MMC message " +
|
|
kvu_numtostr(cmd) + " to device-id " +
|
|
kvu_numtostr(*p) + ".");
|
|
buf[2] = static_cast<unsigned char>(*p);
|
|
send_midi_bytes(1, buf, 6);
|
|
++p;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sends MMC-start to all MMC-send client device ids.
|
|
*/
|
|
void MIDI_SERVER::send_mmc_start(void)
|
|
{
|
|
/* FIXME: should this be 0x03 (deferred play)? */
|
|
// send_mmc_command(0x02);
|
|
send_mmc_command(0x03);
|
|
}
|
|
|
|
/**
|
|
* Sends MMC-stop to all MMC-send client device ids.
|
|
*/
|
|
void MIDI_SERVER::send_mmc_stop(void)
|
|
{
|
|
send_mmc_command(0x01);
|
|
}
|
|
|
|
/**
|
|
* Sends a MIDI-start message.
|
|
*/
|
|
void MIDI_SERVER::send_midi_start(void)
|
|
{
|
|
unsigned char byte[1] = { 0xfa };
|
|
|
|
ECA_LOG_MSG(ECA_LOGGER::system_objects,
|
|
"Sending MIDI-start message.");
|
|
|
|
send_midi_bytes(1, byte, 1);
|
|
}
|
|
|
|
/**
|
|
* Sends a MIDI-continue message.
|
|
*/
|
|
void MIDI_SERVER::send_midi_continue(void)
|
|
{
|
|
unsigned char byte[1] = { 0xfb };
|
|
|
|
ECA_LOG_MSG(ECA_LOGGER::system_objects,
|
|
"Sending MIDI-continue message.");
|
|
|
|
send_midi_bytes(1, byte, 1);
|
|
}
|
|
|
|
/**
|
|
* Sends a MIDI-stop message.
|
|
*/
|
|
void MIDI_SERVER::send_midi_stop(void)
|
|
{
|
|
unsigned char byte[1] = { 0xfc };
|
|
|
|
ECA_LOG_MSG(ECA_LOGGER::system_objects,
|
|
"Sending MIDI-stop message.");
|
|
|
|
send_midi_bytes(1, byte, 1);
|
|
}
|
|
|
|
/**
|
|
* Requests that server will follow the latest value of
|
|
* controller 'ctrl' on channel 'channel'.
|
|
*/
|
|
void MIDI_SERVER::add_controller_trace(int channel, int ctrl, int initial_value)
|
|
{
|
|
controller_values_rep[std::pair<int,int>(channel,ctrl)] = initial_value;
|
|
}
|
|
|
|
/**
|
|
* Requests that server stops following the latest value of
|
|
* controller 'ctrl' on channel 'channel'.
|
|
*/
|
|
void MIDI_SERVER::remove_controller_trace(int channel, int controller)
|
|
{
|
|
std::map<std::pair<int,int>,int>::iterator p = controller_values_rep.find(std::pair<int,int>(channel,controller));
|
|
if (p != controller_values_rep.end()) {
|
|
controller_values_rep.erase(p);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the latest traced value of controller 'ctrl' on
|
|
* channel 'channel'.
|
|
*
|
|
* @return -1 is returned on error
|
|
*/
|
|
int MIDI_SERVER::last_controller_value(int channel, int ctrl) const
|
|
{
|
|
std::map<std::pair<int,int>,int>::iterator p = controller_values_rep.find(std::pair<int,int>(channel,ctrl));
|
|
if (p != controller_values_rep.end()) {
|
|
return controller_values_rep[std::pair<int,int>(channel,ctrl)];
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Parses the received MIDI date.
|
|
*/
|
|
void MIDI_SERVER::parse_receive_queue(void)
|
|
{
|
|
while(buffer_rep.size() > 0) {
|
|
unsigned char byte = buffer_rep.front();
|
|
buffer_rep.pop_front();
|
|
|
|
if (MIDI_PARSER::is_status_byte(byte) == true) {
|
|
if (MIDI_PARSER::is_voice_category_status_byte(byte) == true) {
|
|
running_status_rep = byte;
|
|
if ((running_status_rep & 0xb0) == 0xb0)
|
|
current_ctrl_channel_rep = static_cast<int>((byte & 15));
|
|
}
|
|
else if (MIDI_PARSER::is_system_common_category_status_byte(byte) == true) {
|
|
current_ctrl_channel_rep = -1;
|
|
running_status_rep = 0;
|
|
}
|
|
}
|
|
else { /* non-status bytes */
|
|
/**
|
|
* Any data bytes are ignored if no running status
|
|
*/
|
|
if (running_status_rep != 0) {
|
|
|
|
/**
|
|
* Check for 'controller messages' (status 0xb0 to 0xbf and
|
|
* two data bytes)
|
|
*/
|
|
if (current_ctrl_channel_rep != -1) {
|
|
if (current_ctrl_number == -1) {
|
|
current_ctrl_number = static_cast<int>(byte);
|
|
// cerr << endl << "C:" << current_ctrl_number << ".";
|
|
}
|
|
else {
|
|
if (controller_values_rep.find(std::pair<int,int>(current_ctrl_channel_rep,current_ctrl_number))
|
|
!= controller_values_rep.end()) {
|
|
controller_values_rep[std::pair<int,int>(current_ctrl_channel_rep,current_ctrl_number)] = static_cast<int>(byte);
|
|
// std::cerr << std::endl << "(midi-server) Value:"
|
|
// << controller_values_rep[std::pair<int,int>(current_ctrl_channel_rep,current_ctrl_number)]
|
|
// << ", ch:" << current_ctrl_channel_rep << ", ctrl:" << current_ctrl_number << ".";
|
|
}
|
|
// else {
|
|
// cerr << endl << "E:" << " found an entry we are not following..." << endl;
|
|
// }
|
|
current_ctrl_number = -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|