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