Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions data/developers.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ mmoole. (mmoole)
schnitzeltony
splisp
Heath Dutton (heathdutton)
Alexandr Zyurkalov (Alexander-Zyurkalov)
135 changes: 135 additions & 0 deletions src/engine/midioutput.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// SPDX-FileCopyrightText: Copyright (C) Kushview, LLC.
// SPDX-License-Identifier: GPL-3.0-or-later

#include "midioutput.h"
namespace element {
#if JUCE_MAC

MIDIEndpointRef findDestination (const juce::MidiDeviceInfo& deviceInfo)
{
SInt32 targetUID = deviceInfo.identifier.containsChar (' ')
? deviceInfo.identifier.fromLastOccurrenceOf (" ", false, false).getIntValue()
: deviceInfo.identifier.getIntValue();
ItemCount count = MIDIGetNumberOfDestinations();

for (ItemCount i = 0; i < count; ++i)
{
MIDIEndpointRef endpoint = MIDIGetDestination (i);
SInt32 uid = 0;
MIDIObjectGetIntegerProperty (endpoint, kMIDIPropertyUniqueID, &uid);

if (uid == targetUID)
return endpoint;
}

return 0;
}

ElementMidiOutput::ElementMidiOutput (const juce::MidiDeviceInfo& deviceInfo)
{
destination = findDestination (deviceInfo);
if (destination)
{
MIDIClientCreate (CFSTR ("ElementMIDIOutput"), nullptr, nullptr, &client);
MIDIOutputPortCreate (client, deviceInfo.name.toCFString(), &port);
mach_timebase_info (&timebase);
}
}

juce::Result ElementMidiOutput::openDevice (const juce::MidiDeviceInfo& deviceInfo, std::unique_ptr<ElementMidiOutput>& out)
{
out = std::make_unique<ElementMidiOutput> (deviceInfo);
if (! out->destination)
{
out.reset();
return juce::Result::fail ("MIDI destination not found: " + deviceInfo.name);
}
return juce::Result::ok();
}

void ElementMidiOutput::closeDevice()
{
if (! destination)
return;
MIDIFlushOutput (destination);
MIDIClientDispose (client);
destination = 0;
}

MIDITimeStamp ElementMidiOutput::futureTimestamp (uint64_t nanosFromNow) const
{
MIDITimeStamp ticks = nanosFromNow * timebase.denom / timebase.numer;
return mach_absolute_time() + ticks;
}

juce::Result ElementMidiOutput::sendBlockOfMessages (const juce::MidiBuffer& midi, double delayMs, double sampleRate) const
{
constexpr size_t BUFFER_SIZE_FOR_PACKET_LIST = 1024;
uint8_t buffer[BUFFER_SIZE_FOR_PACKET_LIST];
auto* packetList = reinterpret_cast<MIDIPacketList*> (buffer);
MIDIPacket* packet = MIDIPacketListInit (packetList);

for (juce::MidiMessageMetadata message : midi)
{
const double nanoSeconds = message.samplePosition / sampleRate * 1'000'000'000.0 + delayMs * 1'000'000.0;
const auto ticks = futureTimestamp (static_cast<uint64_t> (std::round (nanoSeconds)));
packet = MIDIPacketListAdd (packetList, BUFFER_SIZE_FOR_PACKET_LIST, packet, ticks, message.numBytes, message.data);
}

OSStatus status = MIDISend (port, destination, packetList);
if (status != noErr)
return juce::Result::fail ("MIDISend failed with OSStatus " + juce::String (status));
return juce::Result::ok();
}

#else

void ElementMidiOutput::closeDevice()
{
if (output)
{
output->stopBackgroundThread();
output->clearAllPendingMessages();
output.reset();
}
}

ElementMidiOutput::ElementMidiOutput (const juce::MidiDeviceInfo& deviceInfo)
{
output = juce::MidiOutput::openDevice (deviceInfo.identifier);

if (output)
{
output->clearAllPendingMessages();
output->startBackgroundThread();
}
else
{
DBG ("[element] could not open MIDI output: " << deviceInfo.name);
}
}

juce::Result ElementMidiOutput::openDevice (const juce::MidiDeviceInfo& deviceInfo, std::unique_ptr<ElementMidiOutput>& out)
{
out = std::make_unique<ElementMidiOutput> (deviceInfo);
if (! out->output)
{
out.reset();
return juce::Result::fail ("Could not open MIDI output: " + deviceInfo.name);
}
return juce::Result::ok();
}

juce::Result ElementMidiOutput::sendBlockOfMessages (const juce::MidiBuffer& midi, double delayMs, double sampleRate) const
{
#if JUCE_WINDOWS
output->sendBlockOfMessagesNow (midi);
#else
output->sendBlockOfMessages (
midi, delayMs + juce::Time::getMillisecondCounterHiRes(), sampleRate);
#endif
return juce::Result::ok();
}
#endif

} // namespace element
41 changes: 41 additions & 0 deletions src/engine/midioutput.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: Copyright (C) Kushview, LLC.
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include "element/juce.hpp"

#if JUCE_MAC
#include <CoreMIDI/CoreMIDI.h>
//#include <CoreFoundation/CoreFoundation.h>
#include <mach/mach_time.h>
#endif

