LCOV - code coverage report
Current view: top level - src/engraving/dom - midimapping.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 90.1 % 202 182
Test Date: 2026-11-05 04:07:02

            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 "excerpt.h"
      24              : #include "instrument.h"
      25              : #include "masterscore.h"
      26              : #include "part.h"
      27              : #include "score.h"
      28              : 
      29              : #include "log.h"
      30              : 
      31              : using namespace mu;
      32              : 
      33              : namespace mu::engraving {
      34              : //---------------------------------------------------------
      35              : //   rebuildMidiMapping
      36              : //---------------------------------------------------------
      37              : 
      38         3022 : void MasterScore::rebuildMidiMapping()
      39              : {
      40         3022 :     Score* playbackScore = m_playbackScore ? m_playbackScore : this;
      41         3022 :     setPlaybackScore(nullptr);
      42              : 
      43         3022 :     removeDeletedMidiMapping();
      44         3022 :     int maxport = updateMidiMapping();
      45         3022 :     reorderMidiMapping();
      46         3022 :     rebuildExcerptsMidiMapping();
      47         3022 :     masterScore()->setMidiPortCount(maxport);
      48              : 
      49         3022 :     setPlaybackScore(playbackScore);
      50         3022 : }
      51              : 
      52              : //---------------------------------------------------------
      53              : //   checkMidiMapping
      54              : //   midi mapping is simple if all ports and channels
      55              : //   don't decrease and don't have 'holes' except drum tracks
      56              : //---------------------------------------------------------
      57              : 
      58         1243 : void MasterScore::checkMidiMapping()
      59              : {
      60         1243 :     m_isSimpleMidiMapping = true;
      61         1243 :     rebuildMidiMapping();
      62              : 
      63         1243 :     std::vector<bool> drum;
      64         1243 :     drum.reserve(m_midiMapping.size());
      65         2836 :     for (Part* part : parts()) {
      66         3209 :         for (const auto& pair : part->instruments()) {
      67         1616 :             const Instrument* instr = pair.second;
      68         3421 :             for (size_t j = 0; j < instr->channel().size(); ++j) {
      69         1805 :                 drum.push_back(instr->useDrumset());
      70              :             }
      71              :         }
      72              :     }
      73         1243 :     int lastChannel  = -1;   // port*16+channel
      74         1243 :     int lastDrumPort = -1;
      75         1243 :     size_t index = 0;
      76         2862 :     for (const MidiMapping& m : m_midiMapping) {
      77         1695 :         if (index >= drum.size()) {
      78            0 :             break;
      79              :         }
      80         1695 :         if (drum[index]) {
      81           36 :             lastDrumPort++;
      82           36 :             if (m.port() != lastDrumPort) {
      83            0 :                 m_isSimpleMidiMapping = false;
      84           76 :                 return;
      85              :             }
      86              :         } else {
      87         1659 :             lastChannel++;
      88         1659 :             if (lastChannel % 16 == 9) {
      89            3 :                 lastChannel++;
      90              :             }
      91         1659 :             int p = lastChannel / 16;
      92         1659 :             int c = lastChannel % 16;
      93         1659 :             if (m.port() != p || m.channel() != c) {
      94           76 :                 m_isSimpleMidiMapping = false;
      95           76 :                 return;
      96              :             }
      97              :         }
      98         1619 :         index++;
      99              :     }
     100         1243 : }
     101              : 
     102              : //---------------------------------------------------------
     103              : //   getNextFreeMidiMapping
     104              : //---------------------------------------------------------
     105              : 
     106         2730 : int MasterScore::getNextFreeMidiMapping(std::set<int>& occupiedMidiChannels, unsigned int& searchMidiMappingFrom, int p, int ch)
     107              : {
     108         2730 :     if (ch != -1 && p != -1) {
     109            0 :         return p * 16 + ch;
     110         2730 :     } else if (ch != -1 && p == -1) {
     111          448 :         for (int port = 0;; port++) {
     112          704 :             if (!muse::contains(occupiedMidiChannels, port * 16 + ch)) {
     113          448 :                 occupiedMidiChannels.insert(port * 16 + ch);
     114          448 :                 return port * 16 + ch;
     115              :             }
     116              :         }
     117         2282 :     } else if (ch == -1 && p != -1) {
     118            0 :         for (int channel = 0; channel < 16; channel++) {
     119            0 :             if (channel != 9 && !muse::contains(occupiedMidiChannels, p * 16 + channel)) {
     120            0 :                 occupiedMidiChannels.insert(p * 16 + channel);
     121            0 :                 return p * 16 + channel;
     122              :             }
     123              :         }
     124              :     }
     125              : 
     126         1123 :     for (;; searchMidiMappingFrom++) {
     127         3405 :         if (searchMidiMappingFrom % 16 != 9 && !muse::contains(occupiedMidiChannels, int(searchMidiMappingFrom))) {
     128         2282 :             occupiedMidiChannels.insert(searchMidiMappingFrom);
     129         2282 :             return searchMidiMappingFrom;
     130              :         }
     131              :     }
     132              : }
     133              : 
     134              : //---------------------------------------------------------
     135              : //   getNextFreeDrumMidiMapping
     136              : //---------------------------------------------------------
     137              : 
     138           45 : int MasterScore::getNextFreeDrumMidiMapping(std::set<int>& occupiedMidiChannels)
     139              : {
     140           45 :     for (int i = 0;; i++) {
     141          194 :         if (!muse::contains(occupiedMidiChannels, i * 16 + 9)) {
     142           45 :             occupiedMidiChannels.insert(i * 16 + 9);
     143           45 :             return i * 16 + 9;
     144              :         }
     145              :     }
     146              : }
     147              : 
     148              : //---------------------------------------------------------
     149              : //   rebuildExcerptsMidiMapping
     150              : //---------------------------------------------------------
     151              : 
     152         3022 : void MasterScore::rebuildExcerptsMidiMapping()
     153              : {
     154         3341 :     for (Excerpt* ex : excerpts()) {
     155          638 :         for (Part* p : ex->excerptScore()->parts()) {
     156          319 :             const Part* masterPart = p->masterPart();
     157          319 :             if (!masterPart->score()->isMaster()) {
     158            0 :                 LOGW() << "rebuildExcerptsMidiMapping: no part in master score is linked with " << p->partName();
     159            0 :                 continue;
     160              :             }
     161          319 :             assert(p->instruments().size() == masterPart->instruments().size());
     162          638 :             for (const auto&[tick, iMaster] : masterPart->instruments()) {
     163          319 :                 Instrument* iLocal = p->instrument(Fraction::fromTicks(tick));
     164          319 :                 const size_t nchannels = iMaster->channel().size();
     165          319 :                 if (iLocal->channel().size() != nchannels) {
     166              :                     // may happen, e.g., if user changes an instrument
     167            2 :                     (*iLocal) = (*iMaster);
     168            2 :                     continue;
     169              :                 }
     170          664 :                 for (size_t c = 0; c < nchannels; ++c) {
     171          347 :                     InstrChannel* cLocal = iLocal->channel(static_cast<int>(c));
     172          347 :                     const InstrChannel* cMaster = iMaster->channel(static_cast<int>(c));
     173          347 :                     cLocal->setChannel(cMaster->channel());
     174              :                 }
     175              :             }
     176              :         }
     177              :     }
     178         3022 : }
     179              : 
     180              : //---------------------------------------------------------
     181              : //   reorderMidiMapping
     182              : //   Set mappings in order you see in Add->Instruments
     183              : //---------------------------------------------------------
     184              : 
     185         3022 : void MasterScore::reorderMidiMapping()
     186              : {
     187              :     using std::swap;
     188         3022 :     int sequenceNumber = 0;
     189         7432 :     for (Part* part : parts()) {
     190         8911 :         for (const auto& pair : part->instruments()) {
     191         4501 :             const Instrument* instr = pair.second;
     192         9843 :             for (InstrChannel* channel : instr->channel()) {
     193         5342 :                 if (m_midiMapping[sequenceNumber].part() == part && m_midiMapping[sequenceNumber].m_masterChannel == channel) {
     194         5196 :                     sequenceNumber++;
     195         5196 :                     continue;
     196              :                 }
     197              : 
     198          146 :                 const int shouldBe = channel->channel();
     199          146 :                 swap(m_midiMapping[sequenceNumber], m_midiMapping[shouldBe]);
     200          146 :                 m_midiMapping[sequenceNumber].articulation()->setChannel(sequenceNumber);
     201          146 :                 channel->setChannel(sequenceNumber);
     202          146 :                 m_midiMapping[shouldBe].articulation()->setChannel(shouldBe);
     203              : 
     204          146 :                 sequenceNumber++;
     205              :             }
     206              :         }
     207              :     }
     208         3022 : }
     209              : 
     210              : //---------------------------------------------------------
     211              : //   removeDeletedMidiMapping
     212              : //   Remove mappings to deleted instruments
     213              : //---------------------------------------------------------
     214              : 
     215         3022 : void MasterScore::removeDeletedMidiMapping()
     216              : {
     217         3022 :     int removeOffset = 0;
     218         3022 :     int mappingSize = int(m_midiMapping.size());
     219         6060 :     for (int index = 0; index < mappingSize; index++) {
     220         3038 :         Part* part = midiMapping(index)->part();
     221         3038 :         if (!muse::contains(parts(), part)) {
     222           14 :             removeOffset++;
     223           23 :             continue;
     224              :         }
     225              :         // Not all channels could exist
     226         3024 :         bool channelExists = false;
     227         3081 :         for (const auto& pair : part->instruments()) {
     228         3072 :             const Instrument* instr = pair.second;
     229         3072 :             channelExists = (m_midiMapping[index].articulation()->channel() != -1
     230         3072 :                              && muse::contains(instr->channel(), m_midiMapping[index].m_masterChannel)
     231         6144 :                              && !(m_midiMapping[index].port() == -1 && m_midiMapping[index].channel() == -1));
     232         3072 :             if (channelExists) {
     233         3015 :                 break;
     234              :             }
     235              :         }
     236         3024 :         if (!channelExists) {
     237            9 :             removeOffset++;
     238            9 :             continue;
     239              :         }
     240              :         // Let's do a left shift by 'removeOffset' items if necessary
     241         3015 :         if (index != 0 && removeOffset != 0) {
     242           17 :             m_midiMapping[index - removeOffset] = std::move(m_midiMapping[index]);
     243              : 
     244           17 :             const int chanVal = m_midiMapping[index - removeOffset].articulation()->channel();
     245           17 :             m_midiMapping[index - removeOffset].articulation()->setChannel(chanVal - removeOffset);
     246              :         }
     247              :     }
     248              :     // We have 'removeOffset' deleted instruments, let's remove their mappings
     249         3045 :     for (int index = 0; index < removeOffset; index++) {
     250           23 :         m_midiMapping.pop_back();
     251              :     }
     252         3022 : }
     253              : 
     254              : //---------------------------------------------------------
     255              : //   updateMidiMapping
     256              : //   Add mappings to new instruments and repair existing ones
     257              : //---------------------------------------------------------
     258              : 
     259         3022 : int MasterScore::updateMidiMapping()
     260              : {
     261         3022 :     int maxport = 0;
     262         3022 :     std::set<int> occupiedMidiChannels; // each entry is port*16+channel, port range: 0-inf, channel: 0-15
     263         3022 :     unsigned int searchMidiMappingFrom = 0; // makes getting next free MIDI mapping faster
     264              : 
     265         6037 :     for (const MidiMapping& mm :m_midiMapping) {
     266         3015 :         if (mm.port() == -1 || mm.channel() == -1) {
     267          448 :             continue;
     268              :         }
     269         2567 :         occupiedMidiChannels.insert(static_cast<int>(mm.port()) * 16 + (int)mm.channel());
     270         2567 :         if (maxport < mm.port()) {
     271          296 :             maxport = mm.port();
     272              :         }
     273              :     }
     274              : 
     275         7432 :     for (Part* part : parts()) {
     276         8911 :         for (const auto& pair : part->instruments()) {
     277         4501 :             const Instrument* instr = pair.second;
     278         4501 :             const bool useDrumset = instr->useDrumset();
     279         9843 :             for (InstrChannel* channel : instr->channel()) {
     280         5342 :                 doUpdateMidiMapping(maxport, occupiedMidiChannels, searchMidiMappingFrom, part, channel, useDrumset);
     281              :             }
     282              :         }
     283              :     }
     284              : 
     285         3022 :     return maxport;
     286         3022 : }
     287              : 
     288         5342 : void MasterScore::doUpdateMidiMapping(int& maxport, std::set<int>& occupiedMidiChannels, unsigned int& searchMidiMappingFrom,
     289              :                                       Part* part, InstrChannel* channel, bool useDrumset)
     290              : {
     291         5342 :     bool channelExists = false;
     292        33947 :     for (const MidiMapping& mapping : m_midiMapping) {
     293        31620 :         const bool validChannelIndex = channel->channel() >= 0 && channel->channel() < static_cast<int>(m_midiMapping.size());
     294        31620 :         if (channel == mapping.m_masterChannel && validChannelIndex) {
     295         3015 :             channelExists = true;
     296         3015 :             break;
     297              :         }
     298              :     }
     299              : 
     300         5342 :     if (!channelExists) {
     301              :         int midiPort;
     302              :         int midiChannel;
     303              : 
     304         2327 :         if (useDrumset) {
     305           45 :             midiPort = getNextFreeDrumMidiMapping(occupiedMidiChannels) / 16;
     306           45 :             midiChannel = 9;
     307              :         } else {
     308         2282 :             const int nm = getNextFreeMidiMapping(occupiedMidiChannels, searchMidiMappingFrom);
     309         2282 :             midiPort    = nm / 16;
     310         2282 :             midiChannel = nm % 16;
     311              :         }
     312              : 
     313         2327 :         if (midiPort > maxport) {
     314           26 :             maxport = midiPort;
     315              :         }
     316              : 
     317         2327 :         addMidiMapping(channel, part, midiPort, midiChannel);
     318         2327 :         return;
     319              :     }
     320              : 
     321              :     // Channel could already exist, but have unassigned port or channel...
     322         3015 :     MidiMapping& mapping = m_midiMapping.at(channel->channel());
     323              : 
     324         3015 :     if (mapping.port() == -1) {
     325          448 :         const int nm = getNextFreeMidiMapping(occupiedMidiChannels, searchMidiMappingFrom, -1, mapping.channel());
     326          448 :         mapping.m_port = nm / 16;
     327          448 :         return;
     328              :     }
     329              : 
     330         2567 :     if (m_midiMapping[channel->channel()].channel() == -1) {
     331            0 :         if (useDrumset) {
     332            0 :             mapping.m_port = getNextFreeDrumMidiMapping(occupiedMidiChannels) / 16;
     333            0 :             mapping.m_channel = 9;
     334            0 :             return;
     335              :         }
     336            0 :         const int nm = getNextFreeMidiMapping(occupiedMidiChannels, searchMidiMappingFrom,  mapping.port());
     337            0 :         mapping.m_port    = nm / 16;
     338            0 :         mapping.m_channel = nm % 16;
     339              :     }
     340              : }
     341              : 
     342              : //---------------------------------------------------------
     343              : //   addMidiMapping
     344              : //---------------------------------------------------------
     345              : 
     346         3485 : void MasterScore::addMidiMapping(InstrChannel* channel, Part* part, int midiPort, int midiChannel)
     347              : {
     348         3485 :     if (!part->score()->isMaster()) {
     349            0 :         return;
     350              :     }
     351              : 
     352         3485 :     MidiMapping mm;
     353         3485 :     mm.m_part = part;
     354         3485 :     mm.m_masterChannel = channel;
     355         3485 :     mm.m_articulation.reset(new InstrChannel(*channel));
     356         3485 :     mm.m_link = PartChannelSettingsLink(mm.articulation(), mm.m_masterChannel, /* excerpt */ false);
     357              : 
     358         3485 :     mm.m_port = midiPort;
     359         3485 :     mm.m_channel = midiChannel;
     360              : 
     361         3485 :     const int mscoreChannel = int(m_midiMapping.size());
     362         3485 :     mm.m_articulation->setChannel(mscoreChannel);
     363         3485 :     mm.m_masterChannel->setChannel(mscoreChannel);
     364              : 
     365         3485 :     m_midiMapping.push_back(std::move(mm));
     366         3485 : }
     367              : 
     368              : //---------------------------------------------------------
     369              : //   updateMidiMapping
     370              : //---------------------------------------------------------
     371              : 
     372           16 : void MasterScore::updateMidiMapping(InstrChannel* channel, Part* part, int midiPort, int midiChannel)
     373              : {
     374           16 :     const int c = channel->channel();
     375           16 :     if (c < 0) {
     376            0 :         return;
     377              :     }
     378           16 :     if (c >= int(masterScore()->midiMapping().size())) {
     379            0 :         LOGD("Can't set midi channel: midiMapping is empty!");
     380            0 :         return;
     381              :     }
     382           16 :     MidiMapping& mm = m_midiMapping[c];
     383              : 
     384           16 :     if (midiChannel != -1) {
     385           11 :         mm.m_channel = midiChannel;
     386              :     }
     387           16 :     if (midiPort != -1) {
     388            5 :         mm.m_port = midiPort;
     389              :     }
     390           16 :     if (part) {
     391           16 :         mm.m_part = part->masterPart();
     392              :     }
     393              : }
     394              : } // namespace mu::engraving
        

Generated by: LCOV version 2.0-1