mirror of
https://github.com/holub/mame
synced 2025-05-30 09:33:05 +03:00
515 lines
15 KiB
C++
515 lines
15 KiB
C++
/* pmhaiku.cpp -- PortMidi os-dependent code */
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <vector>
|
|
#include <MidiConsumer.h>
|
|
#include <MidiEndpoint.h>
|
|
#include <MidiProducer.h>
|
|
#include <MidiRoster.h>
|
|
#include <MidiSynth.h>
|
|
#include "portmidi.h"
|
|
#include "pmutil.h"
|
|
#include "pminternal.h"
|
|
|
|
// as long as Java on Haiku uses ~/.java/... to store user preferences, we can directly reuse the Linux version
|
|
// (it shouldn't, see https://github.com/haikuports/haikuports/issues/7119)
|
|
extern "C" {
|
|
#include "../pm_linux/finddefault.h"
|
|
}
|
|
|
|
namespace {
|
|
struct PmInputConsumer : BMidiLocalConsumer {
|
|
PmInputConsumer(PmInternal *midi) :
|
|
BMidiLocalConsumer("PortMidi input consumer"),
|
|
midi(midi)
|
|
{
|
|
}
|
|
|
|
|
|
void Data(uchar *data, size_t length, bool atomic, bigtime_t time)
|
|
{
|
|
if (!atomic)
|
|
return; // should these be also supported?
|
|
|
|
if (data[0] == B_SYS_EX_START) {
|
|
pm_read_bytes(midi, data, length, time / 1000);
|
|
} else {
|
|
PmEvent event;
|
|
switch (length) {
|
|
case 1:
|
|
event.message = Pm_Message(data[0], 0, 0);
|
|
break;
|
|
case 2:
|
|
event.message = Pm_Message(data[0], data[1], 0);
|
|
break;
|
|
case 3:
|
|
event.message = Pm_Message(data[0], data[1], data[2]);
|
|
break;
|
|
default:
|
|
printf("Unexpected message length for short message, got %" B_PRIuSIZE "\n", length);
|
|
break;
|
|
}
|
|
event.timestamp = time / 1000;
|
|
pm_read_short(midi, &event);
|
|
}
|
|
}
|
|
|
|
private:
|
|
PmInternal *midi;
|
|
};
|
|
|
|
struct PmOutputInfo {
|
|
BMidiLocalProducer *producer;
|
|
std::vector<unsigned char> sysexBuffer;
|
|
};
|
|
|
|
struct PmSynthOutputInfo {
|
|
BMidiSynth midiSynth;
|
|
std::vector<unsigned char> sysexBuffer;
|
|
};
|
|
|
|
|
|
PmTimestamp synchronize(PmInternal *midi)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
PmError in_open(PmInternal *midi, void *driverInfo)
|
|
{
|
|
int32 endpointID = (int32)(intptr_t)pm_descriptors[midi->device_id].descriptor;
|
|
BMidiProducer *producer = BMidiRoster::FindProducer(endpointID);
|
|
if (!producer)
|
|
return pmInvalidDeviceId;
|
|
PmInputConsumer *consumer = new PmInputConsumer(midi);
|
|
status_t status = producer->Connect(consumer);
|
|
if (status != B_OK) {
|
|
consumer->Release();
|
|
producer->Release();
|
|
strcpy(pm_hosterror_text, strerror(status));
|
|
pm_hosterror = TRUE;
|
|
return pmHostError;
|
|
}
|
|
midi->api_info = consumer;
|
|
producer->Release();
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
PmError in_abort(PmInternal *midi)
|
|
{
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
PmError in_close(PmInternal *midi)
|
|
{
|
|
int32 endpointID = (int32)(intptr_t)pm_descriptors[midi->device_id].descriptor;
|
|
BMidiProducer *producer = BMidiRoster::FindProducer(endpointID);
|
|
if (!producer)
|
|
return pmInvalidDeviceId;
|
|
PmInputConsumer *consumer = (PmInputConsumer*)midi->api_info;
|
|
status_t status = producer->Disconnect(consumer);
|
|
if (status != B_OK) {
|
|
consumer->Release();
|
|
producer->Release();
|
|
strcpy(pm_hosterror_text, strerror(status));
|
|
pm_hosterror = TRUE;
|
|
return pmHostError;
|
|
}
|
|
consumer->Release();
|
|
midi->api_info = NULL;
|
|
producer->Release();
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
PmError out_open(PmInternal *midi, void *driverInfo)
|
|
{
|
|
int32 endpointID = (int32)(intptr_t)pm_descriptors[midi->device_id].descriptor;
|
|
BMidiConsumer *consumer = BMidiRoster::FindConsumer(endpointID);
|
|
if (!consumer)
|
|
return pmInvalidDeviceId;
|
|
BMidiLocalProducer *producer = new BMidiLocalProducer("PortMidi output producer");
|
|
status_t status = producer->Connect(consumer);
|
|
if (status != B_OK) {
|
|
consumer->Release();
|
|
producer->Release();
|
|
strcpy(pm_hosterror_text, strerror(status));
|
|
pm_hosterror = TRUE;
|
|
return pmHostError;
|
|
}
|
|
PmOutputInfo *info = new PmOutputInfo;
|
|
info->producer = producer;
|
|
midi->api_info = info;
|
|
consumer->Release();
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
PmError out_abort(PmInternal *midi)
|
|
{
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
PmError out_close(PmInternal *midi)
|
|
{
|
|
int32 endpointID = (int32)(intptr_t)pm_descriptors[midi->device_id].descriptor;
|
|
BMidiConsumer *consumer = BMidiRoster::FindConsumer(endpointID);
|
|
if (!consumer)
|
|
return pmInvalidDeviceId;
|
|
PmOutputInfo *info = (PmOutputInfo*)midi->api_info;
|
|
status_t status = info->producer->Disconnect(consumer);
|
|
if (status != B_OK) {
|
|
consumer->Release();
|
|
midi->api_info = NULL;
|
|
info->producer->Release();
|
|
delete info;
|
|
strcpy(pm_hosterror_text, strerror(status));
|
|
pm_hosterror = TRUE;
|
|
return pmHostError;
|
|
}
|
|
consumer->Release();
|
|
midi->api_info = NULL;
|
|
info->producer->Release();
|
|
delete info;
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
// copied from the macOS implementation
|
|
size_t midi_length(uchar status)
|
|
{
|
|
int high, low;
|
|
static size_t high_lengths[] = {
|
|
1, 1, 1, 1, 1, 1, 1, 1, /* 0x00 through 0x70 */
|
|
3, 3, 3, 3, 2, 2, 3, 1 /* 0x80 through 0xf0 */
|
|
};
|
|
static size_t low_lengths[] = {
|
|
1, 2, 3, 2, 1, 1, 1, 1, /* 0xf0 through 0xf8 */
|
|
1, 1, 1, 1, 1, 1, 1, 1 /* 0xf9 through 0xff */
|
|
};
|
|
|
|
high = status >> 4;
|
|
low = status & 15;
|
|
|
|
return (high != 0xF) ? high_lengths[high] : low_lengths[low];
|
|
}
|
|
|
|
|
|
PmError write_short(PmInternal *midi, PmEvent *buffer)
|
|
{
|
|
PmOutputInfo *info = (PmOutputInfo*)midi->api_info;
|
|
uchar data[3];
|
|
data[0] = Pm_MessageStatus(buffer->message);
|
|
data[1] = Pm_MessageData1(buffer->message);
|
|
data[2] = Pm_MessageData2(buffer->message);
|
|
size_t length = midi_length(data[0]);
|
|
|
|
info->producer->SprayData(data, length, true, buffer->timestamp * 1000);
|
|
|
|
// TODO: handle latency != 0
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
PmError begin_sysex(PmInternal *midi, PmTimestamp timestamp)
|
|
{
|
|
PmOutputInfo *info = (PmOutputInfo*)midi->api_info;
|
|
info->sysexBuffer.clear();
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
PmError end_sysex(PmInternal *midi, PmTimestamp timestamp)
|
|
{
|
|
PmOutputInfo *info = (PmOutputInfo*)midi->api_info;
|
|
info->producer->SpraySystemExclusive(&info->sysexBuffer[0], info->sysexBuffer.size(), timestamp * 1000);
|
|
info->sysexBuffer.clear();
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
PmError write_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp)
|
|
{
|
|
PmOutputInfo *info = (PmOutputInfo*)midi->api_info;
|
|
info->sysexBuffer.push_back(byte);
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
PmError write_realtime(PmInternal *midi, PmEvent *buffer)
|
|
{
|
|
PmOutputInfo *info = (PmOutputInfo*)midi->api_info;
|
|
info->producer->SpraySystemRealTime(Pm_MessageStatus(buffer->message), buffer->timestamp * 1000);
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
PmError synth_open(PmInternal *midi, void *driverInfo)
|
|
{
|
|
PmSynthOutputInfo *info = new PmSynthOutputInfo;
|
|
info->midiSynth.EnableInput(true, true);
|
|
midi->api_info = info;
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
PmError synth_abort(PmInternal *midi)
|
|
{
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
PmError synth_close(PmInternal *midi)
|
|
{
|
|
PmSynthOutputInfo *info = (PmSynthOutputInfo*)midi->api_info;
|
|
delete info;
|
|
midi->api_info = NULL;
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
PmError write_short_synth(PmInternal *midi, PmEvent *buffer)
|
|
{
|
|
PmSynthOutputInfo *info = (PmSynthOutputInfo*)midi->api_info;
|
|
uchar data[3];
|
|
data[0] = Pm_MessageStatus(buffer->message);
|
|
data[1] = Pm_MessageData1(buffer->message);
|
|
data[2] = Pm_MessageData2(buffer->message);
|
|
|
|
switch(data[0] & 0xf0) {
|
|
case B_NOTE_OFF:
|
|
info->midiSynth.NoteOff((data[0] & 0x0f) + 1, data[1], data[2], buffer->timestamp);
|
|
break;
|
|
case B_NOTE_ON:
|
|
info->midiSynth.NoteOn((data[0] & 0x0f) + 1, data[1], data[2], buffer->timestamp);
|
|
break;
|
|
case B_KEY_PRESSURE:
|
|
info->midiSynth.KeyPressure((data[0] & 0x0f + 1), data[1], data[2], buffer->timestamp);
|
|
break;
|
|
case B_CONTROL_CHANGE:
|
|
info->midiSynth.ControlChange((data[0] & 0x0f) + 1, data[1], data[2], buffer->timestamp);
|
|
break;
|
|
case B_PROGRAM_CHANGE:
|
|
info->midiSynth.ProgramChange((data[0] & 0x0f) + 1, data[1], buffer->timestamp);
|
|
break;
|
|
case B_CHANNEL_PRESSURE:
|
|
info->midiSynth.ChannelPressure((data[0] & 0x0f) + 1, data[1], buffer->timestamp);
|
|
break;
|
|
case B_PITCH_BEND:
|
|
info->midiSynth.PitchBend((data[0] & 0x0f) + 1, data[1], data[2], buffer->timestamp);
|
|
break;
|
|
}
|
|
|
|
// TODO: handle latency != 0
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
PmError begin_sysex_synth(PmInternal *midi, PmTimestamp timestamp)
|
|
{
|
|
PmSynthOutputInfo *info = (PmSynthOutputInfo*)midi->api_info;
|
|
info->sysexBuffer.clear();
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
PmError end_sysex_synth(PmInternal *midi, PmTimestamp timestamp)
|
|
{
|
|
PmSynthOutputInfo *info = (PmSynthOutputInfo*)midi->api_info;
|
|
info->midiSynth.SystemExclusive(&info->sysexBuffer[0], info->sysexBuffer.size(), timestamp);
|
|
info->sysexBuffer.clear();
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
PmError write_byte_synth(PmInternal *midi, unsigned char byte, PmTimestamp timestamp)
|
|
{
|
|
PmSynthOutputInfo *info = (PmSynthOutputInfo*)midi->api_info;
|
|
info->sysexBuffer.push_back(byte);
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
PmError write_realtime_synth(PmInternal *midi, PmEvent *buffer)
|
|
{
|
|
PmSynthOutputInfo *info = (PmSynthOutputInfo*)midi->api_info;
|
|
info->midiSynth.SystemRealTime(Pm_MessageStatus(buffer->message), buffer->timestamp);
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
PmError write_flush(PmInternal *midi, PmTimestamp timestamp)
|
|
{
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
unsigned int check_host_error(PmInternal *midi)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
pm_fns_node pm_in_dictionary = {
|
|
none_write_short,
|
|
none_sysex,
|
|
none_sysex,
|
|
none_write_byte,
|
|
none_write_short,
|
|
none_write_flush,
|
|
synchronize,
|
|
in_open,
|
|
in_abort,
|
|
in_close,
|
|
success_poll,
|
|
check_host_error
|
|
};
|
|
|
|
pm_fns_node pm_out_dictionary = {
|
|
write_short,
|
|
begin_sysex,
|
|
end_sysex,
|
|
write_byte,
|
|
write_realtime,
|
|
write_flush,
|
|
synchronize,
|
|
out_open,
|
|
out_abort,
|
|
out_close,
|
|
none_poll,
|
|
check_host_error
|
|
};
|
|
|
|
|
|
pm_fns_node pm_synth_dictionary = {
|
|
write_short_synth,
|
|
begin_sysex_synth,
|
|
end_sysex_synth,
|
|
write_byte_synth,
|
|
write_realtime_synth,
|
|
write_flush,
|
|
synchronize,
|
|
synth_open,
|
|
synth_abort,
|
|
synth_close,
|
|
none_poll,
|
|
check_host_error
|
|
};
|
|
|
|
|
|
PmError create_virtual(int is_input, const char *name, void *driverInfo)
|
|
{
|
|
BMidiEndpoint *endpoint = is_input ? (BMidiEndpoint*)new BMidiLocalProducer(name) : new BMidiLocalConsumer(name);
|
|
if (!endpoint->IsValid()) {
|
|
endpoint->Release();
|
|
strcpy(pm_hosterror_text, "Endpoint could not be created");
|
|
pm_hosterror = TRUE;
|
|
return pmHostError;
|
|
}
|
|
status_t status = endpoint->Register();
|
|
if (status != B_OK) {
|
|
endpoint->Release();
|
|
strcpy(pm_hosterror_text, strerror(status));
|
|
pm_hosterror = TRUE;
|
|
return pmHostError;
|
|
}
|
|
return pm_add_device(const_cast<char*>("Haiku MIDI kit"), name, is_input, TRUE, (void*)(intptr_t)endpoint->ID(), is_input ? &pm_in_dictionary : &pm_out_dictionary);
|
|
}
|
|
|
|
PmError delete_virtual(PmDeviceID id)
|
|
{
|
|
int32 endpointID = (int32)(intptr_t)pm_descriptors[id].descriptor;
|
|
BMidiEndpoint *endpoint = BMidiRoster::FindEndpoint(endpointID);
|
|
//TODO: handle connected producers and consumers
|
|
status_t status = endpoint->Unregister();
|
|
// release twice to actually free the endpoint (FindEndpoint increases the ref-count)
|
|
endpoint->Release();
|
|
endpoint->Release();
|
|
if (status != B_OK) {
|
|
strcpy(pm_hosterror_text, strerror(status));
|
|
pm_hosterror = TRUE;
|
|
return pmHostError;
|
|
}
|
|
return pmNoError;
|
|
}
|
|
|
|
PmDeviceID pm_default_input_device_id = -1;
|
|
PmDeviceID pm_default_output_device_id = -1;
|
|
}
|
|
|
|
extern "C" {
|
|
void pm_init()
|
|
{
|
|
pm_add_interf(const_cast<char*>("Haiku MIDI kit"), create_virtual, delete_virtual);
|
|
|
|
pm_add_device(const_cast<char*>("Haiku MIDI kit"), "Soft Synth", FALSE, FALSE, NULL, &pm_synth_dictionary);
|
|
|
|
int32 id = 0;
|
|
BMidiEndpoint *endpoint;
|
|
|
|
while ((endpoint = BMidiRoster::NextEndpoint(&id)) != NULL) {
|
|
bool isInput = endpoint->IsProducer();
|
|
pm_add_device(const_cast<char*>("Haiku MIDI kit"), endpoint->Name(), isInput, FALSE, (void*)(intptr_t)id, isInput ? &pm_in_dictionary : &pm_out_dictionary);
|
|
endpoint->Release();
|
|
}
|
|
|
|
// the following (default device handling) is copied from the Linux implementation
|
|
|
|
// this is set when we return to Pm_Initialize, but we need it
|
|
// now in order to (successfully) call Pm_CountDevices()
|
|
pm_initialized = TRUE;
|
|
pm_default_input_device_id = find_default_device(
|
|
const_cast<char*>("/PortMidi/PM_RECOMMENDED_INPUT_DEVICE"), TRUE,
|
|
pm_default_input_device_id);
|
|
pm_default_output_device_id = find_default_device(
|
|
const_cast<char*>("/PortMidi/PM_RECOMMENDED_OUTPUT_DEVICE"), FALSE,
|
|
pm_default_output_device_id);
|
|
}
|
|
|
|
|
|
void pm_term()
|
|
{
|
|
int i;
|
|
for (i = 0; i < pm_descriptor_len; i++) {
|
|
PmInternal *midi = pm_descriptors[i].pm_internal;
|
|
if (midi && midi->api_info) {
|
|
// device is still open, close it
|
|
(*midi->dictionary->close)(midi);
|
|
}
|
|
if (pm_descriptors[i].pub.is_virtual && !pm_descriptors[i].deleted) {
|
|
delete_virtual(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
PmDeviceID Pm_GetDefaultInputDeviceID()
|
|
{
|
|
Pm_Initialize();
|
|
return pm_default_input_device_id;
|
|
}
|
|
|
|
|
|
PmDeviceID Pm_GetDefaultOutputDeviceID()
|
|
{
|
|
Pm_Initialize();
|
|
return pm_default_output_device_id;
|
|
}
|
|
|
|
|
|
void *pm_alloc(size_t s)
|
|
{
|
|
return malloc(s);
|
|
}
|
|
|
|
|
|
void pm_free(void *ptr)
|
|
{
|
|
free(ptr);
|
|
}
|
|
}
|