Skip to content

Commit

Permalink
Greatly refined and improved emulation of display facilities (#66)
Browse files Browse the repository at this point in the history
- Introduced display emulation support in mt32emu library itself.
  The startup banner and the SysEx checksum error are now shown. We now
  also take care of the control ROM version to emulate the most prominent
  quirks.
- The new API includes a function for retrieving the current display state
  and a function to reset the display to the Master Volume mode.
- The interface ReportHandler has been extended to provide callbacks to get
  notified about changes in display state.
- Additionally, there is a limited support for displaying the emulated LCD
  on narrow hardware LCDs that have width of 16 characters only.
  • Loading branch information
sergm committed Dec 12, 2021
1 parent 8731689 commit c528ff1
Show file tree
Hide file tree
Showing 16 changed files with 888 additions and 125 deletions.
1 change: 1 addition & 0 deletions mt32emu/CMakeLists.txt
Expand Up @@ -109,6 +109,7 @@ endif(libmt32emu_SHARED)
set(libmt32emu_SOURCES
src/Analog.cpp
src/BReverbModel.cpp
src/Display.cpp
src/File.cpp
src/FileStream.cpp
src/LA32FloatWaveGenerator.cpp
Expand Down
5 changes: 5 additions & 0 deletions mt32emu/NEWS.txt
Expand Up @@ -13,6 +13,11 @@ HEAD:
* Updated the description of the module FindMT32EMU.cmake. It now creates the IMPORTED
target MT32Emu::mt32emu upon success, similarly to the other scripts. Note, this module
is not recommended for use with mt32emu version 2.6 and above. (#65)
* Introduced display emulation support that goes beyond the existing low-level API intended
for retrieving the synth state details. The startup banner and the SysEx checksum error
are now shown. We now also take care of the control ROM version to emulate the most
prominent quirks. Additionally, there is a limited support for displaying the emulated LCD
on narrow hardware LCDs that have width of 16 characters only. (#66)

2021-08-07:

Expand Down
354 changes: 354 additions & 0 deletions mt32emu/src/Display.cpp

Large diffs are not rendered by default.

91 changes: 91 additions & 0 deletions mt32emu/src/Display.h
@@ -0,0 +1,91 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011-2021 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef MT32EMU_DISPLAY_H
#define MT32EMU_DISPLAY_H

#include "globals.h"
#include "Types.h"

namespace MT32Emu {

class Synth;

/** Facilitates emulation of internal state of the MIDI MESSAGE LED and the MT-32 LCD. */
class Display {
public:
static const unsigned int LCD_TEXT_SIZE = 20;

enum Mode {
Mode_MAIN, // a.k.a. Master Volume
Mode_STARTUP_MESSAGE,
Mode_PROGRAM_CHANGE,
Mode_CUSTOM_MESSAGE,
Mode_ERROR_MESSAGE
};

Display(Synth &synth);
void checkDisplayStateUpdated(bool &midiMessageLEDState, bool &midiMessageLEDUpdated, bool &lcdUpdated);
/** Returns whether the MIDI MESSAGE LED is ON and fills the targetBuffer parameter. */
bool getDisplayState(char *targetBuffer, bool narrowLCD);
void setMainDisplayMode();

void midiMessagePlayed();
void rhythmNotePlayed();
void voicePartStateChanged(Bit8u partIndex, bool activated);
void masterVolumeChanged();
void programChanged(Bit8u partIndex);
void checksumErrorOccurred();
bool customDisplayMessageReceived(const Bit8u *message, Bit32u startIndex, Bit32u length);
void displayControlMessageReceived(const Bit8u *messageBytes, Bit32u length);

private:
typedef Bit8u DisplayBuffer[LCD_TEXT_SIZE];

static const unsigned int TIMBRE_NAME_SIZE = 10;

Synth &synth;

bool lastLEDState;
bool lcdDirty;
bool lcdUpdateSignalled;
bool lastRhythmPartState;
bool voicePartStates[8];

Bit8u lastProgramChangePartIndex;
const char *lastProgramChangeSoundGroupName;
Bit8u lastProgramChangeTimbreName[TIMBRE_NAME_SIZE];

Mode mode;
Bit32u displayResetTimestamp;
bool displayResetScheduled;
Bit32u midiMessageLEDResetTimestamp;
bool midiMessagePlayedSinceLastReset;
Bit32u rhythmStateResetTimestamp;
bool rhythmNotePlayedSinceLastReset;

DisplayBuffer displayBuffer;
DisplayBuffer customMessageBuffer;

void scheduleDisplayReset();
bool shouldResetTimer(Bit32u scheduledResetTimestamp);
void maybeResetTimer(bool &timerState, Bit32u scheduledResetTimestamp);
};

} // namespace MT32Emu

#endif // #ifndef MT32EMU_DISPLAY_H
4 changes: 3 additions & 1 deletion mt32emu/src/MemoryRegion.h
Expand Up @@ -120,7 +120,9 @@ class SystemMemoryRegion : public MemoryRegion {
};
class DisplayMemoryRegion : public MemoryRegion {
public:
DisplayMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Display, MT32EMU_MEMADDR(0x200000), SYSEX_BUFFER_SIZE - 1, 1) {}
// Note, we set realMemory to NULL despite the real devices buffer inbound strings. However, it is impossible to retrieve them.
// This entrySize permits emulation of handling a 20-byte display message sent to an old-gen device at address 0x207F7F.
DisplayMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Display, MT32EMU_MEMADDR(0x200000), 0x4013, 1) {}
};
class ResetMemoryRegion : public MemoryRegion {
public:
Expand Down
25 changes: 24 additions & 1 deletion mt32emu/src/Part.cpp
Expand Up @@ -63,6 +63,7 @@ Part::Part(Synth *useSynth, unsigned int usePartNum) {
expression = 100;
pitchBend = 0;
activePartialCount = 0;
activeNonReleasingPolyCount = 0;
memset(patchCache, 0, sizeof(patchCache));
}

Expand Down Expand Up @@ -166,7 +167,7 @@ void Part::refresh() {
patchCache[t].reverb = patchTemp->patch.reverbSwitch > 0;
}
memcpy(currentInstr, timbreTemp->common.name, 10);
synth->newTimbreSet(partNum, patchTemp->patch.timbreGroup, patchTemp->patch.timbreNum, currentInstr);
synth->newTimbreSet(partNum);
updatePitchBenderRange();
}

Expand Down Expand Up @@ -380,6 +381,7 @@ void RhythmPart::noteOn(unsigned int midiKey, unsigned int velocity) {
synth->printDebug("%s: Attempted to play invalid key %d (velocity %d)", name, midiKey, velocity);
return;
}
synth->rhythmNotePlayed();
unsigned int key = midiKey;
unsigned int drumNum = key - 24;
int drumTimbreNum = rhythmTemp[drumNum].timbre;
Expand Down Expand Up @@ -609,6 +611,27 @@ void Part::partialDeactivated(Poly *poly) {
}
}

void RhythmPart::polyStateChanged(PolyState, PolyState) {}

void Part::polyStateChanged(PolyState oldState, PolyState newState) {
switch (newState) {
case POLY_Playing:
if (activeNonReleasingPolyCount++ == 0) synth->voicePartStateChanged(partNum, true);
break;
case POLY_Releasing:
case POLY_Inactive:
if (oldState == POLY_Playing || oldState == POLY_Held) {
if (--activeNonReleasingPolyCount == 0) synth->voicePartStateChanged(partNum, false);
}
break;
default:
break;
}
#ifdef MT32EMU_TRACE_POLY_STATE_CHANGES
synth->printDebug("Part %d: Changed poly state %d->%d, activeNonReleasingPolyCount=%d", partNum, oldState, newState, activeNonReleasingPolyCount);
#endif
}

PolyList::PolyList() : firstPoly(NULL), lastPoly(NULL) {}

bool PolyList::isEmpty() const {
Expand Down
3 changes: 3 additions & 0 deletions mt32emu/src/Part.h
Expand Up @@ -55,6 +55,7 @@ class Part {
bool holdpedal;

unsigned int activePartialCount;
unsigned int activeNonReleasingPolyCount;
PatchCache patchCache[4];
PolyList activePolys;

Expand Down Expand Up @@ -122,6 +123,7 @@ class Part {

// This should only be called by Poly
void partialDeactivated(Poly *poly);
virtual void polyStateChanged(PolyState oldState, PolyState newState);

// These are rather specialised, and should probably only be used by PartialManager
bool abortFirstPoly(PolyState polyState);
Expand All @@ -146,6 +148,7 @@ class RhythmPart: public Part {
unsigned int getAbsTimbreNum() const;
void setPan(unsigned int midiPan);
void setProgram(unsigned int patchNum);
void polyStateChanged(PolyState oldState, PolyState newState);
};

} // namespace MT32Emu
Expand Down
17 changes: 12 additions & 5 deletions mt32emu/src/Poly.cpp
Expand Up @@ -53,7 +53,7 @@ void Poly::reset(unsigned int newKey, unsigned int newVelocity, bool newSustain,
activePartialCount--;
}
}
state = POLY_Inactive;
setState(POLY_Inactive);
}

key = newKey;
Expand All @@ -65,7 +65,7 @@ void Poly::reset(unsigned int newKey, unsigned int newVelocity, bool newSustain,
partials[i] = newPartials[i];
if (newPartials[i] != NULL) {
activePartialCount++;
state = POLY_Playing;
setState(POLY_Playing);
}
}
}
Expand All @@ -80,7 +80,7 @@ bool Poly::noteOff(bool pedalHeld) {
if (state == POLY_Held) {
return false;
}
state = POLY_Held;
setState(POLY_Held);
} else {
startDecay();
}
Expand All @@ -98,7 +98,7 @@ bool Poly::startDecay() {
if (state == POLY_Inactive || state == POLY_Releasing) {
return false;
}
state = POLY_Releasing;
setState(POLY_Releasing);

for (int t = 0; t < 4; t++) {
Partial *partial = partials[t];
Expand All @@ -123,6 +123,13 @@ bool Poly::startAbort() {
return true;
}

void Poly::setState(PolyState newState) {
if (state == newState) return;
PolyState oldState = state;
state = newState;
part->polyStateChanged(oldState, newState);
}

void Poly::backupCacheToPartials(PatchCache cache[4]) {
for (int partialNum = 0; partialNum < 4; partialNum++) {
Partial *partial = partials[partialNum];
Expand Down Expand Up @@ -171,7 +178,7 @@ void Poly::partialDeactivated(Partial *partial) {
}
}
if (activePartialCount == 0) {
state = POLY_Inactive;
setState(POLY_Inactive);
if (part->getSynth()->abortingPoly == this) {
part->getSynth()->abortingPoly = NULL;
}
Expand Down
2 changes: 2 additions & 0 deletions mt32emu/src/Poly.h
Expand Up @@ -41,6 +41,8 @@ class Poly {

Poly *next;

void setState(PolyState state);

public:
Poly();
void setPart(Part *usePart);
Expand Down
4 changes: 4 additions & 0 deletions mt32emu/src/Structures.h
Expand Up @@ -192,6 +192,8 @@ struct ControlROMFeatureSet {
unsigned int quirkKeyShift : 1;
unsigned int quirkTVFBaseCutoffLimit : 1;
unsigned int quirkFastPitchChanges : 1;
unsigned int quirkDisplayCustomMessagePriority : 1;
unsigned int oldMT32DisplayFeatures : 1;

// Features below don't actually depend on control ROM version, which is used to identify hardware model
unsigned int defaultReverbMT32Compatible : 1;
Expand Down Expand Up @@ -222,6 +224,8 @@ struct ControlROMMap {
Bit16u timbreMaxTable; // 72 bytes
Bit16u soundGroupsTable; // 14 bytes each entry
Bit16u soundGroupsCount;
Bit16u startupMessage; // 20 characters + NULL terminator
Bit16u sysexErrorMessage; // 20 characters + NULL terminator
};

struct ControlROMPCMStruct {
Expand Down

0 comments on commit c528ff1

Please sign in to comment.