#include "midioutput.h"
#include <memory>

namespace element {

class ElementMidiOutput
{
public:
ElementMidiOutput() = delete;
ElementMidiOutput (const juce::MidiDeviceInfo& deviceInfo);

void closeDevice();
static juce::Result openDevice (const juce::MidiDeviceInfo& deviceInfo, std::unique_ptr<ElementMidiOutput>& out);
juce::Result sendBlockOfMessages (const juce::MidiBuffer& midi, double delayMs, double sampleRate) const;

private:
#if JUCE_MAC
MIDIClientRef client {};
MIDIEndpointRef destination {};
MIDIPortRef port {};
mach_timebase_info_data_t timebase{};
MIDITimeStamp futureTimestamp (uint64_t nanosFromNow) const;
#else
std::unique_ptr<juce::MidiOutput> output;
#endif
};

} // namespace element
26 changes: 9 additions & 17 deletions src/nodes/mididevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,22 +205,19 @@ void MidiDeviceProcessor::setDevice (const MidiDeviceInfo& newDevice)
{
if (output)
{
output->stopBackgroundThread();
output->clearAllPendingMessages();
output->closeDevice();
output.reset();
}

output = MidiOutput::openDevice (deviceWanted.identifier);
auto openResult = ElementMidiOutput::openDevice (deviceWanted, output);

if (output)
if (openResult.wasOk())
{
output->clearAllPendingMessages();
output->startBackgroundThread();
device = deviceWanted;
}
else
{
DBG ("[element] could not open MIDI output: " << deviceWanted.name);
DBG ("[element] " << openResult.getErrorMessage());
}
}

Expand Down Expand Up @@ -252,10 +249,10 @@ Result MidiDeviceProcessor::closeDevice()
{
if (output)
{
output->clearAllPendingMessages();
std::unique_ptr<MidiOutput> closer;
std::unique_ptr<ElementMidiOutput> closer;
{
ScopedLock sl (getCallbackLock());
output->closeDevice();
std::swap (output, closer);
}
closer.reset();
Expand Down Expand Up @@ -319,12 +316,7 @@ void MidiDeviceProcessor::processBlock (AudioBuffer<float>& audio, MidiBuffer& m
if (output && ! midi.isEmpty())
{
const auto delayMs = midiOutLatency.get();
#if JUCE_WINDOWS
output->sendBlockOfMessagesNow (midi);
#else
output->sendBlockOfMessages (
midi, delayMs + Time::getMillisecondCounterHiRes(), getSampleRate());
#endif
output->sendBlockOfMessages (midi, delayMs, getSampleRate());
}

midi.clear (0, nframes);
Expand Down Expand Up @@ -403,15 +395,15 @@ bool MidiDeviceProcessor::deviceIsAvailable (const String& name)
if (info.name == name)
return true;
}
return true;
return false;
}

bool MidiDeviceProcessor::deviceIsAvailable (const MidiDeviceInfo& dev)
{
for (const auto& info : getAvailableDevices())
if (info.identifier == dev.identifier)
return true;
return true;
return false;
}

void MidiDeviceProcessor::timerCallback()
Expand Down
3 changes: 2 additions & 1 deletion src/nodes/mididevice.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <element/signals.hpp>
#include "nodes/baseprocessor.hpp"
#include <engine/midioutput.h>

namespace element {

Expand Down Expand Up @@ -154,7 +155,7 @@ class MidiDeviceProcessor : public BaseProcessor,
MidiDeviceInfo deviceWanted; // The device as saved in Stage and chosen by users.
MidiMessageCollector inputMessages;
std::unique_ptr<MidiInput> input;
std::unique_ptr<MidiOutput> output;
std::unique_ptr<ElementMidiOutput> output;
Atomic<double> midiOutLatency { 0.0 };

void waitForDevice() {}
Expand Down
7 changes: 5 additions & 2 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ file(GLOB_RECURSE TEST_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
juce_add_console_app(test_element)
target_sources(test_element PRIVATE ${TEST_SOURCES})
target_link_libraries(test_element PRIVATE kv::element)
target_include_directories(test_element
PRIVATE
target_include_directories(test_element
PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}"
${GENERATED_INCLUDE_DIR}
${Boost_INCLUDE_DIRS})
Expand All @@ -32,6 +32,9 @@ add_test(NAME "MidiChannelMapTest" COMMAND test_element --run_test=MidiChannelMa
add_test(NAME "MidiClockTest" COMMAND test_element --run_test=MidiClockTest)
add_test(NAME "MidiProgramMapTests" COMMAND test_element --run_test=MidiProgramMapTests)
add_test(NAME "MidiScriptTests" COMMAND test_element --run_test=MidiScriptTests)
if (APPLE)
add_test(NAME "MidiOutputTests" COMMAND test_element --run_test=MidiOutputTests)
endif()
add_test(NAME "NodeFactoryTests" COMMAND test_element --run_test=NodeFactoryTests)
add_test(NAME "NodeObjectTests" COMMAND test_element --run_test=NodeObjectTests)
add_test(NAME "NodeTests" COMMAND test_element --run_test=NodeTests)
Expand Down
Loading
Loading