/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher * Copyright (C) 2011 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 . */ #ifndef MT32EMU_SYNTH_H #define MT32EMU_SYNTH_H //#include namespace MT32Emu { class TableInitialiser; class Partial; class PartialManager; class Part; /** * Methods for emulating the connection between the LA32 and the DAC, which involves * some hacks in the real devices for doubling the volume. * See also http://en.wikipedia.org/wiki/Roland_MT-32#Digital_overflow */ enum DACInputMode { // Produces samples at double the volume, without tricks. // * Nicer overdrive characteristics than the DAC hacks (it simply clips samples within range) // * Higher quality than the real devices DACInputMode_NICE, // 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. // * Perfect for developers while debugging :) DACInputMode_PURE, // Re-orders the LA32 output bits as in early generation MT-32s (according to Wikipedia). // Bit order at DAC (where each number represents the original LA32 output bit number, and XX means the bit is always low): // 15 13 12 11 10 09 08 07 06 05 04 03 02 01 00 XX DACInputMode_GENERATION1, // Re-orders the LA32 output bits as in later generations (personally confirmed on my CM-32L - KG). // Bit order at DAC (where each number represents the original LA32 output bit number): // 15 13 12 11 10 09 08 07 06 05 04 03 02 01 00 14 DACInputMode_GENERATION2 }; enum ReportType { // Errors ReportType_errorControlROM = 1, ReportType_errorPCMROM, ReportType_errorSampleRate, // Progress ReportType_progressInit, // HW spec ReportType_availableSSE, ReportType_available3DNow, ReportType_usingSSE, ReportType_using3DNow, // General info ReportType_lcdMessage, ReportType_devReset, ReportType_devReconfig, ReportType_newReverbMode, ReportType_newReverbTime, ReportType_newReverbLevel }; enum LoadResult { LoadResult_OK, LoadResult_NotFound, LoadResult_Unreadable, LoadResult_Invalid }; struct SynthProperties { // Sample rate to use in mixing unsigned int sampleRate; // Deprecated - ignored. Use Synth::setReverbEnabled() instead. bool useReverb; // Deprecated - ignored. Use Synth::setReverbOverridden() instead. bool useDefaultReverb; // Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead. unsigned char reverbType; // Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead. unsigned char reverbTime; // Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead. unsigned char reverbLevel; // The name of the directory in which the ROM and data files are stored (with trailing slash/backslash) // Not used if "openFile" is set. May be NULL in any case. const char *baseDir; // This is used as the first argument to all callbacks void *userData; // Callback for reporting various errors and information. May be NULL int (*report)(void *userData, ReportType type, const void *reportData); // Callback for debug messages, in vprintf() format void (*printDebug)(void *userData, const char *fmt, va_list list); // Callback for providing an implementation of File, opened and ready for use // May be NULL, in which case a default implementation will be used. Common::File *(*openFile)(void *userData, const char *filename); // Callback for closing a File. May be NULL, in which case the File will automatically be close()d/deleted. void (*closeFile)(void *userData, Common::File *file); }; // This is the specification of the Callback routine used when calling the RecalcWaveforms // function typedef void (*recalcStatusCallback)(int percDone); typedef void (*FloatToBit16sFunc)(Bit16s *target, const float *source, Bit32u len, float outputGain); const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41; const Bit8u SYSEX_MDL_MT32 = 0x16; const Bit8u SYSEX_MDL_D50 = 0x14; const Bit8u SYSEX_CMD_RQ1 = 0x11; // Request data #1 const Bit8u SYSEX_CMD_DT1 = 0x12; // Data set 1 const Bit8u SYSEX_CMD_WSD = 0x40; // Want to send data const Bit8u SYSEX_CMD_RQD = 0x41; // Request data const Bit8u SYSEX_CMD_DAT = 0x42; // Data set const Bit8u SYSEX_CMD_ACK = 0x43; // Acknowledge 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 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 }; 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 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(unsigned int sampleRate) = 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 Synth { friend class Part; friend class RhythmPart; friend class Poly; friend class Partial; friend class Tables; friend class MemoryRegion; friend class TVA; friend class TVF; friend class TVP; private: PatchTempMemoryRegion *patchTempMemoryRegion; RhythmTempMemoryRegion *rhythmTempMemoryRegion; TimbreTempMemoryRegion *timbreTempMemoryRegion; PatchesMemoryRegion *patchesMemoryRegion; TimbresMemoryRegion *timbresMemoryRegion; SystemMemoryRegion *systemMemoryRegion; DisplayMemoryRegion *displayMemoryRegion; ResetMemoryRegion *resetMemoryRegion; Bit8u *paddedTimbreMaxTable; bool isEnabled; PCMWaveEntry *pcmWaves; // Array const ControlROMMap *controlROMMap; Bit8u controlROMData[CONTROL_ROM_SIZE]; float *pcmROMData; int pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM Bit8s chantable[32]; Bit32u renderedSampleCount; Tables tables; MemParams mt32ram, mt32default; ReverbModel *reverbModels[4]; ReverbModel *reverbModel; bool reverbEnabled; bool reverbOverridden; FloatToBit16sFunc la32FloatToBit16sFunc; FloatToBit16sFunc reverbFloatToBit16sFunc; float outputGain; float reverbOutputGain; bool isOpen; 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, // 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; SynthProperties myProp; 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); void readSysex(unsigned char channel, const Bit8u *sysex, Bit32u len) const; void initMemoryRegions(); void deleteMemoryRegions(); MemoryRegion *findMemoryRegion(Bit32u addr); void writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data); void readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data); LoadResult loadControlROM(const char *filename); LoadResult loadPCMROM(const char *filename); bool initPCMList(Bit16u mapAddress, Bit16u count); bool initTimbres(Bit16u mapAddress, Bit16u offset, int timbreCount, int startTimbre, bool compressed); bool initCompressedTimbre(int drumNum, const Bit8u *mem, unsigned int memLen); void refreshSystemMasterTune(); void refreshSystemReverbParameters(); void refreshSystemReserveSettings(); void refreshSystemChanAssign(unsigned int firstPart, unsigned int lastPart); void refreshSystemMasterVol(); void refreshSystem(); void reset(); unsigned int getSampleRate() const; void printPartialUsage(unsigned long sampleOffset = 0); protected: int report(ReportType type, const void *reportData); Common::File *openFile(const char *filename); void closeFile(Common::File *file); void printDebug(const char *fmt, ...); public: static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum); Synth(); ~Synth(); // Used to initialise the MT-32. Must be called before any other function. // Returns true if initialization was sucessful, otherwise returns false. bool open(SynthProperties &useProp); // 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); 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 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); void setReverbEnabled(bool reverbEnabled); bool isReverbEnabled() const; void setReverbOverridden(bool reverbOverridden); bool isReverbOverridden() const; void setDACInputMode(DACInputMode mode); // Sets output gain factor. Applied to all output samples and unrelated with the synth's Master volume. void setOutputGain(float); // Sets output gain factor for the reverb wet output. setOutputGain() doesn't change reverb output gain. void setReverbOutputGain(float); // 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); // 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); // Returns true when there is at least one active partial, otherwise false. bool hasActivePartials() const; // 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; void readMemory(Bit32u addr, Bit32u len, Bit8u *data); // partNum should be 0..7 for Part 1..8, or 8 for Rhythm const Part *getPart(unsigned int partNum) const; }; } #endif