diff --git a/mt32emu/NEWS.txt b/mt32emu/NEWS.txt index a2b76985..6f0a1bd9 100644 --- a/mt32emu/NEWS.txt +++ b/mt32emu/NEWS.txt @@ -1,3 +1,8 @@ +HEAD: + + * Provided callbacks to retrieve notifications about events when the synth engine runs + out of available partials. (#111) + 2022-07-23: 2.7.0 released. diff --git a/mt32emu/cmake/project_data.cmake b/mt32emu/cmake/project_data.cmake index 5276d615..bbaa9c60 100644 --- a/mt32emu/cmake/project_data.cmake +++ b/mt32emu/cmake/project_data.cmake @@ -3,6 +3,6 @@ set(libmt32emu_URL "http://munt.sourceforge.net/") set(libmt32emu_CONTACT "sergm@muntemu.org") set(libmt32emu_VERSION_MAJOR 2) -set(libmt32emu_VERSION_MINOR 7) +set(libmt32emu_VERSION_MINOR 8) set(libmt32emu_VERSION_PATCH 0) set(libmt32emu_VERSION "${libmt32emu_VERSION_MAJOR}.${libmt32emu_VERSION_MINOR}.${libmt32emu_VERSION_PATCH}") diff --git a/mt32emu/src/Part.cpp b/mt32emu/src/Part.cpp index 5888b97b..75cdb5ec 100644 --- a/mt32emu/src/Part.cpp +++ b/mt32emu/src/Part.cpp @@ -498,9 +498,13 @@ void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhyt synth->printDebug("%s (%s): Insufficient free partials to play key %d (velocity %d); needed=%d, free=%d, assignMode=%d", name, currentInstr, midiKey, velocity, needPartials, synth->partialManager->getFreePartialCount(), patchTemp->patch.assignMode); synth->printPartialUsage(); #endif + synth->getReportHandler3()->onNoteOnIgnored(needPartials, synth->partialManager->getFreePartialCount()); + return; + } + if (synth->isAbortingPoly()) { + synth->getReportHandler3()->onPlayingPolySilenced(needPartials, synth->partialManager->getFreePartialCount()); return; } - if (synth->isAbortingPoly()) return; Poly *poly = synth->partialManager->assignPolyToPart(this); if (poly == NULL) { diff --git a/mt32emu/src/Synth.cpp b/mt32emu/src/Synth.cpp index 0b81edb9..9eafb8b9 100644 --- a/mt32emu/src/Synth.cpp +++ b/mt32emu/src/Synth.cpp @@ -253,8 +253,9 @@ class Extensions { Display *display; bool oldMT32DisplayFeatures; - ReportHandler2 defaultReportHandler; + ReportHandler3 defaultReportHandler; ReportHandler2 *reportHandler2; + ReportHandler3 *reportHandler3; }; Bit32u Synth::getLibraryVersionInt() { @@ -292,6 +293,7 @@ Synth::Synth(ReportHandler *useReportHandler) : reportHandler = useReportHandler != NULL ? useReportHandler : &extensions.defaultReportHandler; extensions.reportHandler2 = &extensions.defaultReportHandler; + extensions.reportHandler3 = &extensions.defaultReportHandler; extensions.preallocatedReverbMemory = false; for (int i = REVERB_MODE_ROOM; i <= REVERB_MODE_TAP_DELAY; i++) { @@ -349,6 +351,23 @@ void Synth::setReportHandler2(ReportHandler2 *reportHandler2) { reportHandler = &extensions.defaultReportHandler; extensions.reportHandler2 = &extensions.defaultReportHandler; } + extensions.reportHandler3 = &extensions.defaultReportHandler; +} + +void Synth::setReportHandler3(ReportHandler3 *reportHandler3) { + if (reportHandler3 != NULL) { + reportHandler = reportHandler3; + extensions.reportHandler2 = reportHandler3; + extensions.reportHandler3 = reportHandler3; + } else { + reportHandler = &extensions.defaultReportHandler; + extensions.reportHandler2 = &extensions.defaultReportHandler; + extensions.reportHandler3 = &extensions.defaultReportHandler; + } +} + +ReportHandler3 *Synth::getReportHandler3() { + return extensions.reportHandler3; } void ReportHandler::showLCDMessage(const char *data) { diff --git a/mt32emu/src/Synth.h b/mt32emu/src/Synth.h index 0f88eb9f..131191c0 100644 --- a/mt32emu/src/Synth.h +++ b/mt32emu/src/Synth.h @@ -128,6 +128,18 @@ class MT32EMU_EXPORT_V(2.6) ReportHandler2 : public ReportHandler { virtual void onMidiMessageLEDStateUpdated(bool /* ledState */) {} }; +// Extends ReportHandler for delivering signals about insufficient partials conditions to the client. +class MT32EMU_EXPORT_V(2.8) ReportHandler3 : public ReportHandler2 { +public: + virtual ~ReportHandler3() {} + + // Invoked in case the NoteOn MIDI message currently being processed cannot be played due to insufficient free partials. + virtual void onNoteOnIgnored(Bit32u /* partialsNeeded */, Bit32u /* partialsFree */) {} + // Invoked in case the partial allocator starts premature silencing a currently playing poly to free partials necessary + // for processing the preceding NoteOn MIDI message. + virtual void onPlayingPolySilenced(Bit32u /* partialsNeeded */, Bit32u /* partialsFree */) {} +}; + class Synth { friend class DefaultMidiStreamParser; friend class Display; @@ -256,6 +268,8 @@ friend class TVP; void resetMasterTunePitchDelta(); Bit32s getMasterTunePitchDelta() const; + ReportHandler3 *getReportHandler3(); + public: static inline Bit16s clipSampleEx(Bit32s sampleEx) { // Clamp values above 32767 to 32767, and values below -32768 to -32768 @@ -313,6 +327,9 @@ friend class TVP; // Sets an implementation of ReportHandler2 interface for reporting various errors, information and debug messages. // If the argument is NULL, the default implementation is installed as a fallback. MT32EMU_EXPORT_V(2.6) void setReportHandler2(ReportHandler2 *reportHandler2); + // Sets an implementation of ReportHandler3 interface for reporting various errors, information and debug messages. + // If the argument is NULL, the default implementation is installed as a fallback. + MT32EMU_EXPORT_V(2.8) void setReportHandler3(ReportHandler3 *reportHandler3); // Used to initialise the MT-32. Must be called before any other function. // Returns true if initialization was successful, otherwise returns false. diff --git a/mt32emu/src/VersionTagging.cpp b/mt32emu/src/VersionTagging.cpp index 0a3388f3..1b9a3850 100644 --- a/mt32emu/src/VersionTagging.cpp +++ b/mt32emu/src/VersionTagging.cpp @@ -25,8 +25,9 @@ extern "C" { MT32EMU_EXPORT_V(2.5) extern const volatile char mt32emu_2_5 = 0; MT32EMU_EXPORT_V(2.6) extern const volatile char mt32emu_2_6 = 0; MT32EMU_EXPORT_V(2.7) extern const volatile char mt32emu_2_7 = 0; +MT32EMU_EXPORT_V(2.8) extern const volatile char mt32emu_2_8 = 0; -#if MT32EMU_VERSION_MAJOR > 2 || MT32EMU_VERSION_MINOR > 7 +#if MT32EMU_VERSION_MAJOR > 2 || MT32EMU_VERSION_MINOR > 8 #error "Missing version tag definition for current library version" #endif } diff --git a/mt32emu/src/c_interface/c_interface.cpp b/mt32emu/src/c_interface/c_interface.cpp index 4c7706be..87771bb9 100644 --- a/mt32emu/src/c_interface/c_interface.cpp +++ b/mt32emu/src/c_interface/c_interface.cpp @@ -142,7 +142,7 @@ static const mt32emu_service_i_v6 SERVICE_VTABLE = { } // namespace MT32Emu struct mt32emu_data { - ReportHandler2 *reportHandler; + ReportHandler3 *reportHandler; Synth *synth; const ROMImage *controlROMImage; const ROMImage *pcmROMImage; @@ -156,7 +156,7 @@ struct mt32emu_data { namespace MT32Emu { -class DelegatingReportHandlerAdapter : public ReportHandler2 { +class DelegatingReportHandlerAdapter : public ReportHandler3 { public: DelegatingReportHandlerAdapter(mt32emu_report_handler_i useReportHandler, void *useInstanceData) : delegate(useReportHandler), instanceData(useInstanceData) {} @@ -295,6 +295,22 @@ class DelegatingReportHandlerAdapter : public ReportHandler2 { delegate.v1->onMidiMessageLEDStateUpdated(instanceData, ledState ? MT32EMU_BOOL_TRUE : MT32EMU_BOOL_FALSE); } } + + void onNoteOnIgnored(Bit32u partialsNeeded, Bit32u partialsFree) { + if (isVersionLess(MT32EMU_REPORT_HANDLER_VERSION_2) || delegate.v2->onNoteOnIgnored == NULL) { + ReportHandler3::onNoteOnIgnored(partialsNeeded, partialsFree); + } else { + delegate.v2->onNoteOnIgnored(instanceData, partialsNeeded, partialsFree); + } + } + + void onPlayingPolySilenced(Bit32u partialsNeeded, Bit32u partialsFree) { + if (isVersionLess(MT32EMU_REPORT_HANDLER_VERSION_2) || delegate.v2->onPlayingPolySilenced == NULL) { + ReportHandler3::onPlayingPolySilenced(partialsNeeded, partialsFree); + } else { + delegate.v2->onPlayingPolySilenced(instanceData, partialsNeeded, partialsFree); + } + } }; class DelegatingMidiStreamParser : public DefaultMidiStreamParser { @@ -546,7 +562,7 @@ mt32emu_context MT32EMU_C_CALL mt32emu_create_context(mt32emu_report_handler_i r data->synth = new Synth; if (report_handler.v0 != NULL) { data->reportHandler = new DelegatingReportHandlerAdapter(report_handler, instance_data); - data->synth->setReportHandler2(data->reportHandler); + data->synth->setReportHandler3(data->reportHandler); } else { data->reportHandler = NULL; } diff --git a/mt32emu/src/c_interface/c_types.h b/mt32emu/src/c_interface/c_types.h index 8928bfea..b2bd2237 100644 --- a/mt32emu/src/c_interface/c_types.h +++ b/mt32emu/src/c_interface/c_types.h @@ -118,7 +118,8 @@ typedef struct { typedef enum { MT32EMU_REPORT_HANDLER_VERSION_0 = 0, MT32EMU_REPORT_HANDLER_VERSION_1 = 1, - MT32EMU_REPORT_HANDLER_VERSION_CURRENT = MT32EMU_REPORT_HANDLER_VERSION_1 + MT32EMU_REPORT_HANDLER_VERSION_2 = 2, + MT32EMU_REPORT_HANDLER_VERSION_CURRENT = MT32EMU_REPORT_HANDLER_VERSION_2 } mt32emu_report_handler_version; /** MIDI receiver interface versions */ @@ -188,6 +189,17 @@ typedef union mt32emu_report_handler_i mt32emu_report_handler_i; /** Invoked when the emulated MIDI MESSAGE LED changes state. The led_state parameter represents whether the LED is ON. */ \ void (MT32EMU_C_CALL *onMidiMessageLEDStateUpdated)(void *instance_data, mt32emu_boolean led_state); +#define MT32EMU_REPORT_HANDLER_I_V2 \ + /** + * Invoked in case the NoteOn MIDI message currently being processed cannot be played due to insufficient free partials. + */ \ + void (MT32EMU_C_CALL *onNoteOnIgnored)(void *instance_data, mt32emu_bit32u partialsNeeded, mt32emu_bit32u partialsFree); \ + /** + * Invoked in case the partial allocator starts premature silencing a currently playing poly to free partials necessary + * for processing the preceding NoteOn MIDI message. + */ \ + void (MT32EMU_C_CALL *onPlayingPolySilenced)(void *instance_data, mt32emu_bit32u partialsNeeded, mt32emu_bit32u partialsFree); + typedef struct { MT32EMU_REPORT_HANDLER_I_V0 } mt32emu_report_handler_i_v0; @@ -197,6 +209,12 @@ typedef struct { MT32EMU_REPORT_HANDLER_I_V1 } mt32emu_report_handler_i_v1; +typedef struct { + MT32EMU_REPORT_HANDLER_I_V0 + MT32EMU_REPORT_HANDLER_I_V1 + MT32EMU_REPORT_HANDLER_I_V2 +} mt32emu_report_handler_i_v2; + /** * Extensible interface for handling reported events. * Union intended to view an interface of any subsequent version as any parent interface not requiring a cast. @@ -205,6 +223,7 @@ typedef struct { union mt32emu_report_handler_i { const mt32emu_report_handler_i_v0 *v0; const mt32emu_report_handler_i_v1 *v1; + const mt32emu_report_handler_i_v2 *v2; }; #undef MT32EMU_REPORT_HANDLER_I_V0 diff --git a/mt32emu/src/c_interface/cpp_interface.h b/mt32emu/src/c_interface/cpp_interface.h index d22897b7..ded2b3cb 100644 --- a/mt32emu/src/c_interface/cpp_interface.h +++ b/mt32emu/src/c_interface/cpp_interface.h @@ -186,6 +186,17 @@ class IReportHandlerV1 : public IReportHandler { ~IReportHandlerV1() {} }; +// Extends IReportHandlerV1 for delivering signals about insufficient partials conditions to the client. +// Corresponds to the mt32emu_report_handler_i_v2 interface. +class IReportHandlerV2 : public IReportHandlerV1 { +public: + virtual void onNoteOnIgnored(Bit32u partialsNeeded, Bit32u partialsFree) = 0; + virtual void onPlayingPolySilenced(Bit32u partialsNeeded, Bit32u partialsFree) = 0; + +protected: +~IReportHandlerV2() {} +}; + // Defines the interface for receiving MIDI messages generated by MIDI stream parser. // Corresponds to the current version of mt32emu_midi_receiver_i interface. class IMidiReceiver { @@ -234,6 +245,7 @@ class Service { void createContext(mt32emu_report_handler_i report_handler = CppInterfaceImpl::NULL_REPORT_HANDLER, void *instance_data = NULL) { freeContext(); c = mt32emu_create_context(report_handler, instance_data); } void createContext(IReportHandler &report_handler) { createContext(CppInterfaceImpl::getReportHandlerThunk(MT32EMU_REPORT_HANDLER_VERSION_0), &report_handler); } void createContext(IReportHandlerV1 &report_handler) { createContext(CppInterfaceImpl::getReportHandlerThunk(MT32EMU_REPORT_HANDLER_VERSION_1), &report_handler); } + void createContext(IReportHandlerV2 &report_handler) { createContext(CppInterfaceImpl::getReportHandlerThunk(MT32EMU_REPORT_HANDLER_VERSION_2), &report_handler); } void freeContext() { if (c != NULL) { mt32emu_free_context(c); c = NULL; } } mt32emu_return_code addROMData(const Bit8u *data, size_t data_size, const mt32emu_sha1_digest *sha1_digest = NULL) { return mt32emu_add_rom_data(c, data, data_size, sha1_digest); } mt32emu_return_code addROMFile(const char *filename) { return mt32emu_add_rom_file(c, filename); } @@ -420,6 +432,13 @@ static void MT32EMU_C_CALL onMidiMessageLEDStateUpdated(void *instance_data, mt3 static_cast(instance_data)->onMidiMessageLEDStateUpdated(led_state != MT32EMU_BOOL_FALSE); } +static void MT32EMU_C_CALL onNoteOnIgnored(void *instance_data, Bit32u partialsNeeded, Bit32u partialsFree) { + static_cast(instance_data)->onNoteOnIgnored(partialsNeeded, partialsFree); +} +static void MT32EMU_C_CALL onPlayingPolySilenced(void *instance_data, Bit32u partialsNeeded, Bit32u partialsFree) { + static_cast(instance_data)->onPlayingPolySilenced(partialsNeeded, partialsFree); +} + #define MT32EMU_REPORT_HANDLER_V0_THUNK \ getReportHandlerVersionID, \ printDebug, \ @@ -437,27 +456,44 @@ static void MT32EMU_C_CALL onMidiMessageLEDStateUpdated(void *instance_data, mt3 onPolyStateChanged, \ onProgramChanged +#define MT32EMU_REPORT_HANDLER_V1_THUNK \ + onLCDStateUpdated, \ + onMidiMessageLEDStateUpdated + +#define MT32EMU_REPORT_HANDLER_V2_THUNK \ + onNoteOnIgnored, \ + onPlayingPolySilenced + static const mt32emu_report_handler_i_v0 REPORT_HANDLER_V0_THUNK = { MT32EMU_REPORT_HANDLER_V0_THUNK }; static const mt32emu_report_handler_i_v1 REPORT_HANDLER_V1_THUNK = { MT32EMU_REPORT_HANDLER_V0_THUNK, - onLCDStateUpdated, - onMidiMessageLEDStateUpdated + MT32EMU_REPORT_HANDLER_V1_THUNK +}; + +static const mt32emu_report_handler_i_v2 REPORT_HANDLER_V2_THUNK = { + MT32EMU_REPORT_HANDLER_V0_THUNK, + MT32EMU_REPORT_HANDLER_V1_THUNK, + MT32EMU_REPORT_HANDLER_V2_THUNK }; #undef MT32EMU_REPORT_HANDLER_THUNK_V0 +#undef MT32EMU_REPORT_HANDLER_THUNK_V1 +#undef MT32EMU_REPORT_HANDLER_THUNK_V2 static mt32emu_report_handler_version MT32EMU_C_CALL getReportHandlerVersionID(mt32emu_report_handler_i thunk) { if (thunk.v0 == &REPORT_HANDLER_V0_THUNK) return MT32EMU_REPORT_HANDLER_VERSION_0; + if (thunk.v1 == &REPORT_HANDLER_V1_THUNK) return MT32EMU_REPORT_HANDLER_VERSION_1; return MT32EMU_REPORT_HANDLER_VERSION_CURRENT; } static mt32emu_report_handler_i getReportHandlerThunk(mt32emu_report_handler_version versionID) { mt32emu_report_handler_i thunk; if (versionID == MT32EMU_REPORT_HANDLER_VERSION_0) thunk.v0 = &REPORT_HANDLER_V0_THUNK; - else thunk.v1 = &REPORT_HANDLER_V1_THUNK; + else if (versionID == MT32EMU_REPORT_HANDLER_VERSION_1) thunk.v1 = &REPORT_HANDLER_V1_THUNK; + else thunk.v2 = &REPORT_HANDLER_V2_THUNK; return thunk; }