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.h347
1 files changed, 126 insertions, 221 deletions
diff --git a/audio/softsynth/mt32/Synth.h b/audio/softsynth/mt32/Synth.h
index 37fb7b280a..97d4644ee2 100644
--- a/audio/softsynth/mt32/Synth.h
+++ b/audio/softsynth/mt32/Synth.h
@@ -19,15 +19,31 @@
#define MT32EMU_SYNTH_H
//#include <cstdarg>
+//#include <cstring>
namespace MT32Emu {
-class TableInitialiser;
+class Analog;
+class BReverbModel;
+class MemoryRegion;
+class MidiEventQueue;
+class Part;
+class Poly;
class Partial;
class PartialManager;
-class Part;
-class ROMImage;
-class BReverbModel;
+
+class PatchTempMemoryRegion;
+class RhythmTempMemoryRegion;
+class TimbreTempMemoryRegion;
+class PatchesMemoryRegion;
+class TimbresMemoryRegion;
+class SystemMemoryRegion;
+class DisplayMemoryRegion;
+class ResetMemoryRegion;
+
+struct ControlROMMap;
+struct PCMWaveEntry;
+struct MemParams;
/**
* Methods for emulating the connection between the LA32 and the DAC, which involves
@@ -43,8 +59,7 @@ enum DACInputMode {
// Produces samples that exactly match the bits output from the emulated LA32.
// * Nicer overdrive characteristics than the DAC hacks (it simply clips samples within range)
// * 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.
+ // * Half the volume of any of the other modes.
// * Output gain is ignored for both LA32 and reverb output.
// * Perfect for developers while debugging :)
DACInputMode_PURE,
@@ -60,6 +75,7 @@ enum DACInputMode {
DACInputMode_GENERATION2
};
+// Methods for emulating the effective delay of incoming MIDI messages introduced by a MIDI interface.
enum MIDIDelayMode {
// Process incoming MIDI events immediately.
MIDIDelayMode_IMMEDIATE,
@@ -72,6 +88,35 @@ enum MIDIDelayMode {
MIDIDelayMode_DELAY_ALL
};
+// Methods for emulating the effects of analogue circuits of real hardware units on the output signal.
+enum AnalogOutputMode {
+ // Only digital path is emulated. The output samples correspond to the digital signal at the DAC entrance.
+ AnalogOutputMode_DIGITAL_ONLY,
+ // Coarse emulation of LPF circuit. High frequencies are boosted, sample rate remains unchanged.
+ AnalogOutputMode_COARSE,
+ // Finer emulation of LPF circuit. Output signal is upsampled to 48 kHz to allow emulation of audible mirror spectra above 16 kHz,
+ // which is passed through the LPF circuit without significant attenuation.
+ AnalogOutputMode_ACCURATE,
+ // Same as AnalogOutputMode_ACCURATE mode but the output signal is 2x oversampled, i.e. the output sample rate is 96 kHz.
+ // This makes subsequent resampling easier. Besides, due to nonlinear passband of the LPF emulated, it takes fewer number of MACs
+ // compared to a regular LPF FIR implementations.
+ AnalogOutputMode_OVERSAMPLED
+};
+
+enum ReverbMode {
+ REVERB_MODE_ROOM,
+ REVERB_MODE_HALL,
+ REVERB_MODE_PLATE,
+ REVERB_MODE_TAP_DELAY
+};
+
+enum PartialState {
+ PartialState_INACTIVE,
+ PartialState_ATTACK,
+ PartialState_SUSTAIN,
+ PartialState_RELEASE
+};
+
const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41;
const Bit8u SYSEX_MDL_MT32 = 0x16;
@@ -87,148 +132,10 @@ const Bit8u SYSEX_CMD_EOD = 0x45; // End of data
const Bit8u SYSEX_CMD_ERR = 0x4E; // Communications error
const Bit8u SYSEX_CMD_RJC = 0x4F; // Rejection
-const int MAX_SYSEX_SIZE = 512;
+const int MAX_SYSEX_SIZE = 512; // FIXME: Does this correspond to a real MIDI buffer used in h/w devices?
const unsigned int CONTROL_ROM_SIZE = 64 * 1024;
-struct ControlROMPCMStruct {
- Bit8u pos;
- Bit8u len;
- Bit8u pitchLSB;
- Bit8u pitchMSB;
-};
-
-struct ControlROMMap {
- Bit16u idPos;
- Bit16u idLen;
- const char *idBytes;
- Bit16u pcmTable; // 4 * pcmCount bytes
- Bit16u pcmCount;
- Bit16u timbreAMap; // 128 bytes
- Bit16u timbreAOffset;
- bool timbreACompressed;
- Bit16u timbreBMap; // 128 bytes
- Bit16u timbreBOffset;
- bool timbreBCompressed;
- Bit16u timbreRMap; // 2 * timbreRCount bytes
- Bit16u timbreRCount;
- Bit16u rhythmSettings; // 4 * rhythmSettingsCount bytes
- Bit16u rhythmSettingsCount;
- Bit16u reserveSettings; // 9 bytes
- Bit16u panSettings; // 8 bytes
- Bit16u programSettings; // 8 bytes
- Bit16u rhythmMaxTable; // 4 bytes
- Bit16u patchMaxTable; // 16 bytes
- Bit16u systemMaxTable; // 23 bytes
- Bit16u timbreMaxTable; // 72 bytes
-};
-
-enum MemoryRegionType {
- MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset
-};
-
-enum ReverbMode {
- REVERB_MODE_ROOM,
- REVERB_MODE_HALL,
- REVERB_MODE_PLATE,
- REVERB_MODE_TAP_DELAY
-};
-
-class MemoryRegion {
-private:
- Synth *synth;
- Bit8u *realMemory;
- Bit8u *maxTable;
-public:
- MemoryRegionType type;
- Bit32u startAddr, entrySize, entries;
-
- MemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable, MemoryRegionType useType, Bit32u useStartAddr, Bit32u useEntrySize, Bit32u useEntries) {
- synth = useSynth;
- realMemory = useRealMemory;
- maxTable = useMaxTable;
- type = useType;
- startAddr = useStartAddr;
- entrySize = useEntrySize;
- entries = useEntries;
- }
- int lastTouched(Bit32u addr, Bit32u len) const {
- return (offset(addr) + len - 1) / entrySize;
- }
- int firstTouchedOffset(Bit32u addr) const {
- return offset(addr) % entrySize;
- }
- int firstTouched(Bit32u addr) const {
- return offset(addr) / entrySize;
- }
- Bit32u regionEnd() const {
- return startAddr + entrySize * entries;
- }
- bool contains(Bit32u addr) const {
- return addr >= startAddr && addr < regionEnd();
- }
- int offset(Bit32u addr) const {
- return addr - startAddr;
- }
- Bit32u getClampedLen(Bit32u addr, Bit32u len) const {
- if (addr + len > regionEnd())
- return regionEnd() - addr;
- return len;
- }
- Bit32u next(Bit32u addr, Bit32u len) const {
- if (addr + len > regionEnd()) {
- return regionEnd() - addr;
- }
- return 0;
- }
- Bit8u getMaxValue(int off) const {
- if (maxTable == NULL)
- return 0xFF;
- return maxTable[off % entrySize];
- }
- Bit8u *getRealMemory() const {
- return realMemory;
- }
- bool isReadable() const {
- return getRealMemory() != NULL;
- }
- void read(unsigned int entry, unsigned int off, Bit8u *dst, unsigned int len) const;
- void write(unsigned int entry, unsigned int off, const Bit8u *src, unsigned int len, bool init = false) const;
-};
-
-class PatchTempMemoryRegion : public MemoryRegion {
-public:
- PatchTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_PatchTemp, MT32EMU_MEMADDR(0x030000), sizeof(MemParams::PatchTemp), 9) {}
-};
-class RhythmTempMemoryRegion : public MemoryRegion {
-public:
- RhythmTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_RhythmTemp, MT32EMU_MEMADDR(0x030110), sizeof(MemParams::RhythmTemp), 85) {}
-};
-class TimbreTempMemoryRegion : public MemoryRegion {
-public:
- TimbreTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_TimbreTemp, MT32EMU_MEMADDR(0x040000), sizeof(TimbreParam), 8) {}
-};
-class PatchesMemoryRegion : public MemoryRegion {
-public:
- PatchesMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Patches, MT32EMU_MEMADDR(0x050000), sizeof(PatchParam), 128) {}
-};
-class TimbresMemoryRegion : public MemoryRegion {
-public:
- TimbresMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Timbres, MT32EMU_MEMADDR(0x080000), sizeof(MemParams::PaddedTimbre), 64 + 64 + 64 + 64) {}
-};
-class SystemMemoryRegion : public MemoryRegion {
-public:
- SystemMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_System, MT32EMU_MEMADDR(0x100000), sizeof(MemParams::System), 1) {}
-};
-class DisplayMemoryRegion : public MemoryRegion {
-public:
- DisplayMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Display, MT32EMU_MEMADDR(0x200000), MAX_SYSEX_SIZE - 1, 1) {}
-};
-class ResetMemoryRegion : public MemoryRegion {
-public:
- ResetMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1) {}
-};
-
class ReportHandler {
friend class Synth;
@@ -254,47 +161,6 @@ protected:
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 {
friend class Part;
friend class RhythmPart;
@@ -335,7 +201,7 @@ private:
volatile Bit32u lastReceivedMIDIEventTimestamp;
volatile Bit32u renderedSampleCount;
- MemParams mt32ram, mt32default;
+ MemParams &mt32ram, &mt32default;
BReverbModel *reverbModels[4];
BReverbModel *reverbModel;
@@ -346,12 +212,6 @@ private:
float outputGain;
float reverbOutputGain;
-#if MT32EMU_USE_FLOAT_SAMPLES
- float effectiveReverbOutputGain;
-#else
- int effectiveOutputGain;
- int effectiveReverbOutputGain;
-#endif
bool reversedStereoEnabled;
@@ -368,11 +228,12 @@ private:
// We emulate this by delaying new MIDI events processing until abortion finishes.
Poly *abortingPoly;
- Bit32u getShortMessageLength(Bit32u msg);
+ Analog *analog;
+
Bit32u addMIDIInterfaceDelay(Bit32u len, Bit32u timestamp);
void produceLA32Output(Sample *buffer, Bit32u len);
- void convertSamplesToOutput(Sample *buffer, Bit32u len, bool reverb);
+ void convertSamplesToOutput(Sample *buffer, Bit32u len);
bool isAbortingPoly() const;
void doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample *reverbDryLeft, Sample *reverbDryRight, Sample *reverbWetLeft, Sample *reverbWetRight, Bit32u len);
@@ -404,13 +265,20 @@ private:
void newTimbreSet(int partNum, Bit8u timbreGroup, const char patchName[]);
void printDebug(const char *fmt, ...);
+ // partNum should be 0..7 for Part 1..8, or 8 for Rhythm
+ const Part *getPart(unsigned int partNum) const;
+
public:
- static inline Bit16s clipBit16s(Bit32s sample) {
+ static inline Sample clipSampleEx(SampleEx sampleEx) {
+#if MT32EMU_USE_FLOAT_SAMPLES
+ return sampleEx;
+#else
// Clamp values above 32767 to 32767, and values below -32768 to -32768
// FIXME: Do we really need this stuff? I think these branches are very well predicted. Instead, this introduces a chain.
// The version below is actually a bit faster on my system...
- //return ((sample + 0x8000) & ~0xFFFF) ? (sample >> 31) ^ 0x7FFF : (Bit16s)sample;
- return ((-0x8000 <= sample) && (sample <= 0x7FFF)) ? (Bit16s)sample : (sample >> 31) ^ 0x7FFF;
+ //return ((sampleEx + 0x8000) & ~0xFFFF) ? (sampleEx >> 31) ^ 0x7FFF : (Sample)sampleEx;
+ return ((-0x8000 <= sampleEx) && (sampleEx <= 0x7FFF)) ? (Sample)sampleEx : (sampleEx >> 31) ^ 0x7FFF;
+#endif
}
static inline void muteSampleBuffer(Sample *buffer, Bit32u len) {
@@ -426,7 +294,8 @@ public:
#endif
}
- static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum);
+ static Bit32u getShortMessageLength(Bit32u msg);
+ static Bit8u calcSysexChecksum(const Bit8u *data, const Bit32u len, const Bit8u initChecksum = 0);
// Optionally sets callbacks for reporting various errors, information and debug messages
Synth(ReportHandler *useReportHandler = NULL);
@@ -435,8 +304,12 @@ 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.
- // 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);
+ // usePartialCount sets the maximum number of partials playing simultaneously for this session (optional).
+ // analogOutputMode sets the mode for emulation of analogue circuitry of the hardware units (optional).
+ bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, unsigned int usePartialCount = DEFAULT_MAX_PARTIALS, AnalogOutputMode analogOutputMode = AnalogOutputMode_COARSE);
+
+ // Overloaded method which opens the synth with default partial count.
+ bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, AnalogOutputMode analogOutputMode);
// Closes the MT-32 and deallocates any memory used by the synthesizer
void close(bool forced = false);
@@ -444,29 +317,34 @@ public:
// All the enqueued events are processed by the synth immediately.
void flushMIDIQueue();
- // Sets size of the internal MIDI event queue.
+ // Sets size of the internal MIDI event queue. The queue size is set to the minimum power of 2 that is greater or equal to the size specified.
// The queue is flushed before reallocation.
- void setMIDIEventQueueSize(Bit32u);
+ // Returns the actual queue size being used.
+ Bit32u setMIDIEventQueueSize(Bit32u);
// Enqueues a MIDI event for subsequent playback.
- // The minimum delay involves the delay introduced while the event is transferred via MIDI interface
+ // 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 (at the native sample rate 32000 Hz).
+ // The minimum delay involves emulation of 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.
+ // Calls from multiple threads must be synchronised, although, no synchronisation is required with the rendering thread.
+ // The methods return false if the MIDI event queue is full and the message cannot be enqueued.
- // 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.
+ // Enqueues a single short MIDI message. The message must contain a status byte.
bool playMsg(Bit32u msg, Bit32u timestamp);
+ // Enqueues a single well formed System Exclusive MIDI message.
bool playSysex(const Bit8u *sysex, Bit32u len, Bit32u timestamp);
- // The MIDI event will be processed ASAP.
+
+ // Overloaded methods for the MIDI events to 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.
+ // A thread that invokes these methods must be explicitly synchronised with the thread performing sample rendering.
- // Sends a 4-byte MIDI message to the MT-32 for immediate playback.
+ // Sends a short MIDI message to the synth for immediate playback. The message must contain a status byte.
void playMsgNow(Bit32u msg);
void playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity);
@@ -495,12 +373,17 @@ public:
void setMIDIDelayMode(MIDIDelayMode mode);
MIDIDelayMode getMIDIDelayMode() const;
- // Sets output gain factor. Applied to all output samples and unrelated with the synth's Master volume.
+ // Sets output gain factor for synth output channels. Applied to all output samples and unrelated with the synth's Master volume,
+ // it rather corresponds to the gain of the output analog circuitry of the hardware units. However, together with setReverbOutputGain()
+ // it offers to the user a capability to control the gain of reverb and non-reverb output channels independently.
// 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.
+ // Sets output gain factor for the reverb wet output channels. It rather corresponds to the gain of the output
+ // analog circuitry of the hardware units. However, together with setOutputGain() it offers to the user a capability
+ // to control the gain of reverb and non-reverb output channels independently.
+ //
// 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
@@ -512,12 +395,21 @@ public:
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).
+ // Returns actual sample rate used in emulation of stereo analog circuitry of hardware units.
+ // See comment for render() below.
+ unsigned int getStereoOutputSampleRate() const;
+
+ // Renders samples to the specified output stream as if they were sampled at the analog stereo output.
+ // When AnalogOutputMode is set to ACCURATE, the output signal is upsampled to 48 kHz in order
+ // to retain emulation accuracy in whole audible frequency spectra. Otherwise, native digital signal sample rate is retained.
+ // getStereoOutputSampleRate() can be used to query actual sample rate of the output signal.
+ // The length is in frames, not bytes (in 16-bit stereo, one frame is 4 bytes).
void render(Sample *stream, Bit32u len);
- // Renders samples to the specified output streams (any or all of which may be NULL).
+ // Renders samples to the specified output streams as if they appeared at the DAC entrance.
+ // No further processing performed in analog circuitry emulation is applied to the signal.
+ // NULL may be specified in place of any or all of the stream buffers.
+ // The length is in samples, not bytes.
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.
@@ -526,15 +418,28 @@ public:
// Returns true if hasActivePartials() returns true, or reverb is (somewhat unreliably) detected as being active.
bool isActive() const;
- 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);
+ // Fills in current states of all the parts into the array provided. The array must have at least 9 entries to fit values for all the parts.
+ // If the value returned for a part is true, there is at least one active non-releasing partial playing on this part.
+ // This info is useful in emulating behaviour of LCD display of the hardware units.
+ void getPartStates(bool *partStates) const;
- // partNum should be 0..7 for Part 1..8, or 8 for Rhythm
- const Part *getPart(unsigned int partNum) const;
+ // Fills in current states of all the partials into the array provided. The array must be large enough to accommodate states of all the partials.
+ void getPartialStates(PartialState *partialStates) const;
+
+ // Fills in information about currently playing notes on the specified part into the arrays provided. The arrays must be large enough
+ // to accommodate data for all the playing notes. The maximum number of simultaneously playing notes cannot exceed the number of partials.
+ // Argument partNumber should be 0..7 for Part 1..8, or 8 for Rhythm.
+ // Returns the number of currently playing notes on the specified part.
+ unsigned int getPlayingNotes(unsigned int partNumber, Bit8u *keys, Bit8u *velocities) const;
+
+ // Returns name of the patch set on the specified part.
+ // Argument partNumber should be 0..7 for Part 1..8, or 8 for Rhythm.
+ const char *getPatchName(unsigned int partNumber) const;
+
+ void readMemory(Bit32u addr, Bit32u len, Bit8u *data);
};
}