1 //=============================================================================
2 // MusE
3 // Linux Music Editor
4 //
5 // metronome_class.cpp
6 // (C) Copyright 2019 Tim E. Real (terminator356 on sourceforge)
7 //
8 // This program is free software; you can redistribute it and/or modify
9 // it under the terms of the GNU General Public License
10 // as published by the Free Software Foundation; version 2 of
11 // the License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 //=============================================================================
22
23 #include <QDir>
24 #include <QDirIterator>
25 #include <QFileInfo>
26 #include <QFileInfoList>
27 #include <QFile>
28
29 #include "metronome_class.h"
30
31 namespace MusECore {
32
33 // Static.
34 std::uint64_t MetroAccentsStruct::_idGen = 0;
35
36 //---------------------------------------------------------
37 // assign
38 // Assigns the members of the given MetroAccentsStruct to this one, EXCEPT for the ID.
39 // Returns this MetroAccentsStruct.
40 //---------------------------------------------------------
41
assign(const MetroAccentsStruct & m)42 MetroAccentsStruct& MetroAccentsStruct::assign(const MetroAccentsStruct& m)
43 {
44 _type = m._type;
45 _accents = m._accents;
46 return *this;
47 }
48
49 //---------------------------------------------------------
50 // isBlank
51 // Returns true if all the accents are 'off'.
52 //---------------------------------------------------------
isBlank(MetroAccent::AccentTypes_t types) const53 bool MetroAccentsStruct::isBlank(MetroAccent::AccentTypes_t types) const
54 {
55 return _accents.isBlank(types);
56 }
57
58 //---------------------------------------------------------
59 // blank
60 // Returns true if all the accents are 'off'.
61 //---------------------------------------------------------
blank(MetroAccent::AccentTypes_t types)62 void MetroAccentsStruct::blank(MetroAccent::AccentTypes_t types)
63 {
64 _accents.blank(types);
65 }
66
67 //---------------------------------------------------------
68 // copy
69 // Creates a copy of this MetroAccentsStruct but with a new ID.
70 //---------------------------------------------------------
71
copy() const72 MetroAccentsStruct MetroAccentsStruct::copy() const
73 {
74 return MetroAccentsStruct(_type).assign(*this);
75 }
76
77 //---------------------------------------------------------
78 // loadMDF (Metronome Definition File)
79 //---------------------------------------------------------
80
loadMDF(const QString & filepath,MetroAccentsPresetsMap * presetMap,bool debug=false)81 static void loadMDF(const QString& filepath, MetroAccentsPresetsMap* presetMap, bool debug = false)
82 {
83 QFile f(filepath);
84 if(!f.open(QIODevice::ReadOnly /*| QIODevice::Text*/))
85 return;
86 if (debug)
87 fprintf(stderr, "READ MDF %s\n", filepath.toLatin1().constData());
88 Xml xml(&f);
89
90 bool skipmode = true;
91 for (;;) {
92 Xml::Token token = xml.parse();
93 const QString& tag = xml.s1();
94 switch (token) {
95 case Xml::Error:
96 case Xml::End:
97 goto loadMDF_end;
98 case Xml::TagStart:
99 if (skipmode && tag == "muse")
100 skipmode = false;
101 else if (skipmode)
102 break;
103 else if (tag == "metroAccPresets") {
104 presetMap->read(xml);
105 }
106 else
107 xml.unknown("muse");
108 break;
109 case Xml::Attribut:
110 break;
111 case Xml::TagEnd:
112 if (!skipmode && tag == "muse")
113 goto loadMDF_end;
114 default:
115 break;
116 }
117 }
118
119 loadMDF_end:
120 f.close();
121 }
122
writeMDF(const QString & filepath,MetroAccentsStruct::MetroAccentsType type) const123 void MetroAccentsPresetsMap::writeMDF(const QString& filepath, MetroAccentsStruct::MetroAccentsType type) const
124 {
125 QFile f(filepath);
126 if(!f.open(QIODevice::WriteOnly /*| QIODevice::Text*/))
127 return;
128 MusECore::Xml xml(&f);
129
130 int level = 0;
131 xml.header();
132 level = xml.putFileVersion(level);
133
134 write(level, xml, type);
135
136 level--;
137 xml.etag(level, "muse");
138 f.close();
139 }
140
defaultAccents(MetroAccentsMap * accents,MetroAccentsStruct::MetroAccentsType type) const141 void MetroAccentsPresetsMap::defaultAccents(MetroAccentsMap* accents, MetroAccentsStruct::MetroAccentsType type) const
142 {
143 const_iterator ipm_end = cend();
144 for(const_iterator i = cbegin(); i != ipm_end; ++i)
145 {
146 const int& beats = i->first;
147 const MetroAccentsPresets& pre = i->second;
148 if(!pre.empty())
149 {
150 MetroAccentsPresets::const_iterator imap_end = pre.cend();
151 for(MetroAccentsPresets::const_iterator imap = pre.cbegin(); imap != imap_end; ++imap)
152 {
153 MetroAccentsStruct mas = *imap;
154 if(mas._type != type)
155 continue;
156 // Change the type to user.
157 mas._type = MetroAccentsStruct::MetroAccentsType::User;
158 std::pair<MetroAccentsMap::iterator, bool> res =
159 accents->insert(std::pair<const int, MetroAccentsStruct>(beats, mas));
160 if(!res.second)
161 res.first->second = mas;
162 break;
163 }
164 }
165 }
166 }
167
write(int level,MusECore::Xml & xml,MetroAccentsStruct::MetroAccentsType type) const168 void MetroAccentsPresetsMap::write(int level, MusECore::Xml& xml, MetroAccentsStruct::MetroAccentsType type) const
169 {
170 for (const_iterator i = cbegin(); i != cend(); ++i)
171 i->second.write(level, xml, i->first, type);
172 }
173
read(MusECore::Xml & xml)174 void MetroAccentsPresetsMap::read(MusECore::Xml& xml)
175 {
176 bool ok;
177 int beats = 0;
178 MetroAccentsStruct::MetroAccentsType type = MetroAccentsStruct::NoType;
179 for (;;) {
180 MusECore::Xml::Token token = xml.parse();
181 const QString& tag = xml.s1();
182 switch (token) {
183 case MusECore::Xml::Error:
184 case MusECore::Xml::End:
185 return;
186 case MusECore::Xml::TagStart:
187 xml.unknown("metroAccPresets");
188 break;
189 case MusECore::Xml::Attribut:
190 if (tag == "type")
191 {
192 const int itype = xml.s2().toInt();
193 switch(itype)
194 {
195 case MetroAccentsStruct::UserPreset:
196 type = MetroAccentsStruct::UserPreset;
197 break;
198
199 case MetroAccentsStruct::FactoryPreset:
200 type = MetroAccentsStruct::FactoryPreset;
201 break;
202
203 // Don't want these.
204 case MetroAccentsStruct::User:
205 default:
206 break;
207 }
208 }
209 else if (tag == "beats")
210 beats = xml.s2().toInt();
211 break;
212 case Xml::Text:
213 {
214 if(type == MetroAccentsStruct::NoType)
215 {
216 fprintf(stderr, "MetroAccentsPresets::read: Unknown type\n");
217 break;
218 }
219
220 int len = tag.length();
221 int i = 0;
222 for(;;)
223 {
224 int acctypes = MetroAccent::NoAccent;
225 MetroAccentsStruct mas(type);
226 for(;;)
227 {
228 while(i < len && (tag[i] == ',' || tag[i] == ' ' || tag[i] == '\n'))
229 ++i;
230 if(i == len)
231 break;
232
233 QString fs;
234 while(i < len && tag[i] != ',' && tag[i] != ' ')
235 {
236 fs.append(tag[i]);
237 ++i;
238 }
239 if(i == len)
240 break;
241
242 acctypes = fs.toInt(&ok);
243 if(!ok)
244 {
245 fprintf(stderr, "MetroAccentsPresets::read failed reading accent types string: %s\n", fs.toLatin1().constData());
246 break;
247 }
248
249 MetroAccent ma;
250 ma._accentType = acctypes;
251 mas._accents.push_back(ma);
252
253 while(i < len && (tag[i] == ' ' || tag[i] == '\n'))
254 ++i;
255 if(i == len || tag[i] != ',')
256 break;
257 }
258
259 // Don't bother reading if there are no accents set.
260 if(beats > 0 && !mas.isBlank())
261 {
262 std::pair<iterator, bool> pm_res = insert(std::pair<const int, MetroAccentsPresets>(beats, MetroAccentsPresets()));
263 iterator ipm = pm_res.first;
264 MetroAccentsPresets& mp = ipm->second;
265 MetroAccentsPresets::iterator imap =
266 mp.find(mas, MetroAccentsStruct::FactoryPreset | MetroAccentsStruct::UserPreset);
267 if(imap == mp.end())
268 mp.push_back(mas);
269 else
270 // In case of duplicates (an existing found), replace with latest.
271 *imap = mas;
272 }
273
274 while(i < len && (tag[i] == ' ' || tag[i] == '\n'))
275 ++i;
276 if(i == len)
277 break;
278 }
279 }
280 break;
281 case MusECore::Xml::TagEnd:
282 if (tag == "metroAccPresets")
283 {
284 return;
285 }
286 default:
287 break;
288 }
289 }
290 }
291
find(const MetroAccentsStruct & mas,const MetroAccentsStruct::MetroAccentsTypes_t & types)292 MetroAccentsPresets::iterator MetroAccentsPresets::find(
293 const MetroAccentsStruct& mas, const MetroAccentsStruct::MetroAccentsTypes_t& types)
294 {
295 iterator iend = end();
296 for(iterator i = begin(); i != iend; ++i)
297 {
298 const MetroAccentsStruct& m = *i;
299 const MetroAccentsStruct::MetroAccentsType& m_type = m._type;
300 if(m._accents == mas._accents && (types & m_type))
301 return i;
302 }
303 return iend;
304 }
305
find(const MetroAccentsStruct & mas,const MetroAccentsStruct::MetroAccentsTypes_t & types) const306 MetroAccentsPresets::const_iterator MetroAccentsPresets::find(
307 const MetroAccentsStruct& mas, const MetroAccentsStruct::MetroAccentsTypes_t& types) const
308 {
309 const_iterator iend = cend();
310 for(const_iterator i = cbegin(); i != iend; ++i)
311 {
312 const MetroAccentsStruct& m = *i;
313 const MetroAccentsStruct::MetroAccentsType& m_type = m._type;
314 if(m._accents == mas._accents && (types & m_type))
315 return i;
316 }
317 return iend;
318 }
319
findId(std::uint64_t id)320 MetroAccentsPresets::iterator MetroAccentsPresets::findId(std::uint64_t id)
321 {
322 iterator iend = end();
323 for(iterator i = begin(); i != iend; ++i)
324 {
325 if(i->id() == id)
326 return i;
327 }
328 return iend;
329 }
330
findId(std::uint64_t id) const331 MetroAccentsPresets::const_iterator MetroAccentsPresets::findId(std::uint64_t id) const
332 {
333 const_iterator iend = cend();
334 for(const_iterator i = cbegin(); i != iend; ++i)
335 {
336 if(i->id() == id)
337 return i;
338 }
339 return iend;
340 }
341
write(int level,MusECore::Xml & xml,int beats,MetroAccentsStruct::MetroAccentsType type) const342 void MetroAccentsPresets::write(int level, MusECore::Xml& xml,
343 int beats, MetroAccentsStruct::MetroAccentsType type) const
344 {
345 if(empty())
346 return;
347 // Check if there's anything to write.
348 const_iterator imap = cbegin();
349 for( ; imap != cend(); ++imap)
350 {
351 if(imap->_type == type)
352 break;
353 }
354 if(imap == cend())
355 return;
356
357 xml.tag(level++, "metroAccPresets type=\"%d\" beats=\"%d\"", type, beats);
358 for(const_iterator i = cbegin(); i != cend(); ++i)
359 {
360 if(i->_type != type)
361 continue;
362 i->write(level, xml);
363 }
364 xml.tag(--level, "/metroAccPresets");
365 }
366
write(int level,MusECore::Xml & xml) const367 void MetroAccentsStruct::write(int level, MusECore::Xml& xml) const
368 {
369 // Don't bother writing if there are no accents set.
370 if(isBlank())
371 return;
372 QString acc_s;
373 const int sz_m1 = _accents.size() - 1;
374 int count1 = 0;
375 int count2 = 0;
376 int level_in = 0;
377 for(MetroAccents::const_iterator i = _accents.cbegin(); i != _accents.cend(); ++count1, ++i)
378 {
379 const MetroAccent& acc = *i;
380 acc_s += QString::number(acc._accentType);
381 if(count1 < sz_m1)
382 acc_s += ", ";
383 ++count2;
384 if(count2 >= 16)
385 {
386 xml.put(level + level_in, "%s", acc_s.toLatin1().constData());
387 if(level_in == 0)
388 level_in = 1;
389 acc_s.clear();
390 count2 = 0;
391 }
392 }
393 if(count2)
394 xml.put(level + level_in, "%s", acc_s.toLatin1().constData());
395 }
396
read(MusECore::Xml & xml)397 void MetroAccentsStruct::read(MusECore::Xml& xml)
398 {
399 bool ok;
400 for (;;) {
401 MusECore::Xml::Token token = xml.parse();
402 const QString& tag = xml.s1();
403 switch (token) {
404 case MusECore::Xml::Error:
405 case MusECore::Xml::End:
406 return;
407 case MusECore::Xml::TagStart:
408 xml.unknown("MetroAccentsStruct");
409 break;
410 case MusECore::Xml::Attribut:
411 break;
412 case Xml::Text:
413 {
414 int len = tag.length();
415 int acctypes = MetroAccent::NoAccent;
416
417 int i = 0;
418 for(;;)
419 {
420 while(i < len && (tag[i] == ',' || tag[i] == ' ' || tag[i] == '\n'))
421 ++i;
422 if(i == len)
423 break;
424
425 QString fs;
426 while(i < len && tag[i] != ',' && tag[i] != ' ')
427 {
428 fs.append(tag[i]);
429 ++i;
430 }
431 if(i == len)
432 break;
433
434 acctypes = fs.toInt(&ok);
435 if(!ok)
436 {
437 fprintf(stderr, "MetroAccentsStruct::read failed reading accent types string: %s\n", fs.toLatin1().constData());
438 break;
439 }
440
441 MetroAccent ma;
442 ma._accentType = acctypes;
443 _accents.push_back(ma);
444
445 while(i < len && (tag[i] == ' ' || tag[i] == '\n'))
446 ++i;
447 if(i == len || tag[i] != ',')
448 break;
449 }
450 }
451 break;
452 case MusECore::Xml::TagEnd:
453 if (tag == "metroAccents")
454 {
455 return;
456 }
457 default:
458 break;
459 }
460 }
461 return;
462 }
463
operator ==(const MetroAccent & other) const464 bool MetroAccent::operator==(const MetroAccent& other) const
465 {
466 return _accentType == other._accentType;
467 }
468
operator !=(const MetroAccent & other) const469 bool MetroAccent::operator!=(const MetroAccent& other) const
470 {
471 return _accentType != other._accentType;
472 }
473
isBlank(AccentTypes_t types) const474 bool MetroAccent::isBlank(AccentTypes_t types) const
475 {
476 return (_accentType & types) == NoAccent;
477 }
478
blank(AccentTypes_t types)479 void MetroAccent::blank(AccentTypes_t types)
480 {
481 _accentType &= ~types;
482 }
483
operator ==(const MetroAccents & other) const484 bool MetroAccents::operator==(const MetroAccents& other) const
485 {
486 const size_type sz = size();
487 if(sz != other.size())
488 return false;
489 for(size_type i = 0; i < sz; ++i)
490 {
491 if(at(i) != other.at(i))
492 return false;
493 }
494 return true;
495 }
496
isBlank(MetroAccent::AccentTypes_t types) const497 bool MetroAccents::isBlank(MetroAccent::AccentTypes_t types) const
498 {
499 const size_type sz = size();
500 for(size_type i = 0; i < sz; ++i)
501 {
502 if(!at(i).isBlank(types))
503 return false;
504 }
505 return true;
506 }
507
blank(MetroAccent::AccentTypes_t types)508 void MetroAccents::blank(MetroAccent::AccentTypes_t types)
509 {
510 iterator iend = end();
511 for(iterator i = begin(); i != iend; ++i)
512 i->blank(types);
513 }
514
515 // Returns beats.
read(MusECore::Xml & xml)516 int MetroAccentsMap::read(MusECore::Xml& xml)
517 {
518 bool ok;
519 int beats = 0;
520 for (;;) {
521 MusECore::Xml::Token token = xml.parse();
522 const QString& tag = xml.s1();
523 switch (token) {
524 case MusECore::Xml::Error:
525 case MusECore::Xml::End:
526 return 0;
527 case MusECore::Xml::TagStart:
528 xml.unknown("MetroAccentsMap");
529 break;
530 case MusECore::Xml::Attribut:
531 if (tag == "beats")
532 beats = xml.s2().toInt();
533 break;
534 case Xml::Text:
535 {
536 int len = tag.length();
537 int i = 0;
538 int acctypes = MetroAccent::NoAccent;
539 MetroAccentsStruct mas(MetroAccentsStruct::User);
540 for(;;)
541 {
542 while(i < len && (tag[i] == ',' || tag[i] == ' ' || tag[i] == '\n'))
543 ++i;
544 if(i == len)
545 break;
546
547 QString fs;
548 while(i < len && tag[i] != ',' && tag[i] != ' ')
549 {
550 fs.append(tag[i]);
551 ++i;
552 }
553 if(i == len)
554 break;
555
556 acctypes = fs.toInt(&ok);
557 if(!ok)
558 {
559 fprintf(stderr, "MetroAccentsMap::read failed reading accent types string: %s\n", fs.toLatin1().constData());
560 break;
561 }
562
563 MetroAccent ma;
564 ma._accentType = acctypes;
565 mas._accents.push_back(ma);
566
567 while(i < len && (tag[i] == ' ' || tag[i] == '\n'))
568 ++i;
569 if(i == len || tag[i] != ',')
570 break;
571 }
572
573 // Don't bother reading if there are no accents set.
574 if(beats > 0 && !mas.isBlank())
575 {
576 std::pair<iterator, bool> res = insert(std::pair<const int, MetroAccentsStruct>(beats, mas));
577 if(!res.second)
578 // In case of duplicate beats (an existing beats entry found), replace with latest.
579 res.first->second = mas;
580 }
581 }
582 break;
583 case MusECore::Xml::TagEnd:
584 if (tag == "metroAccMap") {
585 return beats;
586 }
587 default:
588 break;
589 }
590 }
591 return 0;
592 }
593
write(int level,MusECore::Xml & xml) const594 void MetroAccentsMap::write(int level, MusECore::Xml& xml) const
595 {
596 for(const_iterator i = cbegin(); i != cend(); ++i)
597 {
598 // Don't bother writing if there are no accents set.
599 if(i->second.isBlank())
600 continue;
601 xml.tag(level, "metroAccMap beats=\"%d\"", i->first);
602 i->second.write(level + 1, xml);
603 xml.tag(level, "/metroAccMap");
604 }
605 }
606
607 //---------------------------------------------------------
608 // initMetronomePresets
609 //---------------------------------------------------------
610
initMetronomePresets(const QString & dir,MetroAccentsPresetsMap * presetMap,bool debug)611 void initMetronomePresets(const QString& dir, MetroAccentsPresetsMap* presetMap, bool debug)
612 {
613 if(!QDir(dir).exists())
614 {
615 fprintf(stderr, "Metronome directory not found: %s\n", dir.toLatin1().constData());
616 return;
617 }
618
619 if (debug)
620 fprintf(stderr, "Load metronome presets from <%s>\n", dir.toLatin1().constData());
621 QDirIterator metro_di(dir, QStringList() << "*.mdf", QDir::Files | QDir::Readable | QDir::NoDotAndDotDot);
622 while(metro_di.hasNext())
623 loadMDF(metro_di.next(), presetMap, debug);
624 }
625
MetronomeSettings()626 MetronomeSettings::MetronomeSettings()
627 {
628 preMeasures = 2;
629 measureClickNote = 37;
630 measureClickVelo = 127;
631 beatClickNote = 42;
632 beatClickVelo = 120;
633 accentClick1 = 44;
634 accentClick1Velo = 100;
635 accentClick2 = 42;
636 accentClick2Velo = 100;
637
638 clickChan = 9;
639 clickPort = 0;
640 precountEnableFlag = false;
641 precountFromMastertrackFlag = true;
642 precountSigZ = 4;
643 precountSigN = 4;
644 precountOnPlay = false;
645 precountMuteMetronome = false;
646 precountPrerecord = false;
647 precountPreroll = false;
648 midiClickFlag = false;
649 audioClickFlag = true;
650 audioClickVolume = 0.5f;
651 measClickVolume = 1.0f;
652 beatClickVolume = 1.0f;
653 accent1ClickVolume = 0.1f;
654 accent2ClickVolume = 0.1f;
655 clickSamples = newSamples;
656 measSample = QString("klick1.wav");
657 beatSample = QString("klick2.wav");
658 accent1Sample = QString("klick3.wav");
659 accent2Sample = QString("klick4.wav");
660
661 metroAccentsMap = new MetroAccentsMap();
662 }
663
~MetronomeSettings()664 MetronomeSettings::~MetronomeSettings()
665 {
666 if(metroAccentsMap)
667 delete metroAccentsMap;
668 metroAccentsMap = nullptr;
669 }
670
671 } // namespace MusECore
672