1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "media/midi/usb_midi_output_stream.h"
6
7#include "base/logging.h"
8#include "media/midi/midi_message_util.h"
9#include "media/midi/usb_midi_device.h"
10
11namespace media {
12
13UsbMidiOutputStream::UsbMidiOutputStream(const UsbMidiJack& jack)
14    : jack_(jack), pending_size_(0), is_sending_sysex_(false) {}
15
16void UsbMidiOutputStream::Send(const std::vector<uint8>& data) {
17  // To prevent link errors caused by DCHECK_*.
18  const size_t kPacketContentSize = UsbMidiOutputStream::kPacketContentSize;
19  DCHECK_LT(jack_.cable_number, 16u);
20
21  std::vector<uint8> data_to_send;
22  size_t current = 0;
23  size_t size = GetSize(data);
24  while (current < size) {
25    uint8 first_byte = Get(data, current);
26    if (first_byte == kSysExByte || is_sending_sysex_) {
27      // System Exclusive messages
28      if (!PushSysExMessage(data, &current, &data_to_send))
29        break;
30    } else if ((first_byte & kSysMessageBitMask) == kSysMessageBitPattern) {
31      if (first_byte & 0x08) {
32        // System Real-Time messages
33        PushSysRTMessage(data, &current, &data_to_send);
34      } else {
35        // System Common messages
36        if (!PushSysCommonMessage(data, &current, &data_to_send))
37          break;
38      }
39    } else if (first_byte & 0x80) {
40      if (!PushChannelMessage(data, &current, &data_to_send))
41        break;
42    } else {
43      // Unknown messages
44      DVLOG(1) << "Unknown byte: " << static_cast<unsigned int>(first_byte);
45      ++current;
46    }
47  }
48
49  if (data_to_send.size() > 0)
50    jack_.device->Send(jack_.endpoint_number(), data_to_send);
51
52  DCHECK_LE(current, size);
53  DCHECK_LE(size - current, kPacketContentSize);
54  // Note that this can be a self-copying and the iteration order is important.
55  for (size_t i = current; i < size; ++i)
56    pending_data_[i - current] = Get(data, i);
57  pending_size_ = size - current;
58}
59
60size_t UsbMidiOutputStream::GetSize(const std::vector<uint8>& data) const {
61  return data.size() + pending_size_;
62}
63
64uint8_t UsbMidiOutputStream::Get(const std::vector<uint8>& data,
65                               size_t index) const {
66  DCHECK_LT(index, GetSize(data));
67  if (index < pending_size_)
68    return pending_data_[index];
69  return data[index - pending_size_];
70}
71
72bool UsbMidiOutputStream::PushSysExMessage(const std::vector<uint8>& data,
73                                           size_t* current,
74                                           std::vector<uint8>* data_to_send) {
75  size_t index = *current;
76  size_t message_size = 0;
77  const size_t kMessageSizeMax = 3;
78  uint8 message[kMessageSizeMax] = {};
79
80  while (index < GetSize(data)) {
81    if (message_size == kMessageSizeMax) {
82      // We can't find the end-of-message mark in the three bytes.
83      *current = index;
84      data_to_send->push_back((jack_.cable_number << 4) | 0x4);
85      data_to_send->insert(data_to_send->end(),
86                           message,
87                           message + arraysize(message));
88      is_sending_sysex_ = true;
89      return true;
90    }
91    uint8 byte = Get(data, index);
92    if ((byte & kSysRTMessageBitMask) == kSysRTMessageBitPattern) {
93      // System Real-Time messages interleaved in a SysEx message
94      PushSysRTMessage(data, &index, data_to_send);
95      continue;
96    }
97
98    message[message_size] = byte;
99    ++message_size;
100    if (byte == kEndOfSysExByte) {
101      uint8 code_index = message_size + 0x4;
102      DCHECK(code_index == 0x5 || code_index == 0x6 || code_index == 0x7);
103      data_to_send->push_back((jack_.cable_number << 4) | code_index);
104      data_to_send->insert(data_to_send->end(),
105                           message,
106                           message + arraysize(message));
107      *current = index + 1;
108      is_sending_sysex_ = false;
109      return true;
110    }
111    ++index;
112  }
113  return false;
114}
115
116bool UsbMidiOutputStream::PushSysCommonMessage(
117    const std::vector<uint8>& data,
118    size_t* current,
119    std::vector<uint8>* data_to_send) {
120  size_t index = *current;
121  uint8 first_byte = Get(data, index);
122  DCHECK_LE(0xf1, first_byte);
123  DCHECK_LE(first_byte, 0xf7);
124  const size_t message_size_table[8] = {
125    0, 2, 3, 2, 1, 1, 1, 0,
126  };
127  size_t message_size = message_size_table[first_byte & 0x0f];
128  DCHECK_NE(0u, message_size);
129  DCHECK_LE(message_size, 3u);
130
131  if (GetSize(data) < index + message_size) {
132    // The message is incomplete.
133    return false;
134  }
135
136  uint8 code_index = message_size == 1 ? 0x5 : static_cast<uint8>(message_size);
137  data_to_send->push_back((jack_.cable_number << 4) | code_index);
138  for (size_t i = index; i < index + 3; ++i)
139    data_to_send->push_back(i < index + message_size ? Get(data, i) : 0);
140  *current += message_size;
141  return true;
142}
143
144void UsbMidiOutputStream::PushSysRTMessage(const std::vector<uint8>& data,
145                                           size_t* current,
146                                           std::vector<uint8>* data_to_send) {
147  size_t index = *current;
148  uint8 first_byte = Get(data, index);
149  DCHECK_LE(0xf8, first_byte);
150  DCHECK_LE(first_byte, 0xff);
151
152  data_to_send->push_back((jack_.cable_number << 4) | 0x5);
153  data_to_send->push_back(first_byte);
154  data_to_send->push_back(0);
155  data_to_send->push_back(0);
156  *current += 1;
157}
158
159bool UsbMidiOutputStream::PushChannelMessage(const std::vector<uint8>& data,
160                                           size_t* current,
161                                           std::vector<uint8>* data_to_send) {
162  size_t index = *current;
163  uint8 first_byte = Get(data, index);
164  DCHECK_LE(0x80, (first_byte & 0xf0));
165  DCHECK_LE((first_byte & 0xf0), 0xe0);
166
167  const size_t message_size_table[8] = {
168    3, 3, 3, 3, 2, 3, 3, 0,
169  };
170  uint8 code_index = first_byte >> 4;
171  size_t message_size = message_size_table[code_index & 0x7];
172  DCHECK_NE(0u, message_size);
173  DCHECK_LE(message_size, 3u);
174
175  if (GetSize(data) < index + message_size) {
176    // The message is incomplete.
177    return false;
178  }
179
180  data_to_send->push_back((jack_.cable_number << 4) | code_index);
181  for (size_t i = index; i < index + 3; ++i)
182    data_to_send->push_back(i < index + message_size ? Get(data, i) : 0);
183  *current += message_size;
184  return true;
185}
186
187}  // namespace media
188