Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 :
23 : #include "midiinputoutputcontroller.h"
24 :
25 : #include "midi/miditypes.h"
26 :
27 : #include "notation/inotation.h"
28 : #include "notation/inotationmidiinput.h"
29 :
30 : #include "log.h"
31 :
32 : using namespace mu::notation;
33 : using namespace muse;
34 : using namespace muse::midi;
35 :
36 0 : void MidiInputOutputController::init()
37 : {
38 0 : if (!midiConfiguration()->midiPortIsAvalaible()) {
39 0 : return;
40 : }
41 :
42 0 : checkInputConnection();
43 0 : checkOutputConnection();
44 :
45 0 : midiInPort()->availableDevicesChanged().onNotify(this, [this]() {
46 0 : checkInputConnection();
47 0 : });
48 :
49 0 : midiConfiguration()->midiInputDeviceIdChanged().onNotify(this, [this]() {
50 0 : MidiDeviceID deviceId = midiConfiguration()->midiInputDeviceId();
51 0 : Ret ret = midiInPort()->connect(deviceId);
52 0 : if (!ret) {
53 0 : LOGW() << "failed connect to input device, deviceID: " << deviceId << ", err: " << ret.text();
54 : }
55 0 : });
56 :
57 0 : midiOutPort()->availableDevicesChanged().onNotify(this, [this]() {
58 0 : checkOutputConnection();
59 0 : });
60 :
61 0 : midiConfiguration()->midiOutputDeviceIdChanged().onNotify(this, [this]() {
62 0 : MidiDeviceID deviceId = midiConfiguration()->midiOutputDeviceId();
63 0 : Ret ret = midiOutPort()->connect(deviceId);
64 0 : if (!ret) {
65 0 : LOGW() << "failed connect to output device, deviceID: " << deviceId << ", err: " << ret.text();
66 : }
67 0 : });
68 :
69 0 : midiInPort()->eventReceived().onReceive(this, [this](const muse::midi::tick_t tick, const muse::midi::Event& event) {
70 0 : if (!configuration()->isMidiInputEnabled()) {
71 0 : return;
72 : }
73 :
74 0 : onMidiEventReceived(tick, event);
75 : });
76 : }
77 :
78 0 : void MidiInputOutputController::checkInputConnection()
79 : {
80 0 : checkConnection(midiConfiguration()->midiInputDeviceId(),
81 0 : midiInPort()->deviceID(),
82 0 : midiInPort()->availableDevices(),
83 0 : [this](const muse::midi::MidiDeviceID& deviceId) {
84 0 : return midiInPort()->connect(deviceId);
85 : });
86 0 : }
87 :
88 0 : void MidiInputOutputController::checkOutputConnection()
89 : {
90 0 : checkConnection(midiConfiguration()->midiOutputDeviceId(),
91 0 : midiOutPort()->deviceID(),
92 0 : midiOutPort()->availableDevices(),
93 0 : [this](const muse::midi::MidiDeviceID& deviceId) {
94 0 : return midiOutPort()->connect(deviceId);
95 : });
96 0 : }
97 :
98 0 : void MidiInputOutputController::checkConnection(const muse::midi::MidiDeviceID& preferredDeviceId,
99 : const muse::midi::MidiDeviceID& currentDeviceId,
100 : const muse::midi::MidiDeviceList& availableDevices,
101 : const std::function<Ret(const muse::midi::MidiDeviceID&)>& connectCallback)
102 : {
103 0 : auto containsDevice = [](const MidiDeviceList& devices, const MidiDeviceID& deviceId) {
104 0 : for (const MidiDevice& device : devices) {
105 0 : if (device.id == deviceId) {
106 0 : return true;
107 : }
108 : }
109 :
110 0 : return false;
111 : };
112 :
113 0 : if (!preferredDeviceId.empty() && preferredDeviceId != currentDeviceId && containsDevice(availableDevices, preferredDeviceId)) {
114 0 : Ret ret = connectCallback(preferredDeviceId);
115 0 : if (!ret) {
116 0 : LOGW() << "failed connect to device, deviceID: " << preferredDeviceId << ", err: " << ret.text();
117 : }
118 :
119 0 : return;
120 0 : }
121 :
122 0 : if (containsDevice(availableDevices, currentDeviceId)) {
123 0 : return;
124 : }
125 :
126 0 : MidiDeviceID deviceId = firstAvailableDeviceId(availableDevices);
127 0 : if (deviceId.empty()) {
128 0 : deviceId = NONE_DEVICE_ID;
129 : }
130 :
131 0 : Ret ret = connectCallback(deviceId);
132 0 : if (!ret) {
133 0 : LOGW() << "failed connect to device, deviceID: " << deviceId << ", err: " << ret.text();
134 : }
135 0 : }
136 :
137 0 : void MidiInputOutputController::onMidiEventReceived(const muse::midi::tick_t tick, const muse::midi::Event& event)
138 : {
139 : UNUSED(tick)
140 :
141 0 : Ret ret = midiRemote()->process(event);
142 0 : if (check_ret(ret, Ret::Code::Undefined)) {
143 : //! NOTE Event not command, pass further
144 : // pass
145 0 : } else if (!ret) {
146 0 : LOGE() << "failed midi remote process, err: " << ret.toString();
147 : // pass
148 : } else {
149 : //! NOTE Success midi remote process (the event is command)
150 0 : return;
151 : }
152 :
153 0 : notation::INotationPtr notation = projectSession()->currentNotation();
154 0 : if (notation) {
155 0 : notation->midiInput()->onMidiEventReceived(event);
156 : }
157 0 : }
158 :
159 0 : muse::midi::MidiDeviceID MidiInputOutputController::firstAvailableDeviceId(const muse::midi::MidiDeviceList& devices) const
160 : {
161 0 : for (const muse::midi::MidiDevice& device : devices) {
162 0 : if (device.id == muse::midi::NONE_DEVICE_ID) {
163 0 : continue;
164 : }
165 :
166 0 : return device.id;
167 : }
168 :
169 0 : return muse::midi::MidiDeviceID();
170 : }
|