Skip to content

Commit

Permalink
In mt32emu_qt, made the LCD look more natural (#50, #66)
Browse files Browse the repository at this point in the history
- Simplified class SynthStateMonitor which no longer implements the low-
  level details (except rendering of characters).
- Refactored the old LCD rendering code to use named constants and some
  better suited Qt API.
- Improved layout and rendering of LCDWidget to fit all available space.
  The rendering model is now upscaled by factor 4, so it still works with
  integers but permits an arbitrary gap between the pixels.
- Enabled anti-aliasing rendering hint, so that the LCD looks nicer
  if the underlying painting system supports that, yet still looks OK
  otherwise. The only missing bit is the glowing effect; sadly it is
  burred into QML and absent in C++ API of Qt5.
- Clicking on the LCDWidget now resets it to the main (Master Volume) mode.
- The built-in LCD font has been reworked as well to closely match
  the specification of SED1200D-OA and screenshots of a real device,
  available @ #64 (comment)
  • Loading branch information
sergm committed Dec 12, 2021
1 parent c528ff1 commit 055c566
Show file tree
Hide file tree
Showing 8 changed files with 371 additions and 376 deletions.
9 changes: 9 additions & 0 deletions mt32emu_qt/NEWS.txt
@@ -1,3 +1,12 @@
HEAD:

* Improved emulation of the MT-32 display. It now behaves a lot closer to the real device.
The new behaviour depends on the control ROM version, so that the most prominent quirks
are now emulated. Additionally, the LCD shows the startup banner and the SysEx checksum
error. The built-in LCD font has been reworked as well to closely match the hardware one
yet the new appearance makes the grid of pixels visible. Clicking on the LCD now resets
it to the main (Master Volume) mode. (#50, #66)

2021-05-22:

1.9.0 released.
Expand Down
152 changes: 98 additions & 54 deletions mt32emu_qt/src/QSynth.cpp
Expand Up @@ -99,7 +99,8 @@ class RealtimeHelper : public QThread {
NICE_PARTIAL_MIXING_ENABLED_CHANGED,
EMU_DAC_INPUT_MODE_CHANGED,
MIDI_DELAY_MODE_CHANGED,
MIDI_CHANNELS_ASSIGNMENT_RESET
MIDI_CHANNELS_ASSIGNMENT_RESET,
DISPLAY_RESET
};

QSynth &qsynth;
Expand All @@ -125,7 +126,9 @@ class RealtimeHelper : public QThread {
// On backpressure, the latest values are kept.
struct {
char lcdMessage[LCD_MESSAGE_LENGTH];
bool midiMessagePlayed;
bool lcdStateUpdated;
bool midiMessageLEDState;
bool midiMessageLEDStateUpdated;
int masterVolumeUpdate;
int reverbMode;
int reverbTime;
Expand All @@ -141,7 +144,10 @@ class RealtimeHelper : public QThread {
// Synth state snapshot, guarded by stateSnapshotMutex.
struct {
char lcdMessage[LCD_MESSAGE_LENGTH];
bool midiMessagePlayed;
char lcdState[LCD_MESSAGE_LENGTH];
bool lcdStateUpdated;
bool midiMessageLEDState;
bool midiMessageLEDStateUpdated;
int masterVolumeUpdate;
int reverbMode;
int reverbTime;
Expand All @@ -151,15 +157,13 @@ class RealtimeHelper : public QThread {
bool programChanged;
char soundGroupName[SOUND_GROUP_NAME_LENGTH];
char timbreName[TIMBRE_NAME_LENGTH];
bool active;
Bit32u playingNotesCount;
Bit8u keysOfPlayingNotes[MAX_PARTIAL_COUNT];
Bit8u velocitiesOfPlayingNotes[MAX_PARTIAL_COUNT];
} partStates[PART_COUNT];
PartialState partialStates[MAX_PARTIAL_COUNT];
} stateSnapshot;


/** Ensures atomicity of collecting changes to be applied to the synth settings. */
QMutex settingsMutex;
/** Ensures atomicity of handling the output signals of the synth and capturing its internal state. */
Expand Down Expand Up @@ -215,6 +219,9 @@ class RealtimeHelper : public QThread {
case MIDI_CHANNELS_ASSIGNMENT_RESET:
writeMIDIChannelsAssignmentResetSysex(synth, midiChannelsAssignmentChannel1Engaged);
break;
case DISPLAY_RESET:
synth->setMainDisplayMode();
break;
}
}
}
Expand All @@ -226,9 +233,6 @@ class RealtimeHelper : public QThread {
memcpy(stateSnapshot.lcdMessage, tempState.lcdMessage, LCD_MESSAGE_LENGTH - 1);
tempState.lcdMessage[0] = 0;

stateSnapshot.midiMessagePlayed = tempState.midiMessagePlayed;
tempState.midiMessagePlayed = false;

stateSnapshot.masterVolumeUpdate = tempState.masterVolumeUpdate;
tempState.masterVolumeUpdate = NO_UPDATE_VALUE;

Expand All @@ -241,9 +245,19 @@ class RealtimeHelper : public QThread {
stateSnapshot.reverbLevel = tempState.reverbLevel;
tempState.reverbLevel = NO_UPDATE_VALUE;

stateSnapshot.midiMessageLEDStateUpdated = tempState.midiMessageLEDStateUpdated;
if (tempState.midiMessageLEDStateUpdated) {
tempState.midiMessageLEDStateUpdated = false;
stateSnapshot.midiMessageLEDState = tempState.midiMessageLEDState;
}

Synth *synth = qsynth.synth;
bool partStates[PART_COUNT];
synth->getPartStates(partStates);

stateSnapshot.lcdStateUpdated = tempState.lcdStateUpdated;
if (tempState.lcdStateUpdated) {
tempState.lcdStateUpdated = false;
synth->getDisplayState(stateSnapshot.lcdState);
}

for (int partIx = 0; partIx < PART_COUNT; partIx++) {
stateSnapshot.partStates[partIx].programChanged = tempState.partStates[partIx].programChanged;
Expand All @@ -253,8 +267,6 @@ class RealtimeHelper : public QThread {
memcpy(stateSnapshot.partStates[partIx].timbreName, tempState.partStates[partIx].timbreName, TIMBRE_NAME_LENGTH - 1);
}

stateSnapshot.partStates[partIx].active = partStates[partIx];

stateSnapshot.partStates[partIx].polyStateChanged = tempState.partStates[partIx].polyStateChanged;
if (tempState.partStates[partIx].polyStateChanged) {
tempState.partStates[partIx].polyStateChanged = false;
Expand All @@ -279,9 +291,14 @@ class RealtimeHelper : public QThread {
stateSnapshot.lcdMessage[0] = 0;
}

if (stateSnapshot.midiMessagePlayed) {
emit reportHandler.midiMessagePlayed();
stateSnapshot.midiMessagePlayed = false;
if (stateSnapshot.lcdStateUpdated) {
emit reportHandler.lcdStateChanged();
stateSnapshot.lcdStateUpdated = false;
}

if (stateSnapshot.midiMessageLEDStateUpdated) {
emit reportHandler.midiMessageLEDStateChanged(stateSnapshot.midiMessageLEDState);
stateSnapshot.midiMessageLEDStateUpdated = false;
}

if (stateSnapshot.masterVolumeUpdate > NO_UPDATE_VALUE) {
Expand Down Expand Up @@ -341,6 +358,7 @@ class RealtimeHelper : public QThread {
tempState.reverbMode = NO_UPDATE_VALUE;
tempState.reverbTime = NO_UPDATE_VALUE;
tempState.reverbLevel = NO_UPDATE_VALUE;
stateSnapshot.midiMessageLEDState = qsynth.synth->getDisplayState(stateSnapshot.lcdState);
}

~RealtimeHelper() {
Expand Down Expand Up @@ -448,6 +466,11 @@ class RealtimeHelper : public QThread {
enqueueSynthControlEvent(MIDI_CHANNELS_ASSIGNMENT_RESET);
}

void setMainDisplayMode() {
QMutexLocker settingsLocker(&settingsMutex);
enqueueSynthControlEvent(DISPLAY_RESET);
}

void resetSynth() {
QMutexLocker settingsLocker(&settingsMutex);
enqueueSynthControlEvent(SYNTH_RESET);
Expand Down Expand Up @@ -475,14 +498,6 @@ class RealtimeHelper : public QThread {
}
}

void getPartStates(bool *partStates) {
QMutexLocker stateSnapshotLocker(&stateSnapshotMutex);
if (!qsynth.isOpen()) return;
for (int partIx = 0; partIx < PART_COUNT; partIx++) {
partStates[partIx] = stateSnapshot.partStates[partIx].active;
}
}

void getPartialStates(PartialState *partialStates) {
QMutexLocker stateSnapshotLocker(&stateSnapshotMutex);
if (!qsynth.isOpen()) return;
Expand All @@ -498,16 +513,18 @@ class RealtimeHelper : public QThread {
return playingNotesCount;
}

void onLCDMessage(const char *message) {
memcpy(tempState.lcdMessage, message, LCD_MESSAGE_LENGTH - 1);
bool getDisplayState(char *targetBuffer) {
QMutexLocker stateSnapshotLocker(&stateSnapshotMutex);
memcpy(targetBuffer, stateSnapshot.lcdState, LCD_MESSAGE_LENGTH);
return stateSnapshot.midiMessageLEDState;
}

void onMIDIMessagePlayed() {
tempState.midiMessagePlayed = true;
void onLCDMessage(const char *message) {
memcpy(tempState.lcdMessage, message, LCD_MESSAGE_LENGTH - 1);
}

void onMasterVolumeChanged(Bit8u masterVolume) {
tempState.masterVolumeUpdate = masterVolume;
void onMasterVolumeChanged(Bit8u useMasterVolume) {
tempState.masterVolumeUpdate = useMasterVolume;
}

void onReverbModeUpdated(Bit8u mode) {
Expand All @@ -531,6 +548,15 @@ class RealtimeHelper : public QThread {
memcpy(tempState.partStates[partNum].soundGroupName, soundGroupName, SOUND_GROUP_NAME_LENGTH - 1);
memcpy(tempState.partStates[partNum].timbreName, patchName, TIMBRE_NAME_LENGTH - 1);
}

void onLCDStateUpdated() {
tempState.lcdStateUpdated = true;
}

void onMidiMessageLEDStateUpdated(bool ledState) {
tempState.midiMessageLEDState = ledState;
tempState.midiMessageLEDStateUpdated = true;
}
};

QReportHandler::QReportHandler(QSynth *qsynth) : QObject(qsynth) {
Expand Down Expand Up @@ -562,14 +588,6 @@ void QReportHandler::onErrorPCMROM() {
QMessageBox::critical(NULL, "Cannot open Synth", "PCM ROM file cannot be opened.");
}

void QReportHandler::onMIDIMessagePlayed() {
if (qSynth()->isRealtime()) {
qSynth()->realtimeHelper->onMIDIMessagePlayed();
} else {
emit midiMessagePlayed();
}
}

void QReportHandler::onDeviceReconfig() {
int masterVolume = readMasterVolume(qSynth()->synth);
if (qSynth()->isRealtime()) {
Expand Down Expand Up @@ -630,20 +648,35 @@ void QReportHandler::onProgramChanged(Bit8u partNum, const char soundGroupName[]
}
}

void QReportHandler::onLCDStateUpdated() {
if (qSynth()->isRealtime()) {
qSynth()->realtimeHelper->onLCDStateUpdated();
} else {
emit lcdStateChanged();
}
}

void QReportHandler::onMidiMessageLEDStateUpdated(bool ledState) {
if (qSynth()->isRealtime()) {
qSynth()->realtimeHelper->onMidiMessageLEDStateUpdated(ledState);
} else {
emit midiMessageLEDStateChanged(ledState);
}
}

void QReportHandler::doShowLCDMessage(const char *message) {
qDebug() << "LCD-Message:" << message;
if (Master::getInstance()->getSettings()->value("Master/showLCDBalloons", true).toBool()) {
emit balloonMessageAppeared("LCD-Message:", message);
}
emit lcdMessageDisplayed(message);
}

QSynth::QSynth(QObject *parent) :
QObject(parent), state(SynthState_CLOSED), midiMutex(new QMutex), synthMutex(new QMutex),
controlROMImage(), pcmROMImage(), reportHandler(this), sampleRateConverter(),
controlROMImage(), pcmROMImage(), synth(), reportHandler(this), sampleRateConverter(),
audioRecorder(), realtimeHelper()
{
synth = new Synth(&reportHandler);
createSynth();
}

QSynth::~QSynth() {
Expand All @@ -656,6 +689,12 @@ QSynth::~QSynth() {
delete midiMutex;
}

void QSynth::createSynth() {
delete synth;
synth = new Synth;
synth->setReportHandler2(&reportHandler);
}

bool QSynth::isOpen() const {
return state == SynthState_OPEN;
}
Expand Down Expand Up @@ -782,8 +821,7 @@ bool QSynth::open(uint &targetSampleRate, SamplerateConversionQuality srcQuality
sampleRateConverter = new SampleRateConverter(*synth, targetSampleRate, srcQuality);
return true;
}
delete synth;
synth = new Synth(&reportHandler);
createSynth();
setState(SynthState_CLOSED);
freeROMImages();
return false;
Expand Down Expand Up @@ -944,16 +982,6 @@ const QString QSynth::getPatchName(int partNum) const {
return name;
}

void QSynth::getPartStates(bool *partStates) const {
if (isRealtime()) {
realtimeHelper->getPartStates(partStates);
} else {
QMutexLocker synthLocker(synthMutex);
if (!isOpen()) return;
synth->getPartStates(partStates);
}
}

void QSynth::getPartialStates(PartialState *partialStates) const {
if (isRealtime()) {
realtimeHelper->getPartialStates(partialStates);
Expand Down Expand Up @@ -986,6 +1014,23 @@ bool QSynth::isActive() const {
return isOpen() && synth->isActive();
}

bool QSynth::getDisplayState(char *targetBuffer) const {
if (isRealtime()) {
return realtimeHelper->getDisplayState(targetBuffer);
}
QMutexLocker synthLocker(synthMutex);
return synth->getDisplayState(targetBuffer);
}

void QSynth::setMainDisplayMode() {
if (isRealtime()) {
realtimeHelper->setMainDisplayMode();
} else {
QMutexLocker synthLocker(synthMutex);
synth->setMainDisplayMode();
}
}

void QSynth::reset() const {
if (isRealtime()) {
realtimeHelper->resetSynth();
Expand Down Expand Up @@ -1023,8 +1068,7 @@ void QSynth::close() {
QMutexLocker synthLocker(synthMutex);
synth->close();
// This effectively resets rendered frame counter, audioStream is also going down
delete synth;
synth = new Synth(&reportHandler);
createSynth();
delete sampleRateConverter;
sampleRateConverter = NULL;
}
Expand Down

0 comments on commit 055c566

Please sign in to comment.