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) 2024 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 "mdlmigrator.h"
24 :
25 : #include "engraving/dom/chord.h"
26 : #include "engraving/dom/drumset.h"
27 : #include "engraving/dom/masterscore.h"
28 : #include "engraving/dom/note.h"
29 : #include "engraving/dom/part.h"
30 : #include "engraving/rw/xmlreader.h"
31 : #include "io/file.h"
32 :
33 : #include "log.h"
34 :
35 : using namespace mu;
36 : using namespace mu::project;
37 : using namespace mu::engraving;
38 : using namespace muse;
39 : using namespace muse::io;
40 :
41 0 : void MdlMigrator::remapPercussion()
42 : {
43 0 : IF_ASSERT_FAILED(m_score) {
44 0 : return;
45 : }
46 :
47 0 : for (Part* part : m_score->parts()) {
48 0 : const TrackRange trackRange = part->trackRange();
49 0 : const InstrumentList& instruments = part->instruments();
50 :
51 : // First instrument in list is the "default instrument".
52 : // It's at tick -1 but we must start at tick 0.
53 0 : auto it = instruments.cbegin();
54 0 : for (Fraction startTick(0, 1), endTick; it != instruments.cend(); startTick = endTick) {
55 0 : Instrument* instr = it->second;
56 0 : ++it; // careful, iterator now points to next instrument in the list
57 0 : endTick = (it == instruments.cend())
58 0 : ? m_score->endTick()
59 0 : : Fraction::fromTicks(it->first);
60 :
61 0 : if (!instr || !instr->useDrumset() || !instr->musicXmlId().startsWith(u"mdl.")) {
62 0 : continue;
63 : }
64 :
65 0 : RepitchFunc repitch;
66 0 : path_t drumsetPath = globalConfiguration()->appDataPath() + "templates/";
67 :
68 0 : if (!needToRemap(*instr, repitch, drumsetPath)) {
69 0 : continue;
70 : }
71 :
72 0 : remapPitches(trackRange.startTrack, trackRange.endTrack, startTick, endTick, repitch);
73 :
74 0 : Drumset* drumset = instr->drumset();
75 :
76 0 : if (!loadDrumset(drumset, drumsetPath)) {
77 0 : Drumset newDrumset;
78 0 : newDrumset.clear();
79 0 : for (int i = 0; i < DRUM_INSTRUMENTS; ++i) {
80 0 : if (drumset->isValid(i)) {
81 0 : newDrumset.setDrum(repitch(i), drumset->drum(i));
82 : }
83 : }
84 0 : instr->setDrumset(&newDrumset);
85 : // Note: The new drumset may have fewer drums than the old one.
86 : // This happens if there were two pitches X and Y for which
87 : // repitch(X) == repitch(Y), and the old drumset had drums on
88 : // both X and Y. The winning drum is the higher of X and Y.
89 0 : }
90 0 : }
91 : }
92 : }
93 :
94 0 : bool MdlMigrator::loadDrumset(Drumset* drumset, path_t path)
95 : {
96 0 : File file(path);
97 0 : if (!file.open(IODevice::ReadOnly)) {
98 0 : return false;
99 : }
100 0 : XmlReader xml(&file);
101 0 : drumset->clear();
102 0 : while (xml.readNextStartElement()) {
103 0 : if (xml.name() == "museScore") {
104 0 : bool ok = true;
105 0 : const String verString = xml.attribute("version");
106 0 : const int version = String(verString).remove(u".").toInt(&ok);
107 0 : IF_ASSERT_FAILED(ok && version <= Constants::MSC_VERSION) {
108 0 : LOGW("Loading drumset from unrecognized MuseScore version <%s>", verString.toAscii().constData());
109 : }
110 0 : while (xml.readNextStartElement()) {
111 0 : if (xml.name() == "Drum") {
112 0 : drumset->loadDrum(xml);
113 : } else {
114 0 : xml.unknown();
115 : }
116 : }
117 0 : }
118 : }
119 0 : file.close();
120 0 : return true;
121 0 : }
122 :
123 0 : void MdlMigrator::remapPitches(track_idx_t startTrack, track_idx_t endTrack, Fraction startTick, Fraction endTick,
124 : const RepitchFunc& repitch)
125 : {
126 0 : const auto repitchChord = [repitch] (Chord* chord) {
127 0 : for (Note* note : chord->notes()) {
128 0 : note->setPitch(repitch(note->pitch()));
129 0 : note->setTpcFromPitch(Prefer::SHARPS); // required for playback
130 : }
131 0 : };
132 :
133 0 : for (
134 0 : Segment* seg = m_score->tick2segment(startTick, true, SegmentType::ChordRest);
135 0 : seg && seg->tick() < endTick;
136 0 : seg = seg->next1(SegmentType::ChordRest)
137 : ) {
138 0 : for (track_idx_t track = startTrack; track < endTrack; ++track) {
139 0 : EngravingItem* el = seg->element(track);
140 0 : if (!el || !el->isChord()) {
141 0 : continue;
142 : }
143 0 : Chord* ch = toChord(el);
144 0 : repitchChord(ch);
145 0 : for (Chord* gch : ch->graceNotes()) {
146 0 : repitchChord(gch);
147 : }
148 : }
149 : }
150 0 : }
151 :
152 0 : bool MdlMigrator::needToRemap(const Instrument& instr, RepitchFunc& repitch, path_t& drumsetPath)
153 : {
154 0 : const Drumset* drumset = instr.drumset();
155 0 : const InstrChannel* channel = instr.channel(0);
156 :
157 0 : if (!drumset || !channel || channel->synti() != u"Zerberus") {
158 0 : return false;
159 : }
160 :
161 0 : const int program = channel->program();
162 0 : const String musicXmlId = instr.musicXmlId();
163 :
164 0 : if (musicXmlId == u"mdl.drum.snare-drum" && (program == 5 || program == 6)) {
165 : // MDL Snare Line (id="mdl-snareline")
166 : // MDL Snare Line A (id="mdl-snareline-a")
167 : // MDL Snare (id="mdl-snaresolo")
168 : // MDL Snare A (id="mdl-snaresolo-a")
169 0 : drumsetPath += "Marching_Snare_Drums.drm";
170 0 : repitch = repitchMdlSnares;
171 0 : } else if (musicXmlId == u"mdl.drum.tenor-drum" && (program == 7 || program == 8)) {
172 : // MDL Tenor Line (id="mdl-tenorline")
173 : // MDL Tenors (id="mdl-tenorsolo")
174 0 : drumsetPath += "Marching_Tenors.drm";
175 0 : repitch = repitchMdlTenors;
176 0 : } else if (musicXmlId == u"mdl.drum.bass-drum" && program == 0) {
177 0 : drumsetPath += "Marching_Bass_Drums.drm";
178 0 : if (drumset->isValid(61)) {
179 : // MDL Bass Line (10) (id="mdl-bassline-10")
180 0 : repitch = repitchMdlBassline10;
181 : } else {
182 : // MDL Bass Line (5) (id="mdl-bassline-5")
183 0 : repitch = repitchMdlBassline5;
184 : }
185 0 : } else if (musicXmlId == u"mdl.metal.cymbal.crash" && program == 1) {
186 : // MDL Cymbal Line (id="mdl-cymballine")
187 0 : drumsetPath += "Marching_Cymbals.drm";
188 0 : repitch = repitchMdlCymballine;
189 : } else {
190 0 : return false;
191 : }
192 0 : return true;
193 0 : }
194 :
195 0 : int MdlMigrator::repitchMdlSnares(int pitch)
196 : {
197 0 : switch (pitch) {
198 0 : case 23: return 55;
199 0 : case 27: return 25;
200 0 : case 29: return 92;
201 0 : case 30: return 91;
202 0 : case 31: return 89;
203 0 : case 32: return 58;
204 0 : case 33: return 71;
205 0 : case 36: return 76;
206 0 : case 38: return 40;
207 0 : case 39: return 38;
208 0 : case 40: return 36;
209 0 : case 60: return 50;
210 0 : case 61: return 51;
211 0 : case 62: return 56;
212 0 : case 63: return 53;
213 0 : case 64: return 49;
214 0 : case 65: return 57;
215 0 : case 67: return 60;
216 0 : case 68: return 60;
217 0 : case 72: return 65;
218 0 : case 73: return 67;
219 0 : case 74: return 59;
220 0 : case 76: return 55;
221 0 : case 77: return 24;
222 : }
223 0 : return pitch;
224 : }
225 :
226 0 : int MdlMigrator::repitchMdlTenors(int pitch)
227 : {
228 0 : switch (pitch) {
229 0 : case 47: return 40;
230 0 : case 60: return 36;
231 0 : case 61: return 48;
232 0 : case 62: return 60;
233 0 : case 63: return 72;
234 0 : case 64: return 84;
235 0 : case 65: return 96;
236 0 : case 72: return 41;
237 0 : case 73: return 53;
238 0 : case 74: return 65;
239 0 : case 75: return 77;
240 0 : case 76: return 89;
241 0 : case 77: return 101;
242 0 : case 78: return 37;
243 0 : case 79: return 49;
244 0 : case 80: return 61;
245 0 : case 81: return 73;
246 0 : case 82: return 85;
247 0 : case 83: return 97;
248 0 : case 96: return 37;
249 0 : case 97: return 49;
250 0 : case 98: return 61;
251 0 : case 107: return 36;
252 : }
253 0 : return pitch;
254 : }
255 :
256 0 : int MdlMigrator::repitchMdlBassline10(int pitch)
257 : {
258 0 : switch (pitch) {
259 0 : case 60: return 90;
260 0 : case 61: return 37;
261 0 : case 62: return 37;
262 0 : case 63: return 49;
263 0 : case 64: return 49;
264 0 : case 65: return 61;
265 0 : case 66: return 61;
266 0 : case 67: return 73;
267 0 : case 68: return 73;
268 0 : case 69: return 85;
269 0 : case 70: return 85;
270 0 : case 72: return 92;
271 0 : case 73: return 39;
272 0 : case 74: return 39;
273 0 : case 75: return 51;
274 0 : case 76: return 51;
275 0 : case 77: return 63;
276 0 : case 78: return 63;
277 0 : case 79: return 75;
278 0 : case 80: return 75;
279 0 : case 81: return 87;
280 0 : case 82: return 87;
281 : }
282 0 : return pitch;
283 : }
284 :
285 0 : int MdlMigrator::repitchMdlBassline5(int pitch)
286 : {
287 0 : switch (pitch) {
288 0 : case 60: return 90;
289 0 : case 62: return 37;
290 0 : case 64: return 49;
291 0 : case 67: return 61;
292 0 : case 68: return 73;
293 0 : case 70: return 85;
294 0 : case 72: return 92;
295 0 : case 74: return 39;
296 0 : case 76: return 51;
297 0 : case 79: return 63;
298 0 : case 80: return 75;
299 0 : case 82: return 87;
300 : }
301 0 : return pitch;
302 : }
303 :
304 0 : int MdlMigrator::repitchMdlCymballine(int pitch)
305 : {
306 0 : switch (pitch) {
307 0 : case 38: return 72;
308 0 : case 43: return 81;
309 0 : case 45: return 76;
310 0 : case 47: return 77;
311 0 : case 50: return 84;
312 0 : case 51: return 91;
313 0 : case 62: return 72;
314 0 : case 67: return 81;
315 0 : case 69: return 76;
316 0 : case 71: return 77;
317 0 : case 74: return 84;
318 0 : case 75: return 91;
319 0 : case 86: return 72;
320 0 : case 91: return 81;
321 0 : case 93: return 76;
322 0 : case 95: return 77;
323 0 : case 98: return 84;
324 0 : case 99: return 91;
325 : }
326 0 : return pitch;
327 : }
|