diff options
Diffstat (limited to 'utils/gen_adldata')
-rw-r--r-- | utils/gen_adldata/file_formats/load_ail.h | 1 | ||||
-rw-r--r-- | utils/gen_adldata/file_formats/load_bisqwit.h | 1 | ||||
-rw-r--r-- | utils/gen_adldata/file_formats/load_bnk.h | 4 | ||||
-rw-r--r-- | utils/gen_adldata/file_formats/load_bnk2.h | 10 | ||||
-rw-r--r-- | utils/gen_adldata/file_formats/load_ea.h | 5 | ||||
-rw-r--r-- | utils/gen_adldata/file_formats/load_ibk.h | 1 | ||||
-rw-r--r-- | utils/gen_adldata/file_formats/load_jv.h | 1 | ||||
-rw-r--r-- | utils/gen_adldata/file_formats/load_op2.h | 3 | ||||
-rw-r--r-- | utils/gen_adldata/file_formats/load_tmb.h | 1 | ||||
-rw-r--r-- | utils/gen_adldata/file_formats/load_wopl.h | 9 | ||||
-rw-r--r-- | utils/gen_adldata/gen_adldata.cc | 43 | ||||
-rw-r--r-- | utils/gen_adldata/ini/ini_processing.cpp | 45 | ||||
-rw-r--r-- | utils/gen_adldata/ini/ini_processing.h | 44 | ||||
-rw-r--r-- | utils/gen_adldata/ini/ini_processing_variant.h | 50 | ||||
-rw-r--r-- | utils/gen_adldata/measurer.cpp | 480 | ||||
-rw-r--r-- | utils/gen_adldata/measurer.h | 3 | ||||
-rw-r--r-- | utils/gen_adldata/progs_cache.cpp | 2 | ||||
-rw-r--r-- | utils/gen_adldata/progs_cache.h | 4 |
18 files changed, 456 insertions, 251 deletions
diff --git a/utils/gen_adldata/file_formats/load_ail.h b/utils/gen_adldata/file_formats/load_ail.h index a10ff92..e63b1d7 100644 --- a/utils/gen_adldata/file_formats/load_ail.h +++ b/utils/gen_adldata/file_formats/load_ail.h @@ -89,6 +89,7 @@ static bool LoadMiles(const char *fn, unsigned bank, const char *prefix) struct ins tmp2; tmp2.notenum = gmno < 128 ? 0 : (unsigned char)notenum; tmp2.pseudo4op = false; + tmp2.real4op = (inscount > 1); tmp2.voice2_fine_tune = 0.0; std::string name; if(midi_index >= 0) name = std::string(1, '\377') + MidiInsName[midi_index]; diff --git a/utils/gen_adldata/file_formats/load_bisqwit.h b/utils/gen_adldata/file_formats/load_bisqwit.h index 015b0eb..f5f73dc 100644 --- a/utils/gen_adldata/file_formats/load_bisqwit.h +++ b/utils/gen_adldata/file_formats/load_bisqwit.h @@ -44,6 +44,7 @@ static bool LoadBisqwit(const char *fn, unsigned bank, const char *prefix) (gmno < 128 ? 'M' : 'P'), gmno & 127); tmp[1].diff = (tmp[0] != tmp[1]); + tmp2.real4op = tmp[1].diff; size_t resno = InsertIns(tmp[0], tmp[1], tmp2, name, name2, (tmp[0] == tmp[1])); SetBank(bank, gmno, resno); } diff --git a/utils/gen_adldata/file_formats/load_bnk.h b/utils/gen_adldata/file_formats/load_bnk.h index a41185a..aa7701f 100644 --- a/utils/gen_adldata/file_formats/load_bnk.h +++ b/utils/gen_adldata/file_formats/load_bnk.h @@ -69,7 +69,8 @@ static bool LoadBNK(const char *fn, unsigned bank, const char *prefix, bool is_f if(name[2] == 'O' || name[2] == 'S') { - gmno = 128 + std::stoi(name.substr(3)); + std::string n = name.substr(3); + gmno = 128 + std::atoi(n.c_str()); } } @@ -107,6 +108,7 @@ static bool LoadBNK(const char *fn, unsigned bank, const char *prefix, bool is_f ins tmp2; tmp2.notenum = is_fat ? voice_num : (percussive ? usage_flag : 0); tmp2.pseudo4op = false; + tmp2.real4op = false; tmp2.voice2_fine_tune = 0.0; if(is_fat) tmp.data[10] ^= 1; diff --git a/utils/gen_adldata/file_formats/load_bnk2.h b/utils/gen_adldata/file_formats/load_bnk2.h index 1f3d4a1..4772da5 100644 --- a/utils/gen_adldata/file_formats/load_bnk2.h +++ b/utils/gen_adldata/file_formats/load_bnk2.h @@ -3,6 +3,11 @@ #include "../progs_cache.h" +inline int stdstoi(const std::string& str) +{ + return std::atoi(str.c_str()); +} + static bool LoadBNK2(const char *fn, unsigned bank, const char *prefix, const std::string &melo_filter, const std::string &perc_filter) @@ -43,9 +48,9 @@ static bool LoadBNK2(const char *fn, unsigned bank, const char *prefix, int gmno = 0; if(name.substr(0, melo_filter.size()) == melo_filter) - gmno = std::stoi(name.substr(melo_filter.size())); + gmno = stdstoi(name.substr(melo_filter.size())); else if(name.substr(0, perc_filter.size()) == perc_filter) - gmno = std::stoi(name.substr(perc_filter.size())) + 128; + gmno = stdstoi(name.substr(perc_filter.size())) + 128; else continue; @@ -81,6 +86,7 @@ static bool LoadBNK2(const char *fn, unsigned bank, const char *prefix, ins tmp2; tmp2.notenum = (gmno & 128) ? 35 : 0; tmp2.pseudo4op = false; + tmp2.real4op = false; tmp2.voice2_fine_tune = 0.0; if(xxP24NNN & 8) diff --git a/utils/gen_adldata/file_formats/load_ea.h b/utils/gen_adldata/file_formats/load_ea.h index 5e0baa4..79367d9 100644 --- a/utils/gen_adldata/file_formats/load_ea.h +++ b/utils/gen_adldata/file_formats/load_ea.h @@ -85,16 +85,17 @@ static bool LoadEA(const char *fn, unsigned bank, const char *prefix) ins tmp2{}; tmp2.notenum = 0; tmp2.pseudo4op = false; + tmp2.real4op = false; std::string name; char name2[512]; if(gmno < 20) { - std::snprintf(name2, 512, "%sM%u", prefix, gmno); + snprintf(name2, 512, "%sM%u", prefix, gmno); } else { - std::snprintf(name2, 512, "%sunk%04X", prefix, offset); + snprintf(name2, 512, "%sunk%04X", prefix, offset); } size_t resno = InsertIns(tmp, tmp2, std::string(1, '\377') + name, name2); SetBank(bank, gmno, resno); diff --git a/utils/gen_adldata/file_formats/load_ibk.h b/utils/gen_adldata/file_formats/load_ibk.h index bc03962..12fa520 100644 --- a/utils/gen_adldata/file_formats/load_ibk.h +++ b/utils/gen_adldata/file_formats/load_ibk.h @@ -61,6 +61,7 @@ static bool LoadIBK(const char *fn, unsigned bank, const char *prefix, bool perc struct ins tmp2; tmp2.notenum = gmno < 128 ? 0 : 35; tmp2.pseudo4op = false; + tmp2.real4op = false; tmp2.voice2_fine_tune = 0.0; size_t resno = InsertIns(tmp, tmp2, std::string(1, '\377') + name, name2); diff --git a/utils/gen_adldata/file_formats/load_jv.h b/utils/gen_adldata/file_formats/load_jv.h index b79832f..35caead 100644 --- a/utils/gen_adldata/file_formats/load_jv.h +++ b/utils/gen_adldata/file_formats/load_jv.h @@ -71,6 +71,7 @@ static bool LoadJunglevision(const char *fn, unsigned bank, const char *prefix) struct ins tmp2; tmp2.notenum = data[offset + 1]; tmp2.pseudo4op = false; + tmp2.real4op = (data[offset] != 0); tmp2.voice2_fine_tune = 0.0; while(tmp2.notenum && tmp2.notenum < 20) diff --git a/utils/gen_adldata/file_formats/load_op2.h b/utils/gen_adldata/file_formats/load_op2.h index 8b7fec6..48b7eae 100644 --- a/utils/gen_adldata/file_formats/load_op2.h +++ b/utils/gen_adldata/file_formats/load_op2.h @@ -82,7 +82,7 @@ static bool LoadDoom(const char *fn, unsigned bank, const char *prefix) int gmno = int(a < 128 ? a : ((a | 128) + 35)); char name2[512]; - std::snprintf(name2, 512, "%s%c%u", prefix, (gmno < 128 ? 'M' : 'P'), gmno & 127); + snprintf(name2, 512, "%s%c%u", prefix, (gmno < 128 ? 'M' : 'P'), gmno & 127); Doom_opl_instr &ins = *(Doom_opl_instr *) &data[offset2]; @@ -108,6 +108,7 @@ static bool LoadDoom(const char *fn, unsigned bank, const char *prefix) struct ins tmp2; tmp2.notenum = ins.note; tmp2.pseudo4op = false; + tmp2.real4op = false; tmp2.voice2_fine_tune = 0.0; while(tmp2.notenum && tmp2.notenum < 20) { diff --git a/utils/gen_adldata/file_formats/load_tmb.h b/utils/gen_adldata/file_formats/load_tmb.h index ab1ff37..5b38433 100644 --- a/utils/gen_adldata/file_formats/load_tmb.h +++ b/utils/gen_adldata/file_formats/load_tmb.h @@ -50,6 +50,7 @@ static bool LoadTMB(const char *fn, unsigned bank, const char *prefix) struct ins tmp2; tmp2.notenum = data[offset + 11]; tmp2.pseudo4op = false; + tmp2.real4op = false; tmp2.voice2_fine_tune = 0.0; std::string name; diff --git a/utils/gen_adldata/file_formats/load_wopl.h b/utils/gen_adldata/file_formats/load_wopl.h index 26a2611..fe4865d 100644 --- a/utils/gen_adldata/file_formats/load_wopl.h +++ b/utils/gen_adldata/file_formats/load_wopl.h @@ -57,8 +57,8 @@ static bool LoadWopl(const char *fn, unsigned bank, const char *prefix) uint16_t pbanks_count = toUint16BE((const uint8_t *)data.data() + 0x0f); AdlBankSetup setup; - setup.deepTremolo = (data[0x11] >> 0) & 0x01; - setup.deepVibrato = (data[0x11] >> 1) & 0x01; + setup.deepTremolo = (data[0x11] & 0x01) != 0; + setup.deepVibrato = (data[0x11] & 0x02) != 0; setup.volumeModel = (int)data[0x12]; setup.adLibPercussions = false; setup.scaleModulators = false; @@ -169,6 +169,7 @@ static bool LoadWopl(const char *fn, unsigned bank, const char *prefix) tmp2.notenum = is_percussion ? data[offset + 38] : 0; bool real4op = (flags & (uint8_t)WOPL_Flags::Mode_4op) != 0; tmp2.pseudo4op = (flags & (uint8_t)WOPL_Flags::Mode_DoubleVoice) != 0; + tmp2.real4op = real4op && !tmp2.pseudo4op; tmp2.voice2_fine_tune = 0; tmp[0].diff = false; tmp[1].diff = real4op && !tmp2.pseudo4op; @@ -207,9 +208,9 @@ static bool LoadWopl(const char *fn, unsigned bank, const char *prefix) char name2[512]; std::memset(name2, 0, 512); if(is_percussion) - std::snprintf(name2, 512, "%sP%u", prefix, gmno & 127); + snprintf(name2, 512, "%sP%u", prefix, gmno & 127); else - std::snprintf(name2, 512, "%sM%u", prefix, i); + snprintf(name2, 512, "%sM%u", prefix, i); if(!real4op && !tmp2.pseudo4op) { diff --git a/utils/gen_adldata/gen_adldata.cc b/utils/gen_adldata/gen_adldata.cc index 7c8af8b..bc5c231 100644 --- a/utils/gen_adldata/gen_adldata.cc +++ b/utils/gen_adldata/gen_adldata.cc @@ -312,7 +312,7 @@ int main(int argc, char**argv) i->first.data[10], i->first.finetune); - #ifdef ADLDATA_WITH_COMMENTS +#ifdef ADLDATA_WITH_COMMENTS std::string names; for(std::set<std::string>::const_iterator j = i->second.second.begin(); @@ -326,9 +326,9 @@ int main(int argc, char**argv) names += *j; } std::fprintf(outFile, " }, // %u: %s\n", (unsigned)c, names.c_str()); - #else +#else std::fprintf(outFile, " },\n"); - #endif +#endif } } std::fprintf(outFile, "};\n"); @@ -341,6 +341,7 @@ int main(int argc, char**argv) " unsigned char flags;\n" " long ms_sound_kon; // Number of milliseconds it produces sound;\n" " long ms_sound_koff;\n" + " double voice2_fine_tune;\n" "} adlins[] =\n");*/ std::fprintf(outFile, "const struct adlinsdata adlins[%u] =\n", (unsigned)instab.size()); @@ -348,7 +349,7 @@ int main(int argc, char**argv) MeasureThreaded measureCounter; { - std::printf("Beginning to generate measures data... (Hardware concurrency: %d)\n", std::thread::hardware_concurrency()); + std::printf("Beginning to generate measures data... (hardware concurrency of %d)\n", std::thread::hardware_concurrency()); std::fflush(stdout); measureCounter.LoadCache("fm_banks/adldata-cache.dat"); measureCounter.m_total = instab.size(); @@ -381,7 +382,7 @@ int main(int argc, char**argv) //DurationInfo info = MeasureDurations(i->first); MeasureThreaded::DurationInfoCache::iterator indo_i = measureCounter.m_durationInfo.find(i->first); DurationInfo info = indo_i->second; - #ifdef ADLDATA_WITH_COMMENTS +#ifdef ADLDATA_WITH_COMMENTS { if(info.peak_amplitude_time == 0) { @@ -404,12 +405,14 @@ int main(int argc, char**argv) info.keyoff_out_time / double(info.interval)); } } - #endif +#endif - unsigned flags = (i->first.pseudo4op ? 1 : 0) | (info.nosound ? 2 : 0); + unsigned flags = (i->first.pseudo4op ? ins::Flag_Pseudo4op : 0)| + (i->first.real4op ? ins::Flag_Real4op : 0) | + (info.nosound ? ins::Flag_NoSound : 0); std::fprintf(outFile, " {"); - std::fprintf(outFile, "%4d,%4d,%3d, %d, %6" PRId64 ",%6" PRId64 ",%lf", + std::fprintf(outFile, "%4d,%4d,%3d, %d, %6" PRId64 ",%6" PRId64 ",%g", (unsigned) i->first.insno1, (unsigned) i->first.insno2, (int)(i->first.notenum), @@ -429,11 +432,11 @@ int main(int argc, char**argv) else names += *j; } - #ifdef ADLDATA_WITH_COMMENTS +#ifdef ADLDATA_WITH_COMMENTS std::fprintf(outFile, " }, // %u: %s\n\n", (unsigned)c, names.c_str()); - #else +#else std::fprintf(outFile, " },\n"); - #endif +#endif std::fflush(outFile); adlins_flags.push_back(flags); } @@ -444,11 +447,11 @@ int main(int argc, char**argv) std::fflush(stdout); //fprintf(outFile, "static const unsigned short banks[][256] =\n"); - #ifdef HARD_BANKS +#ifdef HARD_BANKS const unsigned bankcount = sizeof(banknames) / sizeof(*banknames); - #else +#else const size_t bankcount = banknames.size(); - #endif +#endif size_t nosound = InsertNoSoundIns(); @@ -486,25 +489,29 @@ int main(int argc, char**argv) std::fprintf(outFile, "{\n"); for(unsigned bank = 0; bank < bankcount; ++bank) { - #ifdef ADLDATA_WITH_COMMENTS +#ifdef ADLDATA_WITH_COMMENTS std::fprintf(outFile, " { // bank %u, %s\n", bank, banknames[bank].c_str()); - #else +#else std::fprintf(outFile, " {\n"); #endif +#ifdef ADLDATA_WITH_COMMENTS bool redundant = true; +#endif for(unsigned p = 0; p < 256; ++p) { size_t v = bank_data[bank][p]; if(listed.find(v) == listed.end()) { listed.insert(v); +#ifdef ADLDATA_WITH_COMMENTS redundant = false; +#endif } std::fprintf(outFile, "%4d,", (unsigned int)v); if(p % 16 == 15) fprintf(outFile, "\n"); } std::fprintf(outFile, " },\n"); - #ifdef ADLDATA_WITH_COMMENTS +#ifdef ADLDATA_WITH_COMMENTS if(redundant) { std::fprintf(outFile, " // Bank %u defines nothing new.\n", bank); @@ -523,7 +530,7 @@ int main(int argc, char**argv) bank, refbank); } } - #endif +#endif } std::fprintf(outFile, "};\n\n"); diff --git a/utils/gen_adldata/ini/ini_processing.cpp b/utils/gen_adldata/ini/ini_processing.cpp index 26da540..8a2807b 100644 --- a/utils/gen_adldata/ini/ini_processing.cpp +++ b/utils/gen_adldata/ini/ini_processing.cpp @@ -1,27 +1,26 @@ /* -INI Processor - a small library which allows you parsing INI-files - -Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru> - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. - -*/ + * INI Processor - a small library which allows you parsing INI-files + * + * Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ //#define USE_FILE_MAPPER diff --git a/utils/gen_adldata/ini/ini_processing.h b/utils/gen_adldata/ini/ini_processing.h index 8da4b7e..64777a5 100644 --- a/utils/gen_adldata/ini/ini_processing.h +++ b/utils/gen_adldata/ini/ini_processing.h @@ -1,26 +1,26 @@ /* -INI Processor - a small library which allows you parsing INI-files - -Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru> - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -*/ + * INI Processor - a small library which allows you parsing INI-files + * + * Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ #ifndef INIPROCESSING_H #define INIPROCESSING_H diff --git a/utils/gen_adldata/ini/ini_processing_variant.h b/utils/gen_adldata/ini/ini_processing_variant.h index 41a96dd..d659aa9 100644 --- a/utils/gen_adldata/ini/ini_processing_variant.h +++ b/utils/gen_adldata/ini/ini_processing_variant.h @@ -1,31 +1,31 @@ /* -INI Processor - a small library which allows you parsing INI-files - -Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru> - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -*/ + * INI Processor - a small library which allows you parsing INI-files + * + * Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ /* -A QVariant-like thing created just like a proxy between -INI Processor and target value (to be compatible with QSettings) -*/ + * A QVariant-like thing created just like a proxy between + * INI Processor and target value (to be compatible with QSettings) + */ #ifndef INI_PROCESSING_VARIANT_H #define INI_PROCESSING_VARIANT_H diff --git a/utils/gen_adldata/measurer.cpp b/utils/gen_adldata/measurer.cpp index 7a81379..7caf883 100644 --- a/utils/gen_adldata/measurer.cpp +++ b/utils/gen_adldata/measurer.cpp @@ -1,7 +1,9 @@ #include "measurer.h" #include <cmath> -#include "../../src/chips/opl_chip_base.h" +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif // Nuked OPL3 emulator, Most accurate, but requires the powerful CPU #ifndef ADLMIDI_DISABLE_NUKED_EMULATOR @@ -14,208 +16,367 @@ # include "../../src/chips/dosbox_opl3.h" #endif -DurationInfo MeasureDurations(const ins &in) +template <class T> +class AudioHistory { - std::vector<int16_t> stereoSampleBuf; -#ifdef ADLMIDI_USE_DOSBOX_OPL - std::vector<int32_t> stereoSampleBuf_32; -#endif - insdata id[2]; - bool found[2] = {false, false}; - for(InstrumentDataTab::const_iterator j = insdatatab.begin(); - j != insdatatab.end(); - ++j) + std::unique_ptr<T[]> m_data; + size_t m_index = 0; // points to the next write slot + size_t m_length = 0; + size_t m_capacity = 0; + +public: + size_t size() const { return m_length; } + size_t capacity() const { return m_capacity; } + const T *data() const { return &m_data[m_index + m_capacity - m_length]; } + + void reset(size_t capacity) { - if(j->second.first == in.insno1) - { - id[0] = j->first; - found[0] = true; - if(found[1]) break; - } - if(j->second.first == in.insno2) - { - id[1] = j->first; - found[1] = true; - if(found[0]) break; - } + m_data.reset(new T[2 * capacity]()); + m_index = 0; + m_length = 0; + m_capacity = capacity; } - const unsigned rate = 22010; - const unsigned interval = 150; - const unsigned samples_per_interval = rate / interval; - const int notenum = - in.notenum < 20 ? (44 + in.notenum) - : in.notenum >= 128 ? (44 + 128 - in.notenum) - : in.notenum; - OPLChipBase *opl; + void clear() + { + m_length = 0; + } - //DosBoxOPL3 db; opl = &db; - //NukedOPL3 nuke; opl = &nuke; - NukedOPL3v174 nuke74; opl = &nuke74; + void add(const T &item) + { + T *data = m_data.get(); + const size_t capacity = m_capacity; + size_t index = m_index; + data[index] = item; + data[index + capacity] = item; + m_index = (index + 1 != capacity) ? (index + 1) : 0; + size_t length = m_length + 1; + m_length = (length < capacity) ? length : capacity; + } +}; -#define WRITE_REG(key, value) opl->writeReg((uint16_t)(key), (uint8_t)(value)) +static void HannWindow(double *w, unsigned n) +{ + for (unsigned i = 0; i < n; ++i) + w[i] = 0.5 * (1.0 - std::cos(2 * M_PI * i / (n - 1))); +} - static const short initdata[(2 + 3 + 2 + 2) * 2] = +static double MeasureRMS(const double *signal, const double *window, unsigned length) +{ + double mean = 0; +#pragma omp simd reduction(+: mean) + for(unsigned i = 0; i < length; ++i) + mean += window[i] * signal[i]; + mean /= length; + + double rms = 0; +#pragma omp simd reduction(+: rms) + for(unsigned i = 0; i < length; ++i) { - 0x004, 96, 0x004, 128, // Pulse timer - 0x105, 0, 0x105, 1, 0x105, 0, // Pulse OPL3 enable, leave disabled - 0x001, 32, 0x0BD, 0 // Enable wave & melodic - }; - opl->setRate(rate); + double diff = window[i] * signal[i] - mean; + rms += diff * diff; + } + rms = std::sqrt(rms / (length - 1)); - for(unsigned a = 0; a < 18; a += 2) WRITE_REG(initdata[a], initdata[a + 1]); + return rms; +} - const unsigned n_notes = in.insno1 == in.insno2 ? 1 : 2; - unsigned x[2]; +static const unsigned g_outputRate = 49716; - if(n_notes == 2 && !in.pseudo4op) +struct TinySynth +{ + OPLChipBase *m_chip; + unsigned m_notesNum; + int m_notenum; + int8_t m_fineTune; + int16_t m_noteOffsets[2]; + unsigned m_x[2]; + + void resetChip() { - WRITE_REG(0x105, 1); - WRITE_REG(0x104, 1); - } + static const short initdata[(2 + 3 + 2 + 2) * 2] = + { + 0x004, 96, 0x004, 128, // Pulse timer + 0x105, 0, 0x105, 1, 0x105, 0, // Pulse OPL3 enable, leave disabled + 0x001, 32, 0x0BD, 0 // Enable wave & melodic + }; - for(unsigned n = 0; n < n_notes; ++n) - { - static const unsigned char patchdata[11] = - {0x20, 0x23, 0x60, 0x63, 0x80, 0x83, 0xE0, 0xE3, 0x40, 0x43, 0xC0}; - for(unsigned a = 0; a < 10; ++a) WRITE_REG(patchdata[a] + n * 8, id[n].data[a]); - WRITE_REG(patchdata[10] + n * 8, id[n].data[10] | 0x30); + m_chip->setRate(g_outputRate); + + for(unsigned a = 0; a < 18; a += 2) + m_chip->writeReg((uint16_t)initdata[a], (uint8_t)initdata[a + 1]); } - for(unsigned n = 0; n < n_notes; ++n) + void setInstrument(const ins &in) { - double hertz = 172.00093 * std::exp(0.057762265 * (notenum + id[n].finetune)); - if(hertz > 131071) + insdata rawData[2]; + bool found[2] = {false, false}; + for(InstrumentDataTab::const_iterator j = insdatatab.begin(); + j != insdatatab.end(); + ++j) + { + if(j->second.first == in.insno1) + { + rawData[0] = j->first; + found[0] = true; + if(found[1]) break; + } + if(j->second.first == in.insno2) + { + rawData[1] = j->first; + found[1] = true; + if(found[0]) break; + } + } + + std::memset(m_x, 0, sizeof(m_x)); + m_notenum = in.notenum >= 128 ? (in.notenum - 128) : in.notenum; + if(m_notenum == 0) + m_notenum = 25; + m_notesNum = in.insno1 == in.insno2 ? 1 : 2; + m_fineTune = 0; + m_noteOffsets[0] = rawData[0].finetune; + m_noteOffsets[1] = rawData[1].finetune; + if(in.pseudo4op) + m_fineTune = in.voice2_fine_tune; + if(in.real4op) { - std::fprintf(stderr, "MEASURER WARNING: Why does note %d + finetune %d produce hertz %g? \n", - notenum, id[n].finetune, hertz); - hertz = 131071; + m_chip->writeReg(0x105, 1); + m_chip->writeReg(0x104, 0xFF); } - x[n] = 0x2000; - while(hertz >= 1023.5) + + //For clearer measurement, disable tremolo and vibrato + rawData[0].data[0] &= 0x3F; + rawData[0].data[1] &= 0x3F; + rawData[1].data[0] &= 0x3F; + rawData[1].data[1] &= 0x3F; + + for(unsigned n = 0; n < m_notesNum; ++n) { - hertz /= 2.0; // Calculate octave - x[n] += 0x400; + static const unsigned char patchdata[11] = + {0x20, 0x23, 0x60, 0x63, 0x80, 0x83, 0xE0, 0xE3, 0x40, 0x43, 0xC0}; + for(unsigned a = 0; a < 10; ++a) + m_chip->writeReg(patchdata[a] + n * 8, rawData[n].data[a]); + m_chip->writeReg(patchdata[10] + n * 8, rawData[n].data[10] | 0x30); } - x[n] += (unsigned int)(hertz + 0.5); + } + + void noteOn() + { + std::memset(m_x, 0, sizeof(m_x)); + for(unsigned n = 0; n < m_notesNum; ++n) + { + double hertz = 172.00093 * std::exp(0.057762265 * (m_notenum + m_noteOffsets[n])); + if(hertz > 131071) + { + std::fprintf(stdout, "%s:%d:0: warning: Why does note %d + note-offset %d produce hertz %g?\n", __FILE__, __LINE__, + m_notenum, m_noteOffsets[n], hertz); + std::fflush(stdout); + hertz = 131071; + } + m_x[n] = 0x2000; + while(hertz >= 1023.5) + { + hertz /= 2.0; // Calculate octave + m_x[n] += 0x400; + } + m_x[n] += (unsigned int)(hertz + 0.5); - // Keyon the note - WRITE_REG(0xA0 + n * 3, x[n] & 0xFF); - WRITE_REG(0xB0 + n * 3, x[n] >> 8); + // Keyon the note + m_chip->writeReg(0xA0 + n * 3, m_x[n] & 0xFF); + m_chip->writeReg(0xB0 + n * 3, m_x[n] >> 8); + } + } + + void noteOff() + { + // Keyoff the note + for(unsigned n = 0; n < m_notesNum; ++n) + m_chip->writeReg(0xB0 + n * 3, (m_x[n] >> 8) & 0xDF); } + void generate(int16_t *output, size_t frames) + { + m_chip->generate(output, frames); + } +}; + + +DurationInfo MeasureDurations(const ins &in, OPLChipBase *chip) +{ + AudioHistory<double> audioHistory; + + const unsigned interval = 150; + const unsigned samples_per_interval = g_outputRate / interval; + + const double historyLength = 0.1; // maximum duration to memorize (seconds) + audioHistory.reset(std::ceil(historyLength * g_outputRate)); + + std::unique_ptr<double[]> window; + window.reset(new double[audioHistory.capacity()]); + unsigned winsize = 0; + + TinySynth synth; + synth.m_chip = chip; + synth.resetChip(); + synth.setInstrument(in); + synth.noteOn(); + + /* For capturing */ const unsigned max_silent = 6; const unsigned max_on = 40; const unsigned max_off = 60; + unsigned max_period_on = max_on * interval; + unsigned max_period_off = max_off * interval; + + const double min_coefficient_on = 0.008; + const double min_coefficient_off = 0.2; + + unsigned windows_passed_on = 0; + unsigned windows_passed_off = 0; + + /* For Analyze the results */ + double begin_amplitude = 0; + double peak_amplitude_value = 0; + size_t peak_amplitude_time = 0; + size_t quarter_amplitude_time = max_period_on; + bool quarter_amplitude_time_found = false; + size_t keyoff_out_time = 0; + bool keyoff_out_time_found = false; + + const size_t audioBufferLength = 256; + const size_t audioBufferSize = 2 * audioBufferLength; + int16_t audioBuffer[audioBufferSize]; + // For up to 40 seconds, measure mean amplitude. - std::vector<double> amplitudecurve_on; double highest_sofar = 0; short sound_min = 0, sound_max = 0; - for(unsigned period = 0; period < max_on * interval; ++period) + + for(unsigned period = 0; period < max_period_on; ++period, ++windows_passed_on) { - stereoSampleBuf.clear(); - stereoSampleBuf.resize(samples_per_interval * 2); - opl->generate(stereoSampleBuf.data(), samples_per_interval); + for(unsigned i = 0; i < samples_per_interval;) + { + size_t blocksize = samples_per_interval - i; + blocksize = (blocksize < audioBufferLength) ? blocksize : audioBufferLength; + synth.generate(audioBuffer, blocksize); + for (unsigned j = 0; j < blocksize; ++j) + { + int16_t s = audioBuffer[2 * j]; + audioHistory.add(s); + if(sound_min > s) sound_min = s; + if(sound_max < s) sound_max = s; + } + i += blocksize; + } - double mean = 0.0; - for(unsigned long c = 0; c < samples_per_interval; ++c) + if(winsize != audioHistory.size()) { - short s = stereoSampleBuf[c * 2]; - mean += s; - if(sound_min > s) sound_min = s; - if(sound_max < s) sound_max = s; + winsize = audioHistory.size(); + HannWindow(window.get(), winsize); } - mean /= samples_per_interval; - double std_deviation = 0; - for(unsigned long c = 0; c < samples_per_interval; ++c) + + double rms = MeasureRMS(audioHistory.data(), window.get(), winsize); + /* ======== Peak time detection ======== */ + if(period == 0) + { + begin_amplitude = rms; + peak_amplitude_value = rms; + peak_amplitude_time = 0; + } + else if(rms > peak_amplitude_value) { - double diff = (stereoSampleBuf[c * 2] - mean); - std_deviation += diff * diff; + peak_amplitude_value = rms; + peak_amplitude_time = period; + // In next step, update the quater amplitude time + quarter_amplitude_time_found = false; } - std_deviation = std::sqrt(std_deviation / samples_per_interval); - amplitudecurve_on.push_back(std_deviation); - if(std_deviation > highest_sofar) - highest_sofar = std_deviation; + else if(!quarter_amplitude_time_found && (rms <= peak_amplitude_value * min_coefficient_on)) + { + quarter_amplitude_time = period; + quarter_amplitude_time_found = true; + } + /* ======== Peak time detection =END==== */ + if(rms > highest_sofar) + highest_sofar = rms; if((period > max_silent * interval) && - ((std_deviation < highest_sofar * 0.2)|| - (sound_min >= -1 && sound_max <= 1)) + ( (rms < highest_sofar * min_coefficient_on) || (sound_min >= -1 && sound_max <= 1) ) ) break; } - // Keyoff the note - for(unsigned n = 0; n < n_notes; ++n) - WRITE_REG(0xB0 + n * 3, (x[n] >> 8) & 0xDF); + if(!quarter_amplitude_time_found) + quarter_amplitude_time = windows_passed_on; - // Now, for up to 60 seconds, measure mean amplitude. - std::vector<double> amplitudecurve_off; - for(unsigned period = 0; period < max_off * interval; ++period) + if(windows_passed_on >= max_period_on) { - stereoSampleBuf.clear(); - stereoSampleBuf.resize(samples_per_interval * 2); - opl->generate(stereoSampleBuf.data(), samples_per_interval); - - double mean = 0.0; - for(unsigned long c = 0; c < samples_per_interval; ++c) - { - short s = stereoSampleBuf[c * 2]; - mean += s; - if(sound_min > s) sound_min = s; - if(sound_max < s) sound_max = s; - } - mean /= samples_per_interval; - double std_deviation = 0; - for(unsigned long c = 0; c < samples_per_interval; ++c) + // Just Keyoff the note + synth.noteOff(); + } + else + { + // Reset the emulator and re-run the "ON" simulation until reaching the peak time + synth.resetChip(); + synth.setInstrument(in); + synth.noteOn(); + + audioHistory.reset(std::ceil(historyLength * g_outputRate)); + for(unsigned period = 0; + (period < peak_amplitude_time) && (period < max_period_on); + ++period) { - double diff = (stereoSampleBuf[c * 2] - mean); - std_deviation += diff * diff; + for(unsigned i = 0; i < samples_per_interval;) + { + size_t blocksize = samples_per_interval - i; + blocksize = (blocksize < audioBufferLength) ? blocksize : audioBufferLength; + synth.generate(audioBuffer, blocksize); + for (unsigned j = 0; j < blocksize; ++j) + audioHistory.add(audioBuffer[2 * j]); + i += blocksize; + } } - std_deviation = std::sqrt(std_deviation / samples_per_interval); - amplitudecurve_off.push_back(std_deviation); - - if(std_deviation < highest_sofar * 0.2) - break; - - if((period > max_silent * interval) && (sound_min >= -1 && sound_max <= 1)) - break; + synth.noteOff(); } - /* Analyze the results */ - double begin_amplitude = amplitudecurve_on[0]; - double peak_amplitude_value = begin_amplitude; - size_t peak_amplitude_time = 0; - size_t quarter_amplitude_time = amplitudecurve_on.size(); - size_t keyoff_out_time = 0; - - for(size_t a = 1; a < amplitudecurve_on.size(); ++a) + // Now, for up to 60 seconds, measure mean amplitude. + for(unsigned period = 0; period < max_period_off; ++period, ++windows_passed_off) { - if(amplitudecurve_on[a] > peak_amplitude_value) + for(unsigned i = 0; i < samples_per_interval;) { - peak_amplitude_value = amplitudecurve_on[a]; - peak_amplitude_time = a; + size_t blocksize = samples_per_interval - i; + blocksize = (blocksize < 256) ? blocksize : 256; + synth.generate(audioBuffer, blocksize); + for (unsigned j = 0; j < blocksize; ++j) + { + int16_t s = audioBuffer[2 * j]; + audioHistory.add(s); + if(sound_min > s) sound_min = s; + if(sound_max < s) sound_max = s; + } + i += blocksize; } - } - for(size_t a = peak_amplitude_time; a < amplitudecurve_on.size(); ++a) - { - if(amplitudecurve_on[a] <= peak_amplitude_value * 0.2) + + if(winsize != audioHistory.size()) { - quarter_amplitude_time = a; - break; + winsize = audioHistory.size(); + HannWindow(window.get(), winsize); } - } - for(size_t a = 0; a < amplitudecurve_off.size(); ++a) - { - if(amplitudecurve_off[a] <= peak_amplitude_value * 0.2) + + double rms = MeasureRMS(audioHistory.data(), window.get(), winsize); + /* ======== Find Key Off time ======== */ + if(!keyoff_out_time_found && (rms <= peak_amplitude_value * min_coefficient_off)) { - keyoff_out_time = a; - break; + keyoff_out_time = period; + keyoff_out_time_found = true; } - } + /* ======== Find Key Off time ==END=== */ + if(rms < highest_sofar * min_coefficient_off) + break; - if(keyoff_out_time == 0 && amplitudecurve_on.back() < peak_amplitude_value * 0.2) - keyoff_out_time = quarter_amplitude_time; + if((period > max_silent * interval) && (sound_min >= -1 && sound_max <= 1)) + break; + } DurationInfo result; result.peak_amplitude_time = peak_amplitude_time; @@ -227,11 +388,10 @@ DurationInfo MeasureDurations(const ins &in) result.ms_sound_kon = (int64_t)(quarter_amplitude_time * 1000.0 / interval); result.ms_sound_koff = (int64_t)(keyoff_out_time * 1000.0 / interval); result.nosound = (peak_amplitude_value < 0.5) || ((sound_min >= -1) && (sound_max <= 1)); + return result; } -static const char* spinner = "-\\|/"; - void MeasureThreaded::LoadCache(const char *fileName) { FILE *in = std::fopen(fileName, "rb"); @@ -290,6 +450,8 @@ void MeasureThreaded::LoadCache(const char *fileName) inst.insno2 = inval; if(std::fread(&inst.notenum, 1, 1, in) != 1) break; + if(std::fread(&inst.real4op, 1, 1, in) != 1) + break; if(std::fread(&inst.pseudo4op, 1, 1, in) != 1) break; if(std::fread(&inst.voice2_fine_tune, sizeof(double), 1, in) != 1) @@ -418,6 +580,7 @@ void MeasureThreaded::SaveCache(const char *fileName) outval = in.insno2; fwrite(&outval, 1, sizeof(uint64_t), out); fwrite(&in.notenum, 1, 1, out); + fwrite(&in.real4op, 1, 1, out); fwrite(&in.pseudo4op, 1, 1, out); fwrite(&in.voice2_fine_tune, sizeof(double), 1, out); @@ -458,9 +621,13 @@ void MeasureThreaded::SaveCache(const char *fileName) std::fclose(out); } +#ifdef ADL_GENDATA_PRINT_PROGRESS + +static const char* spinner = "-\\|/"; + void MeasureThreaded::printProgress() { - std::printf("Calculating measures... [%c %3u%% (%4u/%4u) Threads %3u, Matches %u] \r", + std::printf("Calculating measures... [%c %3u%% {%4u/%4u} Threads %3u, Matches %u] \r", spinner[m_done.load() % 4], (unsigned int)(((double)m_done.load() / (double)(m_total)) * 100), (unsigned int)m_done.load(), @@ -470,6 +637,12 @@ void MeasureThreaded::printProgress() ); std::fflush(stdout); } +#else +void MeasureThreaded::printProgress() +{ + //Do nothing +} +#endif void MeasureThreaded::printFinal() { @@ -501,14 +674,18 @@ void MeasureThreaded::run(InstrumentsData::const_iterator i) dd->myself = this; dd->start(); m_threads.push_back(dd); +#ifdef ADL_GENDATA_PRINT_PROGRESS printProgress(); +#endif } void MeasureThreaded::waitAll() { for(auto &th : m_threads) { +#ifdef ADL_GENDATA_PRINT_PROGRESS printProgress(); +#endif delete th; } m_threads.clear(); @@ -524,6 +701,7 @@ void MeasureThreaded::destData::callback(void *myself) { destData *s = reinterpret_cast<destData *>(myself); DurationInfo info; + DosBoxOPL3 dosbox; DurationInfoCache::iterator cachedEntry = s->myself->m_durationInfo.find(s->i->first); if(cachedEntry != s->myself->m_durationInfo.end()) @@ -532,7 +710,7 @@ void MeasureThreaded::destData::callback(void *myself) goto endWork; } - info = MeasureDurations(s->i->first); + info = MeasureDurations(s->i->first, &dosbox); s->myself->m_durationInfo_mx.lock(); s->myself->m_durationInfo.insert({s->i->first, info}); s->myself->m_durationInfo_mx.unlock(); diff --git a/utils/gen_adldata/measurer.h b/utils/gen_adldata/measurer.h index b9ae3c6..63475ca 100644 --- a/utils/gen_adldata/measurer.h +++ b/utils/gen_adldata/measurer.h @@ -100,6 +100,7 @@ struct MeasureThreaded void waitAll(); }; -extern DurationInfo MeasureDurations(const ins &in); +class OPLChipBase; +extern DurationInfo MeasureDurations(const ins &in, OPLChipBase *chip); #endif // MEASURER_H diff --git a/utils/gen_adldata/progs_cache.cpp b/utils/gen_adldata/progs_cache.cpp index a59e7b7..81bba4c 100644 --- a/utils/gen_adldata/progs_cache.cpp +++ b/utils/gen_adldata/progs_cache.cpp @@ -112,7 +112,7 @@ size_t InsertNoSoundIns() { // { 0x0F70700,0x0F70710, 0xFF,0xFF, 0x0,+0 }, insdata tmp1 = MakeNoSoundIns(); - struct ins tmp2 = { 0, 0, 0, false, 0.0 }; + struct ins tmp2 = { 0, 0, 0, false, false, 0.0 }; return InsertIns(tmp1, tmp1, tmp2, "nosound", ""); } diff --git a/utils/gen_adldata/progs_cache.h b/utils/gen_adldata/progs_cache.h index 7b4cd39..5a916f5 100644 --- a/utils/gen_adldata/progs_cache.h +++ b/utils/gen_adldata/progs_cache.h @@ -49,9 +49,11 @@ inline bool equal_approx(double const a, double const b) struct ins { + enum { Flag_Pseudo4op = 0x01, Flag_NoSound = 0x02, Flag_Real4op = 0x04 }; size_t insno1, insno2; unsigned char notenum; bool pseudo4op; + bool real4op; double voice2_fine_tune; bool operator==(const ins &b) const @@ -60,6 +62,7 @@ struct ins && insno1 == b.insno1 && insno2 == b.insno2 && pseudo4op == b.pseudo4op + && real4op == b.real4op && equal_approx(voice2_fine_tune, b.voice2_fine_tune); } bool operator< (const ins &b) const @@ -68,6 +71,7 @@ struct ins if(insno2 != b.insno2) return insno2 < b.insno2; if(notenum != b.notenum) return notenum < b.notenum; if(pseudo4op != b.pseudo4op) return pseudo4op < b.pseudo4op; + if(real4op != b.real4op) return real4op < b.real4op; if(!equal_approx(voice2_fine_tune, b.voice2_fine_tune)) return voice2_fine_tune < b.voice2_fine_tune; return 0; } |