LCOV - code coverage report
Current view: top level - muse/framework/midiremote/internal - midiremote.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 156 0
Test Date: 2026-11-05 04:07:02

            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 : }
        

Generated by: LCOV version 2.0-1