aboutsummaryrefslogtreecommitdiff
path: root/audio/softsynth/mt32/Synth.h
diff options
context:
space:
mode:
Diffstat (limited to 'audio/softsynth/mt32/Synth.h')
-rw-r--r--audio/softsynth/mt32/Synth.h209
1 files changed, 143 insertions, 66 deletions
diff --git a/audio/softsynth/mt32/Synth.h b/audio/softsynth/mt32/Synth.h
index 56e88e6156..8816711bf3 100644
--- a/audio/softsynth/mt32/Synth.h
+++ b/audio/softsynth/mt32/Synth.h
@@ -27,6 +27,7 @@ class Partial;
class PartialManager;
class Part;
class ROMImage;
+class BReverbModel;
/**
* Methods for emulating the connection between the LA32 and the DAC, which involves
@@ -44,6 +45,7 @@ enum DACInputMode {
// * Much less likely to overdrive than any other mode.
// * Half the volume of any of the other modes, meaning its volume relative to the reverb
// output when mixed together directly will sound wrong.
+ // * Output gain is ignored for both LA32 and reverb output.
// * Perfect for developers while debugging :)
DACInputMode_PURE,
@@ -58,7 +60,17 @@ enum DACInputMode {
DACInputMode_GENERATION2
};
-typedef void (*FloatToBit16sFunc)(Bit16s *target, const float *source, Bit32u len, float outputGain);
+enum MIDIDelayMode {
+ // Process incoming MIDI events immediately.
+ MIDIDelayMode_IMMEDIATE,
+
+ // Delay incoming short MIDI messages as if they where transferred via a MIDI cable to a real hardware unit and immediate sysex processing.
+ // This ensures more accurate timing of simultaneous NoteOn messages.
+ MIDIDelayMode_DELAY_SHORT_MESSAGES_ONLY,
+
+ // Delay all incoming MIDI events as if they where transferred via a MIDI cable to a real hardware unit.
+ MIDIDelayMode_DELAY_ALL
+};
const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41;
@@ -217,18 +229,6 @@ public:
ResetMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1) {}
};
-class ReverbModel {
-public:
- virtual ~ReverbModel() {}
- // After construction or a close(), open() will be called at least once before any other call (with the exception of close()).
- virtual void open() = 0;
- // May be called multiple times without an open() in between.
- virtual void close() = 0;
- virtual void setParameters(Bit8u time, Bit8u level) = 0;
- virtual void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) = 0;
- virtual bool isActive() const = 0;
-};
-
class ReportHandler {
friend class Synth;
@@ -244,15 +244,55 @@ protected:
virtual void onErrorControlROM() {}
virtual void onErrorPCMROM() {}
virtual void showLCDMessage(const char *message);
+ virtual void onMIDIMessagePlayed() {}
virtual void onDeviceReset() {}
virtual void onDeviceReconfig() {}
virtual void onNewReverbMode(Bit8u /* mode */) {}
virtual void onNewReverbTime(Bit8u /* time */) {}
virtual void onNewReverbLevel(Bit8u /* level */) {}
- virtual void onPartStateChanged(int /* partNum */, bool /* isActive */) {}
virtual void onPolyStateChanged(int /* partNum */) {}
- virtual void onPartialStateChanged(int /* partialNum */, int /* oldPartialPhase */, int /* newPartialPhase */) {}
- virtual void onProgramChanged(int /* partNum */, char * /* patchName */) {}
+ virtual void onProgramChanged(int /* partNum */, int /* bankNum */, const char * /* patchName */) {}
+};
+
+/**
+ * Used to safely store timestamped MIDI events in a local queue.
+ */
+struct MidiEvent {
+ Bit32u shortMessageData;
+ const Bit8u *sysexData;
+ Bit32u sysexLength;
+ Bit32u timestamp;
+
+ ~MidiEvent();
+ void setShortMessage(Bit32u shortMessageData, Bit32u timestamp);
+ void setSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp);
+};
+
+/**
+ * Simple queue implementation using a ring buffer to store incoming MIDI event before the synth actually processes it.
+ * It is intended to:
+ * - get rid of prerenderer while retaining graceful partial abortion
+ * - add fair emulation of the MIDI interface delays
+ * - extend the synth interface with the default implementation of a typical rendering loop.
+ * THREAD SAFETY:
+ * It is safe to use either in a single thread environment or when there are only two threads - one performs only reading
+ * and one performs only writing. More complicated usage requires external synchronisation.
+ */
+class MidiEventQueue {
+private:
+ MidiEvent *ringBuffer;
+ Bit32u ringBufferSize;
+ volatile Bit32u startPosition;
+ volatile Bit32u endPosition;
+
+public:
+ MidiEventQueue(Bit32u ringBufferSize = DEFAULT_MIDI_EVENT_QUEUE_SIZE);
+ ~MidiEventQueue();
+ void reset();
+ bool pushShortMessage(Bit32u shortMessageData, Bit32u timestamp);
+ bool pushSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp);
+ const MidiEvent *peekMidiEvent();
+ void dropMidiEvent();
};
class Synth {
@@ -260,6 +300,7 @@ friend class Part;
friend class RhythmPart;
friend class Poly;
friend class Partial;
+friend class PartialManager;
friend class Tables;
friend class MemoryRegion;
friend class TVA;
@@ -286,22 +327,32 @@ private:
Bit16s *pcmROMData;
size_t pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM
- Bit8s chantable[32];
-
- Bit32u renderedSampleCount;
+ unsigned int partialCount;
+ Bit8s chantable[32]; // FIXME: Need explanation why 32 is set, obviously it should be 16
+ MidiEventQueue *midiQueue;
+ volatile Bit32u lastReceivedMIDIEventTimestamp;
+ volatile Bit32u renderedSampleCount;
MemParams mt32ram, mt32default;
- ReverbModel *reverbModels[4];
- ReverbModel *reverbModel;
+ BReverbModel *reverbModels[4];
+ BReverbModel *reverbModel;
bool reverbEnabled;
bool reverbOverridden;
- FloatToBit16sFunc la32FloatToBit16sFunc;
- FloatToBit16sFunc reverbFloatToBit16sFunc;
+ MIDIDelayMode midiDelayMode;
+ DACInputMode dacInputMode;
+
+#if MT32EMU_USE_FLOAT_SAMPLES
float outputGain;
float reverbOutputGain;
+#else
+ int outputGain;
+ int reverbOutputGain;
+#endif
+
+ bool reversedStereoEnabled;
bool isOpen;
@@ -311,41 +362,18 @@ private:
PartialManager *partialManager;
Part *parts[9];
- // FIXME: We can reorganise things so that we don't need all these separate tmpBuf, tmp and prerender buffers.
- // This should be rationalised when things have stabilised a bit (if prerender buffers don't die in the mean time).
-
- float tmpBufPartialLeft[MAX_SAMPLES_PER_RUN];
- float tmpBufPartialRight[MAX_SAMPLES_PER_RUN];
- float tmpBufMixLeft[MAX_SAMPLES_PER_RUN];
- float tmpBufMixRight[MAX_SAMPLES_PER_RUN];
- float tmpBufReverbOutLeft[MAX_SAMPLES_PER_RUN];
- float tmpBufReverbOutRight[MAX_SAMPLES_PER_RUN];
-
- Bit16s tmpNonReverbLeft[MAX_SAMPLES_PER_RUN];
- Bit16s tmpNonReverbRight[MAX_SAMPLES_PER_RUN];
- Bit16s tmpReverbDryLeft[MAX_SAMPLES_PER_RUN];
- Bit16s tmpReverbDryRight[MAX_SAMPLES_PER_RUN];
- Bit16s tmpReverbWetLeft[MAX_SAMPLES_PER_RUN];
- Bit16s tmpReverbWetRight[MAX_SAMPLES_PER_RUN];
-
- // These ring buffers are only used to simulate delays present on the real device.
- // In particular, when a partial needs to be aborted to free it up for use by a new Poly,
+ // When a partial needs to be aborted to free it up for use by a new Poly,
// the controller will busy-loop waiting for the sound to finish.
- Bit16s prerenderNonReverbLeft[MAX_PRERENDER_SAMPLES];
- Bit16s prerenderNonReverbRight[MAX_PRERENDER_SAMPLES];
- Bit16s prerenderReverbDryLeft[MAX_PRERENDER_SAMPLES];
- Bit16s prerenderReverbDryRight[MAX_PRERENDER_SAMPLES];
- Bit16s prerenderReverbWetLeft[MAX_PRERENDER_SAMPLES];
- Bit16s prerenderReverbWetRight[MAX_PRERENDER_SAMPLES];
- int prerenderReadIx;
- int prerenderWriteIx;
-
- bool prerender();
- void copyPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u pos, Bit32u len);
- void checkPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u &pos, Bit32u &len);
- void doRenderStreams(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u len);
-
- void playAddressedSysex(unsigned char channel, const Bit8u *sysex, Bit32u len);
+ // We emulate this by delaying new MIDI events processing until abortion finishes.
+ Poly *abortingPoly;
+
+ Bit32u getShortMessageLength(Bit32u msg);
+ Bit32u addMIDIInterfaceDelay(Bit32u len, Bit32u timestamp);
+
+ void convertSamplesToOutput(Sample *target, const Sample *source, Bit32u len, bool reverb);
+ bool isAbortingPoly() const;
+ void doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample *reverbDryLeft, Sample *reverbDryRight, Sample *reverbWetLeft, Sample *reverbWetRight, Bit32u len);
+
void readSysex(unsigned char channel, const Bit8u *sysex, Bit32u len) const;
void initMemoryRegions();
void deleteMemoryRegions();
@@ -370,13 +398,19 @@ private:
void printPartialUsage(unsigned long sampleOffset = 0);
- void partStateChanged(int partNum, bool isPartActive);
void polyStateChanged(int partNum);
- void partialStateChanged(const Partial * const partial, int oldPartialPhase, int newPartialPhase);
- void newTimbreSet(int partNum, char patchName[]);
+ void newTimbreSet(int partNum, Bit8u timbreGroup, const char patchName[]);
void printDebug(const char *fmt, ...);
public:
+ static inline Bit16s clipBit16s(Bit32s sample) {
+ // Clamp values above 32767 to 32767, and values below -32768 to -32768
+ if ((sample + 32768) & ~65535) {
+ return (sample >> 31) ^ 32767;
+ }
+ return (Bit16s)sample;
+ }
+
static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum);
// Optionally sets callbacks for reporting various errors, information and debug messages
@@ -386,18 +420,44 @@ public:
// Used to initialise the MT-32. Must be called before any other function.
// Returns true if initialization was sucessful, otherwise returns false.
// controlROMImage and pcmROMImage represent Control and PCM ROM images for use by synth.
- bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage);
+ // usePartialCount sets the maximum number of partials playing simultaneously for this session.
+ bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, unsigned int usePartialCount = DEFAULT_MAX_PARTIALS);
// Closes the MT-32 and deallocates any memory used by the synthesizer
void close(void);
- // Sends a 4-byte MIDI message to the MT-32 for immediate playback
- void playMsg(Bit32u msg);
+ // All the enqueued events are processed by the synth immediately.
+ void flushMIDIQueue();
+
+ // Sets size of the internal MIDI event queue.
+ // The queue is flushed before reallocation.
+ void setMIDIEventQueueSize(Bit32u);
+
+ // Enqueues a MIDI event for subsequent playback.
+ // The minimum delay involves the delay introduced while the event is transferred via MIDI interface
+ // and emulation of the MCU busy-loop while it frees partials for use by a new Poly.
+ // Calls from multiple threads must be synchronised, although,
+ // no synchronisation is required with the rendering thread.
+
+ // The MIDI event will be processed not before the specified timestamp.
+ // The timestamp is measured as the global rendered sample count since the synth was created.
+ bool playMsg(Bit32u msg, Bit32u timestamp);
+ bool playSysex(const Bit8u *sysex, Bit32u len, Bit32u timestamp);
+ // The MIDI event will be processed ASAP.
+ bool playMsg(Bit32u msg);
+ bool playSysex(const Bit8u *sysex, Bit32u len);
+
+ // WARNING:
+ // The methods below don't ensure minimum 1-sample delay between sequential MIDI events,
+ // and a sequence of NoteOn and immediately succeeding NoteOff messages is always silent.
+
+ // Sends a 4-byte MIDI message to the MT-32 for immediate playback.
+ void playMsgNow(Bit32u msg);
void playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity);
// Sends a string of Sysex commands to the MT-32 for immediate interpretation
// The length is in bytes
- void playSysex(const Bit8u *sysex, Bit32u len);
+ void playSysexNow(const Bit8u *sysex, Bit32u len);
void playSysexWithoutFraming(const Bit8u *sysex, Bit32u len);
void playSysexWithoutHeader(unsigned char device, unsigned char command, const Bit8u *sysex, Bit32u len);
void writeSysex(unsigned char channel, const Bit8u *sysex, Bit32u len);
@@ -407,20 +467,34 @@ public:
void setReverbOverridden(bool reverbOverridden);
bool isReverbOverridden() const;
void setDACInputMode(DACInputMode mode);
+ DACInputMode getDACInputMode() const;
+ void setMIDIDelayMode(MIDIDelayMode mode);
+ MIDIDelayMode getMIDIDelayMode() const;
// Sets output gain factor. Applied to all output samples and unrelated with the synth's Master volume.
+ // Ignored in DACInputMode_PURE
void setOutputGain(float);
+ float getOutputGain() const;
// Sets output gain factor for the reverb wet output. setOutputGain() doesn't change reverb output gain.
+ // Note: We're currently emulate CM-32L/CM-64 reverb quite accurately and the reverb output level closely
+ // corresponds to the level of digital capture. Although, according to the CM-64 PCB schematic,
+ // there is a difference in the reverb analogue circuit, and the resulting output gain is 0.68
+ // of that for LA32 analogue output. This factor is applied to the reverb output gain.
+ // Ignored in DACInputMode_PURE
void setReverbOutputGain(float);
+ float getReverbOutputGain() const;
+
+ void setReversedStereoEnabled(bool enabled);
+ bool isReversedStereoEnabled();
// Renders samples to the specified output stream.
// The length is in frames, not bytes (in 16-bit stereo,
// one frame is 4 bytes).
- void render(Bit16s *stream, Bit32u len);
+ void render(Sample *stream, Bit32u len);
// Renders samples to the specified output streams (any or all of which may be NULL).
- void renderStreams(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u len);
+ void renderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample *reverbDryLeft, Sample *reverbDryRight, Sample *reverbWetLeft, Sample *reverbWetRight, Bit32u len);
// Returns true when there is at least one active partial, otherwise false.
bool hasActivePartials() const;
@@ -430,6 +504,9 @@ public:
const Partial *getPartial(unsigned int partialNum) const;
+ // Returns the maximum number of partials playing simultaneously.
+ unsigned int getPartialCount() const;
+
void readMemory(Bit32u addr, Bit32u len, Bit8u *data);
// partNum should be 0..7 for Part 1..8, or 8 for Rhythm