Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-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 "midiremote.h"
24 :
25 : #include "global/types/secs.h"
26 : #include "global/io/buffer.h"
27 : #include "global/serialization/xmlstreamreader.h"
28 : #include "global/serialization/xmlstreamwriter.h"
29 : #include "global/stringutils.h"
30 :
31 : #include "multiwindows/resourcelockguard.h"
32 :
33 : #include "log.h"
34 :
35 : using namespace muse;
36 : using namespace muse::midi;
37 : using namespace muse::midiremote;
38 :
39 : static constexpr std::string_view MIDIMAPPING_TAG("MidiMapping");
40 : static constexpr std::string_view EVENT_TAG("Event");
41 : static constexpr std::string_view MAPPING_ACTION_CODE_TAG("key");
42 : static constexpr std::string_view MAPPING_EVENT_TYPE_TAG("EventType");
43 : static constexpr std::string_view MAPPING_EVENT_VALUE_TAG("EventValue");
44 :
45 : static const rcommand::Command REALTIME_ADVANCE_COMMAND("command://notation/realtime-advance");
46 :
47 : static const std::string MIDI_MAPPING_RESOURCE_NAME("MIDI_MAPPING");
48 :
49 0 : void MidiRemote::init()
50 : {
51 0 : multiwindowsProvider()->resourceChanged().onReceive(this, [this](const std::string& resourceName) {
52 0 : if (resourceName == MIDI_MAPPING_RESOURCE_NAME) {
53 0 : readMidiMappings();
54 : }
55 0 : });
56 :
57 0 : readMidiMappings();
58 :
59 0 : m_mmcDecoder = mmcDecoderFactory()->makeDecoder();
60 0 : }
61 :
62 0 : const MidiMappingList& MidiRemote::midiMappings() const
63 : {
64 0 : return m_midiMappings;
65 : }
66 :
67 0 : Ret MidiRemote::setMidiMappings(const MidiMappingList& midiMappings)
68 : {
69 0 : if (m_midiMappings == midiMappings) {
70 0 : return true;
71 : }
72 :
73 0 : bool ok = writeMidiMappings(midiMappings);
74 :
75 0 : if (ok) {
76 0 : m_midiMappings = midiMappings;
77 0 : m_midiMappingsChanged.notify();
78 : }
79 :
80 0 : return ok;
81 : }
82 :
83 0 : void MidiRemote::resetMidiMappings()
84 : {
85 : {
86 0 : muse::mi::WriteResourceLockGuard resource_guard(multiwindowsProvider(), MIDI_MAPPING_RESOURCE_NAME);
87 0 : fileSystem()->remove(configuration()->midiMappingUserAppDataPath());
88 0 : }
89 :
90 0 : m_midiMappings = {};
91 0 : m_midiMappingsChanged.notify();
92 0 : }
93 :
94 0 : async::Notification MidiRemote::midiMappingsChanged() const
95 : {
96 0 : return m_midiMappingsChanged;
97 : }
98 :
99 0 : void MidiRemote::setIsSettingMode(bool arg)
100 : {
101 0 : m_isSettingMode = arg;
102 0 : }
103 :
104 0 : bool MidiRemote::isSettingMode() const
105 : {
106 0 : return m_isSettingMode;
107 : }
108 :
109 0 : void MidiRemote::setCurrentActionEvent(const Event& ev)
110 : {
111 : UNUSED(ev);
112 0 : NOT_IMPLEMENTED;
113 0 : }
114 :
115 0 : Ret MidiRemote::process(const Event& ev)
116 : {
117 0 : if (needIgnoreEvent(ev)) {
118 0 : return Ret(Ret::Code::Undefined);
119 : }
120 :
121 0 : if (ev.messageType() == Event::MessageType::SystemExclusiveData && m_mmcDecoder) {
122 0 : const std::optional<MMCMessage> msg = m_mmcDecoder->decode(ev);
123 0 : if (msg.has_value()) {
124 0 : processMMC(msg.value());
125 : }
126 :
127 0 : return make_ret(Ret::Code::Ok);
128 0 : }
129 :
130 0 : RemoteEvent event = remoteEventFromMidiEvent(ev);
131 :
132 0 : for (const MidiControlsMapping& midiMapping : m_midiMappings) {
133 0 : if (midiMapping.event == event) {
134 0 : commandDispatcher()->dispatch(midiMapping.command);
135 0 : return make_ret(Ret::Code::Ok);
136 : }
137 : }
138 :
139 0 : return Ret(Ret::Code::Undefined);
140 : }
141 :
142 0 : void MidiRemote::readMidiMappings()
143 : {
144 0 : RetVal<ByteArray> mappingsData;
145 : {
146 0 : muse::mi::ReadResourceLockGuard resource_guard(multiwindowsProvider(), MIDI_MAPPING_RESOURCE_NAME);
147 0 : mappingsData = fileSystem()->readFile(configuration()->midiMappingUserAppDataPath());
148 0 : }
149 :
150 0 : if (!mappingsData.ret) {
151 0 : LOGD() << "failed to open midi mappings file: " << mappingsData.ret.toString();
152 0 : return;
153 : }
154 :
155 0 : XmlStreamReader reader(mappingsData.val);
156 :
157 0 : reader.readNextStartElement();
158 0 : if (reader.name() != MIDIMAPPING_TAG) {
159 0 : return;
160 : }
161 :
162 0 : while (reader.readNextStartElement()) {
163 0 : if (reader.name() != EVENT_TAG) {
164 0 : reader.skipCurrentElement();
165 0 : continue;
166 : }
167 :
168 0 : MidiControlsMapping midiMapping = readMidiMapping(reader);
169 0 : if (midiMapping.isValid()) {
170 0 : m_midiMappings.push_back(midiMapping);
171 : }
172 0 : }
173 :
174 0 : if (reader.isError()) {
175 0 : LOGE() << "failed parse xml, error: " << reader.error();
176 : }
177 0 : }
178 :
179 0 : MidiControlsMapping MidiRemote::readMidiMapping(XmlStreamReader& reader) const
180 : {
181 0 : MidiControlsMapping midiMapping;
182 :
183 0 : while (reader.readNextStartElement()) {
184 0 : const std::string tag(reader.name());
185 :
186 0 : if (tag == MAPPING_ACTION_CODE_TAG) {
187 0 : midiMapping.command = rcommand::Command(reader.readText().toStdString());
188 0 : } else if (tag == MAPPING_EVENT_TYPE_TAG) {
189 0 : midiMapping.event.type = static_cast<RemoteEventType>(reader.readInt());
190 0 : } else if (tag == MAPPING_EVENT_VALUE_TAG) {
191 0 : midiMapping.event.value = reader.readInt();
192 : } else {
193 0 : reader.skipCurrentElement();
194 : }
195 0 : }
196 :
197 0 : return midiMapping;
198 0 : }
199 :
200 0 : bool MidiRemote::writeMidiMappings(const MidiMappingList& midiMappings) const
201 : {
202 0 : TRACEFUNC;
203 :
204 0 : ByteArray data;
205 0 : auto buf = io::Buffer::opened(io::IODevice::WriteOnly, &data);
206 :
207 0 : XmlStreamWriter writer(&buf);
208 0 : writer.startDocument();
209 0 : writer.startElement(MIDIMAPPING_TAG);
210 :
211 0 : for (const MidiControlsMapping& midiMapping : midiMappings) {
212 0 : writeMidiMapping(writer, midiMapping);
213 : }
214 :
215 0 : writer.endElement();
216 0 : writer.flush();
217 :
218 0 : Ret ret;
219 : {
220 0 : muse::mi::WriteResourceLockGuard resource_guard(multiwindowsProvider(), MIDI_MAPPING_RESOURCE_NAME);
221 0 : ret = fileSystem()->writeFile(configuration()->midiMappingUserAppDataPath(), data);
222 0 : }
223 :
224 0 : if (!ret) {
225 0 : LOGE() << ret.toString();
226 : }
227 :
228 0 : return ret;
229 0 : }
230 :
231 0 : void MidiRemote::writeMidiMapping(XmlStreamWriter& writer, const MidiControlsMapping& midiMapping) const
232 : {
233 0 : writer.startElement(EVENT_TAG);
234 0 : writer.element(MAPPING_ACTION_CODE_TAG, midiMapping.command.toString());
235 0 : writer.element(MAPPING_EVENT_TYPE_TAG, midiMapping.event.type);
236 0 : writer.element(MAPPING_EVENT_VALUE_TAG, midiMapping.event.value);
237 0 : writer.endElement();
238 0 : }
239 :
240 0 : bool MidiRemote::needIgnoreEvent(const Event& event) const
241 : {
242 0 : if (isSettingMode()) {
243 0 : return true;
244 : }
245 :
246 0 : if (event.messageType() == Event::MessageType::SystemExclusiveData) {
247 0 : return false;
248 : }
249 :
250 0 : const Event::Opcode opcode = event.opcode();
251 0 : if (opcode != Event::Opcode::NoteOn
252 0 : && opcode != Event::Opcode::NoteOff
253 0 : && opcode != Event::Opcode::ControlChange) {
254 0 : return true;
255 : }
256 :
257 : bool release = opcode == Event::Opcode::NoteOff
258 0 : || (opcode == Event::Opcode::ControlChange && event.data() == 0);
259 :
260 0 : if (release) {
261 0 : bool advanceToNextNoteOnKeyRelease = configuration()->advanceToNextNoteOnKeyRelease();
262 0 : if (!advanceToNextNoteOnKeyRelease) {
263 0 : return true;
264 : }
265 :
266 0 : RemoteEvent remoteEvent = remoteEventFromMidiEvent(event);
267 0 : RemoteEvent realtimeEvent = this->remoteEvent(REALTIME_ADVANCE_COMMAND);
268 0 : if (!realtimeEvent.isValid() || remoteEvent != realtimeEvent) {
269 0 : return true;
270 : }
271 : }
272 :
273 0 : return false;
274 : }
275 :
276 0 : RemoteEvent MidiRemote::remoteEvent(const rcommand::Command& command) const
277 : {
278 0 : for (const MidiControlsMapping& midiMapping : m_midiMappings) {
279 0 : if (midiMapping.command == command) {
280 0 : return midiMapping.event;
281 : }
282 : }
283 :
284 0 : return RemoteEvent();
285 : }
286 :
287 0 : void MidiRemote::processMMC(const MMCMessage& msg)
288 : {
289 0 : switch (msg.command) {
290 0 : case MMCCommand::Play:
291 0 : commandDispatcher()->dispatch(rcommand::Command("command://playback/play"));
292 0 : break;
293 0 : case MMCCommand::Pause:
294 0 : commandDispatcher()->dispatch(rcommand::Command("command://playback/pause"));
295 0 : break;
296 0 : case MMCCommand::Stop:
297 0 : commandDispatcher()->dispatch(rcommand::Command("command://playback/stop"));
298 0 : break;
299 0 : case MMCCommand::Locate: {
300 0 : const std::optional<double> pos = m_mmcDecoder->locateToSeconds(msg);
301 0 : if (pos.has_value()) {
302 0 : commandDispatcher()->dispatch(rcommand::Command("command://playback/rewind"), { { "position", Val(pos.value()) } });
303 : }
304 0 : } break;
305 0 : default: break;
306 : }
307 0 : }
|