/* pmhaiku.cpp -- PortMidi os-dependent code */ #include #include #include #include #include #include #include #include #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 sysexBuffer; }; struct PmSynthOutputInfo { BMidiSynth midiSynth; std::vector 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("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("Haiku MIDI kit"), create_virtual, delete_virtual); pm_add_device(const_cast("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("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("/PortMidi/PM_RECOMMENDED_INPUT_DEVICE"), TRUE, pm_default_input_device_id); pm_default_output_device_id = find_default_device( const_cast("/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); } }