blob: c13b636533882a58a123084103f56b45ce3bb623 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/media/audio/examples/tones/midi_keyboard.h"
#include <dirent.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fit/defer.h>
#include <lib/syslog/cpp/macros.h>
#include <unistd.h>
#include <cstdio>
#include <filesystem>
#include <iostream>
#include "src/media/audio/examples/tones/midi.h"
#include "src/media/audio/examples/tones/tones.h"
namespace examples {
static const char* const kDevMidiPath = "/dev/class/midi";
zx::result<std::unique_ptr<MidiKeyboard>> MidiKeyboard::Create(Tones* owner) {
for (auto const& dir_entry : std::filesystem::directory_iterator{kDevMidiPath}) {
const char* devname = dir_entry.path().c_str();
zx::result controller = component::Connect<fuchsia_hardware_midi::Controller>(devname);
if (controller.is_error()) {
return controller.take_error();
}
auto [client, server] = fidl::Endpoints<fuchsia_hardware_midi::Device>::Create();
{
const fidl::Status result =
fidl::WireCall(controller.value())->OpenSession(std::move(server));
if (!result.ok()) {
return zx::error(result.status());
}
}
const fidl::WireResult result = fidl::WireCall(client)->GetInfo();
if (!result.ok()) {
return zx::error(result.status());
}
const fuchsia_hardware_midi::wire::Info& info = result.value().info;
if (info.is_source) {
std::cout << "Creating MIDI source @ \"" << devname << "\"\n";
std::unique_ptr<MidiKeyboard> keyboard(new MidiKeyboard(owner, std::move(client)));
keyboard->IssueRead();
return zx::ok(std::move(keyboard));
}
}
return zx::error(ZX_ERR_NOT_FOUND);
}
void MidiKeyboard::IssueRead() {
dev_->Read().Then([this](::fidl::WireUnownedResult<fuchsia_hardware_midi::Device::Read>& result) {
if (!result.ok()) {
FX_LOGS(WARNING) << "Failed to read from MIDI device: " << result.error();
return;
}
if (result->is_error()) {
FX_LOGS(WARNING) << "Shutting down MIDI keyboard (status " << result->error_value() << " )";
return;
}
HandleRead(*result->value());
});
}
void MidiKeyboard::HandleRead(const fuchsia_hardware_midi::wire::DeviceReadResponse& response) {
if (response.event.size() == 0) {
IssueRead();
return;
}
if (response.event.size() > 3) {
FX_LOGS(WARNING) << "Shutting down MIDI keyboard, bad event size (" << response.event.size()
<< ")";
return;
}
std::array<uint8_t, 3> event;
memcpy(event.data(), response.event.data(), response.event.size());
// In theory, USB MIDI event sizes are always supposed to be 4 bytes. 1
// byte for virtual MIDI cable IDs, and then 3 bytes of the MIDI event
// padded using 0s to normalize the size. The Fuchsia USB MIDI driver is
// currently stripping the first byte and passing all virtual cable events
// along as the same, but the subsequent bytes may or may not be there.
//
// For now, we fill our RX buffers with zero before reading, and attempt
// to be handle the events in that framework. Specifically, NOTE_ON events
// with a 7-bit velocity value of 0 are supposed to be treated as NOTE_OFF
// values.
uint8_t cmd = event[0] & MIDI_COMMAND_MASK;
if ((cmd == MIDI_NOTE_ON) || (cmd == MIDI_NOTE_OFF)) {
// By default, MIDI event sources map the value 60 to middle C.
static constexpr int kOffsetMiddleC = 60;
int note = static_cast<int>(event[1] & MIDI_NOTE_NUMBER_MASK) - kOffsetMiddleC;
int velocity = static_cast<int>(event[2] & MIDI_NOTE_VELOCITY_MASK);
bool note_on = (cmd == MIDI_NOTE_ON) && (velocity != 0);
owner_->HandleMidiNote(note, velocity, note_on);
}
IssueRead();
}
} // namespace examples