blob: 0e2cb7ff79fdc2131cfac3f1bcef1ee5eba825c3 [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 "garnet/examples/media/tones/midi_keyboard.h"
#include <iostream>
#include <dirent.h>
#include <fcntl.h>
#include <lib/fit/defer.h>
#include <poll.h>
#include <stdio.h>
#include <unistd.h>
#include <zircon/device/midi.h>
#include "garnet/examples/media/tones/midi.h"
#include "garnet/examples/media/tones/tones.h"
#include "lib/fxl/logging.h"
namespace examples {
static const char* const kDevMidiPath = "/dev/class/midi";
std::unique_ptr<MidiKeyboard> MidiKeyboard::Create(Tones* owner) {
struct dirent* de;
DIR* dir = opendir(kDevMidiPath);
if (!dir) {
if (errno != ENOENT) {
FXL_LOG(WARNING) << "Error attempting to open \"" << kDevMidiPath
<< "\" (errno " << errno << ")";
}
return nullptr;
}
auto cleanup = fit::defer([dir]() { closedir(dir); });
while ((de = readdir(dir)) != NULL) {
char devname[128];
snprintf(devname, sizeof(devname), "%s/%s", kDevMidiPath, de->d_name);
fxl::UniqueFD dev(open(devname, O_RDWR | O_NONBLOCK));
if (!dev.is_valid()) {
continue;
}
int device_type;
int ret = ioctl_midi_get_device_type(dev.get(), &device_type);
if (ret != sizeof(device_type)) {
FXL_LOG(WARNING) << "ioctl_midi_get_device_type failed for \"" << devname
<< "\"";
continue;
}
if (device_type == MIDI_TYPE_SOURCE) {
std::cout << "Creating MIDI source @ \"" << devname << "\"\n";
std::unique_ptr<MidiKeyboard> ret(
new MidiKeyboard(owner, std::move(dev)));
ret->Wait();
return ret;
}
}
return nullptr;
}
MidiKeyboard::~MidiKeyboard() {
if (waiting_) {
fd_waiter_.Cancel();
}
}
void MidiKeyboard::Wait() {
fd_waiter_.Wait(
[this](zx_status_t status, uint32_t events) { HandleEvent(); },
dev_.get(), POLLIN);
waiting_ = true;
}
void MidiKeyboard::HandleEvent() {
waiting_ = false;
while (true) {
uint8_t event[3] = {0};
int evt_size = ::read(dev_.get(), event, sizeof(event));
if (evt_size < 0) {
if (errno == EWOULDBLOCK) {
break;
}
FXL_LOG(WARNING) << "Shutting down MIDI keyboard (errno " << errno << ")";
return;
}
if (evt_size == 0) {
break;
}
if (evt_size > 3) {
FXL_LOG(WARNING) << "Shutting down MIDI keyboard, bad event size ("
<< evt_size << ")";
return;
}
// 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 Fuchisa 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. Specifially, 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);
}
}
Wait();
}
} // namespace examples