/* * MIDIMacros.cpp * -------------- * Purpose: Helper functions / classes for MIDI Macro functionality. * Notes : (currently none) * Authors: OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #include "stdafx.h" #include "../soundlib/MIDIEvents.h" #include "MIDIMacros.h" #include "../common/mptStringBuffer.h" #include "../common/misc_util.h" #ifdef MODPLUG_TRACKER #include "Sndfile.h" #include "plugins/PlugInterface.h" #endif // MODPLUG_TRACKER OPENMPT_NAMESPACE_BEGIN ParameteredMacro MIDIMacroConfig::GetParameteredMacroType(uint32 macroIndex) const { const std::string macro = GetSafeMacro(szMidiSFXExt[macroIndex]); for(uint32 i = 0; i < kSFxMax; i++) { ParameteredMacro sfx = static_cast(i); if(sfx != kSFxCustom) { if(macro.compare(CreateParameteredMacro(sfx)) == 0) return sfx; } } // Special macros with additional "parameter": if (macro.compare(CreateParameteredMacro(kSFxCC, MIDIEvents::MIDICC_start)) >= 0 && macro.compare(CreateParameteredMacro(kSFxCC, MIDIEvents::MIDICC_end)) <= 0 && macro.size() == 5) return kSFxCC; if (macro.compare(CreateParameteredMacro(kSFxPlugParam, 0)) >= 0 && macro.compare(CreateParameteredMacro(kSFxPlugParam, 0x17F)) <= 0 && macro.size() == 7) return kSFxPlugParam; return kSFxCustom; // custom / unknown } // Retrieve Zxx (Z80-ZFF) type from current macro configuration FixedMacro MIDIMacroConfig::GetFixedMacroType() const { // Compare with all possible preset patterns for(uint32 i = 0; i < kZxxMax; i++) { FixedMacro zxx = static_cast(i); if(zxx != kZxxCustom) { // Prepare macro pattern to compare Macro macros[128]; CreateFixedMacro(macros, zxx); bool found = true; for(uint32 j = 0; j < 128; j++) { if(strncmp(macros[j], szMidiZXXExt[j], MACRO_LENGTH)) { found = false; break; } } if(found) return zxx; } } return kZxxCustom; // Custom setup } void MIDIMacroConfig::CreateParameteredMacro(Macro ¶meteredMacro, ParameteredMacro macroType, int subType) const { switch(macroType) { case kSFxUnused: mpt::String::WriteAutoBuf(parameteredMacro) = ""; break; case kSFxCutoff: mpt::String::WriteAutoBuf(parameteredMacro) = "F0F000z"; break; case kSFxReso: mpt::String::WriteAutoBuf(parameteredMacro) = "F0F001z"; break; case kSFxFltMode: mpt::String::WriteAutoBuf(parameteredMacro) = "F0F002z"; break; case kSFxDryWet: mpt::String::WriteAutoBuf(parameteredMacro) = "F0F003z"; break; case kSFxCC: mpt::String::WriteAutoBuf(parameteredMacro) = mpt::format("Bc%1z")(mpt::fmt::HEX0<2>(subType & 0x7F)); break; case kSFxPlugParam: mpt::String::WriteAutoBuf(parameteredMacro) = mpt::format("F0F%1z")(mpt::fmt::HEX0<3>(std::min(subType, 0x17F) + 0x80)); break; case kSFxChannelAT: mpt::String::WriteAutoBuf(parameteredMacro) = "Dcz"; break; case kSFxPolyAT: mpt::String::WriteAutoBuf(parameteredMacro) = "Acnz"; break; case kSFxPitch: mpt::String::WriteAutoBuf(parameteredMacro) = "Ec00z"; break; case kSFxProgChange: mpt::String::WriteAutoBuf(parameteredMacro) = "Ccz"; break; case kSFxCustom: default: MPT_ASSERT_NOTREACHED(); break; } } std::string MIDIMacroConfig::CreateParameteredMacro(ParameteredMacro macroType, int subType) const { Macro parameteredMacro; CreateParameteredMacro(parameteredMacro, macroType, subType); return mpt::String::ReadAutoBuf(parameteredMacro); } // Create Zxx (Z80 - ZFF) from preset void MIDIMacroConfig::CreateFixedMacro(Macro (&fixedMacros)[128], FixedMacro macroType) const { for(uint32 i = 0; i < 128; i++) { const char *str = ""; uint32 param = i; switch(macroType) { case kZxxUnused: str = ""; break; case kZxxReso4Bit: param = i * 8; if(i < 16) str = "F0F001%1"; else str = ""; break; case kZxxReso7Bit: str = "F0F001%1"; break; case kZxxCutoff: str = "F0F000%1"; break; case kZxxFltMode: str = "F0F002%1"; break; case kZxxResoFltMode: param = (i & 0x0F) * 8; if(i < 16) str = "F0F001%1"; else if(i < 32) str = "F0F002%1"; else str = ""; break; case kZxxChannelAT: str = "Dc%1"; break; case kZxxPolyAT: str = "Acn%1"; break; case kZxxPitch: str = "Ec00%1"; break; case kZxxProgChange: str = "Cc%1"; break; case kZxxCustom: default: MPT_ASSERT_NOTREACHED(); continue; } mpt::String::WriteAutoBuf(fixedMacros[i]) = mpt::format(str)(mpt::fmt::HEX0<2>(param)); } } #ifdef MODPLUG_TRACKER bool MIDIMacroConfig::operator== (const MIDIMacroConfig &other) const { for(auto left = begin(), right = other.begin(); left != end(); left++, right++) { if(strncmp(*left, *right, MACRO_LENGTH)) return false; } return true; } // Returns macro description including plugin parameter / MIDI CC information CString MIDIMacroConfig::GetParameteredMacroName(uint32 macroIndex, IMixPlugin *plugin) const { const ParameteredMacro macroType = GetParameteredMacroType(macroIndex); switch(macroType) { case kSFxPlugParam: { const int param = MacroToPlugParam(macroIndex); CString formattedName; formattedName.Format(_T("Param %d"), param); #ifndef NO_PLUGINS if(plugin != nullptr) { CString paramName = plugin->GetParamName(param); if(!paramName.IsEmpty()) { formattedName += _T(" (") + paramName + _T(")"); } } else #else MPT_UNREFERENCED_PARAMETER(plugin); #endif // NO_PLUGINS { formattedName += _T(" (N/A)"); } return formattedName; } case kSFxCC: { CString formattedCC; formattedCC.Format(_T("MIDI CC %d"), MacroToMidiCC(macroIndex)); return formattedCC; } default: return GetParameteredMacroName(macroType); } } // Returns generic macro description. CString MIDIMacroConfig::GetParameteredMacroName(ParameteredMacro macroType) const { switch(macroType) { case kSFxUnused: return _T("Unused"); case kSFxCutoff: return _T("Set Filter Cutoff"); case kSFxReso: return _T("Set Filter Resonance"); case kSFxFltMode: return _T("Set Filter Mode"); case kSFxDryWet: return _T("Set Plugin Dry/Wet Ratio"); case kSFxPlugParam: return _T("Control Plugin Parameter..."); case kSFxCC: return _T("MIDI CC..."); case kSFxChannelAT: return _T("Channel Aftertouch"); case kSFxPolyAT: return _T("Polyphonic Aftertouch"); case kSFxPitch: return _T("Pitch Bend"); case kSFxProgChange: return _T("MIDI Program Change"); case kSFxCustom: default: return _T("Custom"); } } // Returns generic macro description. CString MIDIMacroConfig::GetFixedMacroName(FixedMacro macroType) const { switch(macroType) { case kZxxUnused: return _T("Unused"); case kZxxReso4Bit: return _T("Z80 - Z8F controls Resonant Filter Resonance"); case kZxxReso7Bit: return _T("Z80 - ZFF controls Resonant Filter Resonance"); case kZxxCutoff: return _T("Z80 - ZFF controls Resonant Filter Cutoff"); case kZxxFltMode: return _T("Z80 - ZFF controls Resonant Filter Mode"); case kZxxResoFltMode: return _T("Z80 - Z9F controls Resonance + Filter Mode"); case kZxxChannelAT: return _T("Z80 - ZFF controls Channel Aftertouch"); case kZxxPolyAT: return _T("Z80 - ZFF controls Polyphonic Aftertouch"); case kZxxPitch: return _T("Z80 - ZFF controls Pitch Bend"); case kZxxProgChange: return _T("Z80 - ZFF controls MIDI Program Change"); case kZxxCustom: default: return _T("Custom"); } } int MIDIMacroConfig::MacroToPlugParam(uint32 macroIndex) const { const std::string macro = GetSafeMacro(szMidiSFXExt[macroIndex]); int code = 0; const char *param = macro.c_str(); param += 4; if ((param[0] >= '0') && (param[0] <= '9')) code = (param[0] - '0') << 4; else if ((param[0] >= 'A') && (param[0] <= 'F')) code = (param[0] - 'A' + 0x0A) << 4; if ((param[1] >= '0') && (param[1] <= '9')) code += (param[1] - '0'); else if ((param[1] >= 'A') && (param[1] <= 'F')) code += (param[1] - 'A' + 0x0A); if (macro.size() >= 4 && macro.at(3) == '0') return (code - 128); else return (code + 128); } int MIDIMacroConfig::MacroToMidiCC(uint32 macroIndex) const { const std::string macro = GetSafeMacro(szMidiSFXExt[macroIndex]); int code = 0; const char *param = macro.c_str(); param += 2; if ((param[0] >= '0') && (param[0] <= '9')) code = (param[0] - '0') << 4; else if ((param[0] >= 'A') && (param[0] <= 'F')) code = (param[0] - 'A' + 0x0A) << 4; if ((param[1] >= '0') && (param[1] <= '9')) code += (param[1] - '0'); else if ((param[1] >= 'A') && (param[1] <= 'F')) code += (param[1] - 'A' + 0x0A); return code; } int MIDIMacroConfig::FindMacroForParam(PlugParamIndex param) const { for(int macroIndex = 0; macroIndex < NUM_MACROS; macroIndex++) { if(GetParameteredMacroType(macroIndex) == kSFxPlugParam && MacroToPlugParam(macroIndex) == param) { return macroIndex; } } return -1; } #endif // MODPLUG_TRACKER // Check if the MIDI Macro configuration used is the default one, // i.e. the configuration that is assumed when loading a file that has no macros embedded. bool MIDIMacroConfig::IsMacroDefaultSetupUsed() const { const MIDIMacroConfig defaultConfig; // TODO - Global macros (currently not checked because they are not editable) // SF0: Z00-Z7F controls cutoff, all other parametered macros are unused for(uint32 i = 0; i < NUM_MACROS; i++) { if(GetParameteredMacroType(i) != defaultConfig.GetParameteredMacroType(i)) { return false; } } // Z80-Z8F controls resonance if(GetFixedMacroType() != defaultConfig.GetFixedMacroType()) { return false; } return true; } // Reset MIDI macro config to default values. void MIDIMacroConfig::Reset() { MemsetZero(szMidiGlb); MemsetZero(szMidiSFXExt); MemsetZero(szMidiZXXExt); strcpy(szMidiGlb[MIDIOUT_START], "FF"); strcpy(szMidiGlb[MIDIOUT_STOP], "FC"); strcpy(szMidiGlb[MIDIOUT_NOTEON], "9c n v"); strcpy(szMidiGlb[MIDIOUT_NOTEOFF], "9c n 0"); strcpy(szMidiGlb[MIDIOUT_PROGRAM], "Cc p"); // SF0: Z00-Z7F controls cutoff CreateParameteredMacro(0, kSFxCutoff); // Z80-Z8F controls resonance CreateFixedMacro(kZxxReso4Bit); } // Clear all Zxx macros so that they do nothing. void MIDIMacroConfig::ClearZxxMacros() { MemsetZero(szMidiSFXExt); MemsetZero(szMidiZXXExt); } // Sanitize all macro config strings. void MIDIMacroConfig::Sanitize() { for(auto ¯o : *this) { mpt::String::FixNullString(macro); } } // Helper function for UpgradeMacros() void MIDIMacroConfig::UpgradeMacroString(Macro ¯o) const { for(auto &c : macro) { if(c >= 'a' && c <= 'f') // Both A-F and a-f were treated as hex constants { c = c - 'a' + 'A'; } else if(c == 'K' || c == 'k') // Channel was K or k { c = 'c'; } else if(c == 'X' || c == 'x' || c == 'Y' || c == 'y') // Those were pointless { c = 'z'; } } } // Fix old-format (not conforming to IT's MIDI macro definitions) MIDI config strings. void MIDIMacroConfig::UpgradeMacros() { for(auto ¯o : *this) { UpgradeMacroString(macro); } } // Normalize by removing blanks and other unwanted characters from macro strings for internal usage. std::string MIDIMacroConfig::GetSafeMacro(const Macro ¯o) const { std::string sanitizedMacro = macro; std::string::size_type pos; while((pos = sanitizedMacro.find_first_not_of("0123456789ABCDEFabchmnopsuvxyz")) != std::string::npos) { sanitizedMacro.erase(pos, 1); } return sanitizedMacro; } OPENMPT_NAMESPACE_END