diff options
author | Eugene Sandulenko | 2004-11-06 01:41:32 +0000 |
---|---|---|
committer | Eugene Sandulenko | 2004-11-06 01:41:32 +0000 |
commit | 805b21181ab7138da6960ade703b25716120fc29 (patch) | |
tree | 8a8b04662d7e25f0b6d3675452cd50fc589b5ee6 /backends/midi | |
parent | ab7c30e4ed59004f311fd068746d1537c9da5f50 (diff) | |
download | scummvm-rg350-805b21181ab7138da6960ade703b25716120fc29.tar.gz scummvm-rg350-805b21181ab7138da6960ade703b25716120fc29.tar.bz2 scummvm-rg350-805b21181ab7138da6960ade703b25716120fc29.zip |
Major MT-32 emu overhaul based on KingGuppy's code.
o added configure option
o mi2 intro doesn't freeze anymore and has no sound glitches
o missing instruments in many titles are fixed
o numerous memory overwrite bugs are fixed
o code is cleaned a lot and splitted into many smaller files
o mt32.cpp went to backends/midi
o synced with upstream code
o reverberation fixed
* don't complain about File class wrapper :)
* all custom types are back
* #pragmas are to do
* maybe some indentation is wrong too
I prefer smaller commits, but this thing came in one piece.
svn-id: r15715
Diffstat (limited to 'backends/midi')
-rw-r--r-- | backends/midi/mt32.cpp | 243 | ||||
-rw-r--r-- | backends/midi/mt32/file.cpp | 66 | ||||
-rw-r--r-- | backends/midi/mt32/file.h | 62 | ||||
-rw-r--r-- | backends/midi/mt32/i386.cpp | 817 | ||||
-rw-r--r-- | backends/midi/mt32/i386.h | 49 | ||||
-rw-r--r-- | backends/midi/mt32/module.mk | 18 | ||||
-rw-r--r-- | backends/midi/mt32/mt32.cpp | 156 | ||||
-rw-r--r-- | backends/midi/mt32/mt32emu.h | 36 | ||||
-rw-r--r-- | backends/midi/mt32/part.cpp | 595 | ||||
-rw-r--r-- | backends/midi/mt32/part.h | 90 | ||||
-rw-r--r-- | backends/midi/mt32/partial.cpp | 1231 | ||||
-rw-r--r-- | backends/midi/mt32/partial.h | 183 | ||||
-rw-r--r-- | backends/midi/mt32/partialManager.cpp | 269 | ||||
-rw-r--r-- | backends/midi/mt32/partialManager.h | 56 | ||||
-rw-r--r-- | backends/midi/mt32/structures.h | 737 | ||||
-rw-r--r-- | backends/midi/mt32/synth.cpp | 5053 | ||||
-rw-r--r-- | backends/midi/mt32/synth.h | 252 | ||||
-rw-r--r-- | backends/midi/mt32/tables.cpp | 727 | ||||
-rw-r--r-- | backends/midi/mt32/tables.h | 97 |
19 files changed, 5203 insertions, 5534 deletions
diff --git a/backends/midi/mt32.cpp b/backends/midi/mt32.cpp new file mode 100644 index 0000000000..2931e94c71 --- /dev/null +++ b/backends/midi/mt32.cpp @@ -0,0 +1,243 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2001-2004 The ScummVM project + * + * YM2612 tone generation code written by Tomoaki Hayasaka. + * Used under the terms of the GNU General Public License. + * Adpated to ScummVM by Jamieson Christian. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Header$ + */ + +#include "stdafx.h" +#include "common/scummsys.h" + +#ifdef USE_MT32EMU + +#include "backends/midi/mt32/mt32emu.h" + +#include "backends/midi/emumidi.h" +#include "sound/mpu401.h" + +#include "common/util.h" +#include "common/file.h" +#include "common/config-manager.h" + +class MidiDriver_MT32 : public MidiDriver_Emulated { +private: + MidiChannel_MPU401 _midi_channels[16]; + uint16 _channel_mask; + MT32Emu::Synth *_synth; + + int _outputRate; + +protected: + void generate_samples(int16 *buf, int len); + +public: + MidiDriver_MT32(SoundMixer *mixer); + virtual ~MidiDriver_MT32(); + + int open(); + void close(); + void send(uint32 b); + void sysEx(byte *msg, uint16 length); + + uint32 property(int prop, uint32 param); + MidiChannel *allocateChannel(); + MidiChannel *getPercussionChannel(); + + // AudioStream API + bool isStereo() const { return true; } + int getRate() const { return _outputRate; } +}; + +typedef File SFile; + +class MT32File: public MT32Emu::File { + SFile file; +public: + bool open(const char *filename, OpenMode mode) { + SFile::AccessMode accessMode = mode == OpenMode_read ? SFile::kFileReadMode : SFile::kFileWriteMode; + return file.open(filename, accessMode); + } + void close() { + return file.close(); + } + size_t read(void *ptr, size_t size) { + return file.read(ptr, size); + } + bool readLine(char *ptr, size_t size) { + return file.gets(ptr, size) != NULL; + } + size_t write(const void *ptr, size_t size) { + return file.write(ptr, size); + } + int readByte() { + byte b = file.readByte(); + if (file.eof()) + return -1; + return b; + } + bool writeByte(unsigned char out) { + file.writeByte(out); + if (file.ioFailed()) + return false; + return true; + } + bool isEOF() { + return file.eof(); + } +}; + +MT32Emu::File *MT32_OpenFile(void *userData, const char *filename, MT32Emu::File::OpenMode mode) { + MT32File *file = new MT32File(); + if (!file->open(filename, mode)) { + delete file; + return NULL; + } + return file; +} + +//////////////////////////////////////// +// +// MidiDriver_MT32 +// +//////////////////////////////////////// + +void report(int type, ...) {} + +MidiDriver_MT32::MidiDriver_MT32(SoundMixer *mixer) : MidiDriver_Emulated(mixer) { + _channel_mask = 0xFFFF; // Permit all 16 channels by default + uint i; + for (i = 0; i < ARRAYSIZE(_midi_channels); ++i) { + _midi_channels [i].init (this, i); + } + _synth = NULL; + + _baseFreq = 1000; + + _outputRate = 44100; +} + +MidiDriver_MT32::~MidiDriver_MT32() { + if (_synth != NULL) + delete _synth; +} + +static void vdebug(void *data, const char *fmt, va_list list) { + // do nothing here now +} + +int MidiDriver_MT32::open() { + MT32Emu::SynthProperties prop; + + if (_isOpen) + return MERR_ALREADY_OPEN; + + MidiDriver_Emulated::open(); + + memset(&prop, 0, sizeof(prop)); + prop.SampleRate = getRate(); + prop.UseReverb = true; + prop.UseDefault = false; + prop.RevType = 0; + prop.RevTime = 5; + prop.RevLevel = 3; + prop.userData = (void *)1; + prop.printDebug = &vdebug; + prop.openFile = MT32_OpenFile; + _synth = new MT32Emu::Synth(); + if (!_synth->open(prop)) + return MERR_DEVICE_NOT_AVAILABLE; + + _mixer->setupPremix(this); + + return 0; +} + +void MidiDriver_MT32::send(uint32 b) { + _synth->playMsg(b); +} + +void MidiDriver_MT32::sysEx(byte *msg, uint16 length) { + if (msg[0] == 0xf0) { + _synth->playSysex(msg, length); + } else { + _synth->playSysexWithoutFraming(msg, length); + } +} + +void MidiDriver_MT32::close() { + if (!_isOpen) + return; + _isOpen = false; + + // Detach the premix callback handler + _mixer->setupPremix(0); + + _synth->close(); + delete _synth; + _synth = NULL; +} + +void MidiDriver_MT32::generate_samples(int16 *data, int len) { + _synth->render(data, len); +} + +uint32 MidiDriver_MT32::property (int prop, uint32 param) { + switch (prop) { + case PROP_CHANNEL_MASK: + _channel_mask = param & 0xFFFF; + return 1; + } + + return 0; +} + +MidiChannel *MidiDriver_MT32::allocateChannel() { + MidiChannel_MPU401 *chan; + uint i; + + for (i = 0; i < ARRAYSIZE(_midi_channels); ++i) { + if (i == 9 || !(_channel_mask & (1 << i))) + continue; + chan = &_midi_channels[i]; + if (chan->allocate()) { + return chan; + } + } + return NULL; +} + +MidiChannel *MidiDriver_MT32::getPercussionChannel() { + return &_midi_channels [9]; +} + +//////////////////////////////////////// +// +// MidiDriver_MT32 factory +// +//////////////////////////////////////// + +MidiDriver *MidiDriver_MT32_create(SoundMixer *mixer) { + // HACK: It will stay here until engine plugin loader overhaul + if (ConfMan.hasKey("extrapath")) + File::addDefaultDirectory(ConfMan.get("extrapath")); + return new MidiDriver_MT32(mixer); +} + +#endif diff --git a/backends/midi/mt32/file.cpp b/backends/midi/mt32/file.cpp new file mode 100644 index 0000000000..8660e8314c --- /dev/null +++ b/backends/midi/mt32/file.cpp @@ -0,0 +1,66 @@ +/* Copyright (c) 2003-2004 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <stdio.h> + +#include "file.h" + +namespace MT32Emu { + + bool ANSIFile::open(const char *filename, OpenMode mode) { + const char *fmode; + if (mode == OpenMode_read) { + fmode = "rb"; + } else { + fmode = "wb"; + } + fp = fopen(filename, fmode); + return (fp != NULL); + } + + void ANSIFile::close() { + fclose(fp); + } + + int ANSIFile::readByte() { + return fgetc(fp); + } + + size_t ANSIFile::read(void *ptr, size_t size) { + return fread(ptr, 1, size, fp); + } + + bool ANSIFile::readLine(char *ptr, size_t size) { + return fgets(ptr, (int)size, fp) != NULL; + } + + bool ANSIFile::writeByte(unsigned char out) { + return fputc(out, fp) != EOF; + } + + size_t ANSIFile::write(const void *ptr, size_t size) { + return fwrite(ptr, 1, size, fp); + } + + bool ANSIFile::isEOF() { + return feof(fp) != 0; + } +} diff --git a/backends/midi/mt32/file.h b/backends/midi/mt32/file.h new file mode 100644 index 0000000000..6c02712596 --- /dev/null +++ b/backends/midi/mt32/file.h @@ -0,0 +1,62 @@ +/* Copyright (c) 2003-2004 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_FILE_H +#define MT32EMU_FILE_H + +#include <stdio.h> + +namespace MT32Emu { + +class File { +public: + enum OpenMode { + OpenMode_read = 0, + OpenMode_write = 1 + }; + virtual ~File() {} + virtual void close() = 0; + virtual size_t read(void *ptr, size_t size) = 0; + virtual bool readLine(char *ptr, size_t size) = 0; + virtual size_t write(const void *ptr, size_t size) = 0; + // Returns -1 in case of EOF or error + virtual int readByte() = 0; + virtual bool writeByte(unsigned char out) = 0; + virtual bool isEOF() = 0; +}; + +class ANSIFile: public File { +private: + FILE *fp; +public: + bool open(const char *filename, OpenMode mode); + void close(); + size_t read(void *ptr, size_t size); + bool readLine(char *ptr, size_t size); + size_t write(const void *, size_t size); + int readByte(); + bool writeByte(unsigned char out); + bool isEOF(); +}; + +} + +#endif diff --git a/backends/midi/mt32/i386.cpp b/backends/midi/mt32/i386.cpp new file mode 100644 index 0000000000..9bc5bcb4c5 --- /dev/null +++ b/backends/midi/mt32/i386.cpp @@ -0,0 +1,817 @@ +/* Copyright (c) 2003-2004 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "mt32emu.h" + +#ifdef HAVE_X86 + +namespace MT32Emu { + +#ifndef _MSC_VER + +#define eflag(value) __asm__ __volatile__("pushfl \n popfl \n" : : "a"(value)) +#define cpuid_flag (1 << 21) + +static inline bool atti386_DetectCPUID() { + unsigned int result; + + // Is there a cpuid? + result = cpuid_flag; // set test + eflag(result); + if (!(result & cpuid_flag)) + return false; + + result = 0; // clear test + eflag(result); + if (result & cpuid_flag) + return false; + + return true; +} + +static inline bool atti386_DetectSIMD() { + unsigned int result; + + if (atti386_DetectCPUID() == false) + return false; + + /* check cpuid */ + __asm__ __volatile__( + "movl $1, %%eax \n" \ + "cpuid \n" \ + "movl %%edx, %0 \n" \ + : "=r"(result) : : "eax", "ebx", "ecx", "edx"); + + if (result & (1 << 25)) + return true; + + return false; +} + +static inline bool atti386_Detect3DNow() { + unsigned int result; + + if (atti386_DetectCPUID() == false) + return false; + + // get cpuid + __asm__ __volatile__( + "movl $0x80000001, %%eax \n" \ + "cpuid \n" \ + "movl %%edx, %0 \n" \ + : "=r"(result) : : "eax", "ebx", "ecx", "edx"); + + if (result & 0x80000000) + return true; + + return false; +} + + +static inline float atti386_iir_filter_sse(float *output, float *hist1_ptr, float *coef_ptr) { + __asm__ __volatile__ ( + "pushl %1 \n" \ + "pushl %2 \n" \ + "movss 0(%0), %%xmm1 \n" \ + "movups 0(%1), %%xmm2 \n" \ + "movlps 0(%2), %%xmm3 \n" \ + " \n" \ + "shufps $0x44, %%xmm3, %%xmm3 \n" \ + " \n" \ + "mulps %%xmm3, %%xmm2 \n" \ + " \n" \ + "subss %%xmm2, %%xmm1 \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "subss %%xmm2, %%xmm1 \n" \ + " \n" \ + "movss %%xmm1, 0(%2) \n" \ + " \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "addss %%xmm2, %%xmm1 \n" \ + " \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "addss %%xmm2, %%xmm1 \n" \ + " \n" \ + "movss %%xmm3, 4(%2) \n" \ + " \n" \ + "addl $16, %1 \n" \ + "addl $8, %2 \n" \ + " \n" \ + "movups 0(%1), %%xmm2 \n" \ + " \n" \ + "movlps 0(%2), %%xmm3 \n" \ + "shufps $0x44, %%xmm3, %%xmm3 \n" \ + " \n" \ + "mulps %%xmm3, %%xmm2 \n" \ + " \n" \ + "subss %%xmm2, %%xmm1 \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "subss %%xmm2, %%xmm1 \n" \ + " \n" \ + "movss %%xmm1, 0(%2) \n" \ + " \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "addss %%xmm2, %%xmm1 \n" \ + " \n" \ + "shufps $0x39, %%xmm2, %%xmm2 \n" \ + "addss %%xmm2, %%xmm1 \n" \ + " \n" \ + "movss %%xmm3, 4(%2) \n" \ + "movss %%xmm1, 0(%0) \n" \ + "popl %2 \n" \ + "popl %1 \n" \ + : : "r"(output), "r"(coef_ptr), "r"(hist1_ptr) + : "xmm1", "xmm2", "xmm3", "memory"); + + return *output; +} + +static inline float atti386_iir_filter_3DNow(float output, float *hist1_ptr, float *coef_ptr) { + float tmp; + + __asm__ __volatile__ ( + "movq %0, %%mm1 \n" \ + " \n" \ + "movl %1, %%ebx \n" \ + "movq 0(%%ebx), %%mm2 \n" \ + " \n" \ + "movl %2, %%eax; \n" \ + "movq 0(%%eax), %%mm3 \n" \ + " \n" \ + "pfmul %%mm3, %%mm2 \n" \ + "pfsub %%mm2, %%mm1 \n" \ + " \n" \ + "psrlq $32, %%mm2 \n" \ + "pfsub %%mm2, %%mm1 \n" \ + " \n" \ + "movd %%mm1, %3 \n" \ + " \n" \ + "addl $8, %%ebx \n" \ + "movq 0(%%ebx), %%mm2 \n" \ + "movq 0(%%eax), %%mm3 \n" \ + " \n" \ + "pfmul %%mm3, %%mm2 \n" \ + "pfadd %%mm2, %%mm1 \n" \ + " \n" \ + "psrlq $32, %%mm2 \n" \ + "pfadd %%mm2, %%mm1 \n" \ + " \n" \ + "pushl %3 \n" \ + "popl 0(%%eax) \n" \ + " \n" \ + "movd %%mm3, 4(%%eax) \n" \ + " \n" \ + "addl $8, %%ebx \n" \ + "addl $8, %%eax \n" \ + " \n" \ + "movq 0(%%ebx), %%mm2 \n" \ + "movq 0(%%eax), %%mm3 \n" \ + " \n" \ + "pfmul %%mm3, %%mm2 \n" \ + "pfsub %%mm2, %%mm1 \n" \ + " \n" \ + "psrlq $32, %%mm2 \n" \ + "pfsub %%mm2, %%mm1 \n" \ + " \n" \ + "movd %%mm1, %3 \n" \ + " \n" \ + "addl $8, %%ebx \n" \ + "movq 0(%%ebx), %%mm2 \n" \ + "movq 0(%%eax), %%mm3 \n" \ + " \n" \ + "pfmul %%mm3, %%mm2 \n" \ + "pfadd %%mm2, %%mm1 \n" \ + " \n" \ + "psrlq $32, %%mm2 \n" \ + "pfadd %%mm2, %%mm1 \n" \ + " \n" \ + "pushl %3 \n" \ + "popl 0(%%eax) \n" \ + "movd %%mm3, 4(%%eax) \n" \ + " \n" \ + "movd %%mm1, %0 \n" \ + "femms \n" \ + : "=m"(output) : "g"(coef_ptr), "g"(hist1_ptr), "m"(tmp) + : "eax", "ebx", "mm1", "mm2", "mm3", "memory"); + + return output; +} + +static inline void atti386_produceOutput1(int tmplen, Bit16s myvolume, Bit16s *useBuf, Bit16s *snd) { + __asm__ __volatile__( + "movl %0, %%ecx \n" \ + "movw %1, %%ax \n" \ + "shll $16, %%eax \n" \ + "movw %1, %%ax \n" \ + "movd %%eax, %%mm3 \n" \ + "movd %%eax, %%mm2 \n" \ + "psllq $32, %%mm3 \n" \ + "por %%mm2, %%mm3 \n" \ + "movl %2, %%esi \n" \ + "movl %3, %%edi \n" \ + "1: \n" \ + "movq 0(%%esi), %%mm1 \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "pmulhw %%mm3, %%mm1 \n" \ + "paddw %%mm2, %%mm1 \n" \ + "movq %%mm1, 0(%%edi) \n" \ + " \n" \ + "addl $8, %%esi \n" \ + "addl $8, %%edi \n" \ + " \n" \ + "decl %%ecx \n" \ + "cmpl $0, %%ecx \n" \ + "jg 1b \n" \ + "emms \n" \ + : : "g"(tmplen), "g"(myvolume), "g"(useBuf), "g"(snd) + : "eax", "ecx", "edi", "esi", "mm1", "mm2", "mm3", "memory"); +} + +static inline void atti386_produceOutput2(Bit32u len, Bit16s *snd, float *sndbufl, float *sndbufr, float *multFactor) { + __asm__ __volatile__( + "movl %4, %%ecx \n" \ + "shrl $1, %%ecx \n" \ + "addl $4, %%ecx \n" \ + "pushl %%ecx \n" \ + " \n" \ + "movl %0, %%esi \n" \ + "movups 0(%%esi), %%xmm1 \n" \ + " \n" \ + "movl %1, %%esi \n" \ + "movl %2, %%edi \n" \ + "1: \n" \ + "xorl %%eax, %%eax \n" \ + "movw 0(%1), %%ax \n" \ + "cwde \n" \ + "incl %1 \n" \ + "incl %1 \n" \ + "movd %%eax, %%mm1 \n" \ + "psrlq $32, %%mm1 \n" \ + "movw 0(%1), %%ax \n" \ + "incl %1 \n" \ + "incl %1 \n" \ + "movd %%eax, %%mm2 \n" \ + "por %%mm2, %%mm1 \n" \ + " \n" \ + "decl %%ecx \n" \ + "jnz 1b \n" \ + " \n" \ + "popl %%ecx \n" \ + "movl %1, %%esi \n" \ + "movl %3, %%edi \n" \ + "incl %%esi \n" \ + "2: \n" \ + "decl %%ecx \n" \ + "jnz 2b \n" \ + : : "g"(multFactor), "r"(snd), "g"(sndbufl), "g"(sndbufr), "g"(len) + : "eax", "ecx", "edi", "esi", "mm1", "mm2", "xmm1", "memory"); +} + +static inline void atti386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) { + __asm__ __volatile__( + "movl %0, %%ecx \n" \ + "movl %1, %%esi \n" \ + "movl %2, %%edi \n" \ + "1: \n" \ + "movq 0(%%edi), %%mm1 \n" \ + "movq 0(%%esi), %%mm2 \n" \ + "paddw %%mm2, %%mm1 \n" \ + "movq %%mm1, 0(%%esi) \n" \ + "addl $8, %%edi \n" \ + "addl $8, %%esi \n" \ + "decl %%ecx \n" \ + "cmpl $0, %%ecx \n" \ + "jg 1b \n" \ + "emms \n" \ + : : "g"(len), "g"(buf1), "g"(buf2) + : "ecx", "edi", "esi", "mm1", "mm2", "memory"); +} + +static inline void atti386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len) { + __asm__ __volatile__( + "movl %0, %%ecx \n" \ + "movl %1, %%esi \n" \ + "movl %2, %%edi \n" \ + "1: \n" \ + "movq 0(%%esi), %%mm1 \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "movq %%mm1, %%mm3 \n" \ + "pmulhw %%mm2, %%mm1 \n" \ + "paddw %%mm3, %%mm1 \n" \ + "movq %%mm1, 0(%%esi) \n" \ + "addl $8, %%edi \n" \ + "addl $8, %%esi \n" \ + "decl %%ecx \n" \ + "cmpl $0, %%ecx \n" \ + "jg 1b \n" \ + "emms \n" \ + : : "g"(len), "g"(buf1), "g"(buf2) + : "ecx", "edi", "esi", "mm1", "mm2", "mm3", "memory"); +} + +static inline void atti386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) { + __asm__ __volatile__( + "movl %0, %%ecx \n" \ + "movl %1, %%esi \n" \ + "movl %2, %%edi \n" \ + "1: \n" \ + "movq 0(%%esi), %%mm1 \n" \ + "movq 0(%%edi), %%mm2 \n" \ + "pmulhw %%mm2, %%mm1 \n" \ + "movq %%mm1, 0(%%esi) \n" \ + "addl $8, %%edi \n" \ + "addl $8, %%esi \n" \ + "decl %%ecx \n" \ + "cmpl $0, %%ecx \n" \ + "jg 1b \n" \ + "emms \n" \ + : : "g"(len), "g"(buf1), "g"(buf2) + : "ecx", "edi", "esi", "mm1", "mm2", "memory"); +} + +static inline void atti386_partialProductOutput(int quadlen, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *p1buf) { + __asm__ __volatile__( + "movl %0, %%ecx \n" \ + "movw %1, %%ax \n" \ + "shll $16, %%eax \n" \ + "movw %2, %%ax \n" \ + "movd %%eax, %%mm1 \n" \ + "movd %%eax, %%mm2 \n" \ + "psllq $32, %%mm1 \n" \ + "por %%mm2, %%mm1 \n" \ + "movl %3, %%edi \n" \ + "movl %4, %%esi \n" \ + "1: \n" \ + "movw 0(%%esi), %%bx \n" \ + "addl $2, %%esi \n" \ + "movw 0(%%esi), %%dx \n" \ + "addl $2, %%esi \n" \ + "" \ + "movw %%dx, %%ax \n" \ + "shll $16, %%eax \n" \ + "movw %%dx, %%ax \n" \ + "movd %%eax, %%mm2 \n" \ + "psllq $32, %%mm2 \n" \ + "movw %%bx, %%ax \n" \ + "shll $16, %%eax \n" \ + "movw %%bx, %%ax \n" \ + "movd %%eax, %%mm3 \n" \ + "por %%mm3, %%mm2 \n" \ + "" \ + "pmulhw %%mm1, %%mm2 \n" \ + "movq %%mm2, 0(%%edi) \n" \ + "addl $8, %%edi \n" \ + "" \ + "decl %%ecx \n" \ + "cmpl $0, %%ecx \n" \ + "jg 1b \n" \ + "emms \n" \ + : : "g"(quadlen), "g"(leftvol), "g"(rightvol), "g"(partialBuf), "g"(p1buf) + : "eax", "ebx", "ecx", "edx", "edi", "esi", "mm1", "mm2", "mm3", "memory"); +} + +#endif + +bool DetectSIMD() { +#ifdef _MSC_VER + bool found_simd; + __asm { + pushfd + pop eax // get EFLAGS into eax + mov ebx,eax // keep a copy + xor eax,0x200000 + // toggle CPUID bit + + push eax + popfd // set new EFLAGS + pushfd + pop eax // EFLAGS back into eax + + xor eax,ebx + // have we changed the ID bit? + + je NO_SIMD + // No, no CPUID instruction + + // we could toggle the + // ID bit so CPUID is present + mov eax,1 + + cpuid // get processor features + test edx,1<<25 // check the SIMD bit + jz NO_SIMD + mov found_simd,1 + jmp DONE + NO_SIMD: + mov found_simd,0 + DONE: + } + return found_simd; +#else + return atti386_DetectSIMD(); +#endif +} + +bool Detect3DNow() { +#ifdef _MSC_VER + bool found3D = false; + __asm { + pushfd + pop eax + mov edx, eax + xor eax, 00200000h + push eax + popfd + pushfd + pop eax + xor eax, edx + jz NO_3DNOW + + mov eax, 80000000h + cpuid + + cmp eax, 80000000h + jbe NO_3DNOW + + mov eax, 80000001h + cpuid + test edx, 80000000h + jz NO_3DNOW + mov found3D, 1 +NO_3DNOW: + + } + return found3D; +#else + return atti386_Detect3DNow(); +#endif +} + +float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr, int revLevel) { + float output; + + // 1st number of coefficients array is overall input scale factor, or filter gain + output = input * (*coef_ptr++); + +#ifdef _MSC_VER + __asm { + + movss xmm1, output + + mov eax, coef_ptr + movups xmm2, [eax] + + mov eax, hist1_ptr + movlps xmm3, [eax] + shufps xmm3, xmm3, 44h + // hist1_ptr+1, hist1_ptr, hist1_ptr+1, hist1_ptr + + mulps xmm2, xmm3 + + subss xmm1, xmm2 + // Rotate elements right + shufps xmm2, xmm2, 39h + subss xmm1, xmm2 + + // Store new_hist + movss DWORD PTR [eax], xmm1 + + // Rotate elements right + shufps xmm2, xmm2, 39h + addss xmm1, xmm2 + + // Rotate elements right + shufps xmm2, xmm2, 39h + addss xmm1, xmm2 + + // Store previous hist + movss DWORD PTR [eax+4], xmm3 + + add coef_ptr, 16 + add hist1_ptr, 8 + + mov eax, coef_ptr + movups xmm2, [eax] + + mov eax, hist1_ptr + movlps xmm3, [eax] + shufps xmm3, xmm3, 44h + // hist1_ptr+1, hist1_ptr, hist1_ptr+1, hist1_ptr + + mulps xmm2, xmm3 + + subss xmm1, xmm2 + // Rotate elements right + shufps xmm2, xmm2, 39h + subss xmm1, xmm2 + + // Store new_hist + movss DWORD PTR [eax], xmm1 + + // Rotate elements right + shufps xmm2, xmm2, 39h + addss xmm1, xmm2 + + // Rotate elements right + shufps xmm2, xmm2, 39h + addss xmm1, xmm2 + + // Store previous hist + movss DWORD PTR [eax+4], xmm3 + + movss output, xmm1 + } +#else + output = atti386_iir_filter_sse(&output, hist1_ptr, coef_ptr); +#endif + output *= ResonInv[revLevel]; + return output; +} + +float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr, int revLevel) { + float output; + + // 1st number of coefficients array is overall input scale factor, or filter gain + output = input * (*coef_ptr++); + + // I find it very sad that 3DNow requires twice as many instructions as Intel's SSE + // Intel does have the upper hand here. +#ifdef _MSC_VER + float tmp; + __asm { + movq mm1, output + mov ebx, coef_ptr + movq mm2, [ebx] + + mov eax, hist1_ptr; + movq mm3, [eax] + + pfmul mm2, mm3 + pfsub mm1, mm2 + + psrlq mm2, 32 + pfsub mm1, mm2 + + // Store new hist + movd tmp, mm1 + + add ebx, 8 + movq mm2, [ebx] + movq mm3, [eax] + + pfmul mm2, mm3 + pfadd mm1, mm2 + + psrlq mm2, 32 + pfadd mm1, mm2 + + push tmp + pop DWORD PTR [eax] + + movd DWORD PTR [eax+4], mm3 + + add ebx, 8 + add eax, 8 + + movq mm2, [ebx] + movq mm3, [eax] + + pfmul mm2, mm3 + pfsub mm1, mm2 + + psrlq mm2, 32 + pfsub mm1, mm2 + + // Store new hist + movd tmp, mm1 + + add ebx, 8 + movq mm2, [ebx] + movq mm3, [eax] + + pfmul mm2, mm3 + pfadd mm1, mm2 + + psrlq mm2, 32 + pfadd mm1, mm2 + + push tmp + pop DWORD PTR [eax] + movd DWORD PTR [eax+4], mm3 + + movd output, mm1 + + femms + } +#else + output = atti386_iir_filter_3DNow(output, hist1_ptr, coef_ptr); +#endif + output *= ResonInv[revLevel]; + return output; +} + +#if USE_MMX > 0 + +int i386_partialProductOutput(int len, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *mixedBuf) { + int tmplen = len >> 1; + if (tmplen == 0) { + return 0; + } +#ifdef _MSC_VER + __asm { + mov ecx,tmplen + mov ax, leftvol + shl eax,16 + mov ax, rightvol + movd mm1, eax + movd mm2, eax + psllq mm1, 32 + por mm1, mm2 + mov edi, partialBuf + mov esi, mixedBuf +mmxloop1: + mov bx, [esi] + add esi,2 + mov dx, [esi] + add esi,2 + + mov ax, dx + shl eax, 16 + mov ax, dx + movd mm2,eax + psllq mm2, 32 + mov ax, bx + shl eax, 16 + mov ax, bx + movd mm3,eax + por mm2,mm3 + + pmulhw mm2, mm1 + movq [edi], mm2 + add edi, 8 + + dec ecx + cmp ecx,0 + jg mmxloop1 + emms + } +#else + atti386_partialProductOutput(tmplen, leftvol, rightvol, partialBuf, mixedBuf); +#endif + return tmplen << 1; +} + +int i386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) { + int tmplen = len >> 2; + if (tmplen == 0) { + return 0; + } +#ifdef _MSC_VER + __asm { + mov ecx, tmplen + mov esi, buf1 + mov edi, buf2 + +mixloop1: + movq mm1, [edi] + movq mm2, [esi] + paddw mm1,mm2 + movq [esi],mm1 + add edi,8 + add esi,8 + + dec ecx + cmp ecx,0 + jg mixloop1 + emms + } +#else + atti386_mixBuffers(buf1, buf2, tmplen); +#endif + return tmplen << 2; +} + + +int i386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len) { + int tmplen = len >> 2; + if (tmplen == 0) { + return 0; + } +#ifdef _MSC_VER + __asm { + mov ecx, tmplen + mov esi, buf1 + mov edi, buf2 + +mixloop2: + movq mm1, [esi] + movq mm2, [edi] + movq mm3, mm1 + pmulhw mm1, mm2 + paddw mm1,mm3 + movq [esi],mm1 + add edi,8 + add esi,8 + + dec ecx + cmp ecx,0 + jg mixloop2 + emms + } +#else + atti386_mixBuffersRingMix(buf1, buf2, tmplen); +#endif + return tmplen << 2; +} + +int i386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) { + int tmplen = len >> 2; + if (tmplen == 0) { + return 0; + } +#ifdef _MSC_VER + __asm { + mov ecx, tmplen + mov esi, buf1 + mov edi, buf2 + +mixloop3: + movq mm1, [esi] + movq mm2, [edi] + pmulhw mm1, mm2 + movq [esi],mm1 + add edi,8 + add esi,8 + + dec ecx + cmp ecx,0 + jg mixloop3 + emms + } +#else + atti386_mixBuffersRing(buf1, buf2, tmplen); +#endif + return tmplen << 2; +} + +int i386_produceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume) { + int tmplen = (len >> 1); + if (tmplen == 0) { + return 0; + } +#ifdef _MSC_VER + __asm { + mov ecx, tmplen + mov ax,volume + shl eax,16 + mov ax,volume + movd mm3,eax + movd mm2,eax + psllq mm3, 32 + por mm3,mm2 + mov esi, useBuf + mov edi, stream +mixloop4: + movq mm1, [esi] + movq mm2, [edi] + pmulhw mm1, mm3 + paddw mm1,mm2 + movq [edi], mm1 + + add esi,8 + add edi,8 + + dec ecx + cmp ecx,0 + jg mixloop4 + emms + } +#else + atti386_produceOutput1(tmplen, volume, useBuf, stream); +#endif + return tmplen << 1; +} + +#endif + +} + +#endif diff --git a/backends/midi/mt32/i386.h b/backends/midi/mt32/i386.h new file mode 100644 index 0000000000..544a7bf6da --- /dev/null +++ b/backends/midi/mt32/i386.h @@ -0,0 +1,49 @@ +/* Copyright (c) 2003-2004 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_I386_H +#define MT32EMU_I386_H + +namespace MT32Emu { +#ifdef HAVE_X86 + +// Function that detects the availablity of SSE SIMD instructions +bool DetectSIMD(); +// Function that detects the availablity of 3DNow instructions +bool Detect3DNow(); + +float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr, int revLevel); +float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr, int revLevel); +float iir_filter_normal(float input,float *hist1_ptr, float *coef_ptr, int revLevel); + +#if USE_MMX > 0 +int i386_partialProductOutput(int len, Bit16s leftvol, Bit16s rightvol, Bit16s *partialBuf, Bit16s *mixedBuf); +int i386_mixBuffers(Bit16s * buf1, Bit16s *buf2, int len); +int i386_mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len); +int i386_mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len); +int i386_produceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume); +#endif + +#endif + +} + +#endif diff --git a/backends/midi/mt32/module.mk b/backends/midi/mt32/module.mk new file mode 100644 index 0000000000..e5739d90ee --- /dev/null +++ b/backends/midi/mt32/module.mk @@ -0,0 +1,18 @@ +MODULE := backends/midi/mt32 + +MODULE_OBJS := \ + backends/midi/mt32/file.o \ + backends/midi/mt32/i386.o \ + backends/midi/mt32/part.o \ + backends/midi/mt32/partial.o \ + backends/midi/mt32/partialManager.o \ + backends/midi/mt32/synth.o \ + backends/midi/mt32/tables.o \ + backends/midi/mt32/freeverb.o + + +MODULE_DIRS += \ + backends/midi/mt32 + +# Include common rules +include $(srcdir)/common.rules diff --git a/backends/midi/mt32/mt32.cpp b/backends/midi/mt32/mt32.cpp deleted file mode 100644 index 9faaa923ad..0000000000 --- a/backends/midi/mt32/mt32.cpp +++ /dev/null @@ -1,156 +0,0 @@ -/* ScummVM - Scumm Interpreter - * Copyright (C) 2001-2004 The ScummVM project - * - * YM2612 tone generation code written by Tomoaki Hayasaka. - * Used under the terms of the GNU General Public License. - * Adpated to ScummVM by Jamieson Christian. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * $Header$ - */ - -#include "backends/midi/emumidi.h" - -#include "common/util.h" -#include "common/file.h" - -#include "backends/midi/mt32/synth.h" - -class MidiDriver_MT32 : public MidiDriver_Emulated { -private: - CSynthMT32 *_synth; - - int _outputRate; - -protected: - void generate_samples(int16 *buf, int len); - -public: - MidiDriver_MT32(SoundMixer *mixer, const char *path); - virtual ~MidiDriver_MT32(); - - int open(); - void close(); - void send(uint32 b); - uint32 property(int prop, uint32 param) { return 0; } - - void setPitchBendRange(byte channel, uint range) { } - void sysEx(byte *msg, uint16 length); - - MidiChannel *allocateChannel() { return 0; } - MidiChannel *getPercussionChannel() { return 0; } - - - // AudioStream API - bool isStereo() const { return true; } - int getRate() const { return _outputRate; } -}; - - -//////////////////////////////////////// -// -// MidiDriver_MT32 -// -//////////////////////////////////////// - - -MidiDriver_MT32::MidiDriver_MT32(SoundMixer *mixer, const char *path) - : MidiDriver_Emulated(mixer) { - File fp; - - _synth = new CSynthMT32(); - File::addDefaultDirectory(path); - - _baseFreq = 1000; - - fp.open("waveforms.raw"); - - if(!fp.isOpen()) { - error("Unable to open waveforms.raw"); - } - - switch(fp.size()) { - case 1410000: - _outputRate = 32000; - break; - case 1944040: - _outputRate = 44100; - break; - default: - error("MT-32: Unknown waveforms.raw file sample rate"); - } - fp.close(); -} - -MidiDriver_MT32::~MidiDriver_MT32() { - delete _synth; -} - -int MidiDriver_MT32::open() { - SynthProperties prop; - - if (_isOpen) - return MERR_ALREADY_OPEN; - - MidiDriver_Emulated::open(); - - prop.SampleRate = getRate(); // 32000; - prop.UseReverb = true; - prop.UseDefault = true; - //prop.RevType = 0; - //prop.RevTime = 5; - //prop.RevLevel = 3; - - _synth->ClassicOpen(prop); - - _mixer->setupPremix(this); - - return 0; -} - -void MidiDriver_MT32::send(uint32 b) { - _synth->PlayMsg(b); -} - -void MidiDriver_MT32::sysEx(byte *msg, uint16 length) { - _synth->PlaySysex(msg, length); -} - -void MidiDriver_MT32::close() { - if (!_isOpen) - return; - _isOpen = false; - - // Detach the premix callback handler - _mixer->setupPremix(0); - - _synth->Close(); -} - -void MidiDriver_MT32::generate_samples(int16 *data, int len) { - _synth->MT32_CallBack((uint8 *)data, len, _mixer->getMusicVolume()); -} - - -//////////////////////////////////////// -// -// MidiDriver_MT32 factory -// -//////////////////////////////////////// - -MidiDriver *MidiDriver_MT32_create(SoundMixer *mixer, const char *path) { - return new MidiDriver_MT32(mixer, path); -} diff --git a/backends/midi/mt32/mt32emu.h b/backends/midi/mt32/mt32emu.h new file mode 100644 index 0000000000..739be7de59 --- /dev/null +++ b/backends/midi/mt32/mt32emu.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2003-2004 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_MT32EMU_H +#define MT32EMU_MT32EMU_H + +#include "freeverb.h" + +#include "structures.h" +#include "i386.h" +#include "file.h" +#include "partial.h" +#include "partialManager.h" +#include "part.h" +#include "tables.h" +#include "synth.h" + +#endif diff --git a/backends/midi/mt32/part.cpp b/backends/midi/mt32/part.cpp new file mode 100644 index 0000000000..cfb40f3d50 --- /dev/null +++ b/backends/midi/mt32/part.cpp @@ -0,0 +1,595 @@ +/* Copyright (c) 2003-2004 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <string.h> +#include <math.h> + +#include "mt32emu.h" + +// Debugging stuff +// Shows the instruments played +#define DISPLAYINSTR 1 + +namespace MT32Emu { + +static const Bit8u PartialStruct[13] = { + 0, 0, 2, 2, 1, 3, + 3, 0, 3, 0, 2, 1, 3 }; + +static const Bit8u PartialMixStruct[13] = { + 0, 1, 0, 1, 1, 0, + 1, 3, 3, 2, 2, 2, 2 }; + +static const Bit32u drumBend = 0x1000; + +// This caches the timbres/settings in use by the rhythm part +static PatchCache drumCache[94][4]; + +static volset drumPan[64]; + +//FIXME:KG: Put this dpoly stuff somewhere better +bool dpoly::isActive() { + return partials[0] != NULL || partials[1] != NULL || partials[2] != NULL || partials[3] != NULL; +} + +Bit64s dpoly::getAge() { + for (int i = 0; i < 4; i++) { + if (partials[i] != NULL) { + return partials[i]->age; + } + } + return 0; +} + +Part::Part(Synth *useSynth, int usePartNum) { + this->synth = useSynth; + this->partNum = usePartNum; + isRhythm = (usePartNum == 8); + holdpedal = false; + if (isRhythm) { + strcpy(name, "Rhythm"); + patchTemp = NULL; + timbreTemp = NULL; + rhythmTemp = &synth->mt32ram.params.rhythmSettings[0]; + } else { + sprintf(name, "Part %d", partNum + 1); + patchTemp = &synth->mt32ram.params.patchSettings[partNum]; + timbreTemp = &synth->mt32ram.params.timbreSettings[partNum]; + rhythmTemp = NULL; + } + currentInstr[0] = 0; + volume = 102; + volumesetting.leftvol = 32767; + volumesetting.rightvol = 32767; + bend = 0x1000; + memset(polyTable,0,sizeof(polyTable)); + memset(patchCache, 0, sizeof(patchCache)); + + if (isRhythm) { + init = true; + RefreshDrumCache(); + } + init = false; +} + +void Part::SetHoldPedal(bool pedalval) { + if (holdpedal && !pedalval) + StopPedalHold(); + holdpedal = pedalval; +} + +void Part::SetBend(int vol) { + if (isRhythm) { + synth->printDebug("%s: Setting bend (%d) not supported on rhythm", name, vol); + return; + } + //int tmpbend = ((vol - 0x2000) * (int)patchTemp->patch.benderRange) >> 13; + //bend = bendtable[tmpbend+24]; + + float bend_range = (float)patchTemp->patch.benderRange / 24; + bend = (Bit32u)(4096 + (vol - 8192) * bend_range); +} + +void Part::SetModulation(int vol) { + if (isRhythm) { + synth->printDebug("%s: Setting modulation (%d) not supported on rhythm", name, vol); + return; + } + // Just a bloody guess, as always, before I get things figured out + for (int t = 0; t < 4; t++) { + if (patchCache[t].playPartial) { + int newrate = (patchCache[t].modsense * vol) >> 7; + //patchCache[t].lfoperiod = lfotable[newrate]; + patchCache[t].lfodepth = newrate; + //FIXME:KG: timbreTemp->partial[t].lfo.depth = + } + } +} + +void Part::RefreshDrumCache() { + if (!isRhythm) { + synth->printDebug("ERROR: RefreshDrumCache() called on non-rhythm part"); + } + // Cache drum patches + for (int m = 0; m < 64; m++) { + int drumTimbre = rhythmTemp[m].timbre; + if (drumTimbre >= 94) + continue; + SetPatch(drumTimbre + 128); // This is to cache all the mapped drum timbres ahead of time + Bit16s pan = rhythmTemp[m].panpot; // They use R-L 0-14... + // FIXME:KG: If I don't have left/right mixed up here, it's pure luck + if (pan < 7) { + drumPan[m].leftvol = 32767; + drumPan[m].rightvol = pan * 4681; + } else { + drumPan[m].rightvol = 32767; + drumPan[m].leftvol = (14 - pan) * 4681; + } + } +} + +int Part::FixBiaslevel(int srcpnt, int *dir) { + int noteat = srcpnt & 63; + int outnote; + *dir = 1; + if (srcpnt < 64) + *dir = 0; + outnote = 33 + noteat; + //synth->printDebug("Bias note %d, dir %d", outnote, *dir); + + return outnote; +} + +int Part::FixKeyfollow(int srckey, int *dir) { + if (srckey>=0 && srckey<=16) { + //int keyfix[17] = { 256, 128, 64, 0, 32, 64, 96, 128, 128+32, 192, 192+32, 256, 256+64, 256+128, 512, 259, 269 }; + int keyfix[17] = { 256*16, 128*16, 64*16, 0, 32*16, 64*16, 96*16, 128*16, (128+32)*16, 192*16, (192+32)*16, 256*16, (256+64)*16, (256+128)*16, (512)*16, 4100, 4116}; + + if (srckey<3) + *dir = -1; + else if (srckey==3) + *dir = 0; + else + *dir = 1; + + return keyfix[srckey]; + } else { + //LOG(LOG_ERROR|LOG_MISC,"Missed key: %d", srckey); + return 256; + } +} + +void Part::RefreshPatch() { + SetPatch(-1); +} + +void Part::AbortPoly(dpoly *poly) { + if (!poly->isPlaying) { + return; + } + for (int i = 0; i < 4; i++) { + Partial *partial = poly->partials[i]; + if (partial != NULL) { + partial->deactivate(); + } + } + poly->isPlaying = false; +} + +void Part::setPatch(PatchParam *patch) { + patchTemp->patch = *patch; +} + +void Part::setTimbre(TimbreParam *timbre) { + *timbreTemp = *timbre; +} + +unsigned int Part::getAbsTimbreNum() { + if (isRhythm) { + synth->printDebug("%s: Attempted to call getAbsTimbreNum() - doesn't make sense for rhythm"); + return 0; + } + return (patchTemp->patch.timbreGroup * 64) + patchTemp->patch.timbreNum; +} + +void Part::SetPatch(int patchNum) { + int pcm; + int absTimbreNum = -1; // Initialised to please compiler + TimbreParam timSrc; + if (isRhythm) { + // "patchNum" is treated as "timbreNum" for rhythm part + if (patchNum < 128) { + synth->printDebug("%s: Patch #%d is not valid for rhythm (must be >= 128)", name, patchNum); + return; + } + absTimbreNum = patchNum; + timSrc = synth->mt32ram.params.timbres[absTimbreNum].timbre; + } else { + if (patchNum >= 0) { + setPatch(&synth->mt32ram.params.patches[patchNum]); + } + if (patchNum >= 0) { + setTimbre(&synth->mt32ram.params.timbres[getAbsTimbreNum()].timbre); + } + timSrc = *timbreTemp; +#if 0 + // Immediately stop all partials on this part (this is apparently *not* the correct behaviour) + for (int m = 0; m < MAXPOLY; m++) { + AbortPoly(poly); + } +#else + // check if any partials are still playing on this part + // if so then duplicate the cached data from the part to the partial so that + // we can change the part's cache without affecting the partial. + // Hopefully this is fairly rare. + for (int m = 0; m < MAXPOLY; m++) { + for (int i = 0; i < 4; i++) { + Partial *partial = polyTable[m].partials[i]; + if (partial != NULL) { + // copy cache data + partial->cachebackup = patchCache[i]; + // update pointers + partial->patchCache = &partial->cachebackup; + } + } + } +#endif + } + + memcpy(currentInstr, timSrc.common.name, 10); + currentInstr[10] = 0; + + int partialCount = 0; + for (int t=0;t<4;t++) { + if ( ((timSrc.common.pmute >> (t)) & 0x1) == 1 ) { + patchCache[t].playPartial = true; + partialCount++; + } else { + patchCache[t].playPartial = false; + continue; + } + + // Calculate and cache common parameters + + pcm = timSrc.partial[t].wg.pcmwave; + + patchCache[t].pcm = timSrc.partial[t].wg.pcmwave; + patchCache[t].useBender = (timSrc.partial[t].wg.bender == 1); + + switch (t) { + case 0: + patchCache[t].PCMPartial = (PartialStruct[(int)timSrc.common.pstruct12] & 0x2) ? true : false; + patchCache[t].mix = PartialMixStruct[(int)timSrc.common.pstruct12]; + patchCache[t].structurePosition = 0; + patchCache[t].structurePair = 1; + break; + case 1: + patchCache[t].PCMPartial = (PartialStruct[(int)timSrc.common.pstruct12] & 0x1) ? true : false; + patchCache[t].mix = PartialMixStruct[(int)timSrc.common.pstruct12]; + patchCache[t].structurePosition = 1; + patchCache[t].structurePair = 0; + break; + case 2: + patchCache[t].PCMPartial = (PartialStruct[(int)timSrc.common.pstruct34] & 0x2) ? true : false; + patchCache[t].mix = PartialMixStruct[(int)timSrc.common.pstruct34]; + patchCache[t].structurePosition = 0; + patchCache[t].structurePair = 3; + break; + case 3: + patchCache[t].PCMPartial = (PartialStruct[(int)timSrc.common.pstruct34] & 0x1) ? true : false; + patchCache[t].mix = PartialMixStruct[(int)timSrc.common.pstruct34]; + patchCache[t].structurePosition = 1; + patchCache[t].structurePair = 2; + break; + default: + break; + } + + patchCache[t].waveform = timSrc.partial[t].wg.waveform; + patchCache[t].pulsewidth = timSrc.partial[t].wg.pulsewid; + patchCache[t].pwsens = timSrc.partial[t].wg.pwvelo; + patchCache[t].pitchkeyfollow = FixKeyfollow(timSrc.partial[t].wg.keyfollow, &patchCache[t].pitchkeydir); + + // Calculate and cache pitch stuff + patchCache[t].pitchshift = timSrc.partial[t].wg.coarse; + Bit32s pFine, fShift; + pFine = (Bit32s)timSrc.partial[t].wg.fine; + if (isRhythm) { + patchCache[t].pitchshift += 24; + fShift = pFine + 50; + } else { + patchCache[t].pitchshift += patchTemp->patch.keyShift; + fShift = pFine + (Bit32s)patchTemp->patch.fineTune; + } + patchCache[t].fineshift = finetable[fShift]; + + patchCache[t].pitchEnv = timSrc.partial[t].env; + patchCache[t].pitchEnv.sensitivity = (char)((float)patchCache[t].pitchEnv.sensitivity*1.27); + patchCache[t].pitchsustain = patchCache[t].pitchEnv.level[3]; + + // Calculate and cache TVA envelope stuff + patchCache[t].ampEnv = timSrc.partial[t].tva; + for (int i = 0; i < 4; i++) + patchCache[t].ampEnv.envlevel[i] = (char)((float)patchCache[t].ampEnv.envlevel[i]*1.27); + patchCache[t].ampEnv.level = (char)((float)patchCache[t].ampEnv.level*1.27); + float tvelo = ((float)timSrc.partial[t].tva.velosens / 100.0f); + float velo = fabs(tvelo-0.5f) * 2.0f; + velo *= 63.0f; + patchCache[t].ampEnv.velosens = (char)velo; + if (tvelo<0.5f) + patchCache[t].ampenvdir = 1; + else + patchCache[t].ampenvdir = 0; + + patchCache[t].ampbias[0] = FixBiaslevel(patchCache[t].ampEnv.biaspoint1, &patchCache[t].ampdir[0]); + patchCache[t].ampblevel[0] = 12 - patchCache[t].ampEnv.biaslevel1; + patchCache[t].ampbias[1] = FixBiaslevel(patchCache[t].ampEnv.biaspoint2, &patchCache[t].ampdir[1]); + patchCache[t].ampblevel[1] = 12 - patchCache[t].ampEnv.biaslevel2; + patchCache[t].ampdepth = patchCache[t].ampEnv.envvkf * patchCache[t].ampEnv.envvkf; + patchCache[t].ampsustain = patchCache[t].ampEnv.envlevel[3]; + patchCache[t].amplevel = patchCache[t].ampEnv.level; + + // Calculate and cache filter stuff + patchCache[t].filtEnv = timSrc.partial[t].tvf; + patchCache[t].tvfdepth = patchCache[t].filtEnv.envdkf; + patchCache[t].filtkeyfollow = FixKeyfollow(patchCache[t].filtEnv.keyfollow, &patchCache[t].keydir); + patchCache[t].filtEnv.envdepth = (char)((float)patchCache[t].filtEnv.envdepth * 1.27); + patchCache[t].tvfbias = FixBiaslevel(patchCache[t].filtEnv.biaspoint, &patchCache[t].tvfdir); + patchCache[t].tvfblevel = patchCache[t].filtEnv.biaslevel; + patchCache[t].filtsustain = patchCache[t].filtEnv.envlevel[3]; + + // Calculate and cache LFO stuff + patchCache[t].lfodepth = timSrc.partial[t].lfo.depth; + patchCache[t].lfoperiod = lfotable[(int)timSrc.partial[t].lfo.rate]; + patchCache[t].lforate = timSrc.partial[t].lfo.rate; + patchCache[t].modsense = timSrc.partial[t].lfo.modsense; + } + for (int t = 0; t < 4; t++) { + // Common parameters, stored redundantly + patchCache[t].partialCount = partialCount; + patchCache[t].sustain = (timSrc.common.nosustain == 0); + } + //synth->printDebug("Res 1: %d 2: %d 3: %d 4: %d", patchCache[0].waveform, patchCache[1].waveform, patchCache[2].waveform, patchCache[3].waveform); + + if (isRhythm) + memcpy(drumCache[absTimbreNum - 128], patchCache, sizeof(patchCache)); + else + AllStop(); +#if DISPLAYINSTR == 1 + synth->printDebug("%s: Recache, param %d (timbre: %s), %d partials", name, patchNum, currentInstr, partialCount); + for (int i = 0; i < 4; i++) { + synth->printDebug(" %d: play=%s, pcm=%d, wave=%d", i, patchCache[i].playPartial ? "YES" : "NO", timSrc.partial[i].wg.pcmwave, timSrc.partial[i].wg.waveform); + } +#endif +} + +char *Part::getName() { + return name; +} + +void Part::SetVolume(int vol) { + volume = voltable[vol]; +} + +void Part::SetPan(int pan) { + // FIXME:KG: This is unchangeable for drums (they always use drumPan), is that correct? + // FIXME:KG: There is no way to get a centred balance here... And the middle two + // pan settings have a distance of 1024, double the usual. + // Perhaps we should multiply by 516? 520? 520.111..? + // KG: Changed to 516, to mostly eliminate that jump in the middle + if (pan < 64) { + volumesetting.leftvol = 32767; + volumesetting.rightvol = (Bit16s)(pan * 516); + } else { + volumesetting.rightvol = 32767; + volumesetting.leftvol = (Bit16s)((127 - pan) * 516); + } + //synth->printDebug("%s (%s): Set pan to %d", name, currentInstr, panpot); +} + +void Part::PlayNote(PartialManager *partialManager, int f, int vel) { + int drumNum = -1; // Initialised to please compiler + int drumTimbre = -1; // As above + int freqNum; + + if (isRhythm) { + if (f < 24 || f > 87) { + synth->printDebug("%s: Attempted to play invalid note %d", name, f); + return; + } + drumNum = f - 24; + drumTimbre = rhythmTemp[drumNum].timbre; + if (drumTimbre >= 94) { + synth->printDebug("%s: Attempted to play unmapped note %d!", name, f); + return; + } + memcpy(patchCache, drumCache[drumTimbre], sizeof(patchCache)); + memcpy(¤tInstr, synth->mt32ram.params.timbres[128 + drumTimbre].timbre.common.name, 10); + currentInstr[10] = 0; + freqNum = MIDDLEC; + } else { + if (f < 12 || f > 108) { + synth->printDebug("%s (%s): Attempted to play invalid note %d", name, currentInstr, f); + return; + } + freqNum = f; + } + // POLY1 mode, Single Assign + // Haven't found any software that uses any of the other poly modes + // FIXME:KG: Should this also apply to rhythm? + if (!isRhythm) { + for (int i = 0; i < MAXPOLY; i++) { + if (polyTable[i].isActive() && (polyTable[i].note == f)) { + //AbortPoly(&polyTable[i]); + StopNote(f); + break; + } + } + } + + unsigned int needPartials = patchCache[0].partialCount; + + if (needPartials > partialManager->GetFreePartialCount()) { + if (!partialManager->FreePartials(needPartials, partNum)) { + synth->printDebug("%s (%s): Insufficient free partials to play note %d (vel=%d)", name, currentInstr, f, vel); + return; + } + } + // Find free note + int m; + for (m = 0; m < MAXPOLY; m++) { + if (!polyTable[m].isActive()) { + break; + } + } + if (m == MAXPOLY) { + synth->printDebug("%s (%s): No free poly to play note %d (vel %d)", name, currentInstr, f, vel); + return; + } + + dpoly *tpoly = &polyTable[m]; + Bit16s freq = freqtable[freqNum]; + + tpoly->isPlaying = true; + tpoly->note = f; + tpoly->isDecay = false; + tpoly->freq = freq; + tpoly->freqnum = freqNum; + tpoly->vel = vel; + tpoly->pedalhold = false; + + bool allnull = true; + for (int x = 0; x < 4; x++) { + if (patchCache[x].playPartial) { + tpoly->partials[x] = partialManager->AllocPartial(partNum); + allnull = false; + } else { + tpoly->partials[x] = NULL; + } + } + + if (allnull) + synth->printDebug("%s (%s): No partials to play for this instrument", name, this->currentInstr); + + if (isRhythm) { + tpoly->bendptr = &drumBend; + tpoly->pansetptr = &drumPan[drumNum]; + tpoly->reverb = rhythmTemp[drumNum].reverbSwitch > 0; + } else { + tpoly->bendptr = &bend; + tpoly->pansetptr = &volumesetting; + tpoly->reverb = patchTemp->patch.reverbSwitch > 0; + } + tpoly->sustain = patchCache[0].sustain; + tpoly->volumeptr = &volume; + + for (int x = 0; x < 4; x++) { + if (tpoly->partials[x] != NULL) { + tpoly->partials[x]->startPartial(tpoly, &patchCache[x], tpoly->partials[patchCache[x].structurePair]); + } + } + +#if DISPLAYINSTR == 1 + if (isRhythm) { + synth->printDebug("%s (%s): starting note poly %d (drum %d, timbre %d) - Vel %d Vol %d", name, currentInstr, m, drumNum, drumTimbre, vel, volume); + } else { + synth->printDebug("%s (%s): starting note poly %d - Vel %d Freq %d Vol %d", name, currentInstr, m, vel, f, volume); + } +#endif +} + +static void StartDecayPoly(dpoly *tpoly) { + if (tpoly->isDecay) { + return; + } + tpoly->isDecay = true; + + for (int t = 0; t < 4; t++) { + Partial *partial = tpoly->partials[t]; + if (partial == NULL) + continue; + partial->startDecayAll(); + } + tpoly->isPlaying = false; +} + +void Part::AllStop() { + for (int q = 0; q < MAXPOLY; q++) { + dpoly *tpoly = &polyTable[q]; + if (tpoly->isPlaying) { + StartDecayPoly(tpoly); + } + } +} + +void Part::StopPedalHold() { + for (int q = 0; q < MAXPOLY; q++) { + dpoly *tpoly; + tpoly = &polyTable[q]; + if (tpoly->isActive() && tpoly->pedalhold) + StopNote(tpoly->note); + } +} + +void Part::StopNote(int f) { + // Non-sustaining instruments ignore stop note commands. + // They die away eventually anyway + //if (!tpoly->sustain) return; + +#if DISPLAYINSTR == 1 + synth->printDebug("%s (%s): stopping note %d", name, currentInstr, f); +#endif + + if (f != -1) { + for (int q = 0; q < MAXPOLY; q++) { + dpoly *tpoly = &polyTable[q]; + if (tpoly->isPlaying && tpoly->note == f) { + if (holdpedal) + tpoly->pedalhold = true; + else if (tpoly->sustain) + StartDecayPoly(tpoly); + } + } + return; + } + + // Find oldest note... yes, the MT-32 can be reconfigured to kill different note first + // This is simplest + int oldest = -1; + Bit64s oldage = -1; + + for (int q = 0; q < MAXPOLY; q++) { + dpoly *tpoly = &polyTable[q]; + + if (tpoly->isPlaying && !tpoly->isDecay) { + if (tpoly->getAge() >= oldage) { + oldage = tpoly->getAge(); + oldest = q; + } + } + } + + if (oldest!=-1) { + StartDecayPoly(&polyTable[oldest]); + } +} + +} diff --git a/backends/midi/mt32/part.h b/backends/midi/mt32/part.h new file mode 100644 index 0000000000..302ca4d2fb --- /dev/null +++ b/backends/midi/mt32/part.h @@ -0,0 +1,90 @@ +/* Copyright (c) 2003-2004 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_PART_H +#define MT32EMU_PART_H + +#define AMPENV 0 +#define FILTENV 1 +#define PITCHENV 2 + +#define MAXPOLY 64 + +namespace MT32Emu { + +class PartialManager; +class Synth; + +class Part { +private: + Synth *synth; // Only used for sending debug output + + // Pointers to the areas of the MT-32's memory dedicated to this part (for parts 1-8) + MemParams::PatchTemp *patchTemp; + TimbreParam *timbreTemp; + //... and for rhythm + MemParams::RhythmTemp *rhythmTemp; + + bool isRhythm; + bool init; + int partNum; + + char name[8]; // "Part 1".."Part 8", "Rhythm" + char currentInstr[11]; + + bool holdpedal; + + volset volumesetting; + + PatchCache patchCache[4]; + + Bit32u bend; + Bit32s volume; + + dpoly polyTable[MAXPOLY]; + + void AbortPoly(dpoly *poly); + +public: + Part(Synth *synth, int usePartNum); + char *getName(); + void PlayNote(PartialManager *partialManager, int f, int vel); + void StopNote(int f); + void AllStop(); + void SetVolume(int vol); + void SetPan(int vol); + void SetBend(int vol); + void SetModulation(int vol); + void SetPatch(int patchnum); + void SetHoldPedal(bool pedalval); + void StopPedalHold(); + void RefreshPatch(); + void RefreshDrumCache(); + void setPatch(PatchParam *patch); + void setTimbre(TimbreParam *timbre); + unsigned int getAbsTimbreNum(); + + int FixKeyfollow(int srckey, int *dir); + int FixBiaslevel(int srcpnt, int *dir); +}; + +} +#endif diff --git a/backends/midi/mt32/partial.cpp b/backends/midi/mt32/partial.cpp index a5dddfed1f..5a849ae4ad 100644 --- a/backends/midi/mt32/partial.cpp +++ b/backends/midi/mt32/partial.cpp @@ -1,601 +1,884 @@ -/* ScummVM - Scumm Interpreter - * Copyright (C) 2004 The ScummVM project - * Based on Tristan's conversion of Canadacow's code +/* Copyright (c) 2003-2004 Various contributors * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * 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 General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * $Header$ + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. */ -// Implementation of the MT-32 partial class +#include <stdlib.h> +#include <math.h> +#include <string.h> -#include "backends/midi/mt32/synth.h" -#include "backends/midi/mt32/partial.h" +#include "mt32emu.h" -INLINE void CPartialMT32::generateSamples(int16 * partialBuf, long length) { - if (!isActive) return; - if (alreadyOutputed) return; - +using namespace MT32Emu; - alreadyOutputed = true; +Partial::Partial(Synth *useSynth) { + this->synth = useSynth; + ownerPart = -1; + poly = NULL; + pair = NULL; +}; - // Generate samples +int Partial::getOwnerPart() { + return ownerPart; +} - int r; - int i; - int32 envval, ampval, filtval; - soundaddr *pOff = &partCache->partialOff; - int noteval = partCache->keyedval; - for(i=0;i<length;i++) { - int32 ptemp = 0; +bool Partial::isActive() { + return ownerPart > -1; +} - if(partCache->envs[AMPENV].sustaining) { - ampval = partCache->ampEnvCache; - } else { - if(partCache->envs[AMPENV].count<=0) { - - ampval = getAmpEnvelope(partCache,tmppoly); - isActive = partCache->playPartial; -//TODO: check what is going on here -// if (ampval < 0) ampval = 0; - //printf("%d %d\n", (int)ampval, (int)isActive); - if(!isActive) { - tmppoly->partActive[timbreNum] = false; - tmppoly->isActive = tmppoly->partActive[0] || tmppoly->partActive[1] || tmppoly->partActive[2] || tmppoly->partActive[3]; - } - if(ampval>=128) ampval = 127; - - ampval = amptable[ampval]; - int tmpvel = tmppoly->vel; - if(tcache->ampenvdir==1) tmpvel = 127-tmpvel; - ampval = (ampval * ampveltable[tmpvel][(int)tcache->ampEnv.velosens]) >> 8; - //if(partCache->envs[AMPENV].sustaining) - partCache->ampEnvCache = ampval; - } else { - ampval = partCache->ampEnvCache; +void Partial::activate(int part) { + // This just marks the partial as being assigned to a part + ownerPart = part; +} + +void Partial::deactivate() { + ownerPart = -1; + if (poly != NULL) { + for (int i = 0; i < 4; i++) { + if (poly->partials[i] == this) { + poly->partials[i] = NULL; + break; } - --partCache->envs[AMPENV].count; } - + if (pair != NULL) { + pair->pair = NULL; + } + } +} + +void Partial::initKeyFollow(int freqNum) { + // Setup partial keyfollow + // Note follow relative to middle C + int keyfollow; + int realfol = (freqNum * 2 - MIDDLEC * 2) / 2; + int antirealfol = (MIDDLEC * 2 - freqNum * 2) / 2; + // Calculate keyfollow for pitch + switch(patchCache->pitchkeydir) { + case -1: + keyfollow = (antirealfol * patchCache->pitchkeyfollow) >> 12; + break; + case 0: + keyfollow = 0; + break; + case 1: + keyfollow = (realfol * patchCache->pitchkeyfollow) >> 12; + break; + default: + keyfollow = 0; // Please the compiler + } + if ((patchCache->pitchkeyfollow>4096) && (patchCache->pitchkeyfollow<4200)) { + // Be sure to round up on keys below MIDDLEC + if (realfol < 0) + keyfollow++; + } + noteVal = (keyfollow + patchCache->pitchshift); + if (noteVal > 108) + noteVal = 108; + if (noteVal < 12) + noteVal = 12; + + // Calculate keyfollow for filter + switch(patchCache->keydir) { + case -1: + keyfollow = (antirealfol * patchCache->filtkeyfollow) >> 12; + break; + case 0: + keyfollow = freqNum; + break; + case 1: + keyfollow = (realfol * patchCache->filtkeyfollow) >> 12; + break; + } + if (keyfollow > 108) + keyfollow = 108; + if (keyfollow < -108) + keyfollow = -108; + filtVal = keytable[keyfollow + 108]; + realVal = keytable[realfol + 108]; +} + +void Partial::startPartial(dpoly *usePoly, PatchCache *useCache, Partial *pairPartial) { + if (usePoly == NULL || useCache == NULL) { + synth->printDebug("*** Error: Starting partial for owner %d, usePoly=%s, useCache=%s", ownerPart, usePoly == NULL ? "*** NULL ***" : "OK", useCache == NULL ? "*** NULL ***" : "OK"); + return; + } + patchCache = useCache; + poly = usePoly; + mixType = patchCache->mix; + structurePosition = patchCache->structurePosition; + + play = true; + initKeyFollow(poly->freqnum); + lfoPos = 0; + pulsewidth = patchCache->pulsewidth + pwveltable[patchCache->pwsens][poly->vel]; + if (pulsewidth > 100) { + pulsewidth = 100; + } else if (pulsewidth < 0) { + pulsewidth = 0; + } + + for (int e = 0; e < 3; e++) { + envs[e].envpos = 0; + envs[e].envstat = -1; + envs[e].envbase = 0; + envs[e].envdist = 0; + envs[e].envsize = 0; + envs[e].sustaining = false; + envs[e].decaying = false; + envs[e].prevlevel = 0; + envs[e].counter = 0; + envs[e].count = 0; + } + ampEnvCache = 0; + pitchEnvCache = 0; + pitchSustain = false; + loopPos = 0; + partialOff.pcmabs = 0; + pair = pairPartial; + useNoisePair = pairPartial == NULL && (mixType == 1 || mixType == 2); + age = 0; + alreadyOutputed = false; + memset(history,0,sizeof(history)); +} + +Bit16s *Partial::generateSamples(long length) { + if (!isActive() || alreadyOutputed) { + return NULL; + } + if (poly == NULL) { + synth->printDebug("*** ERROR: poly is NULL at Partial::generateSamples()!"); + return NULL; + } + + alreadyOutputed = true; + + // Generate samples + + Bit16s *partialBuf = &myBuffer[0]; + while (length--) { + Bit32s envval, ampval; + Bit32s ptemp = 0; + if (envs[AMPENV].sustaining) + ampval = ampEnvCache; + else { + if (envs[AMPENV].count<=0) { + ampval = getAmpEnvelope(); + if (!play) { + deactivate(); + break; + } + if (ampval < 0) { + //TODO: check what is going on here + synth->printDebug("ampval<0! ampval=%ld, active=%d", ampval, isActive()); + ampval = 0; + } else if (ampval > 127) { + ampval = 127; + } + + ampval = voltable[ampval]; + int tmpvel = poly->vel; + if (patchCache->ampenvdir == 1) + tmpvel = 127 - tmpvel; + ampval = (ampval * ampveltable[tmpvel][(int)patchCache->ampEnv.velosens]) >> 8; + //if (envs[AMPENV].sustaining) + ampEnvCache = ampval; + } else + ampval = ampEnvCache; + --envs[AMPENV].count; + } + int delta = 0x10707; // Calculate Pitch envelope int lfoat = 0x1000; int pdep; - if(partCache->pitchsustain) { + if (pitchSustain) { // Calculate LFO position // LFO does not kick in completely until pitch envelope sustains - - if(tcache->lfodepth>0) { - partCache->lfopos++; - - if(partCache->lfopos>=tcache->lfoperiod) partCache->lfopos = 0; - int lfoatm = (partCache->lfopos << 16) / tcache->lfoperiod; - + if (patchCache->lfodepth>0) { + lfoPos++; + if (lfoPos >= patchCache->lfoperiod) + lfoPos = 0; + int lfoatm = (lfoPos << 16) / patchCache->lfoperiod; int lfoatr = sintable[lfoatm]; - - lfoat = lfoptable[tcache->lfodepth][lfoatr]; + lfoat = lfoptable[patchCache->lfodepth][lfoatr]; } - pdep = partCache->pitchEnvCache; - - + pdep = pitchEnvCache; } else { - envval = getPitchEnvelope(partCache,tmppoly); - int pd=tcache->pitchEnv.depth; + envval = getPitchEnvelope(); + int pd = patchCache->pitchEnv.depth; pdep = penvtable[pd][envval]; - if(partCache->pitchsustain) partCache->pitchEnvCache = pdep; - + if (pitchSustain) + pitchEnvCache = pdep; } - // Get waveform - either PCM or synthesized sawtooth or square - - - if (tcache->PCMPartial) { + if (patchCache->PCMPartial) { // PCM partial - - if(!partCache->PCMDone) { - - int addr,len,tmppcm; - partialTable *tPCM = &tcache->convPCM; - - if(tPCM->aggSound==-1) { - delta = wavtabler[tPCM->pcmnum][noteval]; - addr = tPCM->addr; - len = tPCM->len; - - } else { - tmppcm = LoopPatterns[tPCM->aggSound][partCache->looppos]; - addr = PCM[tmppcm].addr; - len = PCM[tmppcm].len; - delta = looptabler[tPCM->aggSound][partCache->looppos][noteval]; - } - - if(ampval>0) { - int ra,rb,dist; - int taddr; - if(delta<0x10000) { - // Linear sound interpolation - - taddr = addr + pOff->pcmoffs.pcmplace; - ra = romfile[taddr]; - rb = romfile[taddr+1]; - - dist = rb-ra; - r = (ra + ((dist * (int32)(pOff->pcmoffs.pcmoffset>>8)) >>8)); - + int addr,len; + sampleTable *tPCM = &synth->PCMList[patchCache->pcm]; + + if (tPCM->aggSound == -1) { + delta = wavtabler[tPCM->pcmnum][noteVal]; + addr = tPCM->addr; + len = tPCM->len; + if (partialOff.pcmoffs.pcmplace >= len) { + if (tPCM->loop) { + partialOff.pcmabs = 0; } else { - - //r = romfile[addr + pOff->pcmoffs.pcmplace]; - // Sound decimation - // The right way to do it is to use a lowpass filter on the waveform before selecting - // a point. This is too slow. The following appoximates this as fast as possible - int idelta = delta >> 16; - int ix; - taddr = addr + pOff->pcmoffs.pcmplace; - ra = 0; - for(ix=0;ix<idelta;ix++) { - ra += romfile[taddr++]; - } - r = ra / idelta; - - - + play = false; + deactivate(); + break; } - } else r = 0; - - ptemp = r; - - if ((pOff->pcmoffs.pcmplace) >= len) { - if(tPCM->aggSound==-1) { - if(tPCM->loop) { - pOff->pcmabs = 0; - } else { - partCache->PCMDone = true; - partCache->playPartial = false; - } + } + } else { + int tmppcm = LoopPatterns[tPCM->aggSound][loopPos]; + delta = looptabler[tPCM->aggSound][loopPos][noteVal]; + addr = synth->PCM[tmppcm].addr; + len = synth->PCM[tmppcm].len; + if (partialOff.pcmoffs.pcmplace >= len) { + loopPos++; + if (LoopPatterns[tPCM->aggSound][loopPos]==-1) + loopPos=0; + partialOff.pcmabs = 0; + } + } - } else { - partCache->looppos++; - if(LoopPatterns[tPCM->aggSound][partCache->looppos]==-1) partCache->looppos=0; - pOff->pcmabs = 0; + if (ampval>0) { + int ra,rb,dist; + int taddr; + if (delta<0x10000) { + // Linear sound interpolation + taddr = addr + partialOff.pcmoffs.pcmplace; + if (taddr >= ROMSIZE) { + synth->printDebug("Overflow ROMSIZE!"); + taddr = ROMSIZE - 1; } - //LOG_MSG("tPCM %d loops %d done %d playPart %d", tPCM->pcmnum, tPCM->loop, partCache->PCMDone, partCache->playPartial); - + ra = synth->romfile[taddr]; + rb = synth->romfile[taddr+1]; + dist = rb-ra; + ptemp = (ra + ((dist * (Bit32s)(partialOff.pcmoffs.pcmoffset>>8)) >>8)); + } else { + //r = romfile[addr + partialOff.pcmoffs.pcmplace]; + // Sound decimation + // The right way to do it is to use a lowpass filter on the waveform before selecting + // a point. This is too slow. The following approximates this as fast as possible + int idelta = delta >> 16; + taddr = addr + partialOff.pcmoffs.pcmplace; + ra = 0; + for (int ix=0;ix<idelta;ix++) + ra += synth->romfile[taddr++]; + ptemp = ra / idelta; } - - } - } else { // Synthesis partial - int divis, hdivis, ofs, ofs3, toff; - int minorplace; + int divis = divtable[noteVal] >> 15; - int wf = tcache->waveform ; + partialOff.pcmoffs.pcmplace %= (Bit16u)divis; - divis = divtable[noteval]>>15; + if (ampval > 0) { + int wf = patchCache->waveform ; + int toff = partialOff.pcmoffs.pcmplace; + int minorplace = partialOff.pcmoffs.pcmoffset >> 14; - if(pOff->pcmoffs.pcmplace>=divis) pOff->pcmoffs.pcmplace = (uint16)(pOff->pcmoffs.pcmplace-divis); - - toff = pOff->pcmoffs.pcmplace; - minorplace = pOff->pcmoffs.pcmoffset >> 14; - - int pa, pb; + int pa, pb; - if(ampval>0) { + Bit32s filtval = getFiltEnvelope(); - filtval = getFiltEnvelope((int16)ptemp,partCache,tmppoly); + //synth->printDebug("Filtval: %d", filtval); - //LOG_MSG("Filtval: %d", filtval); - - if(wf==0) { + if (wf==0) { // Square waveform. Made by combining two pregenerated bandlimited // sawtooth waveforms // Pulse width is not yet correct - - hdivis = divis >> 1; - int divmark = smalldivtable[noteval]; - //int pw = (tcache->pulsewidth * pulsemod[filtval]) >> 8; - - - ofs = toff % (hdivis); - ofs3 = toff + ((divmark*pulsetable[partCache->pulsewidth])>>16); + int hdivis = divis >> 1; + int divmark = smalldivtable[noteVal]; + + if (hdivis == 0) { + synth->printDebug("ERROR: hdivis=0 generating square wave, this should never happen!"); + hdivis = 1; + } + int ofs = toff % hdivis; + + int ofs3 = toff + ((divmark * pulsetable[pulsewidth]) >> 16); ofs3 = ofs3 % (hdivis); - - pa = waveforms[1][noteval][(ofs<<2)+minorplace]; - pb = waveforms[0][noteval][(ofs3<<2)+minorplace]; - //ptemp = pa+pb+pulseoffset[tcache->pulsewidth]; - ptemp = (pa+pb)*4; - + + pa = waveforms[1][noteVal][(ofs << 2)+minorplace]; + pb = waveforms[0][noteVal][(ofs3 << 2)+minorplace]; + ptemp = (pa + pb) * 4; + // Non-bandlimited squarewave /* - ofs = (divis*pulsetable[tcache->pulsewidth])>>8; - if(toff < ofs) { + ofs = (divis*pulsetable[patchCache->pulsewidth])>>8; + if (toff < ofs) ptemp = 1 * WGAMP; - } else { + else ptemp = -1 * WGAMP; - }*/ - - + */ } else { // Sawtooth. Made by combining the full cosine and half cosine according // to how it looks on the MT-32. What it really does it takes the // square wave and multiplies it by a full cosine - // TODO: This area here crashes DosBox due to read overflow - if(toff < sawtable[noteval][partCache->pulsewidth]) { - ptemp = waveforms[2][noteval][(toff<<2)+minorplace]; - } else { - ptemp = waveforms[3][noteval][(toff<<2)+minorplace]; - } - ptemp = ptemp *4; - -// This is dosbox 0.62 canadacow's code. Reported to be worse than above 0.61 code -#if 0 - uint offsetpos = (toff<<2)+minorplace; - //int a = 0; - if(toff < sawtable[noteval][partCache->pulsewidth]) { - while(offsetpos>waveformsize[2][noteval]) { - offsetpos-=waveformsize[2][noteval]; - } - ptemp = waveforms[2][noteval][offsetpos]; - } else { - while(offsetpos>waveformsize[3][noteval]) { - offsetpos-=waveformsize[3][noteval]; - } - ptemp = waveforms[3][noteval][offsetpos]; - } - ptemp = ptemp *4; -#endif - - // ptemp = (int)(sin((double)toff / 100.0) * 100.0); - //ptemp = pa; + int waveoff = (toff << 2) + minorplace; + if (toff < sawtable[noteVal][pulsewidth]) + ptemp = waveforms[2][noteVal][waveoff % waveformsize[2][noteVal]]; + else + ptemp = waveforms[3][noteVal][waveoff % waveformsize[3][noteVal]]; + ptemp = ptemp * 4; // This is the correct way // Seems slow to me (though bandlimited) -- doesn't seem to // sound any better though /* hdivis = divis >> 1; - int divmark = smalldivtable[noteval]; - //int pw = (tcache->pulsewidth * pulsemod[filtval]) >> 8; - - + int divmark = smalldivtable[noteVal]; + //int pw = (patchCache->pulsewidth * pulsemod[filtval]) >> 8; + ofs = toff % (hdivis); - ofs3 = toff + ((divmark*pulsetable[tcache->pulsewidth])>>16); + ofs3 = toff + ((divmark*pulsetable[patchCache->pulsewidth])>>16); ofs3 = ofs3 % (hdivis); - - pa = waveforms[0][noteval][ofs]; - pb = waveforms[1][noteval][ofs3]; - ptemp = ((pa+pb) * waveforms[3][noteval][toff]) / WGAMP; + + pa = waveforms[0][noteVal][ofs]; + pb = waveforms[1][noteVal][ofs3]; + ptemp = ((pa+pb) * waveforms[3][noteVal][toff]) / WGAMP; ptemp = ptemp *4; */ - - - } - - //Very exact filter - //ptemp[t] = (int)iir_filter((float)ptemp[t],&partCache->history[t],filtcoeff[filtval][tcache->filtEnv.resonance]); - if(filtval>((FILTERGRAN*15)/16)) filtval = ((FILTERGRAN*15)/16); - ptemp = (int32)(usefilter)((float)ptemp,&partCache->history[0],filtcoeff[filtval][(int)tcache->filtEnv.resonance], tcache->filtEnv.resonance); - } else ptemp = 0; - - //ptemp[t] = Moog1(ptemp[t],&partCache->history[t],(float)filtval/8192.0,tcache->filtEnv.resonance); - //ptemp[t] = Moog2(ptemp[t],&partCache->history[t],(float)filtval/8192.0,tcache->filtEnv.resonance); - //ptemp[t] = simpleLowpass(ptemp[t],&partCache->history[t],(float)filtval/8192.0,tcache->filtEnv.resonance); - - // Use this to mute analogue synthesis - // ptemp = 0; - + //Very exact filter + if (filtval > ((FILTERGRAN * 15) / 16)) + filtval = ((FILTERGRAN * 15) / 16); + ptemp = (Bit32s)floor((usefilter)((float)ptemp, &history[0], filtcoeff[filtval][(int)patchCache->filtEnv.resonance], patchCache->filtEnv.resonance)); + } } - - // Build delta for position of next sample - /* - delta = (delta * tcache->fineshift)>>12; - delta = (delta * pdep)>>12; - delta = (delta * lfoat)>>12; - if(tcache->useBender) delta = (delta * *tmppoly->bendptr)>>12; - */ + // Build delta for position of next sample // Fix delta code - int64 tdelta = (int64)delta; - tdelta = (tdelta * tcache->fineshift)>>12; + Bit64s tdelta = (Bit64s)delta; + tdelta = (tdelta * patchCache->fineshift)>>12; tdelta = (tdelta * pdep)>>12; tdelta = (tdelta * lfoat)>>12; - if(tcache->useBender) tdelta = (tdelta * *tmppoly->bendptr)>>12; + if (patchCache->useBender) + tdelta = (tdelta * *poly->bendptr)>>12; // Add calculated delta to our waveform offset - pOff->pcmabs+=(int)tdelta; + partialOff.pcmabs += (int)tdelta; // Put volume envelope over generated sample ptemp = (ptemp * ampval) >> 9; - ptemp = (ptemp * *tmppoly->volumeptr) >> 7; - - partCache->envs[AMPENV].envpos++; - partCache->envs[PITCHENV].envpos++; - partCache->envs[FILTENV].envpos++; - - *partialBuf++ = (int16)ptemp; - } + ptemp = (ptemp * *poly->volumeptr) >> 7; + envs[AMPENV].envpos++; + envs[PITCHENV].envpos++; + envs[FILTENV].envpos++; + *partialBuf++ = (Bit16s)ptemp; + } + if (++length > 0) + memset(partialBuf, 0, length * 2); + return &myBuffer[0]; } -INLINE void CPartialMT32::mixBuffers(int16 * buf1, int16 *buf2, int len) { - // Early exit if no need to mix - if(tibrePair==NULL) return; - -#if USE_MMX == 0 - int i; - for(i=0;i<len;i++) { - int32 tmp1 = buf1[i]; - int32 tmp2 = buf2[i]; - tmp1 += tmp2; - buf1[i] = (int16)tmp1; - } -#else - len = (len>>2)+4; -#ifdef I_ASM - __asm { - mov ecx, len - mov esi, buf1 - mov edi, buf2 - -mixloop1: - movq mm1, [edi] - movq mm2, [esi] - paddw mm1,mm2 - movq [esi],mm1 - add edi,8 - add esi,8 - - dec ecx - cmp ecx,0 - jg mixloop1 - emms - } -#else - atti386_mixBuffers(buf1, buf2, len); -#endif +Bit16s *Partial::mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) { + if (buf1 == NULL) + return buf2; + if (buf2 == NULL) + return buf1; + + Bit16s *outBuf = buf1; +#if USE_MMX >= 1 + // KG: This seems to be fine + int donelen = i386_mixBuffers(buf1, buf2, len); + len -= donelen; + buf1 += donelen; + buf2 += donelen; #endif + while (len--) { + *buf1 = *buf1 + *buf2; + buf1++, buf2++; + } + return outBuf; } -INLINE void CPartialMT32::mixBuffersRingMix(int16 * buf1, int16 *buf2, int len) { -#if USE_MMX != 2 - int i; - for(i=0;i<len;i++) { +Bit16s *Partial::mixBuffersRingMix(Bit16s * buf1, Bit16s *buf2, int len) { + if (buf1 == NULL) + return NULL; + if (buf2 == NULL) { + Bit16s *outBuf = buf1; + while (len--) { + if (*buf1 < -8192) + *buf1 = -8192; + else if (*buf1 > 8192) + *buf1 = 8192; + buf1++; + } + return outBuf; + } + + Bit16s *outBuf = buf1; +#if USE_MMX >= 1 + // KG: This seems to be fine + int donelen = i386_mixBuffersRingMix(buf1, buf2, len); + len -= donelen; + buf1 += donelen; + buf2 += donelen; +#endif + while (len--) { float a, b; - a = ((float)buf1[i]) / 8192.0; - b = ((float)buf2[i]) / 8192.0; + a = ((float)*buf1) / 8192.0f; + b = ((float)*buf2) / 8192.0f; a = (a * b) + a; - if(a>1.0) a = 1.0; - if(a<-1.0) a = -1.0; - buf1[i] = (int16)(a * 8192.0); - - //buf1[i] = (int16)(((int32)buf1[i] * (int32)buf2[i]) >> 10) + buf1[i]; - } -#else - len = (len>>2)+4; -#ifdef I_ASM - __asm { - mov ecx, len - mov esi, buf1 - mov edi, buf2 - -mixloop2: - movq mm1, [esi] - movq mm2, [edi] - movq mm3, mm1 - pmulhw mm1, mm2 - paddw mm1,mm3 - movq [esi],mm1 - add edi,8 - add esi,8 - - dec ecx - cmp ecx,0 - jg mixloop2 - emms + if (a>1.0) + a = 1.0; + if (a<-1.0) + a = -1.0; + *buf1 = (Bit16s)(a * 8192.0f); + buf1++; + buf2++; + //buf1[i] = (Bit16s)(((Bit32s)buf1[i] * (Bit32s)buf2[i]) >> 10) + buf1[i]; } -#else - atti386_mixBuffersRingMix(buf1, buf2, len); -#endif -#endif + return outBuf; } -INLINE void CPartialMT32::mixBuffersRing(int16 * buf1, int16 *buf2, int len) { -#if USE_MMX != 2 - int i; - for(i=0;i<len;i++) { - float a, b; - a = ((float)buf1[i]) / 8192.0; - b = ((float)buf2[i]) / 8192.0; - a *= b; - if(a>1.0) a = 1.0; - if(a<-1.0) a = -1.0; - buf1[i] = (int16)(a * 8192.0); - //buf1[i] = (int16)(((int32)buf1[i] * (int32)buf2[i]) >> 10); +Bit16s *Partial::mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) { + if (buf1 == NULL) { + return NULL; } -#else - len = (len>>2)+4; -#ifdef I_ASM - __asm { - mov ecx, len - mov esi, buf1 - mov edi, buf2 - -mixloop3: - movq mm1, [esi] - movq mm2, [edi] - pmulhw mm1, mm2 - movq [esi],mm1 - add edi,8 - add esi,8 - - dec ecx - cmp ecx,0 - jg mixloop3 - emms + if (buf2 == NULL) { + return NULL; } -#else - atti386_mixBuffersRing(buf1, buf2, len); -#endif -#endif -} -INLINE void CPartialMT32::mixBuffersStereo(int16 *buf1, int16 *buf2, int16 *outBuf, int len) { - int i,m; - m=0; - for(i=0;i<len;i++) { - *outBuf++ = (*buf1); + Bit16s *outBuf = buf1; +#if USE_MMX >= 1 + // FIXME:KG: Not really checked as working + int donelen = i386_mixBuffersRing(buf1, buf2, len); + len -= donelen; + buf1 += donelen; + buf2 += donelen; +#endif + while (len--) { + float a, b; + a = ((float)*buf1) / 8192.0f; + b = ((float)*buf2) / 8192.0f; + a *= b; + if (a>1.0) + a = 1.0; + if (a<-1.0) + a = -1.0; + *buf1 = (Bit16s)(a * 8192.0f); buf1++; - *outBuf++ = (*buf2); buf2++; } - + return outBuf; } -bool CPartialMT32::produceOutput(int16 * partialBuf, long length) { - if (!isActive) return false; - if (alreadyOutputed) return false; - int i; +void Partial::mixBuffersStereo(Bit16s *buf1, Bit16s *buf2, Bit16s *outBuf, int len) { + if (buf2 == NULL) { + while (len--) { + *outBuf++ = *buf1++; + *outBuf++ = 0; + } + } else if (buf1 == NULL) { + while (len--) { + *outBuf++ = 0; + *outBuf++ = *buf2++; + } + } else { + while (len--) { + *outBuf++ = *buf1++; + *outBuf++ = *buf2++; + } + } +} - //alreadyOutputed = true; +bool Partial::produceOutput(Bit16s *partialBuf, long length) { + if (!isActive() || alreadyOutputed) + return false; + if (poly == NULL) { + synth->printDebug("*** ERROR: poly is NULL at Partial::produceOutput()!"); + return false; + } - memset(&pairBuffer[0],0,length*4); - memset(&myBuffer[0],0,length*4); + Bit16s *pairBuf = NULL; // Check for dependant partial - if(tibrePair != NULL) { - if ((tibrePair->ownerChan == this->ownerChan) && (!tibrePair->alreadyOutputed)) { - tibrePair->generateSamples(pairBuffer,length); - } - } else { - if((useMix!=0) && (useMix != 3)){ - // Generate noise for parialless ring mix - for(i=0;i<length;i++) pairBuffer[i] = smallnoise[i] << 2; + if (pair != NULL) { + if (!pair->alreadyOutputed) { + // Note: pair may have become NULL after this + pairBuf = pair->generateSamples(length); } + } else if (useNoisePair) { + // Generate noise for pairless ring mix + pairBuf = smallnoise; } - generateSamples(myBuffer, length); -/* FILE *fo = fopen("/tmp/samp.raw", "a"); - for(i = 0; i < length; i++) - fwrite(myBuffer + i, 1, 2, fo); - fclose(fo); -*/ - int16 * p1buf, * p2buf; - - if((partNum==0) || ((partNum==1) && (tibrePair==NULL))) { - p1buf = &myBuffer[0]; - p2buf = &pairBuffer[0]; + Bit16s *myBuf = generateSamples(length); + + if (myBuf == NULL && pairBuf == NULL) + return false; + + Bit16s * p1buf, * p2buf; + + if (structurePosition == 0 || pairBuf == NULL) { + p1buf = myBuf; + p2buf = pairBuf; } else { - p2buf = &myBuffer[0]; - p1buf = &pairBuffer[0]; + p2buf = myBuf; + p1buf = pairBuf; } - -// LOG_MSG("useMix: %d", useMix); - - switch(useMix) { + + //synth->printDebug("mixType: %d", mixType); + + Bit16s *mixedBuf; + switch(mixType) { case 0: // Standard sound mix - mixBuffers(p1buf, p2buf, length); + mixedBuf = mixBuffers(p1buf, p2buf, length); break; + case 1: // Ring modulation with sound mix - mixBuffersRingMix(p1buf, p2buf, length); + mixedBuf = mixBuffersRingMix(p1buf, p2buf, length); break; + case 2: // Ring modulation alone - mixBuffersRing(p1buf, p2buf, length); + mixedBuf = mixBuffersRing(p1buf, p2buf, length); break; + case 3: - // Stereo mixing. One partial to one channel, one to another. + // Stereo mixing. One partial to one speaker channel, one to another. + // FIXME:KG: Surely we should be multiplying by the left/right volumes here? mixBuffersStereo(p1buf, p2buf, partialBuf, length); return true; - break; + default: - mixBuffers(p1buf, p2buf, length); + mixedBuf = mixBuffers(p1buf, p2buf, length); break; } - - - int m; - m = 0; - int16 leftvol, rightvol; - if (!tmppoly->isRy) { - leftvol = tmppoly->pansetptr->leftvol; - rightvol = tmppoly->pansetptr->rightvol; + + if (mixedBuf == NULL) + return false; + + Bit16s leftvol, rightvol; + leftvol = poly->pansetptr->leftvol; + rightvol = poly->pansetptr->rightvol; + +#if USE_MMX >= 2 + // FIXME:KG: This appears to introduce crackle + int donelen = i386_partialProductOutput(length, leftvol, rightvol, partialBuf, mixedBuf); + length -= donelen; + mixedBuf += donelen; + partialBuf += donelen * 2; +#endif + while (length--) { + *partialBuf++ = (Bit16s)(((Bit32s)*mixedBuf * (Bit32s)leftvol) >> 16); + *partialBuf++ = (Bit16s)(((Bit32s)*mixedBuf * (Bit32s)rightvol) >> 16); + mixedBuf++; + } + return true; +} + +Bit32s Partial::getFiltEnvelope() { + int reshigh; + + int cutoff,depth,keyfollow, realfollow; + + envstatus *tStat = &envs[FILTENV]; + + keyfollow = filtVal; + realfollow = realVal; + + int fr = poly->freqnum; + + if (tStat->decaying) { + reshigh = tStat->envbase; + reshigh = (reshigh + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + if (tStat->envpos >= tStat->envsize) + reshigh = 0; } else { - leftvol = (int16)drumPan[tmppoly->pcmnum][0]; - rightvol = (int16)drumPan[tmppoly->pcmnum][1]; + if (tStat->envstat==4) { + reshigh = patchCache->filtsustain; + if (!poly->sustain) { + startDecay(FILTENV, reshigh); + } + } else { + if ((tStat->envstat==-1) || (tStat->envpos >= tStat->envsize)) { + if (tStat->envstat==-1) + tStat->envbase = 0; + else + tStat->envbase = patchCache->filtEnv.envlevel[tStat->envstat]; + tStat->envstat++; + tStat->envpos = 0; + if (tStat->envstat==3) + tStat->envsize = lasttimetable[(int)patchCache->filtEnv.envtime[tStat->envstat]]; + else + tStat->envsize = (envtimetable[(int)patchCache->filtEnv.envtime[tStat->envstat]] * timekeytable[(int)patchCache->filtEnv.envtkf][poly->freqnum]) >> 8; + + tStat->envsize++; + tStat->envdist = patchCache->filtEnv.envlevel[tStat->envstat] - tStat->envbase; + } + + reshigh = tStat->envbase; + reshigh = (reshigh + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + + } + tStat->prevlevel = reshigh; } -#if USE_MMX == 0 - for(i=0;i<length;i++) { - partialBuf[m] = (int16)(((int32)p1buf[i] * (int32)leftvol) >> 16); - m++; - partialBuf[m] = (int16)(((int32)p1buf[i] * (int32)rightvol) >> 16); - m++; + cutoff = patchCache->filtEnv.cutoff; + + //if (patchCache->waveform==1) reshigh = (reshigh * 3) >> 2; + + depth = patchCache->filtEnv.envdepth; + + //int sensedep = (depth * 127-patchCache->filtEnv.envsense) >> 7; + depth = (depth * filveltable[poly->vel][(int)patchCache->filtEnv.envsense]) >> 8; + + int bias = patchCache->tvfbias; + int dist; + + if (bias!=0) { + //synth->printDebug("Cutoff before %d", cutoff); + if (patchCache->tvfdir == 0) { + if (fr < bias) { + dist = bias - fr; + cutoff = (cutoff * fbiastable[patchCache->tvfblevel][dist]) >> 8; + } + } else { + // > Bias + if (fr > bias) { + dist = fr - bias; + cutoff = (cutoff * fbiastable[patchCache->tvfblevel][dist]) >> 8; + } + + } + //synth->printDebug("Cutoff after %d", cutoff); + } + + depth = (depth * fildeptable[patchCache->tvfdepth][fr]) >> 8; + reshigh = (reshigh * depth) >> 7; + + Bit32s tmp; + + cutoff *= keyfollow; + cutoff /= realfollow; + + reshigh *= keyfollow; + reshigh /= realfollow; + + if (cutoff>100) + cutoff = 100; + else if (cutoff<0) + cutoff = 0; + if (reshigh>100) + reshigh = 100; + else if (reshigh<0) + reshigh = 0; + tmp = nfilttable[fr][cutoff][reshigh]; + //tmp *= keyfollow; + //tmp /= realfollow; + + //synth->printDebug("Cutoff %d, tmp %d, freq %d", cutoff, tmp, tmp * 256); + return tmp; +} + +bool Partial::shouldReverb() { + if (!isActive()) + return false; + return poly->reverb; +} + +Bit32s Partial::getAmpEnvelope() { + Bit32s tc; + + envstatus *tStat = &envs[AMPENV]; + + if (!play) + return 0; + + if (tStat->decaying) { + tc = tStat->envbase; + tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + if (tc < 0) + tc = 0; + if ((tStat->envpos >= tStat->envsize) || (tc == 0)) { + play = false; + // Don't have to worry about prevlevel storage or anything, this partial's about to die + return 0; + } + } else { + if ((tStat->envstat==-1) || (tStat->envpos >= tStat->envsize)) { + if (tStat->envstat==-1) + tStat->envbase = 0; + else + tStat->envbase = patchCache->ampEnv.envlevel[tStat->envstat]; + tStat->envstat++; + tStat->envpos = 0; + + switch(tStat->envstat) { + case 0: + //Spot for velocity time follow + //Only used for first attack + tStat->envsize = (envtimetable[(int)patchCache->ampEnv.envtime[tStat->envstat]] * veltkeytable[(int)patchCache->ampEnv.envvkf][poly->vel]) >> 8; + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + break; + case 3: + // Final attack envelope uses same time table as the decay + //tStat->envsize = decaytimetable[patchCache->ampEnv.envtime[tStat->envstat]]; + tStat->envsize = lasttimetable[(int)patchCache->ampEnv.envtime[tStat->envstat]]; + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + break; + case 4: + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + tc = patchCache->ampsustain; + if (!poly->sustain) + startDecay(AMPENV, tc); + else + tStat->sustaining = true; + + goto PastCalc; + default: + //Spot for timekey follow + //Only used in subsquent envelope parameters, including the decay + tStat->envsize = (envtimetable[(int)patchCache->ampEnv.envtime[tStat->envstat]] * timekeytable[(int)patchCache->ampEnv.envtkf][poly->freqnum]) >> 8; + + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + break; + } + + tStat->envsize++; + tStat->envdist = patchCache->ampEnv.envlevel[tStat->envstat] - tStat->envbase; + + if (tStat->envdist != 0) { + tStat->counter = abs(tStat->envsize / tStat->envdist); + //synth->printDebug("Pos %d, envsize %d envdist %d", tStat->envstat, tStat->envsize, tStat->envdist); + } else { + tStat->counter = 0; + //synth->printDebug("Pos %d, envsize %d envdist %d", tStat->envstat, tStat->envsize, tStat->envdist); + } + } + tc = tStat->envbase; + tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + tStat->count = tStat->counter; +PastCalc: + tc = (tc * (Bit32s)patchCache->amplevel) >> 7; } -#else - long quadlen = (length >> 1)+2; -#ifdef I_ASM - __asm { - mov ecx,quadlen - mov ax, leftvol - shl eax,16 - mov ax, rightvol - movd mm1, eax - movd mm2, eax - psllq mm1, 32 - por mm1, mm2 - mov edi, partialBuf - mov esi, p1buf -mmxloop1: - mov bx, [esi] - add esi,2 - mov dx, [esi] - add esi,2 - - mov ax, dx - shl eax, 16 - mov ax, dx - movd mm2,eax - psllq mm2, 32 - mov ax, bx - shl eax, 16 - mov ax, bx - movd mm3,eax - por mm2,mm3 - - pmulhw mm2, mm1 - movq [edi], mm2 - add edi, 8 - - dec ecx - cmp ecx,0 - jg mmxloop1 - emms + + // Prevlevel storage is bottle neck + tStat->prevlevel = tc; + + //Bias level crap stuff now + + int dist, bias; + + for (int i = 0; i < 2; i++) { + if (patchCache->ampblevel[i]!=0) { + bias = patchCache->ampbias[i]; + if (patchCache->ampdir[i]==0) { + // < Bias + if (poly->freqnum < bias) { + dist = bias-poly->freqnum; + tc = (tc * ampbiastable[patchCache->ampblevel[i]][dist]) >> 8; + } + } else { + // > Bias + if (poly->freqnum > bias) { + dist = poly->freqnum-bias; + tc = (tc * ampbiastable[patchCache->ampblevel[i]][dist]) >> 8; + } + } + } } -#else - atti386_PartProductOutput(quadlen, leftvol, rightvol, partialBuf, p1buf); -#endif -#endif - - return true; + return tc; +} + +Bit32s Partial::getPitchEnvelope() { + envstatus *tStat = &envs[PITCHENV]; + + Bit32s tc; + pitchSustain = false; + if (tStat->decaying) { + if (tStat->envpos >= tStat->envsize) + tc = patchCache->pitchEnv.level[4]; + else { + tc = tStat->envbase; + tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + } + } else { + if (tStat->envstat==3) { + tc = patchCache->pitchsustain; + if (poly->sustain) + pitchSustain = true; + else + startDecay(PITCHENV, tc); + } else { + if ((tStat->envstat==-1) || (tStat->envpos >= tStat->envsize)) { + tStat->envstat++; + + tStat->envbase = patchCache->pitchEnv.level[tStat->envstat]; + tStat->envsize = (envtimetable[(int)patchCache->pitchEnv.time[tStat->envstat]] * timekeytable[(int)patchCache->pitchEnv.timekeyfollow][poly->freqnum]) >> 8; + + tStat->envpos = 0; + tStat->envsize++; + tStat->envdist = patchCache->pitchEnv.level[tStat->envstat+1] - tStat->envbase; + } + tc = tStat->envbase; + tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + } + tStat->prevlevel = tc; + } + return tc; +} + +void Partial::startDecayAll() { + startDecay(AMPENV, envs[AMPENV].prevlevel); + startDecay(FILTENV, envs[FILTENV].prevlevel); + startDecay(PITCHENV, envs[PITCHENV].prevlevel); + pitchSustain = false; } + +void Partial::startDecay(int envnum, Bit32s startval) { + envstatus *tStat = &envs[envnum]; + + tStat->sustaining = false; + tStat->decaying = true; + tStat->envpos = 0; + tStat->envbase = startval; + + switch(envnum) { + case AMPENV: + tStat->envsize = (decaytimetable[(int)patchCache->ampEnv.envtime[4]] * timekeytable[(int)patchCache->ampEnv.envtkf][poly->freqnum]) >> 8; + tStat->envdist = -startval; + break; + case FILTENV: + tStat->envsize = (decaytimetable[(int)patchCache->filtEnv.envtime[4]] * timekeytable[(int)patchCache->filtEnv.envtkf][poly->freqnum]) >> 8; + tStat->envdist = -startval; + break; + case PITCHENV: + tStat->envsize = (decaytimetable[(int)patchCache->pitchEnv.time[3]] * timekeytable[(int)patchCache->pitchEnv.timekeyfollow][poly->freqnum]) >> 8 ; + tStat->envdist = patchCache->pitchEnv.level[4] - startval; + break; + default: + break; + } + tStat->envsize++; +}; diff --git a/backends/midi/mt32/partial.h b/backends/midi/mt32/partial.h index e3af4ad5a9..20160dca5f 100644 --- a/backends/midi/mt32/partial.h +++ b/backends/midi/mt32/partial.h @@ -1,115 +1,124 @@ -/* ScummVM - Scumm Interpreter - * Copyright (C) 2004 The ScummVM project - * Based on Tristan's conversion of Canadacow's code +/* Copyright (c) 2003-2004 Various contributors * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * 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 General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * $Header$ + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. */ -#ifndef CPARTIALMT32_H -#define CPARTIALMT32_H +#ifndef MT32EMU_PARTIAL_H +#define MT32EMU_PARTIAL_H -#include "backends/midi/mt32/structures.h" +namespace MT32Emu { + +struct envstatus { + Bit32s envpos; + Bit32s envstat; + Bit32s envbase; + Bit32s envdist; + Bit32s envsize; + + bool sustaining; + bool decaying; + Bit32s prevlevel; + + Bit32s counter; + Bit32s count; +}; + +class Synth; // Class definition of MT-32 partials. 32 in all. -class CPartialMT32 { +class Partial { private: - int useMix; - int partNum; + Synth *synth; // Only used for sending debug output - int pN; + int ownerPart; // -1 if unassigned + int mixType; + int structurePosition; // 0 or 1 of a structure pair + bool useNoisePair; + Bit16s myBuffer[MAX_SAMPLE_OUTPUT]; + bool play; + // Keyfollowed note value + int noteVal; - int16 myBuffer[2048]; - // For temporary output of paired buffer - int16 pairBuffer[2048]; + // Keyfollowed filter values + int realVal; + int filtVal; - void mixBuffers(int16 * buf1, int16 * buf2, int len); - void mixBuffersRingMix(int16 * buf1, int16 * buf2, int len); - void mixBuffersRing(int16 * buf1, int16 * buf2, int len); - void mixBuffersStereo(int16 * buf1, int16 * buf2, int16 * outBuf, int len); + envstatus envs[3]; + int pulsewidth; -public: - patchCache *tcache; - patchCache cachebackup[4]; + Bit32u lfoPos; + soundaddr partialOff; + + Bit32u ampEnvCache; + Bit32u pitchEnvCache; + + float history[32]; + + bool pitchSustain; + + int loopPos; - //FILE *fp; - //FILE *fp2; + dpoly *poly; - dpoly::partialStatus *partCache; + Bit16s *mixBuffers(Bit16s * buf1, Bit16s * buf2, int len); + Bit16s *mixBuffersRingMix(Bit16s * buf1, Bit16s * buf2, int len); + Bit16s *mixBuffersRing(Bit16s * buf1, Bit16s * buf2, int len); + void mixBuffersStereo(Bit16s * buf1, Bit16s * buf2, Bit16s * outBuf, int len); - CPartialMT32 *tibrePair; - bool isActive; + Bit32s getFiltEnvelope(); + Bit32s getAmpEnvelope(); + Bit32s getPitchEnvelope(); + + void initKeyFollow(int freqNum); + +public: + PatchCache *patchCache; + PatchCache cachebackup; + + Partial *pair; bool alreadyOutputed; - int ownerChan; - int64 age; - int timbreNum; - dpoly *tmppoly; - - CPartialMT32(int partialNum) { - - isActive = false; - pN = partialNum; - - /* - sprintf(buffer, "partial%d.raw",pN); - fp = fopen(buffer,"wb"); - - sprintf(buffer, "partial%dx.raw",pN); - fp2 = fopen(buffer,"wb"); - */ - - - }; - - void startPartial(dpoly *usePoly, patchCache *useCache, dpoly::partialStatus * usePart, CPartialMT32 * pairPart, int mixType, int num, int ownChan, int timNum) { - - //LOG_MSG("Starting partial %d for %d", num, ownChan); - tmppoly = usePoly; - tcache = useCache; - partCache = usePart; - tibrePair = pairPart; - isActive = true; - useMix = mixType; - partNum = num; - age = 0; - ownerChan = ownChan; - alreadyOutputed = false; - timbreNum = timNum; - memset(usePart->history,0,sizeof(usePart->history)); - - } - - void stopPartial(void) { isActive = false; } - - + Bit64s age; + + Partial(Synth *synth); + + int getOwnerPart(); + bool isActive(); + void activate(int part); + void deactivate(void); + void startPartial(dpoly *usePoly, PatchCache *useCache, Partial *pairPartial); + void startDecay(int envnum, Bit32s startval); + void startDecayAll(); + bool shouldReverb(); + // Returns true only if data written to buffer - // This function (unline the one below it) returns processed stereo samples + // This function (unlike the one below it) returns processed stereo samples // made from combining this single partial with its pair, if it has one. - bool produceOutput(int16 * partialBuf, long length); - - // This function produces mono sample output of the specific partial - void generateSamples(int16 * partialBuf, long length); + bool produceOutput(Bit16s * partialBuf, long length); + // This function produces mono sample output using the partial's private internal buffer + Bit16s *generateSamples(long length); }; +} #endif - diff --git a/backends/midi/mt32/partialManager.cpp b/backends/midi/mt32/partialManager.cpp new file mode 100644 index 0000000000..bbb1c17d71 --- /dev/null +++ b/backends/midi/mt32/partialManager.cpp @@ -0,0 +1,269 @@ +/* Copyright (c) 2003-2004 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <memory.h> + +#include "mt32emu.h" + +using namespace MT32Emu; + +PartialManager::PartialManager(Synth *useSynth) { + this->synth = useSynth; + for (int i = 0; i < MAXPARTIALS; i++) + partialTable[i] = new Partial(synth); +} + +PartialManager::~PartialManager(void) { + for (int i = 0; i < MAXPARTIALS; i++) + delete partialTable[i]; +} + +void PartialManager::GetPerPartPartialUsage(int usage[9]) { + memset(usage, 0, 9 * sizeof (int)); + for (int i = 0; i < MAXPARTIALS; i++) { + if (partialTable[i]->isActive()) + usage[partialTable[i]->getOwnerPart()]++; + } +} + +void PartialManager::ClearAlreadyOutputed() { + for (int i = 0; i < MAXPARTIALS; i++) + partialTable[i]->alreadyOutputed = false; +} + +void PartialManager::AgeAll() { + for (int i = 0; i < MAXPARTIALS; i++) + partialTable[i]->age++; +} + +bool PartialManager::shouldReverb(int i) { + return partialTable[i]->shouldReverb(); +} + +bool PartialManager::ProduceOutput(int i, Bit16s *buffer, Bit32u bufferLength) { + return partialTable[i]->produceOutput(buffer, bufferLength); +} + +void PartialManager::DeactivateAll() { + for (int i = 0; i < MAXPARTIALS; i++) { + partialTable[i]->deactivate(); + } +} + +unsigned int PartialManager::SetReserve(char *rset) { + unsigned int pr = 0; + for (int x = 0; x < 9; x++) { + for (int y = 0; y < rset[x]; y++) { + PartialReserveTable[pr] = x; + pr++; + } + } + return pr; +} + +Partial * PartialManager::AllocPartial(int partNum) { + Partial *outPartial = NULL; + + // Use the first inactive partial reserved for the specified part (if there are any) + // Otherwise, use the last inactive partial, if any + for (int i = 0; i < MAXPARTIALS; i++) { + if (!partialTable[i]->isActive()) { + outPartial = partialTable[i]; + if (PartialReserveTable[i] == partNum) + break; + } + } + if (outPartial != NULL) { + outPartial->activate(partNum); + outPartial->age = 0; + } + return outPartial; +} + +unsigned int PartialManager::GetFreePartialCount(void) { + int count = 0; + memset(partialPart, 0, sizeof(partialPart)); + for (int i = 0; i < MAXPARTIALS; i++) { + if (!partialTable[i]->isActive()) + count++; + else + partialPart[partialTable[i]->getOwnerPart()]++; + } + return count; +} + +/* +bool PartialManager::FreePartials(unsigned int needed, int partNum) { + int i; + int myPartPrior = (int)mt32ram.params.system.reserveSettings[partNum]; + if (myPartPrior<partialPart[partNum]) { + //This can have more parts, must kill off those with less priority + int most, mostPart; + while (needed > 0) { + int selectPart = -1; + //Find the worst offender with more partials than allocated and kill them + most = -1; + mostPart = -1; + int diff; + + for (i=0;i<9;i++) { + diff = partialPart[i] - (int)mt32ram.params.system.reserveSettings[i]; + + if (diff>0) { + if (diff>most) { + most = diff; + mostPart = i; + } + } + } + selectPart = mostPart; + if (selectPart == -1) { + // All parts are within the allocated limits, you suck + // Look for first partial not of this part that's decaying perhaps? + return false; + } + bool found; + int oldest; + int oldnum; + while (partialPart[selectPart] > (int)mt32ram.params.system.reserveSettings[selectPart]) { + oldest = -1; + oldnum = -1; + found = false; + for (i = 0; i < MAXPARTIALS; i++) { + if (partialTable[i]->isActive) { + if (partialTable[i]->ownerPart == selectPart) { + found = true; + if (partialTable[i]->age > oldest) { + oldest = partialTable[i]->age; + oldnum = i; + } + } + } + } + if (!found) break; + partialTable[oldnum]->deactivate(); + --partialPart[selectPart]; + --needed; + } + + } + return true; + + } else { + //This part has reached its max, must kill off its own + bool found; + int oldest; + int oldnum; + while (needed > 0) { + oldest = -1; + oldnum = -1; + found = false; + for (i = 0; i < MAXPARTIALS; i++) { + if (partialTable[i]->isActive) { + if (partialTable[i]->ownerPart == partNum) { + found = true; + if (partialTable[i]->age > oldest) { + oldest = partialTable[i]->age; + oldnum = i; + } + } + } + } + if (!found) break; + partialTable[oldnum]->deactivate(); + --needed; + } + // Couldn't free enough partials, sorry + if (needed>0) return false; + return true; + } + +} +*/ +bool PartialManager::FreePartials(unsigned int needed, int partNum) { + if (needed == 0) { + return true; + } + // Reclaim partials reserved for this part + // Kill those that are already decaying first + /* + for (int i = 0; i < MAXPARTIALS; i++) { + if (PartialReserveTable[i] == partNum) { + if (partialTable[i]->ownerPart != partNum) { + if (partialTable[i]->partCache->envs[AMPENV].decaying) { + partialTable[i]->isActive = false; + --needed; + if (needed == 0) + return true; + } + } + } + }*/ + // Then kill those with the lowest part priority -- oldest at the moment + while (needed > 0) { + Bit64s prior = -1; + int priornum = -1; + + for (int i = 0; i < MAXPARTIALS; i++) { + if (PartialReserveTable[i] == partNum && partialTable[i]->isActive() && partialTable[i]->getOwnerPart() != partNum) { + /* + if (mt32ram.params.system.reserveSettings[partialTable[i]->ownerPart] < prior) { + prior = mt32ram.params.system.reserveSettings[partialTable[i]->ownerPart]; + priornum = i; + }*/ + if (partialTable[i]->age > prior) { + prior = partialTable[i]->age; + priornum = i; + } + } + } + if (priornum != -1) { + partialTable[priornum]->deactivate(); + --needed; + } else { + break; + } + } + + // Kill off the oldest partials within this part + + while (needed > 0) { + Bit64s oldest = -1; + Bit64s oldlist = -1; + for (int i = 0; i < MAXPARTIALS; i++) { + if (partialTable[i]->isActive()) { + if (partialTable[i]->getOwnerPart() == partNum) { + if (partialTable[i]->age > oldest) { + oldest = partialTable[i]->age; + oldlist = i; + } + } + } + } + if (oldlist != -1) { + partialTable[oldlist]->deactivate(); + --needed; + } else { + break; + } + } + return needed == 0; +} diff --git a/backends/midi/mt32/partialManager.h b/backends/midi/mt32/partialManager.h new file mode 100644 index 0000000000..6cc50fecd3 --- /dev/null +++ b/backends/midi/mt32/partialManager.h @@ -0,0 +1,56 @@ +/* Copyright (c) 2003-2004 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_PARTIALMANAGER_H +#define MT32EMU_PARTIALMANAGER_H + +#define MAXPARTIALS 32 + +namespace MT32Emu { + +class Synth; + +class PartialManager { +private: + Synth *synth; // Only used for sending debug output + + Partial *partialTable[MAXPARTIALS]; + Bit32s PartialReserveTable[MAXPARTIALS]; + Bit32s partialPart[9]; // The count of partials played per part + +public: + PartialManager(Synth *synth); + ~PartialManager(); + Partial *AllocPartial(int partNum); + unsigned int GetFreePartialCount(void); + bool FreePartials(unsigned int needed, int partNum); + unsigned int SetReserve(char *rset); + void DeactivateAll(); + void AgeAll(); + bool ProduceOutput(int i, Bit16s *buffer, Bit32u bufferLength); + bool shouldReverb(int i); + void ClearAlreadyOutputed(); + void GetPerPartPartialUsage(int usage[9]); +}; + +} + +#endif diff --git a/backends/midi/mt32/structures.h b/backends/midi/mt32/structures.h index 9fbc8bd627..baa4fb588a 100644 --- a/backends/midi/mt32/structures.h +++ b/backends/midi/mt32/structures.h @@ -1,448 +1,60 @@ -/* ScummVM - Scumm Interpreter - * Copyright (C) 2004 The ScummVM project - * Based on Tristan's conversion of Canadacow's code +/* Copyright (c) 2003-2004 Various contributors * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * 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 General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * $Header$ + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. */ -#ifndef MT32STRUCTURES_H -#define MT32STRUCTURES_H +#ifndef MT32EMU_STRUCTURES_H +#define MT32EMU_STRUCTURES_H -#include "stdafx.h" -#include "common/scummsys.h" +#if (defined (_MSC_VER) && defined(_M_IX86)) || (defined(__GNUC__) && defined(__i386__)) +#define HAVE_X86 +#endif -#if defined(_MSC_VER) -typedef unsigned __int64 uint64; -typedef signed __int64 int64; +#define MAX_SAMPLE_OUTPUT 4096 +#ifdef HAVE_X86 +#define USE_MMX 1 #else -typedef unsigned long long uint64; -typedef signed long long int64; +#define USE_MMX 0 #endif -#define INLINE - - -static inline void LOG_MSG(char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vfprintf(stdout, fmt, ap); - va_end(ap); - fprintf(stdout, "\n"); - fflush(stdout); -} - -#define ALIGN_PACKED GCC_PACK - -#if defined(WIN32) && !(defined(__CYGWIN__) || defined(__MINGW32__)) +namespace MT32Emu { +#ifdef _MSC_VER +#define ALIGN_PACKED __declspec(align(1)) +typedef unsigned __int64 Bit64u; +typedef signed __int64 Bit64s; #else - -#ifdef HAVE_X86 -#define eflag(value) __asm__ __volatile__("pushfl \n popfl \n" : : "a"(value)) -#define cpuid_flag (1 << 21) - -static inline bool atti386_DetectCPUID() -{ - unsigned int result; - - /* is there a cpuid */ - result = cpuid_flag; /* set test */ - eflag(result); - if (!(result & cpuid_flag)) - return false; - - result = 0; /* clear test */ - eflag(result); - if (result & cpuid_flag) - return false; - - return true; -} - -static inline bool atti386_DetectSIMD() -{ - unsigned int result; - - if (atti386_DetectCPUID() == false) - return false; - - /* check cpuid */ - __asm__ __volatile__( - "movl $1, %%eax \n" \ - "cpuid \n" \ - "movl %%edx, %0 \n" \ - : "=r"(result) : : "eax", "ebx", "ecx", "edx"); - - if (result & (1 << 25)) - return true; - - return false; -} - -static inline bool atti386_Detect3DNow() -{ - unsigned int result; - - if (atti386_DetectCPUID() == false) - return false; - - /* get cpuid */ - __asm__ __volatile__( - "movl $0x80000001, %%eax \n" \ - "cpuid \n" \ - "movl %%edx, %0 \n" \ - : "=r"(result) : : "eax", "ebx", "ecx", "edx"); - - if (result & 0x80000000) - return true; - - return false; -} - - -static inline float atti386_iir_filter_sse(float *output, float *hist1_ptr, float *coef_ptr) -{ - - __asm__ __volatile__ ( - "pushl %1 \n" \ - "pushl %2 \n" \ - "movss 0(%0), %%xmm1 \n" \ - "movups 0(%1), %%xmm2 \n" \ - "movlps 0(%2), %%xmm3 \n" \ - " \n" \ - "shufps $0x44, %%xmm3, %%xmm3 \n" \ - " \n" \ - "mulps %%xmm3, %%xmm2 \n" \ - " \n" \ - "subss %%xmm2, %%xmm1 \n" \ - "shufps $0x39, %%xmm2, %%xmm2 \n" \ - "subss %%xmm2, %%xmm1 \n" \ - " \n" \ - "movss %%xmm1, 0(%2) \n" \ - " \n" \ - "shufps $0x39, %%xmm2, %%xmm2 \n" \ - "addss %%xmm2, %%xmm1 \n" \ - " \n" \ - "shufps $0x39, %%xmm2, %%xmm2 \n" \ - "addss %%xmm2, %%xmm1 \n" \ - " \n" \ - "movss %%xmm3, 4(%2) \n" \ - " \n" \ - "addl $16, %1 \n" \ - "addl $8, %2 \n" \ - " \n" \ - "movups 0(%1), %%xmm2 \n" \ - " \n" \ - "movlps 0(%2), %%xmm3 \n" \ - "shufps $0x44, %%xmm3, %%xmm3 \n" \ - " \n" \ - "mulps %%xmm3, %%xmm2 \n" \ - " \n" \ - "subss %%xmm2, %%xmm1 \n" \ - "shufps $0x39, %%xmm2, %%xmm2 \n" \ - "subss %%xmm2, %%xmm1 \n" \ - " \n" \ - "movss %%xmm1, 0(%2) \n" \ - " \n" \ - "shufps $0x39, %%xmm2, %%xmm2 \n" \ - "addss %%xmm2, %%xmm1 \n" \ - " \n" \ - "shufps $0x39, %%xmm2, %%xmm2 \n" \ - "addss %%xmm2, %%xmm1 \n" \ - " \n" \ - "movss %%xmm3, 4(%2) \n" \ - "movss %%xmm1, 0(%0) \n" \ - "popl %2 \n" \ - "popl %1 \n" \ - : : "r"(output), "r"(coef_ptr), "r"(hist1_ptr) - : "xmm1", "xmm2", "xmm3", "memory"); - - return(*output); -} - -static inline float atti386_iir_filter_3DNow(float output, float *hist1_ptr, float *coef_ptr) -{ - float tmp; - - __asm__ __volatile__ ( - "movq %0, %%mm1 \n" \ - " \n" \ - "movl %1, %%ebx \n" \ - "movq 0(%%ebx), %%mm2 \n" \ - " \n" \ - "movl %2, %%eax; \n" \ - "movq 0(%%eax), %%mm3 \n" \ - " \n" \ - "pfmul %%mm3, %%mm2 \n" \ - "pfsub %%mm2, %%mm1 \n" \ - " \n" \ - "psrlq $32, %%mm2 \n" \ - "pfsub %%mm2, %%mm1 \n" \ - " \n" \ - "movd %%mm1, %3 \n" \ - " \n" \ - "addl $8, %%ebx \n" \ - "movq 0(%%ebx), %%mm2 \n" \ - "movq 0(%%eax), %%mm3 \n" \ - " \n" \ - "pfmul %%mm3, %%mm2 \n" \ - "pfadd %%mm2, %%mm1 \n" \ - " \n" \ - "psrlq $32, %%mm2 \n" \ - "pfadd %%mm2, %%mm1 \n" \ - " \n" \ - "pushl %3 \n" \ - "popl 0(%%eax) \n" \ - " \n" \ - "movd %%mm3, 4(%%eax) \n" \ - " \n" \ - "addl $8, %%ebx \n" \ - "addl $8, %%eax \n" \ - " \n" \ - "movq 0(%%ebx), %%mm2 \n" \ - "movq 0(%%eax), %%mm3 \n" \ - " \n" \ - "pfmul %%mm3, %%mm2 \n" \ - "pfsub %%mm2, %%mm1 \n" \ - " \n" \ - "psrlq $32, %%mm2 \n" \ - "pfsub %%mm2, %%mm1 \n" \ - " \n" \ - "movd %%mm1, %3 \n" \ - " \n" \ - "addl $8, %%ebx \n" \ - "movq 0(%%ebx), %%mm2 \n" \ - "movq 0(%%eax), %%mm3 \n" \ - " \n" \ - "pfmul %%mm3, %%mm2 \n" \ - "pfadd %%mm2, %%mm1 \n" \ - " \n" \ - "psrlq $32, %%mm2 \n" \ - "pfadd %%mm2, %%mm1 \n" \ - " \n" \ - "pushl %3 \n" \ - "popl 0(%%eax) \n" \ - "movd %%mm3, 4(%%eax) \n" \ - " \n" \ - "movd %%mm1, %0 \n" \ - "femms \n" \ - : "=m"(output) : "g"(coef_ptr), "g"(hist1_ptr), "m"(tmp) - : "eax", "ebx", "mm1", "mm2", "mm3", "memory"); - - return(output); -} - -static inline float atti386_iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr, int revLevel) -{ - return 0; -} - -static inline void atti386_produceOutput1(int tmplen, int16 myvolume, int16 *useBuf, int16 *snd) -{ - __asm__ __volatile__( - "movl %0, %%ecx \n" \ - "movw %1, %%ax \n" \ - "shll $16, %%eax \n" \ - "movw %1, %%ax \n" \ - "movd %%eax, %%mm3 \n" \ - "movd %%eax, %%mm2 \n" \ - "psllq $32, %%mm3 \n" \ - "por %%mm2, %%mm3 \n" \ - "movl %2, %%esi \n" \ - "movl %3, %%edi \n" \ - "1: \n" \ - "movq 0(%%esi), %%mm1 \n" \ - "movq 0(%%edi), %%mm2 \n" \ - "pmulhw %%mm3, %%mm1 \n" \ - "paddw %%mm2, %%mm1 \n" \ - "movq %%mm1, 0(%%edi) \n" \ - " \n" \ - "addl $8, %%esi \n" \ - "addl $8, %%edi \n" \ - " \n" \ - "decl %%ecx \n" \ - "cmpl $0, %%ecx \n" \ - "jg 1b \n" \ - "emms \n" \ - : : "g"(tmplen), "g"(myvolume), "g"(useBuf), "g"(snd) - : "eax", "ecx", "edi", "esi", "mm1", "mm2", "mm3", "memory"); -} - -// FIXME: This is buggy -static inline void atti386_produceOutput2(uint32 len, int16 *snd, float *sndbufl, float *sndbufr, float *multFactor) -{ - __asm__ __volatile__( - "movl %4, %%ecx \n" \ - "shrl $1, %%ecx \n" \ - "addl $4, %%ecx \n" \ - "pushl %%ecx \n" \ - " \n" \ - "movl %0, %%esi \n" \ - "movups 0(%%esi), %%xmm1 \n" \ - " \n" \ - "movl %1, %%esi \n" \ - "movl %2, %%edi \n" \ - "1: \n" \ - "xorl %%eax, %%eax \n" \ - "movw 0(%1), %%ax \n" \ - "cwde \n" \ - "incl %1 \n" \ - "incl %1 \n" \ - "movd %%eax, %%mm1 \n" \ - "psrlq $32, %%mm1 \n" \ - "movw 0(%1), %%ax \n" \ - "incl %1 \n" \ - "incl %1 \n" \ - "movd %%eax, %%mm2 \n" \ - "por %%mm2, %%mm1 \n" \ - " \n" \ - "decl %%ecx \n" \ - "jnz 1b \n" \ - " \n" \ - "popl %%ecx \n" \ - "movl %1, %%esi \n" \ - "movl %3, %%edi \n" \ - "incl %%esi \n" \ - "2: \n" \ - "decl %%ecx \n" \ - "jnz 2b \n" \ - : : "g"(multFactor), "r"(snd), "g"(sndbufl), "g"(sndbufr), "g"(len) - : "eax", "ecx", "edi", "esi", "mm1", "mm2", "xmm1", "memory"); -} - -static inline void atti386_mixBuffers(int16 * buf1, int16 *buf2, int len) -{ - __asm__ __volatile__( - "movl %0, %%ecx \n" \ - "movl %1, %%esi \n" \ - "movl %2, %%edi \n" \ - "1: \n" \ - "movq 0(%%edi), %%mm1 \n" \ - "movq 0(%%esi), %%mm2 \n" \ - "paddw %%mm2, %%mm1 \n" \ - "movq %%mm1, 0(%%esi) \n" \ - "addl $8, %%edi \n" \ - "addl $8, %%esi \n" \ - "decl %%ecx \n" \ - "cmpl $0, %%ecx \n" \ - "jg 1b \n" \ - "emms \n" \ - : : "g"(len), "g"(buf1), "g"(buf2) - : "ecx", "edi", "esi", "mm1", "mm2", "memory"); -} - -static inline void atti386_mixBuffersRingMix(int16 * buf1, int16 *buf2, int len) -{ - __asm__ __volatile__( - "movl %0, %%ecx \n" \ - "movl %1, %%esi \n" \ - "movl %2, %%edi \n" \ - "1: \n" \ - "movq 0(%%esi), %%mm1 \n" \ - "movq 0(%%edi), %%mm2 \n" \ - "movq %%mm1, %%mm3 \n" \ - "pmulhw %%mm2, %%mm1 \n" \ - "paddw %%mm3, %%mm1 \n" \ - "movq %%mm1, 0(%%esi) \n" \ - "addl $8, %%edi \n" \ - "addl $8, %%esi \n" \ - "decl %%ecx \n" \ - "cmpl $0, %%ecx \n" \ - "jg 1b \n" \ - "emms \n" \ - : : "g"(len), "g"(buf1), "g"(buf2) - : "ecx", "edi", "esi", "mm1", "mm2", "mm3", "memory"); -} - -static inline void atti386_mixBuffersRing(int16 * buf1, int16 *buf2, int len) -{ - __asm__ __volatile__( - "movl %0, %%ecx \n" \ - "movl %1, %%esi \n" \ - "movl %2, %%edi \n" \ - "1: \n" \ - "movq 0(%%esi), %%mm1 \n" \ - "movq 0(%%edi), %%mm2 \n" \ - "pmulhw %%mm2, %%mm1 \n" \ - "movq %%mm1, 0(%%esi) \n" \ - "addl $8, %%edi \n" \ - "addl $8, %%esi \n" \ - "decl %%ecx \n" \ - "cmpl $0, %%ecx \n" \ - "jg 1b \n" \ - "emms \n" \ - : : "g"(len), "g"(buf1), "g"(buf2) - : "ecx", "edi", "esi", "mm1", "mm2", "memory"); -} - -static inline void atti386_PartProductOutput(int quadlen, int16 leftvol, int16 rightvol, - int16 *partialBuf, int16 *p1buf) -{ - __asm__ __volatile__( - "movl %0, %%ecx \n" \ - "movw %1, %%ax \n" \ - "shll $16, %%eax \n" \ - "movw %2, %%ax \n" \ - "movd %%eax, %%mm1 \n" \ - "movd %%eax, %%mm2 \n" \ - "psllq $32, %%mm1 \n" \ - "por %%mm2, %%mm1 \n" \ - "movl %3, %%edi \n" \ - "movl %4, %%esi \n" \ - "1: \n" \ - "movw 0(%%esi), %%bx \n" \ - "addl $2, %%esi \n" \ - "movw 0(%%esi), %%dx \n" \ - "addl $2, %%esi \n" \ - "" \ - "movw %%dx, %%ax \n" \ - "shll $16, %%eax \n" \ - "movw %%dx, %%ax \n" \ - "movd %%eax, %%mm2 \n" \ - "psllq $32, %%mm2 \n" \ - "movw %%bx, %%ax \n" \ - "shll $16, %%eax \n" \ - "movw %%bx, %%ax \n" \ - "movd %%eax, %%mm3 \n" \ - "por %%mm3, %%mm2 \n" \ - "" \ - "pmulhw %%mm1, %%mm2 \n" \ - "movq %%mm2, 0(%%edi) \n" \ - "addl $8, %%edi \n" \ - "" \ - "decl %%ecx \n" \ - "cmpl $0, %%ecx \n" \ - "jg 1b \n" \ - "emms \n" \ - : :"g"(quadlen), "g"(leftvol), "g"(rightvol), "g"(partialBuf), "g"(p1buf) - : "eax", "ebx", "ecx", "edx", "edi", "esi", "mm1", "mm2", "mm3", "memory"); -} +//#define ALIGN_PACKED __attribute__ ((__packed__)) +#define ALIGN_PACKED __attribute__ ((aligned (1))) +typedef unsigned long long Bit64u; +typedef signed long long Bit64s; #endif -#endif - -#if !defined(__GNUC__) - #pragma START_PACK_STRUCTS -#endif +typedef unsigned int Bit32u; +typedef signed int Bit32s; +typedef unsigned short int Bit16u; +typedef signed short int Bit16s; +typedef unsigned char Bit8u; +typedef signed char Bit8s; -struct timbreParam { +#pragma pack(1) +struct TimbreParam { struct commonParam { char name[10]; char pstruct12; // 1&2 0-12 (1-13) @@ -470,7 +82,7 @@ struct timbreParam { char time[4]; // 1-100 char level[5]; // 1-100 (-50 - +50) } ALIGN_PACKED env; - + struct lfoParam { char rate; // 0-100 char depth; // 0-100 @@ -490,7 +102,7 @@ struct timbreParam { char envtime[5]; // 1-100 char envlevel[4]; // 1-100 } ALIGN_PACKED tvf; - + struct tvaParam { char level; // 0-100 char velosens; // 0-100 @@ -503,46 +115,47 @@ struct timbreParam { char envtime[5]; // 1-100 char envlevel[4]; // 1-100 } ALIGN_PACKED tva; - + } ALIGN_PACKED partial[4]; //char dummy[20]; } ALIGN_PACKED; -struct memParams { - struct patchTemp { - char timbreGroup; // TIMBRE GROUP 0-3 (group A, group B, Memory, Rhythm) - char timbreNum; // TIMBRE NUMBER 0-63 - char keyShift; // KEY SHIFT 0-48 (-24 - +24) - char fineTune; // FINE TUNE 0-100 (-50 - +50) - char benderRange; // BENDER RANGE 0-24 - char assignMode; // ASSIGN MODE 0-3 (POLY1, POLY2, POLY3, POLY4) - char reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON) - char dummy; // (DUMMY) +struct PatchParam { + char timbreGroup; // TIMBRE GROUP 0-3 (group A, group B, Memory, Rhythm) + char timbreNum; // TIMBRE NUMBER 0-63 + char keyShift; // KEY SHIFT 0-48 (-24 - +24) + char fineTune; // FINE TUNE 0-100 (-50 - +50) + char benderRange; // BENDER RANGE 0-24 + char assignMode; // ASSIGN MODE 0-3 (POLY1, POLY2, POLY3, POLY4) + char reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON) + char dummy; // (DUMMY) +} ALIGN_PACKED; + +struct MemParams { + struct PatchTemp { + PatchParam patch; char outlevel; // OUTPUT LEVEL 0-100 char panpot; // PANPOT 0-14 (R-L) char dummyv[6]; - } ALIGN_PACKED tmpSettings[8]; - struct ryhTemp { + } ALIGN_PACKED patchSettings[8]; + + struct RhythmTemp { char timbre; // TIMBRE 0-94 (M1-M64,R1-30,OFF) char outlevel; // OUTPUT LEVEL 0-100 char panpot; // PANPOT 0-14 (R-L) char reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON) - } ALIGN_PACKED rhySettings[64]; + } ALIGN_PACKED rhythmSettings[64]; - timbreParam timTemp[8]; + TimbreParam timbreSettings[8]; - struct patchArea { - char timbreGroup; // TIMBRE GROUP 0-3 (group A, group B, Memory, Rhythm) - char timbreNum; // TIMBRE NUMBER 0-63 - char keyShift; // KEY SHIFT 0-48 (-24 - +24) - char fineTune; // FINE TUNE 0-100 (-50 - +50) - char benderRange; // BENDER RANGE 0-24 - char assignMode; // ASSIGN MODE 0-3 (POLY1, POLY2, POLY3, POLY4) - char reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON) - char dummy; // (DUMMY) - } ALIGN_PACKED pSettings[128]; - timbreParam patch[192]; - struct systemArea { + PatchParam patches[128]; + + struct PaddedTimbre { + TimbreParam timbre; + char padding[10]; + } ALIGN_PACKED timbres[64 + 64 + 64 + 30]; // Group A, Group B, Memory, Rhythm + + struct SystemArea { char masterTune; // MASTER TUNE 0-127 432.1-457.6Hz char reverbMode; // REVERB MODE 0-3 (room, hall, plate, tap delay) char reverbTime; // REVERB TIME 0-7 (1-8) @@ -551,84 +164,74 @@ struct memParams { char chanAssign[9]; // MIDI CHANNEL (PART1) 0-16 (1-16,OFF) char masterVol; // MASTER VOLUME 0-100 } ALIGN_PACKED system; - } ALIGN_PACKED; -struct memBanks { - char pTemp[8][sizeof(memParams::patchTemp)]; - char rTemp[64][sizeof(memParams::ryhTemp)]; - char tTemp[8][sizeof(timbreParam)]; - char patchmemory[128][sizeof(memParams::patchArea)]; - char patchbanks[128][sizeof(timbreParam)]; - char timbrebanks[64][sizeof(timbreParam)]; - char systemBank[sizeof(memParams::systemArea)]; +struct MemBanks { + char pTemp[8][sizeof(MemParams::PatchTemp)]; + char rTemp[64][sizeof(MemParams::RhythmTemp)]; + char tTemp[8][sizeof(TimbreParam)]; + char patchBank[128][sizeof(PatchParam)]; + char timbreBank[64 + 64 + 64 + 30][sizeof(MemParams::PaddedTimbre)]; + char systemBank[sizeof(MemParams::SystemArea)]; } ALIGN_PACKED; -struct memAbsolute { - char mt32memory[sizeof(memBanks)]; +union MT32RAMFormat { + MemParams params; + MemBanks banks; + + // System memory 10 + + // Display 20 + + // Reset 7F + } ALIGN_PACKED; -#if !defined(__GNUC__) - #pragma END_PACK_STRUCTS -#endif +#pragma pack() -struct partialFormat { - uint32 addr; - uint16 len; +struct sampleFormat { + Bit32u addr; + Bit32u len; bool loop; float tune; - int32 ampval; + Bit32s ampval; }; -struct partialTable { - uint32 addr; - uint32 len; - uint32 pcmnum; - int32 ampval; +struct sampleTable { + Bit32u addr; + Bit32u len; + Bit32u pcmnum; bool loop; - int32 aggSound; // This variable is for the last 9 PCM samples, which are actually loop combinations + Bit32s aggSound; // This variable is for the last 9 PCM samples, which are actually loop combinations }; - - union soundaddr { - uint32 pcmabs; + Bit32u pcmabs; struct offsets { -#if defined(SCUMM_LITTLE_ENDIAN) - uint16 pcmoffset; - uint16 pcmplace; -#else - uint16 pcmplace; - uint16 pcmoffset; -#endif + Bit16u pcmoffset; + Bit16u pcmplace; } pcmoffs; }; - struct volset { - int16 leftvol; - int16 rightvol; - int16 leftvol2; - int16 rightvol2; + Bit16s leftvol; + Bit16s rightvol; }; -struct patchCache { - int rawPCM; - partialTable convPCM; - +// This is basically a per-partial, pre-processed combination of timbre and patch/rhythm settings +struct PatchCache { bool playPartial; - bool usePartial; bool PCMPartial; + int pcm; char waveform; int pulsewidth; int pwsens; int pitchshift; int fineshift; - bool sustain; int lfodepth; int lforate; - uint32 lfoperiod; + Bit32u lfoperiod; int modsense; int keydir; @@ -650,121 +253,51 @@ struct patchCache { int amplevel; int tvfdepth; - int prevsample; - bool useBender; - timbreParam::partialParam::envParam pitchEnv; - timbreParam::partialParam::tvaParam ampEnv; - timbreParam::partialParam::tvfParam filtEnv; - - int32 ampsustain; - int32 pitchsustain; - int32 filtsustain; + TimbreParam::partialParam::envParam pitchEnv; + TimbreParam::partialParam::tvaParam ampEnv; + TimbreParam::partialParam::tvfParam filtEnv; - uint32 partCount; + Bit32s ampsustain; + Bit32s pitchsustain; + Bit32s filtsustain; - uint8 padding[64]; //Used to pad the patch cache to 4096 bytes. This replaces an imul with a shl 12 + Bit32u mix; + int structurePosition; + int structurePair; + // The following fields are actually common to all partials in the timbre + Bit32u partialCount; + bool sustain; }; +class Partial; // Forward reference for class defined in partial.h + struct dpoly { bool isPlaying; - bool isDecay; - bool isActive; - - bool partActive[4]; - bool isRy; - uint32 *bendptr; - uint32 drumbend; - int32 *volumeptr; - volset *pansetptr; - - int pcmnum; + int note; int freq; int freqnum; int vel; - uint32 partCount; - - soundaddr pcmoff; - uint32 pcmdelta; - - - struct partialStatus { - // Note played on keyboard - int noteval; - // Keyfollowed note values - int keyedval; - - // Keyfollowed filter values - int realval; - int filtval; - // Keyfollowed filter w/o table - int filtnoval; - int pulsewidth; - - struct envstatus { - int32 envpos; - int32 envstat; - int32 envbase; - int32 envdist; - int32 envsize; - - bool sustaining; - bool decaying; - bool notdecayed; - uint32 decay; - int32 prevlevel; - - int32 counter; - int32 count; - - } envs[4]; - - uint32 lfopos; - soundaddr partialOff; - soundaddr wgOff; - - uint32 ampEnvCache; - uint32 pitchEnvCache; - - bool isDecayed; - bool PCMDone; - - float history[32]; - - float pastfilt; - bool pitchsustain; - bool playPartial; - bool usePartial; - - int looppos; - int partNum; - - patchCache *tcache; - - void * myPart; - - - - } pStatus[4]; - - - int chan; + bool reverb; + bool isDecay; - int origpat; - int drumnum; - - int age; + const Bit32u *bendptr; + Bit32s *volumeptr; + volset *pansetptr; - bool pedalhold; - bool firstsamp; + Partial *partials[4]; - uint32 P1Mix; - uint32 P2Mix; + bool pedalhold; // This marks keys that have been released on the keyboard, but are being held by the pedal bool sustain; + + bool isActive(); + Bit64s getAge(); }; +} + #endif diff --git a/backends/midi/mt32/synth.cpp b/backends/midi/mt32/synth.cpp index 138ca29cb7..26448ac3d1 100644 --- a/backends/midi/mt32/synth.cpp +++ b/backends/midi/mt32/synth.cpp @@ -1,4446 +1,1277 @@ -/* ScummVM - Scumm Interpreter - * Copyright (C) 2004 The ScummVM project - * Based on Tristan's conversion of Canadacow's code +/* Copyright (c) 2003-2004 Various contributors * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * 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 General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * $Header$ + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. */ -#include "stdafx.h" - -#include "backends/midi/mt32/synth.h" -#include "backends/midi/mt32/partial.h" -#include "backends/midi/mt32/freeverb.h" -#include "common/system.h" -#include "common/file.h" - -#define NOMANSLAND - -// ************************** -// CSynth reporting codes -// ************************** - -// files missing -#define ERR_PRESET1 1 -#define ERR_PRESET2 2 -#define ERR_DRUMPAT 3 -#define ERR_PATCHLOG 4 -#define ERR_MT32ROM 5 - -// HW spec -#define PRESENT_SSE 6 -#define PRESENT_3DNOW 7 -#define USING_SSE 8 -#define USING_3DNOW 9 - -// General info -#define LCD_MESSAGE 10 -#define DEV_RESET 11 -#define DEV_RECONFIG 12 -#define NEW_REVERB_MODE 13 -#define NEW_REVERB_TIME 14 -#define NEW_REVERB_LEVEL 15 - -#if !defined(__GNUC__) - #pragma START_PACK_STRUCTS -#endif - -static union mt32ramFormat { - memParams params; - memBanks patchabs; - memAbsolute memabs; - // System memory 10 - // Display 20 - // Reset 7F -} ALIGN_PACKED mt32ram, mt32default; +#define BENCHMARK 0 -#if !defined(__GNUC__) - #pragma END_PACK_STRUCTS +#include <math.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#if BENCHMARK > 0 +#include <time.h> #endif -int axtoi(char *str) { - int result = 0; - - while (*str) { - char hex = *str++; - int digit; - - if (hex >= '0' && hex <= '9') - digit = hex - '0'; - else if (hex >= 'a' && hex <= 'f') - digit = 10 + hex - 'a'; - else if (hex >= 'A' && hex <= 'F') - digit = 10 + hex - 'A'; - else - break; - - result = (result << 4) | digit; - } - - return result; -} - -struct FILTER { - unsigned int length; // size of filter - float *history; // pointer to history in filter - float *coef; // pointer to coefficients of filter -}; - -#define FILTER_SECTIONS 2 // 2 filter sections for 24 db/oct filter - -struct BIQUAD { - double a0, a1, a2; // numerator coefficients - double b0, b1, b2; // denominator coefficients -}; - -// Filter prototype coefficients, 1 for each filter section -BIQUAD ProtoCoef[FILTER_SECTIONS]; - -/** - * Pre-warp the coefficients of a numerator or denominator. Note that a0 is - * assumed to be 1, so there is no wrapping of it. - */ - -void prewarp(double *a0, double *a1, double *a2, double fc, double fs) { - double wp; - - wp = 2.0 * fs * tan(PI * fc / fs); - - *a2 = (*a2) / (wp * wp); - *a1 = (*a1) / wp; -} - -/** - * Transform the numerator and denominator coefficients of s-domain biquad - * section into corresponding z-domain coefficients. - * - * Store the 4 IIR coefficients in array pointed by coef in following order: - * beta1, beta2 (denominator) - * alpha1, alpha2 (numerator) - * - * @param a0-a2 s-domain numerator coefficients - * @param b0-b2 s-domain denominator coefficients - * @param k filter gain factor. initially set to 1 and modified by each - * biquad section in such a way, as to make it the coefficient by - * which to multiply the overall filter gain in order to achieve a - * desired overall filter gain, specified in initial value of k. - * @param fs sampling rate (Hz) - * @param coef array of z-domain coefficients to be filled in. - * - * @returns On return, set coef z-domain coefficients - */ - -void bilinear(double a0, double a1, double a2, double b0, double b1, double b2, double *k, double fs, float *coef) { - double ad, bd; - - // alpha (Numerator in s-domain) - ad = 4. * a2 * fs * fs + 2. * a1 * fs + a0; - // beta (Denominator in s-domain) - bd = 4. * b2 * fs * fs + 2. * b1 * fs + b0; - - // update gain constant for this section - *k *= ad / bd; - - // Denominator - *coef++ = (2. * b0 - 8. * b2 * fs * fs) / bd; // beta1 - *coef++ = (4. * b2 * fs * fs - 2. * b1 * fs + b0) / bd; // beta2 - - // Nominator - *coef++ = (2. * a0 - 8. * a2 * fs * fs) / ad; // alpha1 - *coef = (4. * a2 * fs * fs - 2. * a1 * fs + a0) / ad; // alpha2 -} - -/** - * @param a0-a2 numerator coefficients - * @param b0-b2 denominator coefficients - * @param fc filter cutoff frequency - * @param fs sampling rate - * @param coef pointer to 4 iir coefficients - */ - -void szxform(double *a0, double *a1, double *a2, double *b0, double *b1, double *b2, double fc, double fs, double *k, float *coef) { - // Calculate a1 and a2 and overwrite the original values - prewarp(a0, a1, a2, fc, fs); - prewarp(b0, b1, b2, fc, fs); - bilinear(*a0, *a1, *a2, *b0, *b1, *b2, k, fs, coef); -} - -#ifdef HAVE_X86 -#if defined(WIN32) && !(defined(__CYGWIN__) || defined(__MINGW32__)) -bool DetectSIMD() { - bool found_simd = false; - - _asm { - pushfd - pop eax // get EFLAGS into eax - mov ebx,eax // keep a copy - xor eax,0x200000 - // toggle CPUID bit - - push eax - popfd // set new EFLAGS - pushfd - pop eax // EFLAGS back into eax - - xor eax,ebx - // have we changed the ID bit? - - je NO_SIMD - // No, no CPUID instruction - - // we could toggle the - // ID bit so CPUID is present - mov eax,1 - - cpuid // get processor features - test edx,1<<25 // check the SIMD bit - jz NO_SIMD - mov found_simd,1 - jmp DONE - NO_SIMD: - mov found_simd,0 -DONE: - } - - return found_simd; -} - -bool Detect3DNow() { - bool found3D = false; - - __asm { - pushfd - pop eax - mov edx, eax - xor eax, 00200000h - push eax - popfd - pushfd - pop eax - xor eax, edx - jz NO_3DNOW - - mov eax, 80000000h - cpuid - - cmp eax, 80000000h - jbe NO_3DNOW - - mov eax, 80000001h - cpuid - test edx, 80000000h - jz NO_3DNOW - mov found3D, 1 -NO_3DNOW: - } - - return found3D; -} -#else -bool DetectSIMD() { - return atti386_DetectSIMD(); -} - -bool Detect3DNow() { - return atti386_Detect3DNow(); -} -#endif -#endif - -#ifdef NOMANSLAND - -// #define SETRATE 32000 -#define SETRATE myProp.SampleRate -// #define SETRATE 44100 - -// Used to regenerate waveform file after sampling rate change -#define MAKEWAVES 0 +#include "mt32emu.h" +// Debugging stuff // Used to dump drum patches to syx file for viewing #define DUMPDRUMS 0 -#define USEREVERB 1 - -// Debuging stuff -// Shows the instruments played -#define DISPLAYINSTR 0 -// Shows number of partials MT-32 is playing -#define MONITORPARTIALS 1 -// Dump syx file of temp tibres right before reset -#define SAVECUSTOM 0 - -// Constant tuning for now -#define TUNING 440.0 -#define SAMPLETUNING 207.64 -#define MIDDLEC 60 - -#define ROMSIZE 512*1024 -#define PCMSIZE ROMSIZE/2 -#define GRAN 512 -#define LN 2.30258509 - -#define MAXPOLY 64 -#define MAXPARTIALS 32 - -// Reverb room sizes (in metres) - -#define REV_ROOMSIZE 2.50f -#define REV_HALLSIZE 3.60f -#define REV_PLATESIZE 1.50f -#define REV_TAPSIZE 1.0f - -// Reverb t60 coeff - -#define REV_ROOMT60 ( REV_ROOMSIZE * REV_ROOMSIZE * REV_ROOMSIZE ) / 5 -#define REV_HALLT60 ( REV_HALLSIZE * REV_HALLSIZE * REV_HALLSIZE ) / 5 -#define REV_PLATET60 ( REV_PLATESIZE * REV_PLATESIZE * REV_PLATESIZE ) / 7 -#define REV_TAPT60 ( REV_TAPSIZE * REV_TAPSIZE * REV_TAPSIZE ) / 1 - -// #define HLRATIO 2.0f - #define SYSEX_SIZE 512 -// These are all the filters I tried without much success - -int16 Moog1(int16 wg, float *hist, float usefilt, float res) { - float f, p, q; // filter coefficients - float t1, t2; // temporary buffers - - // Set coefficients given frequency & resonance [0.0...1.0] - - float frequency = usefilt; - float in = (float)wg / 32767.0; - float resonance = res / 31.0; - resonance = resonance * resonance; - - q = 1.0f - frequency; - //p = frequency + 0.8f * frequency * q; - p = frequency + 0.8f * frequency * q; - - f = p + p - 1.0f; - q = resonance * (1.0f + 0.5f * q * (1.0f - q + 5.6f * q * q)); - - // Filter (in [-1.0...+1.0]) - - // feedback - in -= q * hist[4]; - t1 = hist[1]; - hist[1] = (in + hist[0]) * p - hist[1] * f; - t2 = hist[2]; - hist[2] = (hist[1] + t1) * p - hist[2] * f; - t1 = hist[3]; - hist[3] = (hist[2] + t2) * p - hist[3] * f; - hist[4] = (hist[3] + t1) * p - hist[4] * f; - - // clipping - hist[4] = hist[4] - hist[4] * hist[4] * hist[4] * 0.166667f; - hist[0] = in; - //LOG_MSG("In %d Hist: %f", wg, hist[4]*32767); - - return (int16)(hist[4] * 32767.0); -} - -int16 Moog2(int16 wg, float *hist, float usefilt, float resonance) { - float res = resonance / 30.0; - double f = usefilt; - double invf = 1.0 - f; - double fb = res * (1.0 - 0.15 * f * f); - float input = (float)wg / 32767.0; - - input -= hist[4] * fb; - input *= 0.35013 * (f*f)*(f*f); - hist[1] = input + 0.3 * hist[5] + (invf) * hist[1]; // Pole 1 - hist[5] = input; - hist[2] = hist[1] + 0.3 * hist[6] + (invf) * hist[2]; // Pole 2 - hist[6] = hist[1]; - hist[3] = hist[2] + 0.3 * hist[7] + (invf) * hist[3]; // Pole 3 - hist[7] = hist[2]; - hist[4] = hist[3] + 0.3 * hist[0] + (invf) * hist[4]; // Pole 4 - hist[0] = hist[3]; - - return (int16)(hist[4] * 32767.0); -} - -int16 simpleLowpass(int16 wg, float *hist, float usefilt, float resonance) { - float in = (float)wg / 32767.0; - float res_lp = resonance / 31.0; - res_lp = res_lp * res_lp; - float cut_lp = usefilt; - float n1, n2, fb_lp; - - n1 = hist[0]; - n2 = hist[1]; - - fb_lp = res_lp + res_lp / (1 - cut_lp); - n1 = n1 + cut_lp * (in - n1 + fb_lp * (n1 - n2)); - n2 = n2 + cut_lp * (n1 - n2); - - hist[0] = n1; - hist[1] = n2; - - return (int)(n2 * 32767.0); -} - -/* Reverb stuff */ - -#define NUM_COMBS 6 +namespace MT32Emu { -struct LOWPASS_STATE { - float coef; - float lastval; -}; +iir_filter_type usefilter; -struct COMB_STATE { - float tau; - float g; - float gsqu; - float *delbuf; - int bufsiz; - int bufpos; +static const Bit8u InitPatches[8] = { + 68, 48, 95, 78, 41, 3, 110, 122 }; -struct ST_REVERB { - int lastsamp; - int cursamp; - int done; - LOWPASS_STATE lowpass[NUM_COMBS]; - COMB_STATE comb[NUM_COMBS]; - COMB_STATE allpass[2]; +// Maps MIDI channel numbers to MT-32 parts (not to be confused with "partials") +// This is the default (FIXME: the mapping from 11->9 is undocumented, is this correct?): +static const Bit8s InitChanTable[16] = { + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, -1, -1, -1, -1, -1 }; +// This alternative configuration can be selected by holding "Master Volume" +// and pressing "PART button 1" on the real MT-32's frontpanel. +//static const Bit8s InitChanTable[16] = { +// 0, 1, 2, 3, 4, 5, 6, 7, -1, 8, -1, -1, -1, -1, -1, -1 +//}; -class Reverb { -private: - ST_REVERB *revstate; - int SR; - -public: - Reverb(float t60, float hlratio, float dur, float hall_f, int smpr); - ~Reverb(); - - void run(float *lchan, float *rchan, float revfrac); - - float lowpass(float input, LOWPASS_STATE *state); - - float lpcomb(float input, LOWPASS_STATE *lpstate, COMB_STATE *cstate); - float allpassfilt(float input, COMB_STATE *state); -}; - -/** - * @param t60 reverb time - * @param hlratio ratio of low freq t60 to high freq t60 - * @param dur duration of event/dealloc. on last samp - * @param hall_fact mult. factor for delay times - * @param revstate running values for event reverb - */ - -Reverb::Reverb(float t60, float hlratio, float dur, float hall_fact, int sampling_rate) { - revstate = new ST_REVERB; - SR = sampling_rate; - int i; - float glow[NUM_COMBS], ghi[NUM_COMBS]; - // initialize sample counter and compute last sample - revstate->cursamp=0; - revstate->lastsamp = (int)(dur * (float)SR); - revstate->done=0; - - // ALLPASS INITIALIZATIONS - revstate->allpass[0].tau = .006 * hall_fact; - revstate->allpass[1].tau = .0065 * hall_fact; - - // allocate allpass delay buffers and head/tail ptr. - for (i = 0; i < 2; i++) { - revstate->allpass[i].bufsiz = (int) (revstate->allpass[i].tau * SR + .5); - revstate->allpass[i].delbuf = new float[revstate->allpass[i].bufsiz]; - memset(revstate->allpass[i].delbuf, 0, revstate->allpass[i].bufsiz * sizeof(float)); - revstate->allpass[i].bufpos = -1; - } - - revstate->allpass[0].g = .71f; - revstate->allpass[1].g = .7f; - - revstate->allpass[0].gsqu = revstate->allpass[0].g * revstate->allpass[0].g; - revstate->allpass[1].gsqu = revstate->allpass[1].g * revstate->allpass[1].g; - - // COMB AND IIR LOWPASS FILTER INITIALIZATIONS - - revstate->comb[0].tau = .0050 * hall_fact; - revstate->comb[1].tau = .0068 * hall_fact; - revstate->comb[2].tau = .0056 * hall_fact; - revstate->comb[3].tau = .0072 * hall_fact; - revstate->comb[4].tau = .0061 * hall_fact; - revstate->comb[5].tau = .0078 * hall_fact; - - // allocate comb delay buffers and head/tail ptr. - for (i = 0; i < NUM_COMBS; i++) { - revstate->comb[i].bufsiz = (int)(revstate->comb[i].tau * SR + .5); - - revstate->comb[i].delbuf = new float[revstate->comb[i].bufsiz]; - memset(revstate->comb[i].delbuf, 0, revstate->comb[i].bufsiz * sizeof(float)); - - revstate->comb[i].bufpos = -1; - - revstate->lowpass[i].lastval = 0.; - } - - // if hlratio set by user, set various values - // else, use default g's and coef's - if (hlratio != 0.) { - for (i = 0; i < NUM_COMBS; i++) { - - // compute reverb attenuation factor for hi and low - // frequency reverberation times - glow[i] = pow(10.,(-3. * revstate->comb[i].tau) / t60); - ghi[i] = pow(10.,(-3. * revstate->comb[i].tau) / ( t60 * hlratio)); - - // compute recursive lowpass factor and comb - // attenuation factor to produce the correct - // reverberation time for both hi and low frequencies - revstate->lowpass[i].coef = (glow[i] - ghi[i]) / (glow[i] + ghi[i]); - revstate->comb[i].g = glow[i] * (1. - revstate->lowpass[i].coef); - } - } else { - revstate->lowpass[0].coef = .24f; - revstate->lowpass[1].coef = .26f; - revstate->lowpass[2].coef = .28f; - revstate->lowpass[3].coef = .29f; - revstate->lowpass[4].coef = .30f; - revstate->lowpass[5].coef = .32f; - - for (i = 0; i < 6; i++) { - // compute reverb attenuation factor and comb - // attenuation factor based on default coef - glow[i] = pow(10., (-3. * revstate->comb[i].tau) / t60); - revstate->comb[i].g = glow[i] * (1. - revstate->lowpass[i].coef); - } - } -} - -Reverb:: ~Reverb() { - int i; - - for (i = 0; i < NUM_COMBS; i++) - delete[] revstate->comb[i].delbuf; - for (i = 0; i < 2; i++) - delete[] revstate->allpass[i].delbuf; - delete revstate; -} - -/** - * @param lchan non-reverberated input sample - * @param rchan non-reverberated input sample - * @param revfrac percent of output to be reverberated - */ - -INLINE void Reverb::run(float *lchan, float *rchan, float revfrac) { - int i; - float lchanrev, rchanrev, tot = 0; - - if (revstate->done) { - *lchan = 0.0; - *rchan = 0.0; - return; - } - - for (i = 0; i < NUM_COMBS; i++) - tot = tot + lpcomb((*lchan) + (*rchan), &(revstate->lowpass[i]), &(revstate->comb[i])); - - tot = tot / (float)NUM_COMBS; - - lchanrev = allpassfilt(tot * .7, &(revstate->allpass[0])); - rchanrev = allpassfilt(tot * .7, &(revstate->allpass[1])); - - if (revstate->cursamp == revstate->lastsamp) { - for (i = 0; i < NUM_COMBS; i++) - delete[] revstate->comb[i].delbuf; - for (i = 0; i < 2; i++) - delete[] revstate->allpass[i].delbuf; - revstate->done = 1; - } - - revstate->cursamp++; - - *lchan = lchanrev * revfrac + (*lchan) * (1. - revfrac); - *rchan = rchanrev * revfrac + (*rchan) * (1. - revfrac); -} - -INLINE float Reverb::lowpass(float input, LOWPASS_STATE *state) { - // simple IIR lowpass filter algorithm - // y(n) = x(n) + coef * y(n - 1) - state->lastval = (input + state->coef * state->lastval); - return state->lastval; -} - -INLINE float Reverb::lpcomb(float input, LOWPASS_STATE *lpstate, COMB_STATE *cstate) { - float temp; - - // move head-tail pointer in circular queue - cstate->bufpos = (cstate->bufpos + 1) % cstate->bufsiz; - - // pop circular queue - temp = cstate->delbuf[cstate->bufpos]; - - // add new value to end of queue - lpstate->lastval = (cstate->delbuf[cstate->bufpos] + lpstate->coef * lpstate->lastval); - - cstate->delbuf[cstate->bufpos] = input + cstate->g * lpstate->lastval; - - // return popped value - return temp; -} - -INLINE float Reverb::allpassfilt(float input, COMB_STATE* state) { - float temp; - - // move head-tail pointer in circular queue - state->bufpos = (state->bufpos + 1) % state->bufsiz; - - // pop circular queue - temp = state->delbuf[state->bufpos]; - - // add new value to end of queue - state->delbuf[state->bufpos] = input + state->g * state->delbuf[state->bufpos]; - - // return a sum of the current in with the delay out - return -1. * state->g * input + (1. - state->gsqu) * temp; -} - -// End reverb stuff - -// Begin filter stuff - -void InitFilter(float fs, float fc, float *icoeff, float Q, float resfac) { - float *coef; - unsigned nInd; - double a0, a1, a2, b0, b1, b2; - double k; // overall gain factor - - // Section 1 - ProtoCoef[0].a0 = 1.0; - ProtoCoef[0].a1 = 0; - ProtoCoef[0].a2 = 0; - ProtoCoef[0].b0 = 1.0; - ProtoCoef[0].b1 = 0.765367; - ProtoCoef[0].b2 = 1.0; - - // Section 2 - ProtoCoef[1].a0 = 1.0; - ProtoCoef[1].a1 = 0; - ProtoCoef[1].a2 = 0; - ProtoCoef[1].b0 = 1.0; - ProtoCoef[1].b1 = 1.847759; - ProtoCoef[1].b2 = 1.0; - - k = 1.5; // Set overall filter gain - coef = icoeff + 1; // Skip k, or gain - - for (nInd = 0; nInd < 2; nInd++) { - a0 = ProtoCoef[nInd].a0; - a1 = ProtoCoef[nInd].a1; - a2 = ProtoCoef[nInd].a2; - - b0 = ProtoCoef[nInd].b0; - b1 = ProtoCoef[nInd].b1 / Q; // Divide by resonance or Q - b2 = ProtoCoef[nInd].b2; - - szxform(&a0, &a1, &a2, &b0, &b1, &b2, fc, fs, &k, coef); - coef += 4; // Point to next filter section - } - icoeff[0] = k; -} - -#if FILTER_FLOAT == 1 - -iir_filter_type usefilter; - -#if defined(WIN32) && !(defined(__CYGWIN__) || defined(__MINGW32__)) - -float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr, int revLevel) { - float *hist2_ptr; - float output; - - hist2_ptr = hist1_ptr + 1; // next history - - // 1st number of coefficients array is overall input scale factor, or - // filter gain - output = input * (*coef_ptr++); - - __asm { - movss xmm1, output - - mov eax, coef_ptr - movups xmm2, [eax] - - mov eax, hist1_ptr - movlps xmm3, [eax] - shufps xmm3, xmm3, 44h - // hist2_ptr, hist1_ptr, hist2_ptr, hist1_ptr - - mulps xmm2, xmm3 - - subss xmm1, xmm2 - // Rotate elements right - shufps xmm2, xmm2, 39h - subss xmm1, xmm2 - - // Store new_hist - movss DWORD PTR [eax], xmm1 - - // Rotate elements right - shufps xmm2, xmm2, 39h - addss xmm1, xmm2 - - // Rotate elements right - shufps xmm2, xmm2, 39h - addss xmm1, xmm2 - - // Store previous hist - movss DWORD PTR [eax+4], xmm3 - - add coef_ptr, 16 - add hist1_ptr, 8 - - mov eax, coef_ptr - movups xmm2, [eax] - - mov eax, hist1_ptr - movlps xmm3, [eax] - shufps xmm3, xmm3, 44h - // hist2_ptr, hist1_ptr, hist2_ptr, hist1_ptr - - mulps xmm2, xmm3 - - subss xmm1, xmm2 - // Rotate elements right - shufps xmm2, xmm2, 39h - subss xmm1, xmm2 - - // Store new_hist - movss DWORD PTR [eax], xmm1 - - // Rotate elements right - shufps xmm2, xmm2, 39h - addss xmm1, xmm2 - - // Rotate elements right - shufps xmm2, xmm2, 39h - addss xmm1, xmm2 - - // Store previous hist - movss DWORD PTR [eax+4], xmm3 - - movss output, xmm1 - } - - output *= ResonInv[revLevel]; - - return output; -} - -float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr, int revLevel) { - float *hist2_ptr; - float output; - float tmp; - - hist2_ptr = hist1_ptr + 1; // next history - - // 1st number of coefficients array is overall input scale factor, or - // filter gain - output = input * (*coef_ptr++); - - // I find it very sad that 3DNow requires twice as many instructions as - // Intel's SSE. Intel does have the upper hand here. - - __asm { - movq mm1, output - mov ebx, coef_ptr - movq mm2, [ebx] - - mov eax, hist1_ptr; - movq mm3, [eax] - - pfmul mm2, mm3 - pfsub mm1, mm2 - - psrlq mm2, 32 - pfsub mm1, mm2 - - // Store new hist - movd tmp, mm1 - - add ebx, 8 - movq mm2, [ebx] - movq mm3, [eax] - - pfmul mm2, mm3 - pfadd mm1, mm2 - - psrlq mm2, 32 - pfadd mm1, mm2 - - push tmp - pop DWORD PTR [eax] - - movd DWORD PTR [eax+4], mm3 - - add ebx, 8 - add eax, 8 - - movq mm2, [ebx] - movq mm3, [eax] - - pfmul mm2, mm3 - pfsub mm1, mm2 - - psrlq mm2, 32 - pfsub mm1, mm2 - - // Store new hist - movd tmp, mm1 - - add ebx, 8 - movq mm2, [ebx] - movq mm3, [eax] - - pfmul mm2, mm3 - pfadd mm1, mm2 - - psrlq mm2, 32 - pfadd mm1, mm2 - - push tmp - pop DWORD PTR [eax] - movd DWORD PTR [eax+4], mm3 - - movd output, mm1 - - femms +static int axtoi(char *str) { + int result = 0; + while (*str) { + char ch = *str++; + if (ch >= '0' && ch <= '9') + ch -= '0'; + else if (ch >= 'a' && ch <= 'f') + ch = ch + 10 - 'a'; + else if (ch >= 'A' && ch <= 'F') + ch = ch + 10 - 'A'; + else + break; + result = (result << 4) | ch; } - - output *= ResonInv[revLevel]; - - return output; -} - -#elif defined(HAVE_X86) - -float iir_filter_sse(float input,float *hist1_ptr, float *coef_ptr, int revLevel) { - float *hist2_ptr; - float output; - - hist2_ptr = hist1_ptr + 1; // next history - - // 1st number of coefficients array is overall input scale factor, or - // filter gain - output = input * (*coef_ptr++); - output = atti386_iir_filter_sse(&output, hist1_ptr, coef_ptr); - output *= ResonInv[revLevel]; - - return output; -} - -float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr, int revLevel) { - float *hist2_ptr; - float output; - - hist2_ptr = hist1_ptr + 1; // next history - - /* 1st number of coefficients array is overall input scale factor, - * or filter gain */ - output = input * (*coef_ptr++); - output = atti386_iir_filter_3DNow(output, hist1_ptr, coef_ptr); - output *= ResonInv[revLevel]; - - return output; + return result; } -#endif - float iir_filter_normal(float input,float *hist1_ptr, float *coef_ptr, int revLevel) { float *hist2_ptr; - float output, new_hist; + float output,new_hist; - hist2_ptr = hist1_ptr + 1; // next history + hist2_ptr = hist1_ptr + 1; // next history - // 1st number of coefficients array is overall input scale factor, or - // filter gain + // 1st number of coefficients array is overall input scale factor, or filter gain output = input * (*coef_ptr++); output = output - *hist1_ptr * (*coef_ptr++); - new_hist = output - *hist2_ptr * (*coef_ptr++); // poles + new_hist = output - *hist2_ptr * (*coef_ptr++); // poles output = new_hist + *hist1_ptr * (*coef_ptr++); - output = output + *hist2_ptr * (*coef_ptr++); // zeros + output = output + *hist2_ptr * (*coef_ptr++); // zeros *hist2_ptr++ = *hist1_ptr; *hist1_ptr++ = new_hist; hist1_ptr++; hist2_ptr++; + // i = 1 output = output - *hist1_ptr * (*coef_ptr++); - new_hist = output - *hist2_ptr * (*coef_ptr++); // poles + new_hist = output - *hist2_ptr * (*coef_ptr++); // poles output = new_hist + *hist1_ptr * (*coef_ptr++); - output = output + *hist2_ptr * (*coef_ptr++); // zeros - + output = output + *hist2_ptr * (*coef_ptr++); // zeros + *hist2_ptr++ = *hist1_ptr; *hist1_ptr++ = new_hist; output *= ResonInv[revLevel]; - return output; + return(output); } -#endif - -#if FILTER_64BIT == 1 - -// 64-bit version -long iir_filter(long input, int64 *hist1_ptr, int64 *coef_ptr) { - int64 *hist2_ptr; - int64 output, new_hist, history1, history2; - - hist2_ptr = hist1_ptr + 1; // next history - - // 1st number of coefficients array is overall input scale factor, or - // filter gain - output = input * (*coef_ptr++); - - for (int i = 0 ; i < 2; i++) { - history1 = *hist1_ptr; // history values - history2 = *hist2_ptr; - - output = output - ((history1 * (*coef_ptr++)) >> 20); - new_hist = output - ((history2 * (*coef_ptr++)) >> 20); // poles +Synth::Synth() { + isOpen = false; + reverbModel = NULL; + partialManager = NULL; + memset(waveforms, 0, sizeof(waveforms)); + memset(parts, 0, sizeof(parts)); +} - output = new_hist + ((history1 * (*coef_ptr++)) >> 20); - output = output + ((history2 * (*coef_ptr++)) >> 20); // zeros +Synth::~Synth() { + close(); // Make sure we're closed and everything is freed +} - *hist2_ptr++ = *hist1_ptr; - *hist1_ptr++ = new_hist; - hist1_ptr++; - hist2_ptr++; +void Synth::report(ReportType type, void *data) { + if (myProp.report != NULL) { + myProp.report(myProp.userData, type, data); } - - return output >> 20; } -#endif - -#if FILTER_INT == 1 - -long iir_filter(long input, signed long *hist1_ptr, signed long *coef_ptr) { - signed long *hist2_ptr; - signed long output, new_hist, history1, history2; - - hist2_ptr = hist1_ptr + 1; // next history - - // 1st number of coefficients array is overall input scale factor, or - // filter gain - output = input * (*coef_ptr++); - - for (int i = 0 ; i < 2; i++) { - history1 = *hist1_ptr; // history values - history2 = *hist2_ptr; - - output = output - ((history1 * (*coef_ptr++)) >> 10); - new_hist = output - ((history2 * (*coef_ptr++))>>10); // poles - - output = new_hist + ((history1 * (*coef_ptr++)) >> 10); - output = output + ((history2 * (*coef_ptr++)) >> 10); // zeros - - *hist2_ptr++ = *hist1_ptr; - *hist1_ptr++ = new_hist; - hist1_ptr++; - hist2_ptr++; +void Synth::printDebug(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + if (myProp.printDebug != NULL) { + myProp.printDebug(myProp.userData, fmt, ap); + } else { + vprintf(fmt, ap); + printf("\n"); } - - return output >> 10; + va_end(ap); } -#endif - -// End filter stuff - -partialFormat PCM[54]; -partialTable PCMList[128]; -uint32 PCMReassign[55]; -int32 PCMLoopTable[55]; - -timbreParam drums[30]; - -int16 romfile[PCMSIZE+GRAN]; // 256K -static int16 chantable[32]; // 64 bytes -static int16 miditable[9]; // 18 bytes - -static CPartialMT32 *partTable[MAXPARTIALS]; -static int32 PartialReserveTable[32]; +void Synth::initReverb(char newRevMode, char newRevTime) { + // FIXME:KG: I don't think it's necessary to recreate the reverbModel... Just set the parameters + if (reverbModel != NULL) + delete reverbModel; + reverbModel = new revmodel(); -// For debuging partial allocation -//static FILE *pInfo; -struct partUsage { - int active[32]; - int assign[32]; - int owner[32]; - int status[32]; -}; - -static int32 activeChannels; - -// Some optimization stuff -int32 divtable[256]; // 1K -int32 smalldivtable[256]; // 1K -static int16 freqtable[256]; // 512 bytes -static uint32 sqrtable[101]; // 404 bytes -static int32 keytable[256]; // 1K -static uint32 wavtable[256]; // 1K -uint32 wavtabler[64][256]; // 64K -uint32 looptabler[16][16][256]; // 256K -static uint32 drumdelta[256]; // 1K -int16 sintable[65536]; // 128K -static int32 ptable[101]; // 404 bytes -static int32 lfotable[101]; // 404 bytes -int32 penvtable[16][128]; // 8K -static int32 fildeptable[5][128]; // 3K -static int32 timekeytable[5][128]; // 3K -static int32 filveltable[128][128]; // 64K -static int32 veltkeytable[5][128]; // 3K -int32 pulsetable[101]; // 400 bytes -int32 pulseoffset[101]; // 400 bytes -int32 sawtable[128][128]; // 64K -static int32 restable[201]; // 804 bytes -//static int32 biastable[13]; // 56 bytes -static int32 ampbiastable[16][128]; // 8K -static int32 fbiastable[16][128]; // 8K -static int filttable[2][128][256]; // 256K -static int nfilttable[128][128][128]; // 64K -float filtcoeff[FILTERGRAN][32][16]; // 512K - hmmm -#if FILTER_64BIT == 1 -static int64 filtcoefffix[FILTERGRAN][32][16]; -#endif -#if FILTER_INT == 1 -static int32 filtcoefffix[FILTERGRAN][32][16]; -#endif -static float revtable[8]; // 16 bytes -static int32 finetable[201]; // 804 bytes -uint32 lfoptable[101][128]; // 32K -int32 ampveltable[128][64]; // 32K -int32 pwveltable[15][128]; -static int32 envtimetable[101]; // 404 bytes -static int32 decaytimetable[101]; // 404 bytes -static int32 lasttimetable[101]; // 404 bytes -int32 amptable[129]; // 516 bytes -static int32 voltable[129]; // 516 bytes -static float padjtable[51]; // 204 bytes -static int32 bendtable[49]; // 195 bytes -float ResonFactor[32]; -float ResonInv[32]; - -int16 smallnoise[441]; // 4410 bytes at 44Khz -int32 samplepos = 0; - -int16* waveforms[4][256]; // 2K -uint32 waveformsize[4][256]; -int16 tmpforms[4][65536]; // 128K -int16 finalforms[4][8192]; // 64K - -// Corresponding drum patches as matched to keyboard -int8 DrumTable[42] = { - 0, 0, 10, 1, 11, 5, 4, 6, 4, 29, 3, 7, 3, 2, 8, 2, 9, -1, -1, 22, - -1, 12, -1, -1, -1, 18, 19, 13, 14, 15, 16, 17, 20, 21, 27, 24, - 26, 25, 28, -1, 23, -1 }; - -// Pan-pot position of drums -int16 drmPanTable[42] = { - 64, 64, 72, 64, 48, 72, 24, 72, 24, 72, 48, 72, 48, 96, 72, 96, 48, 1, 1, 40, - 1, 64, 1, 1, 1, 104, 88, 48, 40, 32, 64, 80, 104 , 104, 40, 88, - 40, 40, 32, 1, 16, 1 }; - -uint8 PartialStruct[13] = { - 0, 0, 2, 2, 1, 3, - 3, 0, 3, 0, 2, 1, 3 }; - -uint8 PartMixStruct[13] = { - 0, 1, 0, 1, 1, 0, - 1, 3, 3, 2, 2, 2, 2 }; - -uint8 InitInstr[8] = { - 68, 48, 95, 78, 41, 3, 110, 122}; - -int8 LoopPatterns[16][16] = { - { 2,3,4,5,6,7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { 8,9,10,11,12,13,14,15,16,-1,-1,-1,-1,-1,-1,-1 }, - { 17,18,19,20,21,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { 22,23,24,25,26,27,28,29,-1,-1,-1,-1,-1,-1,-1,-1 }, - { 30,31,32,33,34,35,36,37,-1,-1,-1,-1,-1,-1,-1,-1 }, - { 45,46,47,48,49,50,51,52,53,-1,-1,-1,-1,-1,-1,-1 }, - { 15,11,12,13,14,15,16,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { 30,35,32,33,34,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { 2,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 } -}; - - -int32 LoopPatternTuning[16][16] = { - { 0x1294A,0x1294A,0x1294A,0x1294A,0x1294A,0x1294A,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { 0x1294A,0x1294A,0x1294A,0x1294A,0x1294A,0x1294A,0x1294A,0x1294A, 0x1294A,-1,-1,-1,-1,-1,-1,-1 }, - { 0x1294A,0x1294A,0x1294A,0x1294A,0x1294A,0x1294A,0x1294A,0x1294A, 0x1294A,-1,-1,-1,-1,-1,-1,-1 }, - { 0x1294A,0x1294A,0x1294A,0x1294A,0x1294A,0x1294A,0x1294A,0x1294A, 0x1294A,-1,-1,-1,-1,-1,-1,-1 }, - { 0x1294A,0x1294A,0x1294A,0x1294A,0x1294A,0x1294A,0x1294A,0x1294A, 0x1294A,-1,-1,-1,-1,-1,-1,-1 }, - { 0x1294A,0x1294A,0x1294A,0x1294A,0x1294A,0x1294A,0x1294A,0x1294A, 0x1294A,-1,-1,-1,-1,-1,-1,-1 }, - { 0x2590B,0x1294A,0x1294A,0x1294A,0x1294A,0x1294A,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { 0x1294A,0x1294A,0x1294A,0x1294A,0x1294A,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { 0x1294A,0x1294A,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, - { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 } - -}; - - -// These are division constants for the TVF depth key follow -uint32 depexp[5] = {3000,950,485,255,138}; - -//Amplitude time velocity follow exponential coefficients -double tvcatconst[5] = {0.0, 0.002791309, 0.005942882, 0.012652792, 0.026938637}; -double tvcatmult[5] = {1.0, 1.072662811, 1.169129367, 1.288579123, 1.229630539}; - -//Envelope time keyfollow exponential coefficients -double tkcatconst[5] = {0.0, 0.005853144, 0.011148054, 0.019086143, 0.043333215}; -double tkcatmult[5] = {1.0, 1.058245688, 1.048488989, 1.016049301, 1.097538067}; - -// This caches the drum information -patchCache drumCache[30][4]; -int drumPan[30][2]; - -bool isEnabled; - - -Reverb *myReverb; -revmodel *newReverb; -bool usingSIMD; - -int16 mastervolume; - -uint32 curRevMode; -uint32 curRevTime; -uint32 curRevLevel; - -uint32 partialsPlayed; // Variable containing the whole count of partials played -uint32 avgPartials; // Tally of average number of partials a second -int32 partialChan[9]; // The count of partials played per channel - -#if SAVECUSTOM == 1 -uint32 filenum = 0; -#endif - -/* -t60 = reverb time -hlratio = ratio of low freq t60 to high freq t60 -dur = duration of event/dealloc. on last samp -hall_fact= mult. factor for delay times -revstate = running values for event reverb -*/ - -// t60 follows standard sabine equation -// t60 = .049 * (V / A) -// Where V = is the volume of the enclosure in cubic feet -// And A is the absorbtion of the room in square feet -// Using metric measurements (metres) the .049 is replaced with 0.161 - - - -void InitReverb(uint32 newRevMode, uint32 newRevTime, uint32 sampRate) { - if(newReverb != NULL) delete newReverb; - newReverb = new revmodel(); - - /* - if(myReverb != NULL) delete myReverb; - - float t60; - switch(newRevMode) { - case 0: - t60 = .161f * REV_ROOMT60; - break; - case 1: - t60 = .161f * REV_HALLT60; - break; - case 2: - t60 = .161f * REV_PLATET60; - break; - case 3: - t60 = .161f * REV_TAPT60; - break; - default: - t60 = .161f * REV_ROOMT60; - break; - } - - t60 = t60 * 0.625; - myReverb = new Reverb(t60,HLRATIO,sampRate/(8/(newRevTime+1)),(newRevTime+1),sampRate); - */ curRevTime = newRevTime; curRevMode = newRevMode; switch(newRevMode) { case 0: - newReverb->setroomsize((float).1); - newReverb->setdamp((float).75); + reverbModel->setroomsize(.1f); + reverbModel->setdamp(.75f); break; case 1: - newReverb->setroomsize((float).5); - newReverb->setdamp((float).5); + reverbModel->setroomsize(.5f); + reverbModel->setdamp(.5f); break; case 2: - newReverb->setroomsize((float).5); - newReverb->setdamp((float).1); + reverbModel->setroomsize(.5f); + reverbModel->setdamp(.1f); break; case 3: - newReverb->setroomsize((float)1); - newReverb->setdamp((float).75); + reverbModel->setroomsize(1.0f); + reverbModel->setdamp(.75f); break; default: - newReverb->setroomsize((float).1); - newReverb->setdamp((float).5); + reverbModel->setroomsize(.1f); + reverbModel->setdamp(.5f); break; } - newReverb->setdry(1); - newReverb->setwet((float)mt32ram.params.system.reverbLevel / 8.0); - newReverb->setwidth((float)curRevTime / 8.0); - - -} - -class MidiChannel { -public: - bool isPlaying; - - volset volumesetting; - - int reverbat, reverbleft, reverbright; - int panpot; - - patchCache pcache[4]; - - uint32 bend; - int32 volume; - - dpoly polyTable[MAXPOLY]; - - -private: - bool isRy; - int sampRate; - int freq; - int channum; - int partialCount; - int velocity; - long sndoff; - int octave; - int note; - - int patch; - char currentInstr[20]; - - int keyshift; - int assignmode; - int storedpatch; - bool sustain; - bool init; - - uint32 P1Mix; - uint32 P2Mix; - - bool holdpedal; - - -public: - MidiChannel(int samp, int cnum); - //short getSample(short *lspecial, short *rspecial); - void PlayPoly(int m, int f,int vel); - void PlayDrum(int m, int f,int vel); - void InitKeyFollow(dpoly *tpoly); - void PlayNote(int f, int vel); - void StopNote(int f, int vel); - void AllStop(); - void SetVolume(int vol); - void SetPan(int vol); - void SetBend(int vol); - void SetModulation(int vol); - void SetPatch(int patchnum,int drumNum); - void SetHoldPedal(bool pedalval); - void StopPedalHold(); - void RefreshPatch(); - void CheckNoteList(); - - int FixKeyfollow(int srckey, int *dir); - int FixBiaslevel(int srcpnt, int *dir); - - //int32 getPitchEnvelope(dpoly::partialStatus *pStat, dpoly *poly, bool inDecay); - //int32 getAmpEnvelope(dpoly::partialStatus *pStat, dpoly *poly, bool inDecay); - //int32 getFiltEnvelope(int16 wg, dpoly::partialStatus *pStat, dpoly *poly, bool inDecay); - - //void StartDecay(int envnum, int32 startval, dpoly::partialStatus *pStat, dpoly *poly); - -}; - -void MidiChannel::SetHoldPedal(bool pedalval) { - holdpedal = pedalval; + reverbModel->setdry(1); + reverbModel->setwet((float)mt32ram.params.system.reverbLevel / 8.0f); + reverbModel->setwidth((float)curRevTime / 8.0f); } -void MidiChannel::SetBend(int vol) { - - //int tmpbend = ((vol - 0x2000) * (int)mt32ram.params.pSettings[storedpatch].benderRange) >> 13; - //bend = bendtable[tmpbend+24]; - - float bend_range = (float)mt32ram.params.pSettings[storedpatch].benderRange / 24; - bend = 4096 + (int)((float)(vol - 8192) * bend_range); -} - -void MidiChannel::SetModulation(int vol) { - - // Just a bloody guess, as always, before I get things figured out - int t; - for(t=0;t<4;t++) { - if(pcache[t].playPartial) { - int newrate = (pcache[t].modsense * vol) >> 7; - //pcache[t].lfoperiod = lfotable[newrate]; - pcache[t].lfodepth = newrate; - } +File *Synth::openFile(const char *filename, File::OpenMode mode) { + if (myProp.openFile != NULL) { + return myProp.openFile(myProp.userData, filename, mode); } - -} - -INLINE void StartDecay(int envnum, int32 startval, dpoly::partialStatus *pStat, dpoly *poly) { - - patchCache *tcache = pStat->tcache; - dpoly::partialStatus::envstatus *tStat = &pStat->envs[envnum]; - - tStat->sustaining = false; - tStat->decaying = true; - tStat->envpos = 0; - tStat->envbase = startval; - - switch(envnum) { - case AMPENV: - tStat->envsize = (decaytimetable[(int)tcache->ampEnv.envtime[4]] * timekeytable[(int)tcache->ampEnv.envtkf][poly->freqnum]) >> 8; - tStat->envdist = -startval; - break; - case FILTENV: - tStat->envsize = (decaytimetable[(int)tcache->filtEnv.envtime[4]] * timekeytable[(int)tcache->filtEnv.envtkf][poly->freqnum]) >> 8; - tStat->envdist = -startval; - break; - case PITCHENV: - tStat->envsize = (decaytimetable[(int)tcache->pitchEnv.time[3]] * timekeytable[(int)tcache->pitchEnv.timekeyfollow][poly->freqnum]) >> 8 ; - tStat->envdist = tcache->pitchEnv.level[4] - startval; - break; - default: - break; + char pathBuf[2048]; + if (myProp.baseDir != NULL) { + strcpy(&pathBuf[0], myProp.baseDir); + strcat(&pathBuf[0], filename); + filename = pathBuf; } - tStat->envsize++; - - + ANSIFile *file = new ANSIFile(); + if (!file->open(filename, mode)) { + delete file; + return NULL; + } + return file; } - - - - -INLINE int32 getAmpEnvelope(dpoly::partialStatus *pStat, dpoly *poly) { - int32 tc; - - patchCache *tcache = pStat->tcache; - dpoly::partialStatus::envstatus *tStat = &pStat->envs[AMPENV]; - - if(!pStat->playPartial) return 0; - - if(tStat->decaying) { - - if(pStat->isDecayed) { - pStat->playPartial = false; - - tc = 0; - } else { - tc = tStat->envbase; - tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); - - // TRISTAN - if (tc < 0) - tc = 0; - - if((tStat->envpos >= tStat->envsize) || (tc == 0)){ - pStat->PCMDone = true; - pStat->isDecayed = true; - pStat->playPartial = false; - } - } - +void Synth::closeFile(File *file) { + if (myProp.closeFile != NULL) { + myProp.closeFile(myProp.userData, file); } else { - - if((tStat->envstat==-1) || (tStat->envpos >= tStat->envsize)) { - if(tStat->envstat==-1) { - tStat->envbase = 0; - } else { - tStat->envbase = tcache->ampEnv.envlevel[tStat->envstat]; - } - tStat->envstat++; - tStat->envpos = 0; - - switch(tStat->envstat) { - case 0: - //Spot for velocity time follow - //Only used for first attack - tStat->envsize = (envtimetable[(int)tcache->ampEnv.envtime[tStat->envstat]] * veltkeytable[(int)tcache->ampEnv.envvkf][poly->vel]) >> 8; - //LOG_MSG("Envstat %d, size %d, %d %d", tStat->envstat, tStat->envsize, tcache->ampEnv.envtime[tStat->envstat], veltkeytable[tcache->ampEnv.envvkf][poly->vel]); - break; - case 3: - // Final attack envelope uses same time table as the decay - //tStat->envsize = decaytimetable[tcache->ampEnv.envtime[tStat->envstat]]; - tStat->envsize = lasttimetable[(int)tcache->ampEnv.envtime[(int)tStat->envstat]]; - //LOG_MSG("Envstat %d, size %d", tStat->envstat, tStat->envsize); - break; - case 4: - //LOG_MSG("Envstat %d, size %d", tStat->envstat, tStat->envsize); - tc =tcache->ampsustain; - if(!poly->sustain) - StartDecay(AMPENV, tc, pStat, poly); - else - tStat->sustaining = true; - - goto PastCalc; - default: - //Spot for timekey follow - //Only used in subsquent envelope parameters, including the decay - tStat->envsize = (envtimetable[(int)tcache->ampEnv.envtime[tStat->envstat]] * timekeytable[(int)tcache->ampEnv.envtkf][poly->freqnum]) >> 8; - - //LOG_MSG("Envstat %d, size %d", tStat->envstat, tStat->envsize); - break; - } - - tStat->envsize++; - tStat->envdist = tcache->ampEnv.envlevel[tStat->envstat] - tStat->envbase; - - if(tStat->envdist != 0) { - tStat->counter = abs(tStat->envsize / tStat->envdist); - //LOG_MSG("Pos %d, envsize %d envdist %d", tStat->envstat, tStat->envsize, tStat->envdist); - } else { - tStat->counter = 0; - //LOG_MSG("Pos %d, envsize %d envdist %d", tStat->envstat, tStat->envsize, tStat->envdist); - } - } - - tc = tStat->envbase; - tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); - tStat->count = tStat->counter; - - -PastCalc: - - tc = (tc * (int32)tcache->amplevel) >> 7; - + file->close(); + delete file; } - - // Prevlevel storage is bottle neck - tStat->prevlevel = tc; - - //Bias level crap stuff now - //I unrolled the loop - - int dist,bias; - - // Bias 1 - if(tcache->ampblevel[0]!=0) { - bias = tcache->ampbias[0]; - if(tcache->ampdir[0]==0) { - // < Bias - if(poly->freqnum < bias) { - dist = bias-poly->freqnum; - tc = (tc * ampbiastable[tcache->ampblevel[0]][dist]) >> 8; +} - } - } else { - // > Bias - if(poly->freqnum > bias) { - dist = poly->freqnum-bias; - tc = (tc * ampbiastable[tcache->ampblevel[0]][dist]) >> 8; - } - } +bool Synth::loadPreset(const char *filename) { + File *file = openFile(filename, File::OpenMode_read); + if (file == NULL) { + printDebug("*** Error: Failed to load preset %s", filename); + return false; } - - //Bias 2 - if(tcache->ampblevel[1]!=0) { - bias = tcache->ampbias[1]; - if(tcache->ampdir[1]==0) { - // < Bias - if(poly->freqnum < bias) { - dist = bias-poly->freqnum; - tc = (tc * ampbiastable[tcache->ampblevel[1]][dist]) >> 8; - - } - } else { - // > Bias - if(poly->freqnum > bias) { - dist = poly->freqnum-bias; - tc = (tc * ampbiastable[tcache->ampblevel[1]][dist]) >> 8; + bool inSys = false; + Bit8u sysexBuf[SYSEX_SIZE]; + Bit16u syslen = 0; + int filePos = 0; + bool rc = true; + for (;;) { + int fc = file->readByte(); + if (fc == -1) { + if (!file->isEOF()) { + rc = false; } + break; } - } - - - - return tc; -} - - -INLINE int32 getPitchEnvelope(dpoly::partialStatus *pStat, dpoly *poly) { - patchCache *tcache = pStat->tcache; - dpoly::partialStatus::envstatus *tStat = &pStat->envs[PITCHENV]; - - int32 tc; - pStat->pitchsustain = false; - if(tStat->decaying) { - - if((pStat->isDecayed) || (tStat->envpos >= tStat->envsize)) { - tc = tcache->pitchEnv.level[4]; - } else { - tc = tStat->envbase; - tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); - } - } else { - - if(tStat->envstat==3) { - tc =tcache->pitchsustain; - if(poly->sustain) { - pStat->pitchsustain = true; - } else { - StartDecay(PITCHENV, tc, pStat, poly); - } - - } else { - - if((tStat->envstat==-1) || (tStat->envpos >= tStat->envsize)) { - tStat->envstat++; - - tStat->envbase = tcache->pitchEnv.level[tStat->envstat]; - tStat->envsize = (envtimetable[(int)tcache->pitchEnv.time[tStat->envstat]] * timekeytable[(int)tcache->pitchEnv.timekeyfollow][poly->freqnum]) >> 8; - - - tStat->envpos = 0; - tStat->envsize++; - tStat->envdist = tcache->pitchEnv.level[tStat->envstat+1] - tStat->envbase; - } - - tc = tStat->envbase; - tc = (tc + ((tStat->envdist * tStat->envpos) / tStat->envsize)); - + Bit8u c = (Bit8u)fc; + sysexBuf[syslen] = c; + syslen++; + filePos++; + if (c==0xf0) + inSys = true; + if ((c==0xf7) && (inSys)) { + playSysex(&sysexBuf[0],syslen); + inSys = false; + syslen = 0; } - tStat->prevlevel = tc; - - } - - return tc; - + closeFile(file); + return rc; } - -INLINE int32 getFiltEnvelope(int16 wg, dpoly::partialStatus *pStat, dpoly *poly) { - - int reshigh; - - //float *hist = pStat->history; - //int64 *hist = pStat->history; - //long *hist = pStat->history; - int filt,cutoff,depth,keyfollow, realfollow; - - patchCache *tcache = pStat->tcache; - dpoly::partialStatus::envstatus *tStat = &pStat->envs[FILTENV]; - - keyfollow = pStat->filtval; - realfollow = pStat->realval; - - int fr = poly->freqnum; - - if(tStat->decaying) { - if(pStat->isDecayed) { - reshigh = 0; - } else { - reshigh = tStat->envbase; - reshigh = (reshigh + ((tStat->envdist * tStat->envpos) / tStat->envsize)); - if(tStat->envpos >= tStat->envsize) reshigh = 0; - } - - } else { - if(tStat->envstat==4) { - reshigh =tcache->filtsustain; - if(!poly->sustain) StartDecay(FILTENV, reshigh, pStat, poly); - } else { - - if((tStat->envstat==-1) || (tStat->envpos >= tStat->envsize)) { - if(tStat->envstat==-1) { - tStat->envbase = 0; - } else { - tStat->envbase = tcache->filtEnv.envlevel[tStat->envstat]; - } - tStat->envstat++; - tStat->envpos = 0; - if(tStat->envstat==3) { - tStat->envsize = lasttimetable[(int)tcache->filtEnv.envtime[tStat->envstat]]; - } else { - tStat->envsize = (envtimetable[(int)tcache->filtEnv.envtime[tStat->envstat]] * timekeytable[(int)tcache->filtEnv.envtkf][poly->freqnum]) >> 8; - } - - tStat->envsize++; - tStat->envdist = tcache->filtEnv.envlevel[tStat->envstat] - tStat->envbase; - } - - reshigh = tStat->envbase; - reshigh = (reshigh + ((tStat->envdist * tStat->envpos) / tStat->envsize)); - - } - tStat->prevlevel = reshigh; +unsigned char calcChecksum(unsigned char *data, unsigned int len, unsigned char checksum) { + for (unsigned int i = 0; i < len; i++) { + checksum = checksum + data[i]; } - - cutoff = (tcache->filtEnv.cutoff); - - //if(tcache->waveform==1) reshigh = (reshigh * 3) >> 2; - - depth = (tcache->filtEnv.envdepth); - - //int sensedep = (depth * 127-tcache->filtEnv.envsense) >> 7; - depth = (depth * filveltable[poly->vel][(int)tcache->filtEnv.envsense]) >> 8; - - int bias = tcache->tvfbias; - int dist; - - - if(bias!=0) { - //LOG_MSG("Cutoff before %d", cutoff); - if(tcache->tvfdir == 0) { - if(fr < bias) { - dist = bias - fr; - cutoff = (cutoff * fbiastable[tcache->tvfblevel][dist]) >> 8; - - } - } else { - // > Bias - if(fr > bias) { - dist = fr - bias; - cutoff = (cutoff * fbiastable[tcache->tvfblevel][dist]) >> 8; - } - - } - //LOG_MSG("Cutoff after %d", cutoff); - - } - - - - depth = (depth * fildeptable[tcache->tvfdepth][fr]) >> 8; - reshigh = (reshigh * depth) >> 7; - - - int32 tmp; - - cutoff *= keyfollow; - cutoff /= realfollow; - - reshigh *= keyfollow; - reshigh /= realfollow; - - if(cutoff>100) cutoff = 100; - if(reshigh>100) reshigh = 100; - if(cutoff<0) cutoff = 0; - if(reshigh<0) reshigh = 0; - tmp = nfilttable[fr][cutoff][reshigh]; - //tmp *= keyfollow; - //tmp /= realfollow; - - //LOG_MSG("Cutoff %d, tmp %d, freq %d", cutoff, tmp, tmp * 256); - - return tmp; - //reshigh = (reshigh * depth * 6)>>10; - - filt = (cutoff + reshigh) * keyfollow; - filt = filt / realfollow; - - - - - if(filt<0) filt = 0; - if(filt>=200) filt = 199; - tmp = filttable[(int)tcache->waveform][fr][filt]; - return tmp; - + checksum = checksum & 0x7f; + if (checksum) + checksum = 0x80 - checksum; + return checksum; } -MidiChannel::MidiChannel(int samp, int cnum) { - isRy = holdpedal = isPlaying = false; - volumesetting.rightvol = volumesetting.leftvol = volumesetting.rightvol2 = volumesetting.leftvol2 = 32767; - patch = storedpatch = 0; - sampRate = samp; - channum = cnum; - volume = 102; - panpot = 64; - init = true; - bend = 0x1000; - memset(polyTable,0,sizeof(polyTable)); - memset(pcache, 0, sizeof(pcache)); - - if(cnum==8) { - isRy = true; - int pan; - volume = 102; - // Cache drum patches - int q,m; - for(q=0;q<30;q++) { - SetPatch(0,q); - for(m=0;m<42;m++) { - if(DrumTable[m]==q) { - pan = drmPanTable[m]; - if(pan<64) { - drumPan[q][0] = 32767; // lv - drumPan[q][1] = pan << 9; // rv - } else { - drumPan[q][0] = (63-(pan-63)) << 9; // lv - drumPan[q][1] = 32767; // rv - } +bool Synth::loadDrums(const char *filename) { + File *file = openFile(filename, File::OpenMode_read); + if (file == NULL) { + return false; + } + int drumnum = 0; + for (;;) { + //Read common area + TimbreParam *timbre = &mt32ram.params.timbres[drumnum + 192].timbre; + if (file->read(&timbre->common, 14) != 14) + break; + char drumname[11]; + strncpy(drumname, timbre->common.name, 10); + drumname[10] = 0; + bool breakout = false; + for (int t=0;t<4;t++) { + if (((timbre->common.pmute >> t) & 0x1) == 0x1) { + if (file->read(&timbre->partial[t], 58) != 58) { + breakout = true; break; } + //printDebug("Loaded drum #%d (%s) - t %d", drumnum, drumname, t); } } + if (breakout) { + break; + } + //printDebug("Loaded drum #%d (%s)", drumnum, drumname); + drumnum++; } - init = false; -} - -INLINE int MidiChannel::FixBiaslevel(int srcpnt, int *dir) { - int noteat = srcpnt & 63; - int outnote; - *dir = 1; - if(srcpnt < 64) *dir = 0; - outnote = 33 + noteat; - //LOG_MSG("Bias note %d, dir %d", outnote, *dir); - - return outnote; - -} - -INLINE int MidiChannel::FixKeyfollow(int srckey, int *dir) { - if (srckey>=0 && srckey<=16) { - //int keyfix[17] = { 256, 128, 64, 0, 32, 64, 96, 128, 128+32, 192, 192+32, 256, 256+64, 256+128, 512, 259, 269 }; - int keyfix[17] = { 256*16, 128*16, 64*16, 0, 32*16, 64*16, 96*16, 128*16, (128+32)*16, 192*16, (192+32)*16, 256*16, (256+64)*16, (256+128)*16, (512)*16, 4100, 4116}; - - if (srckey<3) - *dir = -1; - else if (srckey==3) - *dir = 0; - else - *dir = 1; - - return keyfix[srckey]; - } else { - //LOG_MSG("Missed key: %d", srckey); - return 256; - } -} - - -void MidiChannel::RefreshPatch() { - SetPatch(storedpatch,-1); + closeFile(file); + return true; } -void MidiChannel::SetPatch(int patchnum,int drumNum) { - int i, j, k, pcm,t; - - //int chanoff = channum; - /* TRISTAN: flush all partials on this channel. This is a hack. */ -// for(i=0; i < MAXPARTIALS; i++) -// if(partTable[i]->ownerChan == channum) -// partTable[i]->isActive = false; - - /* TRISTAN: check if any partials are still playing on this channel, if * - * so then duplicate the cached data from the channel to the partial so that * - * we can change the channels cache without affecting the partial. Hopefully * - * this is fairly rare. */ - if (storedpatch != patchnum) - for (i = 0; i < MAXPARTIALS; i++) - if(partTable[i]->ownerChan == channum) - if (partTable[i]->isActive) - { - /* copy cache data */ - for (j = 0; j < 4; j++) - partTable[i]->cachebackup[j] = pcache[j]; - - /* update pointers */ - for (j = 0; j < partTable[i]->timbreNum; j++) - for (k = 0; k < 4; k++) - partTable[i]->tmppoly[j].pStatus[k].tcache = partTable[i]->cachebackup + k; - - partTable[i]->tcache = partTable[i]->cachebackup; - } - - storedpatch = patchnum; - - patch = (mt32ram.params.pSettings[patchnum].timbreGroup * 64) + mt32ram.params.pSettings[patchnum].timbreNum; - - timbreParam timSrc; - //timSrc = mt32ram.params.patch[patch]; - if (drumNum==-1) { - timSrc = mt32ram.params.timTemp[channum]; - memset(¤tInstr,0,16); - memcpy(¤tInstr,timSrc.common.name,10); - - - } else { - // This is to cache all the drum tibres ahead of time - timSrc = drums[drumNum]; - } - //LOG_MSG("Reloading patch %d", channum); - sustain = (timSrc.common.nosustain == 0); - P1Mix = PartMixStruct[(int)timSrc.common.pstruct12]; - P2Mix = PartMixStruct[(int)timSrc.common.pstruct34]; - - //sustain = true; - partialCount = 0; - - for(t=0;t<4;t++) { - - // Calculate and cache common parameters - pcm = timSrc.partial[t].wg.pcmwave; - pcache[t].rawPCM = pcm; - - pcache[t].convPCM = PCMList[pcm]; - pcache[t].useBender = (timSrc.partial[t].wg.bender == 1); - - // pcm > -1 - switch (t) { - case 0: - pcache[t].PCMPartial = (PartialStruct[(int)timSrc.common.pstruct12] >> 1) & 0x1; +void Synth::dumpDrums(const char *filename) { + File *file = openFile(filename, File::OpenMode_write); + if (file == NULL) + return; + char dumbtext[10]; + memset(dumbtext,0,10); + for (int drumnum=0;drumnum<30;drumnum++) { + // Sysex header + if (!file->writeByte(0xf0)) break; - case 1: - pcache[t].PCMPartial = PartialStruct[(int)timSrc.common.pstruct12] & 0x1; + if (!file->writeByte(0x41)) break; - case 2: - pcache[t].PCMPartial = (PartialStruct[(int)timSrc.common.pstruct34] >> 1) & 0x1; + if (!file->writeByte(0x10)) break; - case 3: - pcache[t].PCMPartial = PartialStruct[(int)timSrc.common.pstruct34] & 0x1; + if (!file->writeByte(0x16)) break; - default: + if (!file->writeByte(0x12)) break; - } - - - if( ((timSrc.common.pmute >> (t)) & 0x1) == 1 ) { - pcache[t].playPartial = true; - pcache[t].usePartial = true; - - // Hack and a half... needed so drum partial numbers come through - pcache[0].partCount = t+1; - - partialCount++; - } else { - pcache[t].playPartial = false; - pcache[t].usePartial = false; - continue; - } - - pcache[t].sustain = (timSrc.common.nosustain == 0); - pcache[t].waveform = timSrc.partial[t].wg.waveform; - pcache[t].pulsewidth = timSrc.partial[t].wg.pulsewid; - pcache[t].pwsens = timSrc.partial[t].wg.pwvelo; - pcache[t].pitchkeyfollow = FixKeyfollow(timSrc.partial[t].wg.keyfollow, &pcache[t].pitchkeydir); - - // Calculate and cache pitch stuff - pcache[t].pitchshift = (timSrc.partial[t].wg.coarse+mt32ram.params.pSettings[patchnum].keyShift); - int32 pFine, tFine, fShift; - pFine = (int32)timSrc.partial[t].wg.fine; - tFine = (int32)mt32ram.params.pSettings[patchnum].fineTune; - fShift = ((pFine - 50) + (tFine - 50)) + 100; - pcache[t].fineshift = finetable[fShift]; - - keyshift = mt32ram.params.pSettings[patchnum].keyShift-24; - assignmode = mt32ram.params.pSettings[patchnum].assignMode; - - pcache[t].pitchEnv = timSrc.partial[t].env; - pcache[t].pitchEnv.sensitivity = (int)((float)pcache[t].pitchEnv.sensitivity*1.27); - pcache[t].pitchsustain = pcache[t].pitchEnv.level[3]; - - // Calculate and cache TVA envelope stuff - pcache[t].ampEnv = timSrc.partial[t].tva; - int l; - for(l=0;l<4;l++) { - pcache[t].ampEnv.envlevel[l] = (int)((float)pcache[t].ampEnv.envlevel[l]*1.27); - } - pcache[t].ampEnv.level = (int)((float)pcache[t].ampEnv.level*1.27); - float tvelo = ((float)pcache[t].ampEnv.velosens/100.0); - float velo = (fabs(tvelo-.5)/.5); - pcache[t].ampenvdir = 0; - if(tvelo<.5) pcache[t].ampenvdir = 1; - velo *= 63.0; - pcache[t].ampEnv.velosens = (int)(velo); - - pcache[t].ampbias[0] = FixBiaslevel(pcache[t].ampEnv.biaspoint1, &pcache[t].ampdir[0]); - pcache[t].ampblevel[0] = 12-pcache[t].ampEnv.biaslevel1; - pcache[t].ampbias[1] = FixBiaslevel(pcache[t].ampEnv.biaspoint2, &pcache[t].ampdir[1]); - pcache[t].ampblevel[1] = 12-pcache[t].ampEnv.biaslevel2; - pcache[t].ampdepth = pcache[t].ampEnv.envvkf * pcache[t].ampEnv.envvkf; - pcache[t].ampsustain = pcache[t].ampEnv.envlevel[3]; - pcache[t].amplevel = pcache[t].ampEnv.level; - - - // Calculate and cache filter stuff - pcache[t].filtEnv = timSrc.partial[t].tvf; - pcache[t].tvfdepth = pcache[t].filtEnv.envdkf; - pcache[t].filtkeyfollow = FixKeyfollow(pcache[t].filtEnv.keyfollow, &pcache[t].keydir); - pcache[t].filtEnv.envdepth = (int)((float)pcache[t].filtEnv.envdepth * 1.27); - pcache[t].tvfbias = FixBiaslevel(pcache[t].filtEnv.biaspoint, &pcache[t].tvfdir); - pcache[t].tvfblevel = pcache[t].filtEnv.biaslevel; - pcache[t].filtsustain = pcache[t].filtEnv.envlevel[3]; - - // Calculate and cache LFO stuff - //pcache[t].lfodepth = (int)((float)timSrc.partial[t].lfo.depth * 1.27); - pcache[t].lfodepth = timSrc.partial[t].lfo.depth; - pcache[t].lfoperiod = lfotable[(int)timSrc.partial[t].lfo.rate]; - pcache[t].lforate = timSrc.partial[t].lfo.rate; - pcache[t].modsense = timSrc.partial[t].lfo.modsense; - - } - //LOG_MSG("Res 1: %d 2: %d 3: %d 4: %d", pcache[0].waveform, pcache[1].waveform, pcache[2].waveform, pcache[3].waveform); - - if(drumNum!=-1) memcpy(drumCache[drumNum],pcache,sizeof(pcache)); - if(!init) AllStop(); - - //LOG_MSG("Channel #%d set instrument: %s - %d - %d - %d - %d - pc %d", chanoff, currentInstr, timSrc.partial[0].wg.pcmwave, timSrc.partial[1].wg.pcmwave, timSrc.partial[2].wg.pcmwave, timSrc.partial[3].wg.pcmwave, pcache[0].partCount); - -} - -void MidiChannel::SetVolume(int vol) { - - volume = voltable[vol]; - -} - -void MidiChannel::SetPan(int pan) { - panpot = pan; - - if(pan<64) { - volumesetting.leftvol = 32767; - volumesetting.rightvol = pan << 9; - - } - if(pan>=64) { - volumesetting.rightvol = 32767; - volumesetting.leftvol = (63-(pan-63)) << 9; - - } - - - - //LOG(LOG_ERROR|LOG_MISC,"Pan %d",panpot); -} - -INLINE int16 RingMod(int16 p1, int16 p2, bool useFirst) { - if(useFirst) { - //return (int16)( ( ((float)p1/32767.0) * ((float)p2/32767.0) ) * 32767); - return (int16)( ((int32)p1 * (int32)p2) >> 15); - - } else { - // An interesting undocumented feature of the MT-32 - // Putting ring mod on a muted partial introduces noise to the ring modulator - // Dune 2 makes use of this - return (int16)( ((int32)smallnoise[samplepos/100] * (int32)p2) >> 15); - } -} - - -INLINE void MidiChannel::InitKeyFollow(dpoly *tpoly) { - // Setup partial keyfollow - int keyfollow = 0; - int tmpval = tpoly->freqnum,t; - int keyedval; - - // Note follow relative to middle C - for(t=0;t<4;t++) { - - // Calculate keyfollow for pitch - switch(pcache[t].pitchkeydir) { - case -1: - keyfollow = ((int)((MIDDLEC*2-tmpval*2)/2) * pcache[t].pitchkeyfollow) >> 12; + int useaddr = drumnum * 256; + char lsb = (char)(useaddr & 0x7f); + char isb = (char)((useaddr >> 7) & 0x7f); + char msb = (char)(((useaddr >> 14) & 0x7f) | 0x08); + //Address + if (!file->writeByte(msb)) break; - case 0: - keyfollow = 0; + if (!file->writeByte(isb)) break; - case 1: - keyfollow = ((int)((tmpval*2-MIDDLEC*2)/2)*pcache[t].pitchkeyfollow)>>12; + if (!file->writeByte(lsb)) break; - } - if((pcache[t].pitchkeyfollow>4096) && (pcache[t].pitchkeyfollow<4200)) { - // Be sure to round up on keys below MIDDLEC - if(((tmpval*2-MIDDLEC*2)/2) < 0) keyfollow++; - } - keyedval = (keyfollow + pcache[t].pitchshift); - if(keyedval>108) keyedval = 108; - if(keyedval<12) keyedval = 12; - - tpoly->pStatus[t].keyedval = keyedval; - tpoly->pStatus[t].noteval = tmpval; - // Calculate keyfollow for filter - - int realfol = ((tmpval*2)-(MIDDLEC*2))/2; - switch(pcache[t].keydir) { - case -1: - keyfollow = ((int)((MIDDLEC*2-tmpval*2)/2) * pcache[t].filtkeyfollow )>>12; + TimbreParam *timbre = &mt32ram.params.timbres[192 + drumnum].timbre; + //Data + if (file->write(&timbre->common,0xE) != 0xE) break; - case 0: - keyfollow = tmpval; + if (file->write(&timbre->partial[0],0x3A) != 0x3A) break; - case 1: - keyfollow = ((int)((tmpval*2-(MIDDLEC*2))/2) * pcache[t].filtkeyfollow )>>12; + if (file->write(&timbre->partial[1],0x3A) != 0x3A) break; - } - - if (keyfollow>108) keyfollow=108; - if (keyfollow<-108) keyfollow =-108; - tpoly->pStatus[t].filtnoval = keyfollow+108; - tpoly->pStatus[t].filtval = keytable[keyfollow+108]; - tpoly->pStatus[t].realval = keytable[realfol+108]; - - } - - -} - -CPartialMT32 * AllocPartial(int chanNum) { - int i; - - CPartialMT32 *outPart = NULL; - -#if MAXPARTIALS == 32 - for(i=0;i<MAXPARTIALS;i++) { - if(PartialReserveTable[i] == chanNum) { - if(outPart == NULL) { - if(!partTable[i]->isActive) { - outPart = partTable[i]; - } - } - } - } - if(outPart != NULL) { - outPart->isActive = true; - outPart->age = 0; - } else { - for(i=0;i<MAXPARTIALS;i++) { - if(!partTable[i]->isActive) { - outPart = partTable[i]; - outPart->isActive = true; - outPart->age = 0; - break; - } - } - } -#else - for(i=0;i<MAXPARTIALS;i++) { - if(outPart == NULL) { - if(!partTable[i]->isActive) { - outPart = partTable[i]; - } - } - } - if(outPart != NULL) { - outPart->isActive = true; - outPart->age = 0; - } - -#endif - return outPart; -} - -int GetFreePartialCount(void) { - int i; - int count = 0; - memset(partialChan,0,sizeof(partialChan)); - for(i=0;i<MAXPARTIALS;i++) { - if(!partTable[i]->isActive) { - count++; - } else { - partialChan[partTable[i]->ownerChan]++; - } - } - return count; -} - - -INLINE void MidiChannel::PlayPoly(int m, int f,int vel) { - - CPartialMT32 *tmpParts[4]; - f += keyshift; - if((f<0) || (f >255)) return; - freq = freqtable[f]; - dpoly *tpoly = &polyTable[m]; - - tpoly->isPlaying = true; - tpoly->isDecay = false; - tpoly->isActive = true; - tpoly->pcmoff.pcmabs = 0; - tpoly->freq = freq; - tpoly->freqnum = f; - tpoly->pcmdelta = 0x100; - tpoly->age = 0; - tpoly->vel = vel; - tpoly->chan = this->channum; - tpoly->pedalhold = false; - tpoly->firstsamp = true; - memset(tpoly->pStatus,0,sizeof(tpoly->pStatus)); - int x,e; - - for(x=0;x<4;x++) { - - tpoly->pStatus[x].partNum = x; - tpoly->pStatus[x].lfopos = 0; - tpoly->pStatus[x].playPartial = pcache[x].playPartial; - tpoly->pStatus[x].usePartial = pcache[x].usePartial; - tpoly->pStatus[x].tcache = &pcache[x]; - tpoly->pStatus[x].pulsewidth = pcache[x].pulsewidth + pwveltable[pcache[x].pwsens][vel]; - - if(tpoly->pStatus[x].pulsewidth > 100) tpoly->pStatus[x].pulsewidth = 100; - if(tpoly->pStatus[x].pulsewidth < 0) tpoly->pStatus[x].pulsewidth = 0; - - if(pcache[x].playPartial) { - tmpParts[x] = AllocPartial(channum); - } else { - tmpParts[x] = NULL; - } - tpoly->pStatus[x].myPart = (void *)tmpParts[x]; - - for(e=0;e<4;e++) { - tpoly->pStatus[x].envs[e].envstat = -1; - tpoly->pStatus[x].envs[e].sustaining = false; - tpoly->pStatus[x].envs[e].decaying = false; - tpoly->pStatus[x].envs[e].envpos = 0; - tpoly->pStatus[x].envs[e].count = 0; - tpoly->pStatus[x].envs[e].counter = 0; - - - } - } - - bool allnull = true; - for(x=0;x<4;x++) { - //if(tmpParts[x] != NULL) allnull = false; - if(pcache[x].playPartial) allnull = false; - } - //if(allnull) LOG_MSG("No paritals to play for %s", this->currentInstr); - - tpoly->partCount = pcache[0].partCount; - tpoly->P1Mix = P1Mix; - tpoly->P2Mix = P2Mix; - tpoly->sustain = sustain; - tpoly->isRy = false; - tpoly->bendptr = &bend; - tpoly->volumeptr = &volume; - tpoly->pansetptr = &volumesetting; - - InitKeyFollow(tpoly); - - for(x=0;x<4;x++) { - if(tmpParts[x] != NULL) { - int pairPart, useMix, partNum; - switch(x) { - case 0: - useMix = P1Mix; - partNum = 0; - pairPart = 1; - break; - case 1: - useMix = P1Mix; - partNum = 1; - pairPart = 0; - break; - case 2: - useMix = P2Mix; - partNum = 0; - pairPart = 3; - break; - case 3: - useMix = P2Mix; - partNum = 1; - pairPart = 2; - break; - default: - useMix = P1Mix; - partNum = 0; - pairPart = 0; - break; - } - tmpParts[x]->startPartial(tpoly,tpoly->pStatus[x].tcache,&tpoly->pStatus[x],tmpParts[pairPart],useMix,partNum,channum,x); - tpoly->partActive[x] = true; - } else { - tpoly->partActive[x] = false; - } - } - -#if DISPLAYINSTR == 1 - memset(¤tInstr,0,16); - memcpy(¤tInstr,mt32ram.params.patch[patch].common.name,10); - //LOG_MSG("MT32 chan %d (\"%s\") s note poly %d - Vel %d Freq %d Vol %d", channum, currentInstr, m, vel, f, volume); -#endif -} - -INLINE void MidiChannel::PlayDrum(int m, int f,int vel) { - if(!((f>=35) && (f<= 76))) return; - CPartialMT32 *tmpParts[4]; - freq = freqtable[60]; - dpoly *tpoly = &polyTable[m]; - - tpoly->drumnum = f; - tpoly->isPlaying = true; - tpoly->isDecay = false; - tpoly->isActive = true; - tpoly->pcmnum = DrumTable[f-35]; - tpoly->pcmoff.pcmabs = 0; - tpoly->freq = freq; - tpoly->freqnum = 60; - tpoly->pcmdelta = 0x100; - tpoly->age = 0; - tpoly->vel = vel; - tpoly->chan = this->channum; - tpoly->pedalhold = false; - tpoly->firstsamp = true; - memset(tpoly->pStatus,0,sizeof(tpoly->pStatus)); - memcpy(pcache,drumCache[tpoly->pcmnum],sizeof(pcache)); - int x,e; - for(x=0;x<4;x++) { - tpoly->pStatus[x].partNum = x; - tpoly->pStatus[x].playPartial = pcache[x].playPartial; - tpoly->pStatus[x].usePartial = pcache[x].usePartial; - tpoly->pStatus[x].tcache = &drumCache[tpoly->pcmnum][x]; - - if(pcache[x].playPartial) { - tmpParts[x] = AllocPartial(channum); - } else { - tmpParts[x] = NULL; - } - tpoly->pStatus[x].myPart = (void *)tmpParts[x]; - - for(e=0;e<4;e++) { - tpoly->pStatus[x].envs[e].envstat = -1; - tpoly->pStatus[x].envs[e].count = 0; - tpoly->pStatus[x].envs[e].counter = 0; - } - } - - tpoly->P1Mix = PartMixStruct[(int)drums[tpoly->pcmnum].common.pstruct12]; - tpoly->P2Mix = PartMixStruct[(int)drums[tpoly->pcmnum].common.pstruct34]; - tpoly->sustain = (drums[tpoly->pcmnum].common.nosustain == 0); - tpoly->isRy = true; - tpoly->bendptr = &tpoly->drumbend; - tpoly->drumbend = 0x1000; - - tpoly->partCount = pcache[0].partCount; - tpoly->volumeptr = &volume; - tpoly->pansetptr = &volumesetting; - - InitKeyFollow(tpoly); - - for(x=0;x<4;x++) { - if(tmpParts[x] != NULL) { - int pairPart, useMix, partNum; - switch(x) { - case 0: - useMix = P1Mix; - partNum = 0; - pairPart = 1; - break; - case 1: - useMix = P1Mix; - partNum = 1; - pairPart = 0; - break; - case 2: - useMix = P2Mix; - partNum = 0; - pairPart = 3; - break; - case 3: - useMix = P2Mix; - partNum = 1; - pairPart = 2; - break; - default: - useMix = P1Mix; - partNum = 0; - pairPart = 0; - break; - } - tmpParts[x]->startPartial(tpoly,tpoly->pStatus[x].tcache,&tpoly->pStatus[x],tmpParts[pairPart],useMix,partNum,channum,x); - } - } - -#if DISPLAYINSTR == 1 - memset(¤tInstr,0,16); - memcpy(¤tInstr,drums[tpoly->pcmnum].common.name,10); - //LOG_MSG("MT32 drum chan (f %d = %d) (\"%s\") starting note poly %d - Velocity %d", f, tpoly->pcmnum, currentInstr, m, vel); -#endif -} - -/* -bool FreePartials(int needed, int chanNum) { - int i; - int myChanPrior = (int)mt32ram.params.system.reserveSettings[chanNum]; - if(myChanPrior<partialChan[chanNum]) { - //This can have more channels, must kill off those with less priority - int most, mostchan; - while(needed > 0) { - int selectChan = -1; - //Find the worst offender with more partials than allocated and kill them - most = -1; - mostchan = -1; - int diff; - - for(i=0;i<9;i++) { - diff = partialChan[i] - (int)mt32ram.params.system.reserveSettings[i]; - - if(diff>0) { - if(diff>most) { - most = diff; - mostchan = i; - } - } - } - selectChan = mostchan; - if(selectChan==-1) { - // All channels are within the allocated limits, you suck - // Look for first partial not of this channel that's decaying perhaps? - return false; - } - bool found; - int oldest; - int oldnum; - while(partialChan[selectChan] > (int)mt32ram.params.system.reserveSettings[selectChan]) { - oldest = -1; - oldnum = -1; - found = false; - for(i=0;i<32;i++) { - if(partTable[i]->isActive) { - if(partTable[i]->ownerChan == selectChan) { - found = true; - if(partTable[i]->age > oldest) { - oldest = partTable[i]->age; - oldnum = i; - } - } - } - } - if(!found) break; - partTable[oldnum]->stopPartial(); - --partialChan[selectChan]; - --needed; - } - - } - return true; - - } else { - //This channel has reached its max, must kill off its own - bool found; - int oldest; - int oldnum; - while(needed > 0) { - oldest = -1; - oldnum = -1; - found = false; - for(i=0;i<32;i++) { - if(partTable[i]->isActive) { - if(partTable[i]->ownerChan == chanNum) { - found = true; - if(partTable[i]->age > oldest) { - oldest = partTable[i]->age; - oldnum = i; - } - } - } - } - if(!found) break; - partTable[oldnum]->stopPartial(); - --needed; - } - // Couldn't free enough partials, sorry - if(needed>0) return false; - return true; - } - -} -*/ -bool FreePartials(int needed, int chanNum) { - - int i; -#if MAXPARTIALS == 32 - // Reclaim partials reserved for this channel - // Kill those that are already decaying first - /* - for(i=0;i<32;i++) { - if(PartialReserveTable[i] == chanNum) { - if(partTable[i]->ownerChan != chanNum) { - if(partTable[i]->partCache->envs[AMPENV].decaying) { - partTable[i]->isActive = false; - --needed; - if(needed<=0) return true; - } - } - } - - }*/ - // Then kill those with the lowest channel priority --- oldest at the moment - bool found; - int64 prior, priornum; - dpoly *killPoly; - found = true; - while(found) { - found = false; - prior = -1; - priornum = -1; - - for(i=0;i<32;i++) { - if(PartialReserveTable[i] == chanNum) { - if(partTable[i]->isActive) { - if(partTable[i]->ownerChan != chanNum) { - /* - if(mt32ram.params.system.reserveSettings[partTable[i]->ownerChan] < prior) { - prior = mt32ram.params.system.reserveSettings[partTable[i]->ownerChan]; - priornum = i; - }*/ - if(partTable[i]->age > prior) { - prior = partTable[i]->age; - priornum = i; - } - - found = true; - } - } - } - } - if(priornum != -1) { - partTable[priornum]->isActive = false; - - killPoly = partTable[priornum]->tmppoly; - killPoly->partActive[partTable[priornum]->timbreNum] = false; - killPoly->isActive = killPoly->partActive[0] || killPoly->partActive[1] || killPoly->partActive[2] || killPoly->partActive[3]; - --needed; - if(needed<=0) return true; - } - } - - - // Kill off the oldest partials within this channel - int64 oldest, oldlist; - - while(needed>0) { - oldest = -1; - oldlist = -1; - for(i=0;i<32;i++) { - if(partTable[i]->isActive) { - if(partTable[i]->ownerChan == chanNum) { - if(partTable[i]->age > oldest) { - oldest = partTable[i]->age; - oldlist = i; - } - } - } - } - if(oldlist != -1) { - partTable[oldlist]->isActive = false; - killPoly = partTable[oldlist]->tmppoly; - killPoly->partActive[partTable[oldlist]->timbreNum] = false; - killPoly->isActive = killPoly->partActive[0] || killPoly->partActive[1] || killPoly->partActive[2] || killPoly->partActive[3]; - --needed; - } else { + if (file->write(&timbre->partial[2],0x3A) != 0x3A) break; - } - } - if(needed<=0) return true; - - - return false; -#else - //No priority table when not using standard MT-32 configuration - // Kill off the oldest partials within this channel - int oldest, oldlist; - dpoly *killPoly; - oldest = -1; - oldlist = -1; - while(needed>0) { - for(i=0;i<MAXPARTIALS;i++) { - if(partTable[i]->ownerChan == chanNum) { - if(partTable[i]->age > oldest) { - oldest = partTable[i]->age; - oldlist = i; - } - } - } - if(oldlist != -1) { - partTable[oldlist]->isActive = false; - killPoly = partTable[oldlist]->tmppoly; - killPoly->partActive[partTable[oldlist]->timbreNum] = false; - killPoly->isActive = killPoly->partActive[0] || killPoly->partActive[1] || killPoly->partActive[2] || killPoly->partActive[3]; - --needed; - } else { + if (file->write(&timbre->partial[3],0x3A) != 0x3A) break; - } - } - if(needed<=0) return true; - //LOG_MSG("Out of paritals!"); - return false; -#endif - - -} - - -void MidiChannel::CheckNoteList() { - int q,t; - for(q=0;q<MAXPOLY;q++) { - if(polyTable[q].isActive) { - bool isActive = false; - CPartialMT32 * tmpPart; - for(t=0;t<4;t++) { - tmpPart = (CPartialMT32 *)polyTable[q].pStatus[t].myPart; - if(tmpPart != NULL) { - if(tmpPart->ownerChan == channum) { - isActive = isActive || tmpPart->isActive; - } - } - } - polyTable[q].isActive = isActive; - - } - } -} - -INLINE void MidiChannel::PlayNote(int f,int vel) { - int m; - sndoff=0; - velocity = vel; - - isPlaying = false; - - //if(channum!=0) return; - - if(isRy) memcpy(pcache,drumCache[DrumTable[f-35]],sizeof(pcache)); - - // POLY1 mode, Single Assign - // Haven't found any software that uses any of the other poly modes - if(!isRy) { - for(m=0;m<MAXPOLY;m++) { - if((polyTable[m].isActive) && (polyTable[m].freqnum == f)) { - StopNote(f,vel); - break; - } - } - } - - int needPartials = pcache[0].partCount; - - if(needPartials > GetFreePartialCount()) { - if(!FreePartials(needPartials, channum)) { - // Before we quit, see if there are other channels willing to donate - if(needPartials > GetFreePartialCount()) { - // Unable to get needed partials to play this note - return; - } - } - } - - // Find free note allocator - for(m=0;m<MAXPOLY;m++) { - if(!polyTable[m].isActive){ - isPlaying=true; - if (!isRy) { - PlayPoly(m,f,vel); - } else { - if(DrumTable[f-35]>-1) PlayDrum(m,f,vel); - } + //Checksum + unsigned char *dat = (unsigned char *)timbre; + unsigned char checksum = calcChecksum(dat, 246, msb + isb + lsb); + if (!file->writeByte(checksum)) break; - } - } - - -} -void MidiChannel::AllStop() { - int q,t; - for(q=0;q<MAXPOLY;q++) { - dpoly *tpoly = &polyTable[q]; - if(tpoly->isPlaying) { - tpoly->isDecay = true; - for(t=0;t<4;t++) { - - //memset(tpoly->pStatus[t].decay,0,sizeof(tpoly->pStatus[t].decay)); - //tpoly->pStatus[t].isDecayed = 0; - //memset(tpoly->pStatus[t].decaying,true,sizeof(tpoly->pStatus[t].decaying)); - - StartDecay(AMPENV,tpoly->pStatus[t].envs[AMPENV].prevlevel, &tpoly->pStatus[t], tpoly); - StartDecay(FILTENV,tpoly->pStatus[t].envs[FILTENV].prevlevel, &tpoly->pStatus[t], tpoly); - StartDecay(PITCHENV,tpoly->pStatus[t].envs[PITCHENV].prevlevel, &tpoly->pStatus[t], tpoly); - - tpoly->pStatus[t].pitchsustain = false; - } - tpoly->isPlaying = false; - } - - } - -} - -void MidiChannel::StopPedalHold() { - int q; - for(q=0;q<MAXPOLY;q++) { - dpoly *tpoly; - tpoly = &polyTable[q]; - if (tpoly->pedalhold) StopNote(tpoly->freqnum,0); - } - -} - -void MidiChannel::StopNote(int f,int vel) { - // Find oldest note... yes, the MT-32 can be reconfigured to kill different note first - // This is simplest - int oldest = -1; - int oldage = 0; - int count = 0; - int q,t; - bool found = false; - dpoly *tpoly; - - // Non-sustaining instruments ignore stop note commands. - // They die away eventually anyway - //if(!tpoly->sustain) return; - - //LOG_MSG("MT32 chan %d (\"%s\") stopping note %d", this->channum, currentInstr, f); - - for(q=0;q<MAXPOLY;q++) { - tpoly = &polyTable[q]; - - if(tpoly->isPlaying) { - if(tpoly->freqnum == f) { - if (holdpedal) { - tpoly->pedalhold = true; - - } else { - if(tpoly->sustain) { - tpoly->isDecay = true; - - - for(t=0;t<4;t++) { - //memset(tpoly->pStatus[t].decay,0,sizeof(tpoly->pStatus[t].decay)); - //tpoly->pStatus[t].isDecayed = 0; - //memset(tpoly->pStatus[t].decaying,true,sizeof(tpoly->pStatus[t].decaying)); - StartDecay(AMPENV,tpoly->pStatus[t].envs[AMPENV].prevlevel, &tpoly->pStatus[t], tpoly); - StartDecay(FILTENV,tpoly->pStatus[t].envs[FILTENV].prevlevel, &tpoly->pStatus[t], tpoly); - StartDecay(PITCHENV,tpoly->pStatus[t].envs[PITCHENV].prevlevel, &tpoly->pStatus[t], tpoly); - tpoly->pStatus[t].pitchsustain = false; - } - tpoly->isPlaying = false; - } - //return; - } - found = true; - - - } - } - - } - - if(f!=-1) return; - oldest = -1; - for(q=0;q<MAXPOLY;q++) { - tpoly = &polyTable[q]; - - if((tpoly->isPlaying) && (!tpoly->isDecay) && (tpoly->chan==channum)) { - if(tpoly->age>=oldage) { - oldage = tpoly->age; - oldest = q; - } - count++; - } - } - - if(oldest!=-1) { - tpoly = &polyTable[oldest]; - tpoly->isDecay = true; - for(t=0;t<4;t++) { - //memset(tpoly->pStatus[t].decay,0,sizeof(tpoly->pStatus[t].decay)); - //tpoly->pStatus[t].isDecayed = 0; - //memset(tpoly->pStatus[t].decaying,true,sizeof(tpoly->pStatus[t].decaying)); - StartDecay(AMPENV,tpoly->pStatus[t].envs[AMPENV].prevlevel, &tpoly->pStatus[t], tpoly); - StartDecay(FILTENV,tpoly->pStatus[t].envs[FILTENV].prevlevel, &tpoly->pStatus[t], tpoly); - StartDecay(PITCHENV,tpoly->pStatus[t].envs[PITCHENV].prevlevel, &tpoly->pStatus[t], tpoly); - - tpoly->pStatus[t].pitchsustain = false; - } - tpoly->isPlaying = false; - //LOG(LOG_MISC|LOG_ERROR,"MT32 chan %d stopping note %d, %d remaining", this->channum, oldest, count-1); + //End of sysex + if (!file->writeByte(0xf7)) + break; } - + closeFile(file); } -MidiChannel *mchan[16]; - -#endif - - -bool CSynthMT32::InitTables() { +bool Synth::loadPCMToROMMap(const char *filename) { + File *file = openFile(filename, File::OpenMode_read); // Original specified text mode -#ifdef NOMANSLAND - - int noteat,f; - - //LOG_MSG("MT-32 Initializing Pitch Tables"); - for(f=-108;f<109;f++) { - keytable[f + 108] = (int)(256 * pow((float)2, (float)f / (float)24.0)); - //LOG_MSG("KT %d = %d", f, keytable[f+108]); - - } - float ff = 0; - for(f=0;f<=101;f++) { - ff = (float)f/100.00; - sqrtable[f] = (int)(100*sqrt(ff)); - float crapff = ff * (22000.0/32000.0); - if (crapff>1.0) crapff = 1.0; - //filttable[f] = (ff) * (22000.0/16000.0); - - } - - for(f=0;f<8;f++) { - ff = (float)f/14.00; - revtable[f] = (ff); - } - File fp; -#if MAKEWAVES == 1 - fp.open("waveforms.raw", File::kFileWriteMode); -#else - fp.open("waveforms.raw"); -#endif - if(!fp.isOpen()) { - error("Unable to open waveforms.raw"); + if (file == NULL) { return false; } - for(f=12;f<109;f++) { - - //int octave = (f / 12) - 6; - //int note = f % 12; - - //int cents = (octave * 100) + (note * 100); - //int freq = (int)((double)262 * pow((double)2,(double)((double)cents/1200))); - int freq = (int)(TUNING * pow( 2.0, ((double)f - 69.0) / 12.0 )); - freqtable[f] = freq; - divtable[f] = (int)( ((float)SETRATE / (float)(freq))); - smalldivtable[f] = divtable[f] << 8; - divtable[f] = divtable[f] << 16; - int rsaw,dep; - - for(rsaw=0;rsaw<=100;rsaw++) { - //(66-(((A8-50)/50)^.63)*50)/132 - float fsaw = (float)rsaw; - if(rsaw<50) fsaw = 50.0; - int tmpdiv = divtable[f] << 1; - - float sawfact = (66.0-(pow((fsaw-50.0)/50,.63)*50.0))/132.0; - sawtable[f][rsaw] = (int)(sawfact * (float)tmpdiv) >> 16; - //LOG_MSG("F %d divtable %d saw %d sawtable %d", f, divtable[f]>>16, rsaw, sawtable[f][rsaw]); - - } - - - for(dep=0;dep<5;dep++) { - if(dep>0) { - float depfac = 3000; - float ff1, tempdep; - depfac = (float)depexp[dep]; - - ff1 = ((float)f - (float)MIDDLEC) / depfac; - tempdep = pow((float)2,(float)ff) * 256; - fildeptable[dep][f] = (int)tempdep; - - ff1 = exp(tkcatconst[dep] * ((float)MIDDLEC-(float)f)) * tkcatmult[dep]; - timekeytable[dep][f] = (int)(ff1 * 256); - - } else { - fildeptable[dep][f] = 256; - timekeytable[dep][f] = 256; - } - } - //LOG_MSG("F %d d1 %x d2 %x d3 %x d4 %x d5 %x", f, fildeptable[0][f],fildeptable[1][f],fildeptable[2][f],fildeptable[3][f],fildeptable[4][f]); - - - - - noteat = 69-12; - -#if MAKEWAVES ==1 - double ampsize = WGAMP; - int halfdiv = divtable[f] >> 1; - int fa=0; - - float period = ((float)SETRATE / ((float)freq)); - float m=2*(int)(period/2)+1.0f; - float k=(int)(((float)50.0/100.0)*period); - double sd = (2.0*PI)/((((float)divtable[f]/65536.0)) * 4.0); - double sa = 0.0; - - //LOG_MSG("F %d sd %f div %d", f, sd, divtable[f]); - - int j; - float dumbfire; - double square=0.0f; - double saw = 0.0f; - - memset(waveformsize, 0,sizeof(tmpforms)); - - while(sa<=(2.0*PI)) { - float sqp; - - if(sa<PI) { - sqp = -1; - sqp = sqp + (.25 * (sa/PI)); - } else { - sqp=1; - sqp = sqp - (.25 * ((sa-PI)/PI)); - } - - square=0; - saw = 0; - bool odd = true; - for(int32 sinus=1;(sinus*freq)<(SETRATE);sinus++) { - float sinusval = (((1.0/((float)sinus))*(sin(((float)sinus)*sa)))); - saw=saw + sinusval; - } - - - dumbfire = sa/2; - - //This works pretty good - tmpforms[2][fa] += cos(dumbfire) * -ampsize; - tmpforms[3][(fa*2)] += cos(sa-PI) * -ampsize; - tmpforms[3][(fa*2)+1] += cos((sa+(sd/2))-PI) * -ampsize; - - tmpforms[0][fa] += (saw * -ampsize)/2; - tmpforms[1][fa] += (saw * ampsize)/2; - - //tmpforms[1][fa>>4] += saw * ampsize; - - - //waveforms[1][f][fa] = ((prevv2 - prevv)) * ampsize; - - - fa++; - sa+=sd; - } - //LOG_MSG("f num %d freq %d and fa %d", f, freq, fa); - - - waveformsize[0][f] = waveformsize[1][f] = waveformsize[2][f] = fa*2; - waveformsize[3][f] = fa*4; - - for (int i = 0; i < 4; ++i) { - waveforms[i][f] = (int16 *)malloc(waveformsize[i][f]); - memcpy(waveforms[i][f], &tmpforms[i][0],waveformsize[i][f]); - // TODO / FIXME: The following code is not endian safe! - out = fp.write(waveforms[i][f],waveformsize[i][f]); - } -#else - waveformsize[0][f] = waveformsize[1][f] = waveformsize[2][f] = divtable[f]>>13; - waveformsize[3][f] = divtable[f]>>12; - - for (int i = 0; i < 4; ++i) { - waveforms[i][f] = (int16 *)malloc(waveformsize[i][f]); - for (uint j = 0; j < waveformsize[i][f]/2; ++j) - waveforms[i][f][j] = fp.readSint16LE(); - } -#endif - - - // Create the pitch tables - - float tuner = (32000.0 / (float)SETRATE) * 65536.0; - - wavtable[f] = (int)(tuner * ((float)freq/(float)SAMPLETUNING)); // C4 Tuning?; - drumdelta[f] = (int)(tuner * ((float)freq/(float)SAMPLETUNING)); // C4 Tuning?; - int pc,lp,tr,ln; - for(pc=0;pc<54;pc++) { - wavtabler[pc][f] = (int)(tuner * ((float)freq/PCM[pc].tune)); - } - for(lp=0;lp<16;lp++) { - for(ln=0;ln<16;ln++) { - looptabler[lp][ln][f] = (int)((float)LoopPatternTuning[lp][ln] * ((float)freq/(float)220.0)); - } - } - - for(tr=0;tr<=200;tr++) { - float brsq, brsa; - float ftr = (float)tr; - - // Verified exact on MT-32 - if(tr>100) ftr=100+(pow(((ftr-100)/100),3)*100); - brsq = exp(0.0464 * ftr) / 5; - //EXP((90*0.0175)^2) - brsq = exp(pow(((float)tr * 0.0153),2)); - - // I think this is the one - brsq = pow((float)10,((float)(tr/50.0)-1)); - - //brsa = exp(0.04 * ftr) / 5; - - //Last good one that worked - //brsa = exp(pow(((float)tr * 0.0133),2)); - brsa = exp(pow(((float)tr * 0.005),3)) * 125; - - //brsa = pow((float)10,((float)(tr/46.5)-1))/2; - brsa = pow((float)10,((float)(tr/55)-1))/2; - - //brsa = pow(exp((ftr-40)/40),1.15); - //brsq = exp((ftr-35)/35); - //brsq = exp((ftr-35)/23); - //brsa = exp((ftr-35)/23); - //brsq = pow((ftr / 200), LN) * 48.0; - //brsa = pow((ftr / 200), LN) * 32.0; - //brsq = brsa = - - filttable[0][f][tr] = (int)(((float)freq * brsq)/(float)(SETRATE/2)*FILTERGRAN); - if(filttable[0][f][tr]>=((FILTERGRAN*15)/16)) filttable[0][f][tr] = ((FILTERGRAN*15)/16); - filttable[1][f][tr] = (int)(((float)freq * brsa)/(float)(SETRATE/2)*FILTERGRAN); - if(filttable[1][f][tr]>=((FILTERGRAN*15)/16)) filttable[1][f][tr] = ((FILTERGRAN*15)/16); - - } - - int cf, tf; - for(cf=0;cf<=100;cf++) { - float cfmult = (float)cf; - - for(tf=0;tf<=100;tf++) { - - //float tfadd = exp((((float)tf / 100.0) - 1.03) * 3.0) * 100; - //float tfadd = (pow(((float)tf /100.0),4) * 100.0) - 10; - float tfadd = tf - 0; - - if (tfadd < 0) tfadd = 0; - float freqsum = exp((cfmult + tfadd) / 30.0) / 4.0; - //float freqsum = exp((cfmult + tfadd) / 33.7) / 3.0; - - - nfilttable[f][cf][tf] = (int)(((float)freq * freqsum)/(float)(SETRATE/2)*FILTERGRAN); - if(nfilttable[f][cf][tf]>=((FILTERGRAN*15)/16)) nfilttable[f][cf][tf] = ((FILTERGRAN*15)/16); - } - } - } - - fp.close(); - - int j,res; - float fres = 0, tres = 0; - for(res=0;res<31;res++) { - fres = (float)res/30.0; - ResonFactor[res] = (pow((float)2,log(pow((float)fres,(float)16))) * 2.5)+1.0; - ResonInv[res] = 1 / ResonFactor[res]; - - } - - for(j=0;j<FILTERGRAN;j++) { - for(res=0;res<31;res++) { - tres = ResonFactor[res]; - InitFilter((float)SETRATE, (((float)(j+1.0)/FILTERGRAN)) * ((float)SETRATE/2), filtcoeff[j][res],tres, fres); - - // 64-bit variant -#if FILTER_64BIT == 1 - for(int co=0;co<9;co++) { - filtcoefffix[j][res][co] = (int64)(filtcoeff[j][res][co] * pow(2,20)); - - } -#endif - -#if FILTER_INT == 1 - for(int co=0;co<9;co++) { - filtcoefffix[j][res][co] = (long)(filtcoeff[j][res][co] * pow(2,10)); - - } -#endif - } - - } - - int period = 65536; - - int ang; - for(ang=0;ang<period;ang++) { - int halfang = (period / 2); - int angval = ang % halfang; - float tval = (((float)angval / (float)halfang) - 0.5) * 2; - if(ang>=halfang) tval = -tval; - sintable[ang] = (int)(tval * 50.0)+50; - - } - - - //for(ang=0;ang<period;ang++) sintable[period] *= 50; - int velt, dep; - float tempdep; - for(velt=0;velt<128;velt++) { - for(dep=0;dep<5;dep++) { - if(dep>0) { - float ff1 = exp(3.5*tvcatconst[dep] * (59.0-(float)velt)) * tvcatmult[dep]; - tempdep = 256.0 * ff1; - veltkeytable[dep][velt] = (int)tempdep; - if((velt % 16) == 0) { - //LOG_MSG("Key %d, depth %d, factor %d", velt, dep, (int)tempdep); - } - } else { - veltkeytable[dep][velt] = 256; - } - } -#define divpart 14.285714285714285714285714285714 - - for(dep=-7;dep<8;dep++) { - float fldep = fabs((float)dep) / 7.0; - fldep = pow((float)fldep,(float)2.5); - if(dep<0) fldep = fldep * -1.0; - pwveltable[dep+7][velt] = int32((fldep * (float)velt * 100) / 128.0); - - } - } - - for(dep=0;dep<=100;dep++) { - for(velt=0;velt<128;velt++) { - float fdep = (float)dep * 0.000347013; // Another MT-32 constant - float fv = ((float)velt - 64.0)/7.26; - float flogdep = pow((float)10, (float)(fdep * fv)); - float fbase; - - if(velt>64) { - filveltable[velt][dep] = (int)(flogdep * 256.0); - } else { - //lff = 1 - (pow(((128.0 - (float)lf) / 64.0),.25) * ((float)velt / 96)); - fbase = 1 - (pow(((float)dep / 100.0),.25) * ((float)(64-velt) / 96.0)); - filveltable[velt][dep] = (int)(fbase * 256.0); - - } - - //LOG_MSG("Filvel dep %d velt %d = %x", dep, velt, filveltable[velt][dep]); - } - } - - int lf; - for(lf=0;lf<=100;lf++) { - float elf = (float)lf; - - // General envelope - float logtime = elf * 0.088362939; - envtimetable[lf] = (int)((exp(logtime)/312.12) * (float)SETRATE); - - // Decay envelope -- shorter for some reason - // This is also the timing for the envelope right before the - // amp and filter envelope sustains - - lasttimetable[lf] = decaytimetable[lf] = (int)((exp(logtime)/(312.12*2)) * (float)SETRATE); - - //lasttimetable[lf] = (int)((exp(logtime)/(312.12*6)) * (float)SETRATE); - - // Finetuning table - //finetable[lf] = (int) ((pow(2, (((float)lf/100.0)-0.5)/6.0))*4096.0); - - } - for(lf=0;lf<=200;lf++) { - finetable[lf] = (int) ((pow((float)2, (float)((((float)lf/200.0)-1.0)/12.0))*4096.0)); - - } - for(lf=0;lf<=48;lf++) { - bendtable[lf] = (int) ((pow((float)2, (float)((((float)lf/12.0)-2.0)))*4096.0)); - - } - - float lff; - for(lf=0;lf<128;lf++) { - for(velt = 0;velt<64;velt++) { - lff = 1 - (pow(((128.0 - (float)lf) / 64.0),.25) * ((float)velt / 96)); - //lff = ((128.0 - (float)lf) / 128.0) * ((float)velt / 64); - //lff = 1.0 / pow(10, lff/2); - - - - ampveltable[lf][velt] =(int)(lff * 256.0); - //LOG_MSG("Ampveltable: %d, %d = %d", lf, velt, ampveltable[lf][velt]); - } - } - for(lf=0;lf<=127;lf++) { - restable[lf] = (int)( (pow((float)2,pow((float)(lf/127.0),(float)2.5))-1)*100 ); - //LOG_MSG("lf %d = amp %d", lf, restable[lf]); - } - - for(lf=0;lf<=127;lf++) { - //amptable[lf] = (int)( (pow(2,pow((float)lf/127.0,1))-1)*127 ); - //amptable[lf] = lf; - //float pubical = (float)lf/127.0; // Yes, as in boobical pubical (because its cubical!) - - // Converting MIDI to volume. - // Thanks Microsoft DDK - // value = 2^(log10((index/127)^4))*127 where index = 0..127 - amptable[lf] = voltable[lf] = (int)(127.0 * pow((float)2,log(pow((float)(lf/127.0),(float)4)))); - - // Nope, lets try this again - //amptable[lf] = voltable[lf] = (int)(127.0 * log10((float)lf/12.70)); - - //amptable[lf] = (int)(pubical * pubical * pubical * 127.0); - - // Once more... - - //float indec = 128.0 - (float)lf; - //indec = -(indec / 1.33333); - voltable[lf] = amptable[lf] = (int)(127.0 * pow((float)lf/127.0,LN)); - - //indec = 40 * log((float)lf / 127.0); - //voltable[lf] = (int)(127.0 * exp(indec/40.0)); - - //LOG_MSG("lf %d = vol %d", lf, voltable[lf]); - } - for(lf=0;lf<441;lf++) { - int myRand; - myRand = rand(); - myRand = ((myRand - 16383) * WGAMP) >> 18; - smallnoise[lf] = (int16)myRand; - } - - for(lf=0;lf<=100;lf++) { - ptable[lf] = (int)(pow((float)2,(float)((float)(lf-50)/25.0)) * 256); - - } - float tdist; - for(lf=0;lf<=50;lf++) { - if(lf==0) - padjtable[lf] = 7; - else if (lf==1) - padjtable[lf] = 6; - else if (lf==2) - padjtable[lf] = 5; - else if (lf==3) - padjtable[lf] = 4; - else if (lf==4) - padjtable[lf] = 4-(.333333f); - else if (lf==5) - padjtable[lf] = 4-(.333333f*2); - else if (lf==6) - padjtable[lf] = 3; - else if ((lf>6) && (lf<=12)) { - tdist = (lf-6.0) / 6.0; - padjtable[lf] = 3.0 - tdist; - } else if ((lf>12) && (lf<=25)) { - tdist = (lf-12.0) / 13.0; - padjtable[lf] = 2.0 - tdist; - - } else { - tdist = (lf-25.0) / 25.0; - padjtable[lf] = 1.0 - tdist; - } - //LOG_MSG("lf %d = padj %f", lf, padjtable[lf]); - } - for(lf=0;lf<=100;lf++) { - float mv = (float)lf / 100.0; - float pt = mv-0.5; - if(pt<0) pt = 0; - - pulsetable[lf] = (int)((pt) * 215.04) + 128; - pulseoffset[lf] = (int)(pt * WGAMP); - - /* - // I am certain of this: Verified by hand LFO log */ - lfotable[lf] = (int32)(((float)SETRATE) / (pow((float)1.088883372,(float)lf) * 0.021236044)); - - //LOG_MSG("lf %d = lfo %d pulsetable %d", lf, lfotable[lf], pulsetable[lf]); - } - - float lfp, depf, finalval, tlf; - int depat, pval, depti; - for(lf=0;lf<=10;lf++) { - // I believe the depth is cubed or something - - for(depat=0;depat<=100;depat++) { - if(lf>0) { - depti = abs(depat-50); - tlf = (float)lf - padjtable[depti]; - if(tlf<0) tlf = 0; - lfp = exp(0.713619942 * tlf) / 407.4945111; - - if(depat<50) - finalval = 4096.0 * pow((float)2,(float)-lfp); - else - finalval = 4096.0 * pow((float)2, (float)lfp); - pval = (int)(finalval); - - penvtable[lf][depat] = pval; - } else { - penvtable[lf][depat] = 4096; - - } - - //LOG_MSG("lf %d depat %d pval %d tlf %f lfp %f", lf,depat,pval, tlf, lfp); - - } - } - for(lf=0;lf<=100;lf++) { - // Maybe its linear - // It is - verified on MT-32 - one of the few things linear - lfp = ((float)lf * .1904) / 310.55; - - for(depat=0;depat<=100;depat++) { - depf = ((float)depat - 50.0) / 50.0; - //finalval = pow(2, lfp * depf * .5); - finalval = 4096.0 + (4096.0 * lfp * depf); - - pval = (int)(finalval); - - lfoptable[lf][depat] = pval; - - //LOG_MSG("lf %d depat %d pval %x", lf,depat,pval); - - } - } - - - int distval; - float amplog, dval; - - for(lf=0;lf<=12;lf++) { - for(distval=0;distval<128;distval++) { - if(lf==0) { - amplog = 0; - dval = 1; - ampbiastable[lf][distval] = 256; - } else { - amplog = pow((float)1.431817011,(float)lf) / PI; - dval = ((128.0-(float)distval)/128.0); - amplog = exp(amplog); - dval = pow(amplog,dval)/amplog; - ampbiastable[lf][distval] = (int)(dval * 256.0); - } - //LOG_MSG("Ampbias lf %d distval %d = %f (%x) %f", lf, distval, dval, ampbiastable[lf][distval],amplog); - } - - - - } - - for(lf=0;lf<=14;lf++) { - for(distval=0;distval<128;distval++) { - float filval = fabs((((float)lf - 7.0) * 12.0) / 7.0); - - if(lf==7) { - amplog = 0; - dval = 1; - fbiastable[lf][distval] = 256; - } else { - //amplog = pow(1.431817011,filval) / PI; - amplog = pow((float)1.531817011, (float)filval) / PI; - dval = ((128.0-(float)distval)/128.0); - amplog = exp(amplog); - dval = pow(amplog,dval)/amplog; - if(lf<8) { - fbiastable[lf][distval] = (int)(dval * 256.0); - } else { - dval = pow((float)dval, (float).3333333); - if(dval<.01) dval = .01f; - dval = 1 / dval; - fbiastable[lf][distval] = (int)(dval * 256.0); - } - } - //LOG_MSG("Fbias lf %d distval %d = %f (%x) %f", lf, distval, dval, fbiastable[lf][distval],amplog); - } - - - - } - - // Benchmark 3DNow, Floating point, and SSE filters -/* - uint32 bench; - __time64_t start, end; - float histval[50]; - - _time64(&start); - for(bench=0;bench<20000000;bench++) { - iir_filter_sse(0,&histval[0],filtcoeff[0][0],0); - } - _time64(&end); - //LOG_MSG("Bench completed in %ld seconds", end - start); -*/ - - -#endif - - return true; -} - -bool RecalcWaveforms(int sampRate, recalcStatusCallback callBack) { - -#ifdef NOMANSLAND - - File fp; - fp.open("waveforms.raw", File::kFileWriteMode); - - if(!fp.isOpen()) return false; - - double ampsize = WGAMP; - int f; - for(f=12;f<109;f++) { - - if(callBack != NULL) { - int perc = ((f - 12) * 100) / 96; - (callBack)(perc); - } - - //int octave = (f / 12) - 6; - //int note = f % 12; - - //int cents = (octave * 100) + (note * 100); - int freq = (int)(TUNING * pow( 2.0, ((double)f - 69.0) / 12.0 )); - freqtable[f] = freq; - divtable[f] = (int)( ((float)sampRate / (float)freq) ); - smalldivtable[f] = divtable[f] << 8; - divtable[f] = divtable[f] << 16; - - //int halfdiv = divtable[f] >> 1; - - //float period = ((float)sampRate / ((float)freq)); - //float m=2*(int)(period/2)+1.0f; - //float k=(int)(((float)50.0/100.0)*period); - - - double sd = (2.0*PI)/((float)divtable[f]/4096.0); - double sa = 0.0; - - - int fa=0; - - - int j; - float dumbfire; - double square=0.0f; - double saw = 0.0f; - - memset(tmpforms, 0,sizeof(tmpforms)); - - while(sa<=(2.0*PI)) { - float sqp; - - if(sa<PI) { - sqp = -1; - sqp = sqp + (.25 * (sa/PI)); - } else { - sqp=1; - sqp = sqp - (.25 * ((sa-PI)/PI)); - } - - square=0; - saw = 0; - //bool odd = true; - for(double sinus=1.0;sinus<256.0;sinus++) { - float sinusval = (((1/(sinus))*(sin(sinus*sa)))); - if((sinus*freq)<(sampRate*2)) saw=saw + sinusval; - } - - dumbfire = sa /2 ; - - //This works pretty good - tmpforms[2][fa>>4] += (int16)(cos(dumbfire) * -ampsize); - tmpforms[3][fa>>3] += (int16)(cos(sa-PI) * -ampsize); - - tmpforms[0][fa>>4] += (int16)(saw * -ampsize); - tmpforms[1][fa>>4] += (int16)(saw * ampsize); - - fa++; - sa+=sd; - } - - for(j=0;j<=(divtable[f]>>16);j++) { - finalforms[0][j] = tmpforms[0][j] >> 5; - finalforms[1][j] = tmpforms[1][j] >> 5; - finalforms[2][j] = tmpforms[2][j] >> 4; - } - for(j=0;j<=(divtable[f]>>15);j++) { - finalforms[3][j] = tmpforms[3][j] >> 3; - } - - int out; - - out = fp.write(finalforms[0],divtable[f]>>15); - out = fp.write(finalforms[1],divtable[f]>>15); - out = fp.write(finalforms[2],divtable[f]>>15); - out = fp.write(finalforms[3],divtable[f]>>14); - - - } - fp.close(); -#endif - - return true; -} - -bool CSynthMT32::ClassicOpen(SynthProperties useProp) { - -#ifdef NOMANSLAND - - if (isOpen) return false; - int i; - // Initalize patch information - uint8 sysexBuf[SYSEX_SIZE]; - uint16 syslen = 0; - - bool inSys = false; - - File fp; - uint8 c; - - myProp = useProp; - - usefilter = &iir_filter_normal; - - - for(i=0;i<MAXPARTIALS;i++) { - partTable[i] = new CPartialMT32(i); - } - - //pInfo = fopen("partial.nfo","wb"); - - - //LOG_MSG("MT-32 Initializing patch banks"); - - for(initmode=0;initmode<2;initmode++) { - - switch(initmode) { - case 0: - fp.open("Preset1.syx"); - if(!fp.isOpen()) { - // TODO : Fail driver init - error("Unable to open Preset1.syx"); - return false; - } - break; - case 1: - fp.open("Preset2.syx"); - if(!fp.isOpen()) { - // TODO : Fail driver init - error("Unable to open Preset2.syx"); - return false; - } - break; - default: - - // TODO : Fail driver init - return false; - - break; - } - - while(!fp.eof()) { - c = fp.readByte(); - sysexBuf[syslen] = c; - syslen++; - if(c==0xf0) { - inSys = true; - } - if ((c==0xf7) && (inSys)) { - PlaySysex(&sysexBuf[0],syslen); - inSys = false; - syslen = 0; - } - } - fp.close(); - + for (int i=0;i<54;i++) { + PCMReassign[i] = i; + PCM[i].tune = 220.0f; + PCM[i].ampval = 256; } + //PCM[53].ampval = 128; - //LOG_MSG("MT-32 Initializing Drums"); - - File fDrums; - fDrums.open("drumpat.rom"); - if(!fDrums.isOpen()) { - // TODO : Fail driver init - // printf("MT-32 Init Error - Missing drumpat.rom\n"); - error("Unable to open drumpat.rom"); + char tbuf[512]; + char *cp; + if (!file->readLine(tbuf,sizeof(tbuf))) { return false; } - int drumnum=0; - while(!fDrums.eof()) { - //Read common area - fDrums.read(&drums[drumnum].common,14); - int t; - for(t=0;t<4;t++) { - if (((drums[drumnum].common.pmute >> t) & 0x1) == 0x1) { - fDrums.read(&drums[drumnum].partial[t],58); - //LOG_MSG("Loaded drum #%d - t %d", drumnum,t); + Bit32u patchstart = 0; //axtoi(tbuf); + Bit32u patchend = 0; + Bit32u patchcount = 0; + bool rc = true; + for (;;) { + if (!file->readLine(tbuf,sizeof(tbuf))) { + if (!file->isEOF()) { + rc = false; } + break; } - //LOG_MSG("Loaded drum #%d - %s", drumnum,drums[drumnum].common.name); - drumnum++; - } - fDrums.close(); - -#if DUMPDRUMS == 1 - fp.open("drumsys.syx", File::kFileWriteMode); - char dumbtext[10], tmpb; - memset(dumbtext,0,10); - for(drumnum=0;drumnum<30;drumnum++) { - // Sysex header - tmpb = 0xf0; fp.write(&tmpb,1); - tmpb = 0x41; fp.write(&tmpb,1); - tmpb = 0x10; fp.write(&tmpb,1); - tmpb = 0x16; fp.write(&tmpb,1); - tmpb = 0x12; fp.write(&tmpb,1); - - int useaddr = drumnum * 256; - char lsb = useaddr & 0x7f; - char isb = (useaddr >> 7) & 0x7f; - char msb = ((useaddr >> 14) & 0x7f) | 0x08; - //Address - fp.write(&msb, 1); - fp.write(&isb, 1); - fp.write(&lsb, 1); - unsigned int checksum = msb + isb + lsb; - - //Data - fp.write(&drums[drumnum].common,0xe); - fp.write(&drums[drumnum].partial[0],0x3a); - fp.write(&drums[drumnum].partial[1],0x3a); - fp.write(&drums[drumnum].partial[2],0x3a); - fp.write(&drums[drumnum].partial[3],0x3a); - //Checksum - char *dat = (char *)&drums[drumnum]; - int ch; - for(ch=0;ch<246;ch++) checksum += *dat++; - checksum = (checksum & 0x7f); - if(checksum) checksum = 0x80 - checksum; - - fp.write(&checksum,1); - - //End of sysex - tmpb = 0xf7; fp.write(&tmpb,1); - } - fp.close(); -#endif - - //LOG_MSG("MT-32 Initializing Partials"); - - File fPatch; - fPatch.open("patchlog.cfg"); - - if(!fPatch.isOpen()) { - // TODO : Fail driver init - // printf("MT-32 Init Error - Missing patchlog.cfg\n"); - error("Unable to open patchlog.cfg"); - return false; - } - - for(i=0;i<54;i++) { - PCMReassign[i] = i; - PCM[i].tune = 220.0; - PCM[i].ampval = 256; - - } - //PCM[53].ampval = 128; - if (!fPatch.eof()) { - char tbuf[512]; - char *cp; - fPatch.gets(tbuf,sizeof(tbuf)); - uint32 patchstart = 0; //axtoi(tbuf); - uint32 patchend = 0; - uint32 patchcount = 0; - //int16 *romaddr = &romfile[0]; - while (!fPatch.eof()) { - fPatch.gets(tbuf,sizeof(tbuf)); - cp = strtok(tbuf," \n\r"); - PCM[patchcount].loop = false; - if(cp != NULL) { - patchend = axtoi(cp); + cp = strtok(tbuf," \n\r"); + PCM[patchcount].loop = false; + if (cp != NULL) { + patchend = axtoi(cp); + cp = strtok(NULL," \n\r"); + if (cp != NULL) { cp = strtok(NULL," \n\r"); - if(cp != NULL) { + if (cp != NULL) { cp = strtok(NULL," \n\r"); - if(cp != NULL) { + if (cp !=NULL) { + int newpcm = atoi(cp); + PCMReassign[newpcm] = patchcount; cp = strtok(NULL," \n\r"); - if (cp !=NULL) { - int newpcm = atoi(cp); - PCMReassign[newpcm] = patchcount; + if (cp != NULL) { + if (atoi(cp)==1) + PCM[patchcount].loop = true; cp = strtok(NULL," \n\r"); - if(cp != NULL) { - if(atoi(cp)==1) PCM[patchcount].loop = true; - cp = strtok(NULL," \n\r"); - if(cp != NULL) { - PCM[patchcount].tune = (float)atoi(cp) / 100.0; - //LOG_MSG("PCM %d tuning at %f", patchcount, PCM[patchcount].tune); - } + if (cp != NULL) { + PCM[patchcount].tune = (float)atoi(cp) / 100.0f; + //printDebug("PCM %d tuning at %f", patchcount, PCM[patchcount].tune); } } } } } - if (patchend==0) break; - - PCM[patchcount].addr = patchstart; - PCM[patchcount].len = patchend - patchstart; - patchcount++; - //printf("Patch %d %d %d %d\n", patchcount, patchstart, patchend, mt32ram.PCM[patchcount].len); - patchstart = patchend; - } - } else { - // TODO : Fail driver init - return false; + if (patchend==0) + break; - //exit(0); + PCM[patchcount].addr = patchstart; + PCM[patchcount].len = patchend - patchstart; + patchcount++; + //printf("Patch %d %d %d %d", patchcount, patchstart, patchend, mt32ram.PCM[patchcount].len); + patchstart = patchend; } - - fPatch.close(); - + closeFile(file); + if (!rc) + return rc; PCM[53].len = 1950; - int pat = 0; - int p; - for(p=0;p<54;p++) { - int pr = PCMReassign[p]; - if(!PCM[pr].loop) { - PCMLoopTable[pat] = p; - pat++; - } - if(pat==7) { - PCMLoopTable[pat] = p; - pat++; - } - } - // These are the special combination loop patches - for(p=46;p<=54;p++) { - PCMLoopTable[pat] = -(p - 45); - pat++; - } - - //for(p=0;p<54;p++) LOG_MSG("Loop table %d = %d (%d)", p, PCMLoopTable[p], PCM[p].loop); - // Generate official PCM list // Normal sounds - pat = 0; - for(p=0;p<54;p++) { + int pat = 0; + for (int p = 0; p < 54; p++) { PCMList[pat].addr = PCM[PCMReassign[p]].addr; PCMList[pat].len = PCM[PCMReassign[p]].len; PCMList[pat].loop = PCM[PCMReassign[p]].loop; PCMList[pat].aggSound = -1; PCMList[pat].pcmnum = PCMReassign[p]; - PCMList[pat].ampval = PCM[PCMReassign[p]].ampval; pat++; } // Drum specific sounds. Not exactly sure yet how these are different - for(p=0;p<20;p++) { + for (int p = 0; p < 20; p++) { PCMList[pat] = PCMList[p]; pat++; } - + // Looped PCM sounds. The last remaining 9 are aggregate sounds; - for(p=0;p<54;p++) { - if(PCMLoopTable[p]>-1) { - PCMList[pat].addr = PCM[PCMReassign[PCMLoopTable[p]]].addr; - PCMList[pat].ampval = PCM[PCMReassign[PCMLoopTable[p]]].ampval; - PCMList[pat].len = PCM[PCMReassign[PCMLoopTable[p]]].len; - PCMList[pat].pcmnum = PCMReassign[PCMLoopTable[p]]; + // FIXME:KG: I hope this is correct; the original was heavily broken, + // and it was hard to determine the author's intention. + for (int p = 0; p < 54; p++) { + if (p < 45) { + int pcmNum = p > 7 ? p - 1 : p; + PCMList[pat].addr = PCM[PCMReassign[pcmNum]].addr; + PCMList[pat].len = PCM[PCMReassign[pcmNum]].len; + PCMList[pat].pcmnum = PCMReassign[pcmNum]; PCMList[pat].loop = true; PCMList[pat].aggSound = -1; } else { - PCMList[pat].addr = 0; - //Calculate aggregate length - int aggsnd = (-PCMLoopTable[p])-1; + int aggsnd = p - 45; int tmplen = 0; int sndpos = 0; - while(LoopPatterns[aggsnd][sndpos] != -1) { + while (LoopPatterns[aggsnd][sndpos] != -1) { tmplen += PCM[LoopPatterns[aggsnd][sndpos]].len; sndpos++; } - + PCMList[pat].addr = 0; PCMList[pat].len = tmplen; PCMList[pat].loop = true; PCMList[pat].aggSound = aggsnd; - PCMList[pat].ampval = 0x100; } pat++; - } - //for(p=0;p<128;p++) LOG_MSG("PCM #%d addr 0x%x len %d loop %d aggSound %d pcmnum %d", p, PCMList[p].addr, PCMList[p].len, PCMList[p].loop, PCMList[p].aggSound, PCMList[p].pcmnum); + //for (p=0;p<128;p++) + // printDebug("PCM #%d addr 0x%x len %d loop %d aggSound %d pcmnum %d", p, PCMList[p].addr, PCMList[p].len, PCMList[p].loop, PCMList[p].aggSound, PCMList[p].pcmnum); + return true; +} - //LOG_MSG("MT-32 Initializing ROM"); - File fIn; - fIn.open("MT32_PCM.ROM"); -#ifdef MT32OUT - File fOut, fOutb; - char tmpc; - fOut.open("mt32out.raw",File::kFileWriteMode); - fOutb.open("mt32out2.raw",File::kFileWriteMode); -#endif - - if(!fIn.isOpen()) { - // TODO : Fail driver init - // printf("MT-32 Init Error - Missing MT32_PCM.ROM\n"); - error("Unable to open MT32_PCM.ROM"); +bool Synth::loadROM(const char *filename) { + File *file = openFile(filename, File::OpenMode_read); // ROM File + if (file == NULL) { return false; } - i=0; - //int32 maxamp = 0; - while (!fIn.eof()) { - int16 s, c1; - - s = fIn.readByte(); - c1 = fIn.readByte(); - - /* - int e,z,u,bit; - - int order[16] = {0, 9,1 ,2, 3, 4, 5, 6, 7, 10, 11, 12,13, 14, 15,8}; - - e=0; - z = 15; - for(u=0;u<15;u++) { - if((order[u]<8) && (order[u]>=0)) { - bit = (s >> (7-order[u])) & 0x1; +#ifdef MT32OUT + File *outFile = openFile("mt32out.raw", File::OpenMode_write); + File *outFileB = openFile("mt32out2.raw", File::OpenMode_write); +#endif + bool rc = true; + for (int i = 0; ; i++) { + int fc = file->readByte(); + if (fc == -1) { + if (!file->isEOF()) { + rc = false; + } + break; + } + Bit16s s = (Bit16s)fc; + fc = file->readByte(); + if (fc == -1) { + if (!file->isEOF()) { + rc = false; } else { - if(order[u]>=8) { - bit = (c1 >> (7-(order[u]-8))) & 0x1; - } else { - bit = 0; - } + printDebug("ROM file has an odd number of bytes! Ignoring last"); } - e = e | (bit << z); - --z; + break; } - - //if( (e & 0x1) != 0) printf("Last bit = %d\n", e & 0x1); - //int16 e = ( ((s & 0x7f) << 4) | ((c1 & 0x40) << 6) | ((s & 0x80) << 6) | ((c1 & 0x3f))) << 2; - if(e<0) e = -32767 - e; - int ut = abs(e); - int dif = 0x7fff - ut; - x = exp(((float)((float)0x8000-(float)dif) / (float)0x1000)); - e = (int)((float)e * (x/3200));*/ + Bit16s c = (Bit16s)fc; short e; - int z; int bit; int u; - - static const int order[16] = {0, 9,1 ,2, 3, 4, 5, 6, 7, 10, 11, 12,13, 14, 15,8}; - + + int order[16] = {0, 9, 1 ,2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 8}; + e=0; - z = 15; - for(u=0;u<15;u++) { - if((order[u]<8) && (order[u]>=0)) { + for (u=0;u<15;u++) { + if (order[u]<8) bit = (s >> (7-order[u])) & 0x1; - } else { - if(order[u]>=8) { - bit = (c1 >> (7-(order[u]-8))) & 0x1; - } else { - bit = 0; - } - } - e = e | (bit << z); - --z; + else + bit = (c >> (7-(order[u]-8))) & 0x1; + e = e | (short)(bit << (15 - u)); } + /* + //Bit16s e = ( ((s & 0x7f) << 4) | ((c & 0x40) << 6) | ((s & 0x80) << 6) | ((c & 0x3f))) << 2; + if (e<0) + e = -32767 - e; + int ut = abs(e); + int dif = 0x7fff - ut; + x = exp(((float)((float)0x8000-(float)dif) / (float)0x1000)); + e = (int)((float)e * (x/3200)); + */ + #ifdef MT32OUT - tmpc = e & 0xff; fOut.write(&tmpc, 1); - tmpc = (e >> 8) & 0x7f; fOut.write(&tmpc, 1); -#endif + outFile->writeByte(e & 0xff); + outFile->writeByte(((e >> 8) & 0x7f)); +#endif // File is encoded in dB, convert to PCM // MINDB = -96 // MAXDB = -15 float testval; testval = (float)((~e) & 0x7fff); - testval = -(testval / 400.00); + testval = -(testval / 400.00f); //testval = -(testval / 341.32291666666666666666666666667); - //testval = -(testval / 400.00); - float vol = pow((float)8,(float)(testval / 20)) * 32767; - - if (e>0) vol = -vol; - - romfile[i] = (int16)vol; + float vol = powf(8,(testval / 20)) * 32767.0f; -#ifdef MT32OUT - tmpc = (int16)vol & 0xff; fOutb.write(&tmpc, 1); - tmpc = (int16)vol >> 8; fOutb.write(&tmpc, 1); -#endif + if (e>0) + vol = -vol; - i++; + romfile[i] = (Bit16s)vol; + +#ifdef MT32OUT + outFileB->writeByte(romfile[i] & 0xff); + outFileB->writeByte(romfile[i] >> 8); +#endif } - //LOG_MSG("PCM amp = %d", maxamp); #ifdef MT32OUT - fOutb.close(); - fOut.close(); + closeFile(outFileB); + closeFile(outFile); #endif - fIn.close(); - int tmp; - for(tmp=0;tmp<16;tmp++) { - if((tmp>=1) && (tmp<=9)) { - chantable[tmp] = tmp-1; - } else { - chantable[tmp] = -1; - } + closeFile(file); + return rc; +} + +bool Synth::open(SynthProperties &useProp) { + if (isOpen) + return false; + + // Initalise patch information + + myProp = useProp; + + usefilter = &iir_filter_normal; + + partialManager = new PartialManager(this); + + // This is so that names won't be garbage during early setup debug output, but we can detect bugs + memset(&mt32ram, '?', sizeof(mt32ram)); + + printDebug("Initialising patch banks"); + initmode = 0; + if (!loadPreset("Preset1.syx")) { + report(ReportType_errorPreset1, &errno); + return false; } - chantable[10] = 8; - for(i=0;i<128;i++) { - mt32ram.params.pSettings[i].timbreGroup = i >> 6; - mt32ram.params.pSettings[i].timbreNum = i & 63; + initmode = 1; + if (!loadPreset("Preset2.syx")) { + report(ReportType_errorPreset2, &errno); + return false; } + initmode = 2; - // For resetting mt32 mid-execution - memcpy(&mt32default, &mt32ram, sizeof(mt32ram)); + printDebug("Initialising Drums"); + if (!loadDrums("drumpat.rom")) { + report(ReportType_errorDrumpat, &errno); + return false; + } - if (!InitTables()) return false; - if(myProp.UseDefault) { - InitReverb(0,5,SETRATE); - } else { - InitReverb(myProp.RevType, myProp.RevTime,SETRATE); +#if DUMPDRUMS == 1 + strcpy(&pathBuf[0], baseDir); + dumpDrums(strcat(&pathBuf[0],"drumsys.syx")); +#endif + + printDebug("Initialising PCM-to-ROM map"); + if (!loadPCMToROMMap("patchlog.cfg")) { + printDebug("Init Error - Missing patchlog.cfg"); + report(ReportType_errorPatchlog, &errno); + return false; + } + + printDebug("Initialising ROM"); + if (!loadROM("MT32_PCM.ROM")) { + printDebug("Init Error - Missing MT32_PCM.ROM"); + report(ReportType_errorMT32ROM, &errno); + return false; + } + memcpy(chantable, InitChanTable, sizeof (chantable)); + for (unsigned char i = 0; i < 128; i++) { + mt32ram.params.patches[i].timbreGroup = i >> 6; + mt32ram.params.patches[i].timbreNum = i & 63; } - for(i=0;i<10;i++) { - mchan[i] = new MidiChannel(SETRATE,i); + TableInitialiser tableInitialiser; + tableInitialiser.initMT32Tables(this, PCM, (float)myProp.SampleRate); + if (myProp.UseDefault) + initReverb(0,5); + else + initReverb(myProp.RevType, myProp.RevTime); + + for (int i = 0; i < 9; i++) { + parts[i] = new Part(this, i); - if(i<8) mchan[i]->SetPatch(InitInstr[i],-1); - if(i>8) mchan[i]->SetPatch(InitInstr[i-9],-1); + if (i<8) { + // The patch is already set by the presets, now set the timbre it wants + parts[i]->setTimbre(&mt32ram.params.timbres[parts[i]->getAbsTimbreNum()].timbre); + // And refresh the part's cache + parts[i]->RefreshPatch(); + } } - activeChannels = 0; + + // For resetting mt32 mid-execution + mt32default = mt32ram; #ifdef HAVE_X86 - bool useSSE = false, use3DNow = false; + bool availableSSE = DetectSIMD(); + bool available3DNow = Detect3DNow(); + + if (availableSSE) + report(ReportType_availableSSE, NULL); + if (available3DNow) + report(ReportType_available3DNow, NULL); - use3DNow = Detect3DNow(); - useSSE = DetectSIMD(); - - if (useSSE) debug(1, "MT-32: SSE detected and enabled"); - if (use3DNow) debug(1, "MT-32: 3DNow! detected and enabled"); - - if(use3DNow) { - debug(1, "MT-32 using use SIMD (AMD 3DNow) extensions"); + if (available3DNow) { + printDebug("Detected and using SIMD (AMD 3DNow) extensions"); usefilter = &iir_filter_3dnow; + } else if (availableSSE) { + printDebug("Detected and using SIMD (Intel SSE) extensions"); + usefilter = &iir_filter_sse; } +#endif - if(useSSE) { - debug(1, "MT-32 using SIMD (Intel SSE) extensions\n"); - usefilter = &iir_filter_sse; - usingSIMD = true; +#if BENCHMARK > 1 + // Benchmark 3DNow, Floating point, and SSE filters + clock_t start, end; + float histval[50]; + + for (int bench = 0; bench < 3; bench++) { + start = clock(); + for (int benchcnt=0;benchcnt<2000000;benchcnt++) { + switch (bench) { + case 0: + iir_filter_normal(0,&histval[0],filtcoeff[0][0],0); + break; + case 1: + if (!availableSSE) { + printDebug("Skipping SSE benchmark, SSE not available"); + continue; + } + iir_filter_sse(0,&histval[0],filtcoeff[0][0],0); + break; + case 2: + if (!available3DNow) { + printDebug("Skipping 3DNow benchmark, 3DNow not available"); + continue; + } + iir_filter_3dnow(0,&histval[0],filtcoeff[0][0],0); + break; + } + } + end = clock(); + printDebug("Bench %ld completed in %ld milliseconds", bench, (end - start) * 1000 / CLOCKS_PER_SEC); } #endif isOpen=true; isEnabled=false; -#endif - + printDebug("**************** Initialisation complete ****************"); return true; -} +}; -void CSynthMT32::Close(void) { - if (!isOpen) return; +void Synth::close(void) { + if (!isOpen) + return; -#ifdef NOMANSLAND - int t, m; - for(t=0;t<4;t++) { - for(m=0;m<256;m++) { - if(waveforms[t][m]!=NULL) free(waveforms[t][m]); + for (int t = 0; t < 4; t++) { + for (int m = 0; m < 128; m++) { + if (waveforms[t][m]!=NULL) { + delete[] waveforms[t][m]; + waveforms[t][m] = NULL; + } } } + if (partialManager != NULL) { + partialManager = NULL; + delete partialManager; + } -#endif - - isOpen=false; + if (reverbModel != NULL) { + reverbModel = NULL; + delete reverbModel; + } + for (int i = 0; i < 9; i++) { + if (parts[i] != NULL) { + delete parts[i]; + parts[i] = NULL; + } + } -} + isOpen=false; +}; -void CSynthMT32::PlayMsg(uint32 msg) { +void Synth::playMsg(Bit32u msg) { + unsigned char code = (unsigned char)((msg & 0xf0) >> 4); + unsigned char chan = (unsigned char)(msg & 0xf); + unsigned char note = (unsigned char)((msg & 0xff00) >> 8); + unsigned char velocity = (unsigned char)((msg & 0xff0000) >> 16); + isEnabled = true; -#ifdef NOMANSLAND + //if (code!=0xf) printDebug("Playing chan %d, code 0x%01x note: 0x%02x", chan, code, note); - int h; - int code = msg & 0xf0; - int chan = msg & 0xf; - int note = (msg & 0xff00) >> 8; - int velocity = (msg & 0xff0000) >> 16; - isEnabled= true; - - //if(chan!=0x9) { - // if(chan==12) return; - // chan = chan & 0x7; - // - //} else { - // chan = 8; - //} - //if (chan==0) return; - //int prechan = chan; - //if(code!=0xf0) LOG_MSG("Playing chan %d, code 0x%x note: 0x%x", chan, code, note); - - chan = chantable[chan]; - //LOG_MSG("Play msg on chan: %d = %d note: %x velocity: %x", chan, msg & 0xf, note, velocity); - if(chan<0) { - //LOG_MSG("Play msg 0x%x on unreg chan: %d = %x", chan, msg & 0xf); + char part = chantable[chan]; + if (part < 0 || part > 8) { + printDebug("Play msg on unreg chan %d (%d): code=0x%01x, vel=%d", chan, part, code, velocity); return; - } - if(chan>8) return; - + playMsgOnPart(part, code, note, velocity); +} - int patch; - uint32 bend; +void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity) { + Bit32u bend; - //LOG_MSG("Midi code: 0x%x",msg); + //printDebug("Synth::playMsg(0x%02x)",msg); switch (code) { - case 0x80: - //LOG_MSG("Note OFF - Channel %d",chan); - mchan[chan]->StopNote(note,velocity); + case 0x8: + //printDebug("Note OFF - Part %d", part); + // The MT-32 ignores velocity for note off + parts[part]->StopNote(note); break; - case 0x90: - //if(chan!=4) return; - //LOG_MSG("Note ON - Channel %d, Note %d Vel %d",chan, note, velocity); - - if(velocity>0) { - mchan[chan]->PlayNote(note,velocity); + case 0x9: + //printDebug("Note ON - Part %d, Note %d Vel %d", part, note, velocity); + if (velocity == 0) { + // MIDI defines note-on with velocity 0 as being the same as note-off with velocity 40 + parts[part]->StopNote(note); } else { - mchan[chan]->StopNote(note,velocity); + parts[part]->PlayNote(partialManager, note, velocity); } - - break; - case 0xb0: // Control change + case 0xB: // Control change switch (note) { - case 0x1: // Modulation - //LOG_MSG("Modulation: %d", velocity); - mchan[chan]->SetModulation(velocity); + case 0x01: // Modulation + //printDebug("Modulation: %d", velocity); + parts[part]->SetModulation(velocity); break; - case 0xb: - //LOG_MSG("Expression set: %d", velocity); - mchan[chan]->SetVolume(velocity); + case 0x0B: + //printDebug("Expression set: %d", velocity); + parts[part]->SetVolume(velocity); break; - case 0x7: // Set volume - //if(chan!=3) return; - //LOG_MSG("Volume set: %d", velocity); - mchan[chan]->SetVolume(velocity); + case 0x07: // Set volume + //if (part!=3) return; + //printDebug("Volume set: %d", velocity); + parts[part]->SetVolume(velocity); break; - case 0xa: // Pan - mchan[chan]->SetPan(velocity); + case 0x0A: // Pan + //printDebug("Pan set: %d", velocity); + parts[part]->SetPan(velocity); break; case 0x40: // Hold pedal - if(velocity<64) { - mchan[chan]->SetHoldPedal(false); - mchan[chan]->StopPedalHold(); - } else { - mchan[chan]->SetHoldPedal(true); - } + //printDebug("Hold pedal set: %d", velocity); + parts[part]->SetHoldPedal(velocity>=64); break; - case 0x7b: // All notes off - - for(h=0;h<MAXPOLY;h++) { - mchan[chan]->StopNote(-1,0); - } + case 0x7B: // All notes off + //printDebug("All notes off"); + parts[part]->AllStop(); break; case 0x79: // Reset all controllers + printDebug("Reset all controllers (NYI)"); break; default: - //LOG_MSG("Control code: 0x%x - vel %x",note, velocity); + printDebug("Unknown MIDI Control code: 0x%02x - vel %02x",note, velocity); break; } break; - case 0xc0: // Patch setting - char currentInstr[32]; - patch = (mt32ram.params.pSettings[note].timbreGroup * 64) + mt32ram.params.pSettings[note].timbreNum; - memset(¤tInstr,0,16); - memcpy(¤tInstr,mt32ram.params.patch[patch].common.name,10); - - //LOG_MSG("Set patch (%s) %d (%d) chan %d (%d) from ng %d, t %d", currentInstr, patch, note, chan, msg & 0xf, mt32ram.params.pSettings[note].timbreGroup, mt32ram.params.pSettings[note].timbreNum); - if((chan>=0) && (chan<8)) mt32ram.params.timTemp[chan] = mt32ram.params.patch[patch]; - mchan[chan]->SetPatch(note,-1); + case 0xC: // Program change + //printDebug("Program change %01x", note); + if (part < 8) { + parts[part]->SetPatch(note); + } else { + printDebug("Program change attempted on rhythm part"); + } break; - case 0xe0: // Pitch bender + case 0xE: // Pitch bender bend = (velocity << 7) | (note); - //LOG_MSG("Pitch bender %x", bend); - mchan[chan]->SetBend(bend); + //printDebug("Pitch bender %02x", bend); + parts[part]->SetBend(bend); break; default: - //LOG_MSG("Undef Midi code: 0x%x - %x - %x",code, note, velocity); - + printDebug("Unknown Midi code: 0x%01x - %02x - %02x", code, note, velocity); break; } -#endif //midiOutShortMsg(m_out, msg); +}; + +void Synth::playSysex(Bit8u * sysex,Bit32u len) { + if (len < 3) { + printDebug("playSysex: Message is too short for sysex (%d bytes)", len); + } + if (sysex[0] != 0xf0) { + printDebug("playSysex: Message lacks start-of-sysex (0xf0)"); + return; + } + if (sysex[len - 1] != 0xf7) { + printDebug("playSysex: Message lacks end-of-sysex (0xf7)"); + return; + } + playSysexWithoutFraming(sysex + 1, len - 2); } -void CSynthMT32::PlaySysex(uint8 * sysex,uint32 len) { +void Synth::playSysexWithoutFraming(Bit8u * sysex, Bit32u len) { + if (len < 4) { + printDebug("playSysexWithoutFraming: Message is too short (%d bytes)!", len); + return; + } + if (sysex[0] != 0x41) { + printDebug("playSysexWithoutFraming: Header not intended for this device manufacturer: %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); + return; + } + if (sysex[2] == 0x14) { + printDebug("playSysexWithoutFraming: Header is intended for Roland D-50 (not yet supported): %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); + return; + } + else if (sysex[2] != 0x16) { + printDebug("playSysexWithoutFraming: Header not intended for MT-32: %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); + return; + } + if (sysex[3] != 0x12) { + printDebug("playSysexWithoutFraming: Unsupported command %02x", sysex[3]); + return; + } + playSysexWithoutHeader(sysex[1], sysex + 4, len - 4); +} -#ifdef NOMANSLAND +// MEMADDR() converts from sysex-padded, SYSEXMEMADDR converts to it +// Roland provides documentation using the sysex-padded addresses, so we tend to use that int code and output +#define MEMADDR(x) ((((x) & 0x7f0000) >> 2) | (((x) & 0x7f00) >> 1) | ((x) & 0x7f)) +#define SYSEXMEMADDR(x) ((((x) & 0x1FC000) << 2) | (((x) & 0x3F80) << 1) | ((x) & 0x7f)) - uint32 addr; - uint32 *header; - unsigned int off; - int m; - header = (uint32 *)(sysex+1); - //int dummy = 0; - int32 lens = len; +#define NUMTOUCHED(x,y) (((x) + sizeof(y) - 1) / sizeof(y)) - // HACK: For some reason commands in IMuseInternal::initMT32 do not have prefix byte - // Also in some cases, particularly in mi2 "glop" sound at difficulty select screen - // header is wrong. I don't know what causes this as original has neither of these - // problems. - if((READ_BE_UINT32(header) != 0x41101612) || (READ_BE_UINT32(header) == 0x41001612)) { - if((READ_LE_UINT32(sysex) == 0x41101612) || (READ_BE_UINT32(sysex) == 0x41001612)) { - header = (uint32 *)sysex; - sysex--; // We don't access sysex[0], so it's safe - } +void Synth::playSysexWithoutHeader(unsigned char device, Bit8u *sysex, Bit32u len) { + if (device > 0x10) { + // We have device ID 0x10 (default, but changeable, on real MT-32), < 0x10 is for channels + printDebug("playSysexWithoutHeader: Message is not intended for this device ID (provided: %02x, expected: 0x10 or channel)", (int)device); + return; } - - if(READ_BE_UINT32(header) == 0x41101612) { - addr = (sysex[5] << 16) | (sysex[6] << 8) | (sysex[7]); - //LOG_MSG("Sysex addr: %x", addr); - if (addr<0x30000) { - //LOG_MSG("Channel temp area %x", addr); - } - if ((addr>=0x30000) && (addr<0x40000)) { - off = ((addr & 0x7f00) >> 1) | (addr & 0xff); - for(m=0;m<(lens-10);m++) { - mt32ram.memabs.mt32memory[off+m] = sysex[8+m]; + if (len < 4) { + printDebug("playSysexWithoutHeader: Message is too short (%d bytes)!", len); + return; + } + unsigned char checksum = calcChecksum(sysex, len - 1, 0); + if (checksum != sysex[len - 1]) { + printDebug("playSysexWithoutHeader: Message checksum is incorrect (provided: %02x, expected: %02x)!", sysex[len - 1], checksum); + return; + } + len -= 1; // Exclude checksum + Bit32u addr = (sysex[0] << 16) | (sysex[1] << 8) | (sysex[2]); + addr = MEMADDR(addr); + sysex += 3; + len -= 3; + printDebug("Sysex addr: 0x%06x", SYSEXMEMADDR(addr)); + // NOTE: Please keep both lower and upper bounds in each check, for ease of reading + if (device < 0x10) { + printDebug("WRITE-CHANNEL: Channel %d temp area 0x%06x", device, SYSEXMEMADDR(addr)); + if (/*addr >= MEMADDR(0x000000) && */addr < MEMADDR(0x010000)) { + int offset; + if (chantable[device] == -1) { + printDebug(" (Channel not mapped to a partial... 0 offset)"); + offset = 0; + } else if (chantable[device] == 8) { + printDebug(" (Channel mapped to rhythm... 0 offset)"); + offset = 0; + } else { + offset = chantable[device] * sizeof(MemParams::PatchTemp); + printDebug(" (Setting extra offset to %d)", offset); + } + addr += MEMADDR(0x030000) + offset; + } else if (/*addr >= 0x010000 && */ addr < MEMADDR(0x020000)) { + addr += MEMADDR(0x030110) - MEMADDR(0x010000); + } else if (/*addr >= 0x020000 && */ addr < MEMADDR(0x030000)) { + int offset; + if (chantable[device] == -1) { + printDebug(" (Channel not mapped to a partial... 0 offset)"); + offset = 0; + } else if (chantable[device] == 8) { + printDebug(" (Channel mapped to rhythm... 0 offset)"); + offset = 0; + } else { + offset = chantable[device] * sizeof(TimbreParam); + printDebug(" (Setting extra offset to %d)", offset); } - //LOG_MSG("Patch temp %d at %x - len %d", off/16, off % 16, len-10); - - if(initmode>1) { - for(m=0;m<8;m++) { - int tmppat; - for(tmppat=0;tmppat<128;tmppat++) { - if(mt32ram.params.pSettings[tmppat].timbreGroup == mt32ram.params.tmpSettings[m].timbreGroup) { - if(mt32ram.params.pSettings[tmppat].timbreNum == mt32ram.params.tmpSettings[m].timbreNum) { - //LOG_MSG("Setting %d to patch %d", m, tmppat); - this->PlayMsg((tmppat << 8) | 0xc0 | m); - - break; - } - } + addr += MEMADDR(0x040000) - MEMADDR(0x020000) + offset; + } else { + printDebug("PlaySysexWithoutHeader: Invalid channel %d address 0x%06x", device, SYSEXMEMADDR(addr)); + return; + } + } + if (addr >= MEMADDR(0x030000) && addr < MEMADDR(0x030110)) { + int off = addr - MEMADDR(0x030000); + if (off + len > sizeof(mt32ram.banks.pTemp)) { + printDebug("playSysexWithoutHeader: Message goes beyond bounds of memory region (addr=0x%06x, len=%d)!", SYSEXMEMADDR(addr), len); + return; + } + int firstPart = off / sizeof(MemParams::PatchTemp); + off %= sizeof(MemParams::PatchTemp); + for (unsigned int m = 0; m < len; m++) + mt32ram.banks.pTemp[firstPart][off + m] = sysex[m]; + //printDebug("Patch temp: Patch %d, offset %x, len %d", off/16, off % 16, len); + + int lastPart = firstPart + NUMTOUCHED(off + len, MemParams::PatchTemp) - 1; + for (int i = firstPart; i <= lastPart; i++) { + int absTimbreNum = mt32ram.params.patchSettings[i].patch.timbreGroup * 64 + mt32ram.params.patchSettings[i].patch.timbreNum; + char timbreName[11]; + memcpy(timbreName, mt32ram.params.timbres[absTimbreNum].timbre.common.name, 10); + timbreName[10] = 0; + printDebug("WRITE-PARTPATCH (%d-%d@%d..%d): %d; timbre=%d (%s)", firstPart, lastPart, off, off + len, i, absTimbreNum, timbreName); + if (parts[i] != NULL) { + parts[i]->setTimbre(&mt32ram.params.timbres[parts[i]->getAbsTimbreNum()].timbre); + parts[i]->RefreshPatch(); + } + } + } else if (addr >= MEMADDR(0x030110) && addr < MEMADDR(0x040000)) { + int off = addr - MEMADDR(0x030110); + if (off + len > sizeof(mt32ram.banks.rTemp)) { + printDebug("playSysexWithoutHeader: Message goes beyond bounds of memory region (addr=0x%06x, len=%d)!", SYSEXMEMADDR(addr), len); + return; + } + int firstDrum = off / sizeof(MemParams::RhythmTemp); + off %= sizeof(MemParams::RhythmTemp); + for (unsigned int m = 0; m < len; m++) + mt32ram.banks.rTemp[firstDrum][off + m] = sysex[m]; + int lastDrum = firstDrum + NUMTOUCHED(off + len, MemParams::RhythmTemp) - 1; + for (int i = firstDrum; i <= lastDrum; i++) { + int timbreNum = mt32ram.params.rhythmSettings[i].timbre; + char timbreName[11]; + if (timbreNum < 94) { + memcpy(timbreName, mt32ram.params.timbres[128 + timbreNum].timbre.common.name, 10); + timbreName[10] = 0; + } else { + strcpy(timbreName, "[None]"); + } + printDebug("WRITE-RHYTHM (%d-%d@%d..%d): %d; level=%02x, panpot=%02x, reverb=%02x, timbre=%d (%s)", firstDrum, lastDrum, off, off + len, i, mt32ram.params.rhythmSettings[i].outlevel, mt32ram.params.rhythmSettings[i].panpot, mt32ram.params.rhythmSettings[i].reverbSwitch, mt32ram.params.rhythmSettings[i].timbre, timbreName); + } + if (parts[8] != NULL) { + parts[8]->RefreshDrumCache(); + } + } else if (addr >= MEMADDR(0x040000) && addr < MEMADDR(0x050000)) { + int off = addr - MEMADDR(0x040000); + if (off + len > sizeof(mt32ram.banks.tTemp)) { + printDebug("playSysexWithoutHeader: Message goes beyond bounds of memory region (addr=0x%06x, len=%d)!", SYSEXMEMADDR(addr), len); + return; + } + int firstPart = off / sizeof(TimbreParam); + off %= sizeof(TimbreParam); + for (unsigned int m = 0; m < len; m++) + mt32ram.banks.tTemp[firstPart][off + m] = sysex[m]; + int lastPart = firstPart + NUMTOUCHED(off + len, TimbreParam) - 1; + for (int i = firstPart; i <= lastPart; i++) { + char instrumentName[11]; + memcpy(instrumentName, mt32ram.params.timbreSettings[i].common.name, 10); + instrumentName[10] = 0; + printDebug("WRITE-PARTTIMBRE (%d-%d@%d..%d): timbre=%d (%s)", firstPart, lastPart, off, off + len, i, instrumentName); + if (parts[i] != NULL) { + parts[i]->RefreshPatch(); + } + } + } + else if (addr >= MEMADDR(0x050000) && addr < MEMADDR(0x060000)) { + int off = addr - MEMADDR(0x050000); + if (off + len > sizeof(mt32ram.banks.patchBank)) { + printDebug("playSysexWithoutHeader: Message goes beyond bounds of memory region (addr=0x%06x, len=%d)!", SYSEXMEMADDR(addr), len); + return; + } + int firstPatch = off / sizeof(PatchParam); + off %= sizeof(PatchParam); + for (unsigned int m = 0; m < len; m++) + mt32ram.banks.patchBank[firstPatch][off + m] = sysex[m]; + int lastPatch = firstPatch + NUMTOUCHED(off + len, PatchParam) - 1; + for (int i = firstPatch; i <= lastPatch; i++) { + PatchParam *patch = &mt32ram.params.patches[i]; + int patchAbsTimbreNum = patch->timbreGroup * 64 + patch->timbreNum; + char instrumentName[11]; + memcpy(instrumentName, mt32ram.params.timbres[patchAbsTimbreNum].timbre.common.name, 10); + instrumentName[10] = 0; + printDebug("WRITE-PATCH (%d-%d@%d..%d): %d; timbre=%d (%s)", firstPatch, lastPatch, off, off + len, i, patchAbsTimbreNum, instrumentName); + // FIXME:KG: The below is definitely dodgy. We just guess that this is the patch that the part was using + // based on a timbre match (but many patches could have the same timbre!) + // If this refresh is really correct, we should store the patch number in use by each part. + /* + for (int part = 0; part < 8; part++) { + if (parts[part] != NULL) { + int partPatchAbsTimbreNum = mt32ram.params.patchSettings[part].patch.timbreGroup * 64 + mt32ram.params.patchSettings[part].patch.timbreNum; + if (partPatchAbsTimbreNum == patchAbsTimbreNum) { + parts[part]->setPatch(patch); + parts[part]->RefreshPatch(); } - //LOG_MSG("Patch chan %d - Assign mode %d", m,mt32ram.params.tmpSettings[m].fineTune); } } + */ } - if ((addr>=0x40000) && (addr<0x50000)) { - int toffat = sizeof(mt32ram.patchabs.pTemp) + sizeof(mt32ram.patchabs.rTemp); - off = ((addr & 0x7f00) >> 1) | (addr & 0x7f); - for(m=0;m<(lens-10);m++) { - mt32ram.memabs.mt32memory[off+m+toffat] = sysex[8+m]; - } - int chanoff = off / sizeof(timbreParam); - - //LOG_MSG("Timbre temp off %x offdiv %x - len %d", off, chanoff, len-10); - if(mchan[chanoff]!=NULL) mchan[chanoff]->RefreshPatch(); - } - - if ((addr>=0x50000) && (addr<0x60000)) { - off = (((addr & 0x7f00) >> 1) | (addr & 0xff)) + sizeof(mt32ram.patchabs.pTemp) + sizeof(mt32ram.patchabs.rTemp) + sizeof(mt32ram.patchabs.tTemp); - - for(m=0;m<(lens-10);m++) { - mt32ram.memabs.mt32memory[off+m] = sysex[8+m]; - } - //LOG_MSG("Patch area %d, assigning to %d, patch %d - len %d", off/16, mt32ram.params.pSettings[off/16].timbreGroup, mt32ram.params.pSettings[off/16].timbreNum, len-10); + } else if (addr >= MEMADDR(0x080000) && addr < MEMADDR(0x090000)) { + // Timbre patches + int off = addr - MEMADDR(0x080000); + if (off + len > sizeof(MemParams::PaddedTimbre) * 64) { + // You can only write to one group at a time + printDebug("playSysexWithoutHeader: Message goes beyond bounds of memory region (addr=0x%06x, len=%d)!", SYSEXMEMADDR(addr), len); + return; } - if ((addr>=0x80000) && (addr<0x90000)) { - // Timbre patches - int tc = (addr & 0xfe00) >> 9; - off = ((addr & 0x100) >> 1) | (addr & 0xff); - int calcoff; - int pn=0; - - switch(initmode) { + unsigned int firstTimbre = off / sizeof (MemParams::PaddedTimbre); + off %= sizeof (MemParams::PaddedTimbre); + switch (initmode) { case 0: - calcoff = tc * sizeof(timbreParam); - pn = tc; + // Write into first built-in timbre group break; case 1: - calcoff = (tc+ 64) * sizeof(timbreParam); - pn = tc + 64; + // Write into second built-in timbre group + firstTimbre += 64; break; default: - calcoff = (tc + 128) * sizeof(timbreParam); - pn = tc + 128; - break; - } - - // Transfer sysex parameter data into MT-32 memory - calcoff += (off + sizeof(mt32ram.patchabs.pTemp) + sizeof(mt32ram.patchabs.rTemp) + sizeof(mt32ram.patchabs.tTemp) + sizeof(mt32ram.patchabs.patchmemory)); - for(m=0;m<(lens-10);m++) { - mt32ram.memabs.mt32memory[calcoff+m] = sysex[8+m]; + firstTimbre += 128; + // Write into user timbre group + } + for (unsigned int m = 0; m < len; m++) + mt32ram.banks.timbreBank[firstTimbre][off + m] = sysex[m]; + unsigned int lastTimbre = firstTimbre + NUMTOUCHED(len + off, MemParams::PaddedTimbre) - 1; + for (unsigned int i = firstTimbre; i <= lastTimbre; i++) { + char instrumentName[11]; + memcpy(instrumentName, mt32ram.params.timbres[i].timbre.common.name, 10); + instrumentName[10] = 0; + printDebug("WRITE-TIMBRE (%d-%d@%d..%d): %d; name=\"%s\"", firstTimbre, lastTimbre, off, off + len, i, instrumentName); + // FIXME:KG: Not sure if the stuff below should be done (for rhythm and/or parts)... + // Does the real MT-32 automatically do this? + if (i >= 128 && parts[8] != NULL) { + // FIXME:KG: Only bother to re-cache when this timbre's actually in the rhythm map + parts[8]->SetPatch(i); // Re-cache this timbre + } + for (unsigned int part = 0; part < 8; part++) { + if (parts[part] != NULL) { + if (parts[part]->getAbsTimbreNum() == i) { + parts[part]->RefreshPatch(); + } + } } - //LOG_MSG("Loaded patch %s at pn %d", mt32ram.params.patch[pn].common.name, pn); } - if ((addr>=0x100000) && (addr<=0x1fffff)) { - off = ((addr & 0x100) >> 1) | (addr & 0xff); - for(m=0;m<(lens-10);m++) { - mt32ram.patchabs.systemBank[m+off] = sysex[8+m]; - } - - //LOG_MSG("System Reconfiguration:"); - memset(chantable,-1,sizeof(chantable)); - memset(miditable,-1,sizeof(miditable)); + } else if (addr >= MEMADDR(0x100000) && addr < MEMADDR(0x200000)) { + int off = addr - MEMADDR(0x100000); + if (off + len > sizeof(mt32ram.banks.systemBank)) { + printDebug("playSysexWithoutHeader: Message goes beyond bounds of memory region (addr=0x%06x, len=%d)!", SYSEXMEMADDR(addr), len); + return; + } + for (unsigned int m = 0; m < len; m++) + mt32ram.banks.systemBank[m + off] = sysex[m]; - for(m=0;m<9;m++) { - //LOG_MSG("Channel %d set to MIDI channel %d",m,mt32ram.params.system.chanAssign[m]); - if(mt32ram.params.system.chanAssign[m]==16) { - mchan[m]->AllStop(); - } else { - chantable[(int)mt32ram.params.system.chanAssign[m]]=m; - miditable[m] = mt32ram.params.system.chanAssign[m]; - } - } + report(ReportType_devReconfig, NULL); - //LOG_MSG("Master Tune: %f", ((float)mt32ram.params.system.masterTune)*0.2+432.1); - //LOG_MSG("Reverb mode: %d", mt32ram.params.system.reverbMode); - //LOG_MSG("Reverb time: %d", mt32ram.params.system.reverbTime); - //LOG_MSG("Reverb level: %d", mt32ram.params.system.reverbLevel); - - if(((uint32)mt32ram.params.system.reverbMode != curRevMode) || ((uint32)mt32ram.params.system.reverbTime!=curRevTime)) { - if(myProp.UseDefault) { - InitReverb(mt32ram.params.system.reverbMode, mt32ram.params.system.reverbTime,SETRATE); - curRevLevel = mt32ram.params.system.reverbLevel; - } else { - InitReverb(myProp.RevType, myProp.RevTime,SETRATE); - curRevLevel = myProp.RevLevel; - } - } + printDebug("System Reconfiguration:"); + memset(chantable,-1,sizeof(chantable)); - - char *rset = mt32ram.params.system.reserveSettings; - //LOG_MSG("Partial reserve: 1=%d 2=%d 3=%d 4=%d 5=%d 6=%d 7=%d 8=%d 9=%d", rset[0], rset[1], rset[2], rset[3], rset[4], rset[5], rset[6], rset[7], rset[8]); - int x,y,pr; - pr = 0; - for(x=0;x<9;x++) { - for(y=0;y<rset[x];y++) { - PartialReserveTable[pr] = x; - pr++; - } + for (unsigned int i = 0; i < 9; i++) { + //LOG(LOG_MISC|LOG_ERROR,"Part %d set to MIDI channel %d",i,mt32ram.params.system.chanAssign[i]); + if (mt32ram.params.system.chanAssign[i] == 16) { + parts[i]->AllStop(); + } else { + chantable[(int)mt32ram.params.system.chanAssign[i]] = (char)i; } - //if(pr != 32) LOG_MSG("Partial Reserve Table with less than 32 partials reserved!"); - rset = mt32ram.params.system.chanAssign; - //LOG_MSG("Chan assign: 1=%d 2=%d 3=%d 4=%d 5=%d 6=%d 7=%d 8=%d 9=%d", rset[0], rset[1], rset[2], rset[3], rset[4], rset[5], rset[6], rset[7], rset[8]); - //LOG_MSG("Master volume: %d",mt32ram.params.system.masterVol); - int16 tv = (int16)((float)mt32ram.params.system.masterVol * 327.0); - mastervolume = tv; - - } - if (addr==0x200000) { - char buf[SYSEX_SIZE]; - memset(&buf,0,SYSEX_SIZE); - memcpy(&buf,&sysex[8],lens-10); - //LOG_MSG("MT-32 LCD Display: %s", buf); - g_system->displayMessageOnOSD(buf); - } - if ((addr & 0xff0000) == 0x7f0000) { - //LOG_MSG("MT-32 Reset"); - for (uint32 m1=0;m1<MAXPARTIALS;m1++) partTable[m1]->isActive = false; - - memcpy(&mt32ram, &mt32default, sizeof(mt32ram)); - isEnabled = false; } + printDebug(" Master Tune: %f", ((float)mt32ram.params.system.masterTune)*0.2+432.1); + printDebug(" Reverb: mode=%d, time=%d, level=%d", mt32ram.params.system.reverbMode, mt32ram.params.system.reverbTime, mt32ram.params.system.reverbLevel); + report(ReportType_newReverbMode, &mt32ram.params.system.reverbMode); + report(ReportType_newReverbTime, &mt32ram.params.system.reverbTime); + report(ReportType_newReverbLevel, &mt32ram.params.system.reverbLevel); + if ((mt32ram.params.system.reverbMode != curRevMode) || (mt32ram.params.system.reverbTime != curRevTime)) { + if (myProp.UseDefault) { + initReverb(mt32ram.params.system.reverbMode, mt32ram.params.system.reverbTime); + curRevLevel = mt32ram.params.system.reverbLevel; + } else { + initReverb(myProp.RevType, myProp.RevTime); + curRevLevel = myProp.RevLevel; + } + } + + char *rset = mt32ram.params.system.reserveSettings; + printDebug(" Partial reserve: 1=%02d 2=%02d 3=%02d 4=%02d 5=%02d 6=%02d 7=%02d 8=%02d Rhythm=%02d", rset[0], rset[1], rset[2], rset[3], rset[4], rset[5], rset[6], rset[7], rset[8]); + int pr = partialManager->SetReserve(rset); + if (pr != 32) + printDebug(" (Partial Reserve Table with less than 32 partials reserved!)"); + rset = mt32ram.params.system.chanAssign; + printDebug(" Part assign: 1=%02d 2=%02d 3=%02d 4=%02d 5=%02d 6=%02d 7=%02d 8=%02d Rhythm=%02d", rset[0], rset[1], rset[2], rset[3], rset[4], rset[5], rset[6], rset[7], rset[8]); + printDebug(" Master volume: %d", mt32ram.params.system.masterVol); + mastervolume = (Bit16s)((float)mt32ram.params.system.masterVol * 327.0); + } else if (addr == MEMADDR(0x200000)) { + char buf[SYSEX_SIZE]; + memset(&buf, 0, SYSEX_SIZE); + memcpy(&buf, &sysex[0], len); + printDebug("LCD Display: %s", buf); + report(ReportType_lcdMessage, buf); + } else if (addr >= MEMADDR(0x7f0000)) { + printDebug("Reset"); + report(ReportType_devReset, NULL); + partialManager->DeactivateAll(); + mt32ram = mt32default; + for (int i = 0; i < 8; i++) { + parts[i]->RefreshPatch(); + } + parts[8]->RefreshDrumCache(); + isEnabled = false; } else { - // Header not intended for Roland MT-32 + printDebug("Sysex write to unrecognised address %06x", SYSEXMEMADDR(addr)); } +}; -#endif - -} - - -int CSynthMT32::DumpSysex(char *filename) { - File fp; - byte tmpc; - fp.open(filename,File::kFileWriteMode); - if(!fp.isOpen()) +int Synth::dumpSysex(char *filename) { + File *file = openFile(filename, File::OpenMode_write); + if (file == NULL) return -1; - + int patchnum; - for(patchnum=0;patchnum<64;patchnum++) { + for (patchnum=0;patchnum<64;patchnum++) { // Sysex header - tmpc = 0xf0; fp.write(&tmpc, 1); - tmpc = 0x41; fp.write(&tmpc, 1); - tmpc = 0x10; fp.write(&tmpc, 1); - tmpc = 0x16; fp.write(&tmpc, 1); - tmpc = 0x12; fp.write(&tmpc, 1); - + if (!file->writeByte(0xF0)) + break; + if (!file->writeByte(0x41)) + break; + if (!file->writeByte(0x10)) + break; + if (!file->writeByte(0x16)) + break; + if (!file->writeByte(0x12)) + break; + int useaddr = patchnum * 256; - char lsb = useaddr & 0x7f; - char isb = (useaddr >> 7) & 0x7f; - char msb = ((useaddr >> 14) & 0x7f) | 0x08; + char lsb = (char)(useaddr & 0x7f); + char isb = (char)((useaddr >> 7) & 0x7f); + char msb = (char)(((useaddr >> 14) & 0x7f) | 0x08); //Address - fp.write(&msb, 1); - fp.write(&isb, 1); - fp.write(&lsb, 1); - unsigned int checksum = msb + isb + lsb; - + if (!file->writeByte(msb)) + break; + if (!file->writeByte(isb)) + break; + if (!file->writeByte(lsb)) + break; + //Data - fp.write(&mt32ram.params.patch[patchnum+128].common,0xe); - fp.write(&mt32ram.params.patch[patchnum+128].partial[0],0x3a); - fp.write(&mt32ram.params.patch[patchnum+128].partial[1],0x3a); - fp.write(&mt32ram.params.patch[patchnum+128].partial[2],0x3a); - fp.write(&mt32ram.params.patch[patchnum+128].partial[3],0x3a); + if (file->write(&mt32ram.params.timbres[patchnum + 128].timbre.common,0xE) != 0xE) + break; + if (file->write(&mt32ram.params.timbres[patchnum + 128].timbre.partial[0],0x3A) != 0x3A) + break; + if (file->write(&mt32ram.params.timbres[patchnum + 128].timbre.partial[1],0x3A) != 0x3A) + break; + if (file->write(&mt32ram.params.timbres[patchnum + 128].timbre.partial[2],0x3A) != 0x3A) + break; + if (file->write(&mt32ram.params.timbres[patchnum + 128].timbre.partial[3],0x3A) != 0x3A) + break; //Checksum - char *dat = (char *)&mt32ram.params.patch[patchnum+128]; - int ch; - for(ch=0;ch<246;ch++) checksum += *dat++; - checksum = (checksum & 0x7f); - if(checksum) checksum = 0x80 - checksum; - - fp.write(&checksum,1); - + unsigned char *dat = (unsigned char *)&mt32ram.params.timbres[patchnum + 128].timbre; + unsigned char checksum = calcChecksum(dat, 246, msb + isb + lsb); + + if (!file->writeByte(checksum)) + break; + //End of sysex - tmpc = 0xf7; fp.write(&tmpc, 1); + if (!file->writeByte(0xF7)) + break; } - fp.close(); - //LOG_MSG("Wrote temp patches to %s", usefile); - + closeFile(file); + printDebug("Wrote temp patches to %s", filename); return 0; } - - -static int16 tmpBuffer[4096]; -static float sndbufl[4096]; -static float sndbufr[4096]; -static float outbufl[4096]; -static float outbufr[4096]; - -#if USE_MMX == 3 -static float multFactor[4] = { 32767.0, 32767.0, 32767.0, 32767.0 }; +void ProduceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume) { +#if USE_MMX > 2 + //FIXME:KG: This appears to introduce crackle + int donelen = i386_produceOutput1(useBuf, stream, len, volume); + len -= donelen; + stream += donelen * 2; + useBuf += donelen * 2; #endif - -void CSynthMT32::MT32_CallBack(uint8 * stream,uint32 len, int volume) { - -#ifdef NOMANSLAND - int32 i,m; - int16 *snd, *useBuf; - uint32 outlen; - snd = (int16 *)stream; - memset(stream,0,len*4); - if(!isEnabled) return; - useBuf = snd; - - outlen = len; - - assert(len < 1024); // tmpBuffer is 4096 bytes - /* - partUsage outUsage; - for(i=0;i<32;i++) { - if(partTable[i]->isActive) { - outUsage.active[i] = -1; - } else { - outUsage.active[i] = 0; - } - outUsage.owner[i] = partTable[i]->ownerChan; - outUsage.assign[i] = PartialReserveTable[i]; + int end = len * 2; + while (end--) { + *stream = *stream + (Bit16s)(((Bit32s)*useBuf++ * (Bit32s)volume)>>15); + stream++; } - fwrite(&outUsage,sizeof(outUsage),1,pInfo);*/ - - for(i=0;i<MAXPARTIALS;i++) partTable[i]->age++; +} - for(i=0;i<MAXPARTIALS;i++) { +void Synth::render(Bit16s *stream, Bit32u len) { + memset(stream, 0, len * sizeof (Bit16s) * 2); + if (!isEnabled) + return; + while (len > 0) { + Bit32u thisLen = len > MAX_SAMPLE_OUTPUT ? MAX_SAMPLE_OUTPUT : len; + doRender(stream, thisLen); + len -= thisLen; + stream += 2 * thisLen; + } +} - if(partTable[i]->produceOutput(tmpBuffer,outlen)==true) { -#if USE_MMX == 0 - int16 *tmpoff = snd; - int q = 0; - for(m=0;m<(int32)outlen;m++) { - tmpoff[q] += (int16)(((int32)tmpBuffer[q] * (int32)mastervolume)>>15); - q++; - tmpoff[q] += (int16)(((int32)tmpBuffer[q] * (int32)mastervolume)>>15); - q++; +void Synth::doRender(Bit16s * stream,Bit32u len) { + Bit32u m; - } -#else - int tmplen = (outlen >> 1) + 4; -#ifdef I_ASM - __asm { - mov ecx, tmplen - mov ax,mastervolume - shl eax,16 - mov ax,mastervolume - movd mm3,eax - movd mm2,eax - psllq mm3, 32 - por mm3,mm2 - mov esi, useBuf - mov edi, snd -mixloop4: - movq mm1, [esi] - movq mm2, [edi] - pmulhw mm1, mm3 - paddw mm1,mm2 - movq [edi], mm1 - - add esi,8 - add edi,8 + partialManager->AgeAll(); - dec ecx - cmp ecx,0 - jg mixloop4 - emms + if (myProp.UseReverb) { + bool hasOutput = false; + for (unsigned int i = 0; i < MAXPARTIALS; i++) { + if (partialManager->shouldReverb(i)) { + if (partialManager->ProduceOutput(i, &tmpBuffer[0], len)) { + ProduceOutput1(&tmpBuffer[0], stream, len, mastervolume); + hasOutput = true; + } } -#else - atti386_produceOutput1(tmplen, mastervolume, useBuf, snd); -#endif -#endif } - } - - if(myProp.UseReverb) { -#if USE_MMX == 3 - if(!usingSIMD) { -#endif + // No point in doing reverb on a mute buffer... + if (hasOutput) { m=0; - for(i=0;i<(int32)len;i++) { - sndbufl[i] = (float)snd[m] / 32767.0; + for (unsigned int i = 0; i < len; i++) { + sndbufl[i] = (float)stream[m] / 32767.0f; m++; - sndbufr[i] = (float)snd[m] / 32767.0; + sndbufr[i] = (float)stream[m] / 32767.0f; m++; } - newReverb->processreplace(sndbufl, sndbufr, outbufl, outbufr, len, 1); + reverbModel->processreplace(sndbufl, sndbufr, outbufl, outbufr, len, 1); m=0; - for(i=0;i<(int32)len;i++) { - snd[m] = (int16)(outbufl[i] * 32767.0); + for (unsigned int i = 0; i < len; i++) { + stream[m] = (Bit16s)(outbufl[i] * 32767.0f); m++; - snd[m] = (int16)(outbufr[i] * 32767.0); + stream[m] = (Bit16s)(outbufr[i] * 32767.0f); m++; } -#if USE_MMX == 3 - } else { -#ifdef I_ASM - // Use SIMD instructions to quickly convert between integer and floating point - __asm { - mov ecx, len - shr ecx, 1 - add ecx, 4 - push ecx - - mov esi, multFactor - movups xmm1, [esi] - - // One speaker at a time - mov esi, snd - mov edi, sndbufl - -convloop1: - xor eax,eax - mov ax,[snd] - cwde // Sign extended ax - inc snd - inc snd - movd mm1,eax - psrlq mm1, 32 - mov ax,[snd] - inc snd - inc snd - movd mm2,eax - por mm1,mm2 - - dec ecx - jnz convloop1 - - pop ecx - mov esi, snd - mov edi, sndbufr - inc esi -convloop2: - - dec ecx - jnz convloop2 - - } -#else - atti386_produceOutput2(len, snd, sndbufl, sndbufr, multFactor); -#endif } -#endif - } - - // for(i=0;i<9;i++) { mchan[i]->CheckNoteList(); } - - for(i=0;i<MAXPARTIALS;i++) { partTable[i]->alreadyOutputed = false; } - - -#if MAXPARTIALS == 0 - // Reorganize partials - CPartialMT32 *tmpPartial; - int y; -resetSearch: - for(i=0;i<MAXPARTIALS;i++) { - // Try to relocate partials not assigned to the right channel - if((partTable[i]->isActive) && (partTable[i]->ownerChan != PartialReserveTable[i])) { - for(y=0;y<MAXPARTIALS;y++) { - if((!partTable[y]->isActive) && (PartialReserveTable[y] == partTable[i]->ownerChan)) { - // Found a free channel that can use this partial - swap it out - tmpPartial = partTable[y]; - partTable[y] = partTable[i]; - partTable[i] = tmpPartial; - goto resetSearch; + for (unsigned int i = 0; i < MAXPARTIALS; i++) { + if (!partialManager->shouldReverb(i)) { + if (partialManager->ProduceOutput(i, &tmpBuffer[0], len)) { + ProduceOutput1(&tmpBuffer[0], stream, len, mastervolume); } } } + } else { + for (unsigned int i = 0; i < MAXPARTIALS; i++) { + if (partialManager->ProduceOutput(i, &tmpBuffer[0], len)) + ProduceOutput1(&tmpBuffer[0], stream, len, mastervolume); + } } -#endif + + partialManager->ClearAlreadyOutputed(); #if MONITORPARTIALS == 1 - samplepos+=outlen; - if(samplepos>SETRATE*5) { + samplepos += len; + if (samplepos > myProp.SampleRate * 5) { samplepos = 0; - int partUse[9]; - memset(partUse,0,sizeof(partUse)); - for(i=0;i<MAXPARTIALS;i++) { - if(partTable[i]->isActive) partUse[partTable[i]->ownerChan]++; - } - //LOG_MSG("C1: %d C2: %d C3: %d C4 %d", partUse[0], partUse[1], partUse[2], partUse[3]); - //LOG_MSG("C5: %d C6: %d C7: %d C8 %d", partUse[4], partUse[5], partUse[6], partUse[7]); - //LOG_MSG("Rythmn: %d", partUse[8]); - + int partialUsage[9]; + partialManager->GetPerPartPartialUsage(partialUsage); + printDebug("1:%02d 2:%02d 3:%02d 4:%02d 5:%02d 6:%02d 7:%02d 8:%02d", partialUsage[0], partialUsage[1], partialUsage[2], partialUsage[3], partialUsage[4], partialUsage[5], partialUsage[6], partialUsage[7]); + printDebug("Rhythm: %02d TOTAL: %02d", partialUsage[8], MAXPARTIALS - partialManager->GetFreePartialCount()); } - #endif - - -#endif - +} } diff --git a/backends/midi/mt32/synth.h b/backends/midi/mt32/synth.h index da2c685d34..e181c4d763 100644 --- a/backends/midi/mt32/synth.h +++ b/backends/midi/mt32/synth.h @@ -1,68 +1,73 @@ -/* ScummVM - Scumm Interpreter - * Copyright (C) 2004 The ScummVM project - * Based on Tristan's conversion of Canadacow's code +/* Copyright (c) 2003-2004 Various contributors * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * 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 General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * $Header$ + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. */ -#ifndef CSYNTHMT32_H -#define CSYNTHMT32_H - -#ifdef HAVE_X86 -#if defined(_MSC_VER) -#define USE_MMX 2 -#define I_ASM -#else -#define USE_MMX 0 -#undef I_ASM -#endif -#else -#define USE_MMX 0 -#endif - -#define AMPENV 0 -#define FILTENV 1 -#define PITCHENV 2 - -// Filter setting -#define FILTER_FLOAT 1 -#define FILTER_64BIT 0 -#define FILTER_INT 0 - -#define FILTERGRAN 512 - -// Amplitude of waveform generator -#define WGAMP (7168) -//#define WGAMP (8192) - -#include "backends/midi/mt32/structures.h" -#include "sound/mixer.h" - -// Function that detects the availablity of SSE SIMD instructions -// On non-MSVC compilers it automatically returns FALSE as inline assembly is required -bool DetectSIMD(); -// Function that detects the availablity of 3DNow instructions -// On non-MSVC compilers it automatically returns FALSE as inline assembly is required -bool Detect3DNow(); +#ifndef MT32EMU_SYNTH_H +#define MT32EMU_SYNTH_H + +#include <stdarg.h> + +// Shows number of partials MT-32 is playing +#define MONITORPARTIALS 0 + +#define ROMSIZE (512 * 1024) +#define PCMSIZE (ROMSIZE / 2) +#define GRAN 512 + +class revmodel; + +namespace MT32Emu { + +class File; +class TableInitialiser; +class Partial; +class PartialManager; +class Part; + +enum ReportType { + // Files missing + ReportType_errorPreset1 = 1, + ReportType_errorPreset2 = 2, + ReportType_errorDrumpat = 3, + ReportType_errorPatchlog = 4, + ReportType_errorMT32ROM = 5, + + // HW spec + ReportType_availableSSE = 6, + ReportType_available3DNow = 7, + ReportType_asingSSE = 8, + ReportType_asing3DNow = 9, + + // General info + ReportType_lcdMessage = 10, + ReportType_devReset = 11, + ReportType_devReconfig = 12, + ReportType_newReverbMode = 13, + ReportType_newReverbTime = 14, + ReportType_newReverbLevel = 15 +}; struct SynthProperties { // Sample rate to use in mixing int SampleRate; + // Flag to activate reverb. True = use reverb, False = no reverb bool UseReverb; // Flag True to use software set reverb settings, Flag False to set reverb settings in @@ -70,11 +75,25 @@ struct SynthProperties { bool UseDefault; // When not using the default settings, this specifies one of the 4 reverb types // 1 = Room 2 = Hall 3 = Plate 4 = Tap - int RevType; + unsigned char RevType; // This specifies the delay time, from 0-7 (not sure of the actual MT-32's measurement) - int RevTime; + unsigned char RevTime; // This specifies the reverb level, from 0-7 (not sure of the actual MT-32's measurement) - int RevLevel; + unsigned char RevLevel; + // 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. + char *baseDir; + // This is used as the first argument to all callbacks + void *userData; + // Callback for reporting various errors and information. May be NULL + void (*report)(void *userData, ReportType type, 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. + File *(*openFile)(void *userData, const char *filename, File::OpenMode mode); + // Callback for closing a File. May be NULL, in which case the File will automatically be close()d/deleted. + void (*closeFile)(void *userData, File *file); }; // This is the specification of the Callback routine used when calling the RecalcWaveforms @@ -85,73 +104,98 @@ typedef void (*recalcStatusCallback)(int percDone); // sampling rate. The callback routine provides interactivity to let the user know what // percentage is complete in regenerating the waveforms. When a NULL pointer is used as the // callback routine, no status is reported. -bool RecalcWaveforms(int sampRate, recalcStatusCallback callBack); +bool RecalcWaveforms(char * baseDir, int sampRate, recalcStatusCallback callBack); typedef float (*iir_filter_type)(float input,float *hist1_ptr, float *coef_ptr, int revLevel); extern iir_filter_type usefilter; -extern partialFormat PCM[54]; -extern int16 romfile[262656]; -extern int32 divtable[256]; -extern int32 smalldivtable[256]; -extern uint32 wavtabler[64][256]; -extern uint32 looptabler[16][16][256]; -extern int16 sintable[65536]; -extern int32 penvtable[16][128]; -extern int32 pulsetable[101]; -extern int32 pulseoffset[101]; -extern int32 sawtable[128][128]; -extern float filtcoeff[FILTERGRAN][32][16]; -extern uint32 lfoptable[101][128]; -extern int32 ampveltable[128][64]; -extern int32 amptable[129]; -extern int16 smallnoise[441]; -extern int32 samplepos; -extern int16* waveforms[4][256]; -extern uint32 waveformsize[4][256]; -extern int8 LoopPatterns[16][16]; -extern int drumPan[30][2]; -extern float ResonFactor[32]; -extern float ResonInv[32]; - -extern int32 getPitchEnvelope(dpoly::partialStatus *pStat, dpoly *poly); -extern int32 getAmpEnvelope(dpoly::partialStatus *pStat, dpoly *poly); -extern int32 getFiltEnvelope(int16 wg, dpoly::partialStatus *pStat, dpoly *poly); - -class CSynthMT32 { +class Synth { +friend class Part; +friend class Partial; +friend class TableInitialiser; private: + bool isEnabled; + + sampleFormat PCM[54]; + sampleTable PCMList[128]; + Bit32u PCMReassign[54]; + Bit32s PCMLoopTable[54]; + + Bit16s romfile[PCMSIZE + GRAN]; + Bit8s chantable[32]; + + #if MONITORPARTIALS == 1 + static Bit32s samplepos = 0; + #endif + + MT32RAMFormat mt32ram, mt32default; + + revmodel *reverbModel; + + Bit16s mastervolume; + + char curRevMode; + char curRevTime; + Bit32u curRevLevel; unsigned char initmode; bool isOpen; + + PartialManager *partialManager; + Part *parts[9]; + + Bit16s tmpBuffer[MAX_SAMPLE_OUTPUT * 2]; + float sndbufl[MAX_SAMPLE_OUTPUT]; + float sndbufr[MAX_SAMPLE_OUTPUT]; + float outbufl[MAX_SAMPLE_OUTPUT]; + float outbufr[MAX_SAMPLE_OUTPUT]; + SynthProperties myProp; - - bool InitTables(); + + bool loadPreset(const char *filename); + void initReverb(char newRevMode, char newRevTime); + void doRender(Bit16s * stream, Bit32u len); + void playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity); + void playSysexWithoutHeader(unsigned char channel, Bit8u *sysex, Bit32u len); + + bool loadDrums(const char *filename); + bool loadPCMToROMMap(const char *filename); + bool loadROM(const char *filename); + void dumpDrums(const char *filename); + // Save the system state to a sysex file specified by filename + int dumpSysex(char *filename); + +protected: + void report(ReportType type, void *reportData); + File *openFile(const char *filename, File::OpenMode mode); + void closeFile(File *file); + void printDebug(const char *fmt, ...); public: - CSynthMT32() : isOpen(false) {}; + Synth(); + ~Synth(); - // Used to initialized the MT-32. The useProp parameter specifies - // properties for the synthesizer, as outlined in the structure above. - // Returns TRUE if initialization was sucessful, otherwise returns FALSE. - bool ClassicOpen(SynthProperties useProp); + // 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); + void close(void); // Sends a 4-byte MIDI message to the MT-32 for immediate playback - void PlayMsg(uint32 msg); + void playMsg(Bit32u msg); // Sends a string of Sysex commands to the MT-32 for immediate interpretation - void PlaySysex(uint8 * sysex, uint32 len); - - // Save the system state to a sysex file specified by filename - int DumpSysex(char *filename); + // The length is in bytes + void playSysex(Bit8u *sysex, Bit32u len); + void playSysexWithoutFraming(Bit8u *sysex, Bit32u len); // This callback routine is used to have the MT-32 generate samples to the specified // output stream. The length is in whole samples, not bytes. (I.E. in 16-bit stereo, // one sample is 4 bytes) - void MT32_CallBack(uint8 * stream, uint32 len, int volume); - + void render(Bit16s * stream, Bit32u len); }; +} + #endif diff --git a/backends/midi/mt32/tables.cpp b/backends/midi/mt32/tables.cpp new file mode 100644 index 0000000000..b09b3c58bc --- /dev/null +++ b/backends/midi/mt32/tables.cpp @@ -0,0 +1,727 @@ +/* Copyright (c) 2003-2004 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#include "mt32emu.h" + +// Determines how the waveform cache file is handled (must be regenerated after sampling rate change) +#define WAVECACHEMODE 0 // Load existing cache if possible, otherwise generate and save cache +//#define WAVECACHEMODE 1 // Load existing cache if possible, otherwise generage but don't save cache +//#define WAVECACHEMODE 2 // Ignore existing cache, generate and save cache +//#define WAVECACHEMODE 3 // Ignore existing cache, generate but don't save cache + +// Constant tuning for now +#define TUNING 440.0f + +namespace MT32Emu { + +//Amplitude time velocity follow exponential coefficients +const double tvcatconst[5] = {0.0, 0.002791309, 0.005942882, 0.012652792, 0.026938637}; +const double tvcatmult[5] = {1.0, 1.072662811, 1.169129367, 1.288579123, 1.229630539}; + +const Bit8s LoopPatterns[9][10] = { + { 2, 3, 4, 5, 6, 7, -1, -1, -1, -1}, + { 8, 9, 10, 11, 12, 13, 14, 15, 16, -1}, + {17, 18, 19, 20, 21, -1, -1, -1, -1, -1}, + {22, 23, 24, 25, 26, 27, 28, 29, -1, -1}, + {30, 31, 32, 33, 34, 35, 36, 37, -1, -1}, + {45, 46, 47, 48, 49, 50, 51, 52, 53, -1}, + {15, 11, 12, 13, 14, 15, 16, -1, -1, -1}, + {30, 35, 32, 33, 34, -1, -1, -1, -1, -1}, + { 2, 3, -1, -1, -1, -1, -1, -1, -1, -1}, +}; + +static const Bit32s LoopPatternTuning[9][10] = { + {0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, -1, -1, -1, -1}, + {0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, -1}, + {0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, -1, -1, -1, -1, -1}, + {0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, -1, -1}, + {0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, -1, -1}, + {0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, -1}, + {0x2590B, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, -1, -1, -1}, + {0x1294A, 0x1294A, 0x1294A, 0x1294A, 0x1294A, -1, -1, -1, -1, -1}, + {0x1294A, 0x1294A, -1, -1, -1, -1, -1, -1, -1, -1}, +}; + +// These are division constants for the TVF depth key follow +static const Bit32u depexp[5] = {3000, 950, 485, 255, 138}; + +//Envelope time keyfollow exponential coefficients +static const double tkcatconst[5] = {0.0, 0.005853144, 0.011148054, 0.019086143, 0.043333215}; +static const double tkcatmult[5] = {1.0, 1.058245688, 1.048488989, 1.016049301, 1.097538067}; + +static float initialisedSampleRate = 0.0f; + +Bit16s smallnoise[MAX_SAMPLE_OUTPUT]; + +// Some optimization stuff +Bit32s keytable[217]; +Bit16s sintable[65536]; +Bit32u lfotable[101]; +Bit32s penvtable[16][101]; +Bit32s filveltable[128][101]; +Bit32s veltkeytable[5][128]; +Bit32s pulsetable[101]; +Bit32s pulseoffset[101]; +Bit32s ampbiastable[13][128]; +Bit32s fbiastable[15][128]; +float filtcoeff[FILTERGRAN][31][8]; +Bit32s finetable[201]; +Bit32u lfoptable[101][101]; +Bit32s ampveltable[128][64]; +Bit32s pwveltable[15][128]; +Bit32s envtimetable[101]; +Bit32s decaytimetable[101]; +Bit32s lasttimetable[101]; +Bit32s voltable[128]; +Bit32s bendtable[49]; +float ResonFactor[31]; +float ResonInv[31]; + +// Per-note initialisation tables follow + +Bit16s freqtable[NUMNOTES]; +Bit32s fildeptable[5][NUMNOTES]; +Bit32s timekeytable[5][NUMNOTES]; +int filttable[2][NUMNOTES][201]; +int nfilttable[NUMNOTES][101][101]; + +Bit32s divtable[NUMNOTES]; +Bit32s smalldivtable[NUMNOTES]; +Bit32u wavtabler[54][NUMNOTES]; +Bit32u looptabler[9][10][NUMNOTES]; +Bit32s sawtable[NUMNOTES][101]; + +Bit16s *waveforms[4][NUMNOTES]; +Bit32u waveformsize[4][NUMNOTES]; +static Bit16s tmpforms[4][65536]; + + +// Begin filter stuff + +// Pre-warp the coefficients of a numerator or denominator. +// Note that a0 is assumed to be 1, so there is no wrapping +// of it. +static void prewarp(double *a1, double *a2, double fc, double fs) { + double wp; + + wp = 2.0 * fs * tan(PI * fc / fs); + + *a2 = *a2 / (wp * wp); + *a1 = *a1 / wp; +} + +// Transform the numerator and denominator coefficients +// of s-domain biquad section into corresponding +// z-domain coefficients. +// +// Store the 4 IIR coefficients in array pointed by coef +// in following order: +// beta1, beta2 (denominator) +// alpha1, alpha2 (numerator) +// +// Arguments: +// a0-a2 - s-domain numerator coefficients +// b0-b2 - s-domain denominator coefficients +// k - filter gain factor. initially set to 1 +// and modified by each biquad section in such +// a way, as to make it the coefficient by +// which to multiply the overall filter gain +// in order to achieve a desired overall filter gain, +// specified in initial value of k. +// fs - sampling rate (Hz) +// coef - array of z-domain coefficients to be filled in. +// +// Return: +// On return, set coef z-domain coefficients +static void bilinear(double a0, double a1, double a2, double b0, double b1, double b2, double *k, double fs, float *coef) { + double ad, bd; + + // alpha (Numerator in s-domain) + ad = 4. * a2 * fs * fs + 2. * a1 * fs + a0; + // beta (Denominator in s-domain) + bd = 4. * b2 * fs * fs + 2. * b1* fs + b0; + + // update gain constant for this section + *k *= ad/bd; + + // Denominator + *coef++ = (float)((2. * b0 - 8. * b2 * fs * fs) / bd); // beta1 + *coef++ = (float)((4. * b2 * fs * fs - 2. * b1 * fs + b0) / bd); // beta2 + + // Nominator + *coef++ = (float)((2. * a0 - 8. * a2 * fs * fs) / ad); // alpha1 + *coef = (float)((4. * a2 * fs * fs - 2. * a1 * fs + a0) / ad); // alpha2 +} + +// a0-a2: numerator coefficients +// b0-b2: denominator coefficients +// fc: Filter cutoff frequency +// fs: sampling rate +// k: overall gain factor +// coef: pointer to 4 iir coefficients +static void szxform(double *a0, double *a1, double *a2, double *b0, double *b1, double *b2, double fc, double fs, double *k, float *coef) { + // Calculate a1 and a2 and overwrite the original values + prewarp(a1, a2, fc, fs); + prewarp(b1, b2, fc, fs); + bilinear(*a0, *a1, *a2, *b0, *b1, *b2, k, fs, coef); +} + +static void initFilter(float fs, float fc, float *icoeff, float Q) { + float *coef; + double a0, a1, a2, b0, b1, b2; + + double k = 1.5; // Set overall filter gain factor + coef = icoeff + 1; // Skip k, or gain + + // Section 1 + a0 = 1.0; + a1 = 0; + a2 = 0; + b0 = 1.0; + b1 = 0.765367 / Q; // Divide by resonance or Q + b2 = 1.0; + szxform(&a0, &a1, &a2, &b0, &b1, &b2, fc, fs, &k, coef); + coef += 4; // Point to next filter section + + // Section 2 + a0 = 1.0; + a1 = 0; + a2 = 0; + b0 = 1.0; + b1 = 1.847759 / Q; + b2 = 1.0; + szxform(&a0, &a1, &a2, &b0, &b1, &b2, fc, fs, &k, coef); + + icoeff[0] = (float)k; +} + +static void initFiltCoeff(float samplerate) { + for (int j = 0; j < FILTERGRAN; j++) { + for (int res = 0; res < 31; res++) { + float tres = ResonFactor[res]; + initFilter((float)samplerate, (((float)(j+1.0)/FILTERGRAN)) * ((float)samplerate/2), filtcoeff[j][res], tres); + } + } +} + +static void initEnvelopes(float samplerate) { + for (int lf = 0; lf <= 100; lf++) { + float elf = (float)lf; + + // General envelope + float logtime = elf * 0.088362939f; + envtimetable[lf] = (int)((exp(logtime)/312.12) * (float)samplerate); + + // Decay envelope -- shorter for some reason + // This is also the timing for the envelope right before the + // amp and filter envelope sustains + + lasttimetable[lf] = decaytimetable[lf] = (int)((exp(logtime)/(312.12*2)) * (float)samplerate); + //lasttimetable[lf] = (int)((exp(logtime)/(312.12*6)) * (float)samplerate); + + float mv = (float)lf / 100.0f; + float pt = mv-0.5f; + if (pt < 0) + pt = 0; + + pulsetable[lf] = (int)((pt) * 215.04f) + 128; + + // I am certain of this: Verified by hand LFO log + lfotable[lf] = (Bit32u)(((float)samplerate) / (pow(1.088883372f,(float)lf) * 0.021236044f)); + + //LOG(LOG_ERROR|LOG_MISC,"lf %d = lfo %d pulsetable %d", lf, lfotable[lf], pulsetable[lf]); + } +} + +void TableInitialiser::initMT32ConstantTables(Synth *synth) { + if (initialisedSampleRate > 0.0f) { + return; + } + int lf; + synth->printDebug("Initialising Pitch Tables"); + for (lf = -108; lf <= 108; lf++) { + keytable[lf + 108] = (int)(256 * powf(2.0f, (float)(lf / 24.0f))); + //synth->printDebug("KT %d = %d", f, keytable[f+108]); + } + + int res; + float fres; + for (res = 0; res < 31; res++) { + fres = (float)res / 30.0f; + ResonFactor[res] = (powf(2.0f, logf(powf(fres, 16.0f))) * 2.5f) + 1.0f; + ResonInv[res] = 1 / ResonFactor[res]; + } + + int period = 65536; + + for (int ang = 0; ang < period; ang++) { + int halfang = (period / 2); + int angval = ang % halfang; + float tval = (((float)angval / (float)halfang) - 0.5f) * 2; + if (ang >= halfang) + tval = -tval; + sintable[ang] = (Bit16s)(tval * 50.0f) + 50; + } + + int velt, dep; + float tempdep; + for (velt = 0; velt < 128; velt++) { + for (dep = 0; dep < 5; dep++) { + if (dep > 0) { + float ff = (float)(exp(3.5f*tvcatconst[dep] * (59.0f-(float)velt)) * tvcatmult[dep]); + tempdep = 256.0f * ff; + veltkeytable[dep][velt] = (int)tempdep; + //if ((velt % 16) == 0) { + // synth->printDebug("Key %d, depth %d, factor %d", velt, dep, (int)tempdep); + //} + } else + veltkeytable[dep][velt] = 256; + } + + for (dep = -7; dep < 8; dep++) { + float fldep = (float)abs(dep) / 7.0f; + fldep = powf(fldep,2.5f); + if (dep < 0) + fldep = fldep * -1.0f; + pwveltable[dep+7][velt] = Bit32s((fldep * (float)velt * 100) / 128.0); + } + } + + for (dep = 0; dep <= 100; dep++) { + for (velt = 0; velt < 128; velt++) { + float fdep = (float)dep * 0.000347013f; // Another MT-32 constant + float fv = ((float)velt - 64.0f)/7.26f; + float flogdep = powf(10, fdep * fv); + float fbase; + + if (velt > 64) + filveltable[velt][dep] = (int)(flogdep * 256.0); + else { + //lff = 1 - (pow(((128.0 - (float)lf) / 64.0),.25) * ((float)velt / 96)); + fbase = 1 - (powf(((float)dep / 100.0f),.25f) * ((float)(64-velt) / 96.0f)); + filveltable[velt][dep] = (int)(fbase * 256.0); + } + //synth->printDebug("Filvel dep %d velt %d = %x", dep, velt, filveltable[velt][dep]); + } + } + + for (lf = 0; lf <= 200; lf++) { + //FIXME:KG: I'm fairly sure this is wrong... lf=100 should yield no fine-tuning (4096)? + finetable[lf] = (int)((powf(2.0f, (((float)lf / 200.0f) - 1.0f) / 12.0f)) * 4096.0f); + } + for (lf = 0; lf <= 48; lf++) + bendtable[lf] = (int)((powf(2.0f, (((float)lf / 12.0f) - 2.0f))) * 4096.0f); + + float lff; + for (lf = 0; lf < 128; lf++) { + for (velt = 0; velt < 64; velt++) { + lff = 1 - (powf(((128.0f - (float)lf) / 64.0f), 0.25f) * ((float)velt / 96)); + ampveltable[lf][velt] = (int)(lff * 256.0); + //synth->printDebug("Ampveltable: %d, %d = %d", lf, velt, ampveltable[lf][velt]); + } + } + + for (lf = 0; lf < 128; lf++) { + // Converts MIDI velocity to volume. + voltable[lf] = (int)(127.0 * pow((float)lf / 127.0, LN)); + } + for (lf = 0; lf < MAX_SAMPLE_OUTPUT; lf++) { + int myRand; + myRand = rand(); + int origRand = myRand; + //myRand = ((myRand - 16383) * WGAMP) >> 16; + // This one is slower but works with all values of RAND_MAX + myRand = (int)((origRand - RAND_MAX / 2) / (float)RAND_MAX * (WGAMP / 2)); + //FIXME:KG: Original ultimately set the lowest two bits to 0, for no obvious reason + smallnoise[lf] = (Bit16s)myRand; + } + + float tdist; + float padjtable[51]; + for (lf = 0; lf <= 50; lf++) { + if (lf == 0) + padjtable[lf] = 7; + else if (lf == 1) + padjtable[lf] = 6; + else if (lf == 2) + padjtable[lf] = 5; + else if (lf == 3) + padjtable[lf] = 4; + else if (lf == 4) + padjtable[lf] = 4 - (0.333333f); + else if (lf == 5) + padjtable[lf] = 4 - (0.333333f * 2); + else if (lf == 6) + padjtable[lf] = 3; + else if ((lf > 6) && (lf <= 12)) { + tdist = (lf-6.0f) / 6.0f; + padjtable[lf] = 3.0f - tdist; + } else if ((lf > 12) && (lf <= 25)) { + tdist = (lf - 12.0f) / 13.0f; + padjtable[lf] = 2.0f - tdist; + } else { + tdist = (lf - 25.0f) / 25.0f; + padjtable[lf] = 1.0f - tdist; + } + //synth->printDebug("lf %d = padj %f", lf, padjtable[lf]); + } + + float lfp, depf, finalval, tlf; + int depat, pval, depti; + for (lf = 0; lf <= 10; lf++) { + // I believe the depth is cubed or something + + for (depat = 0; depat <= 100; depat++) { + if (lf > 0) { + depti = abs(depat - 50); + tlf = (float)lf - padjtable[depti]; + if (tlf < 0) + tlf = 0; + lfp = expf(0.713619942f * tlf) / 407.4945111f; + + if (depat < 50) + finalval = 4096.0f * powf(2, -lfp); + else + finalval = 4096.0f * powf(2, lfp); + pval = (int)finalval; + + penvtable[lf][depat] = pval; + //synth->printDebug("lf %d depat %d pval %d tlf %f lfp %f", lf,depat,pval, tlf, lfp); + } else { + penvtable[lf][depat] = 4096; + //synth->printDebug("lf %d depat %d pval 4096", lf, depat); + } + } + } + for (lf = 0; lf <= 100; lf++) { + // It's linear - verified on MT-32 - one of the few things linear + lfp = ((float)lf * 0.1904f) / 310.55f; + + for (depat = 0; depat <= 100; depat++) { + depf = ((float)depat - 50.0f) / 50.0f; + //finalval = pow(2, lfp * depf * .5); + finalval = 4096.0f + (4096.0f * lfp * depf); + + pval = (int)finalval; + + lfoptable[lf][depat] = pval; + + //synth->printDebug("lf %d depat %d pval %x", lf,depat,pval); + } + } + + for (lf = 0; lf <= 12; lf++) { + for (int distval = 0; distval < 128; distval++) { + float amplog, dval; + if (lf == 0) { + amplog = 0; + dval = 1; + ampbiastable[lf][distval] = 256; + } else { + amplog = powf(1.431817011f, (float)lf) / (float)PI; + dval = ((128.0f - (float)distval) / 128.0f); + amplog = expf(amplog); + dval = powf(amplog, dval) / amplog; + ampbiastable[lf][distval] = (int)(dval * 256.0); + } + //synth->printDebug("Ampbias lf %d distval %d = %f (%x) %f", lf, distval, dval, ampbiastable[lf][distval],amplog); + } + } + + for (lf = 0; lf <= 14; lf++) { + for (int distval = 0; distval < 128; distval++) { + float filval = fabsf((float)((lf - 7) * 12) / 7.0f); + float amplog, dval; + if (lf == 7) { + amplog = 0; + dval = 1; + fbiastable[lf][distval] = 256; + } else { + //amplog = pow(1.431817011, filval) / (float)PI; + amplog = powf(1.531817011f, filval) / (float)PI; + dval = (128.0f - (float)distval) / 128.0f; + amplog = expf(amplog); + dval = powf(amplog,dval)/amplog; + if (lf < 8) { + fbiastable[lf][distval] = (int)(dval * 256.0f); + } else { + dval = powf(dval, 0.3333333f); + if (dval < 0.01f) + dval = 0.01f; + dval = 1 / dval; + fbiastable[lf][distval] = (int)(dval * 256.0f); + } + } + //synth->printDebug("Fbias lf %d distval %d = %f (%x) %f", lf, distval, dval, fbiastable[lf][distval],amplog); + } + } +} + +// Per-note table initialisation follows + +static void initSaw(int f, Bit32s div) { + for (int rsaw = 0; rsaw <= 100; rsaw++) { + float fsaw; + if (rsaw < 50) + fsaw = 50.0f; + else + fsaw = (float)rsaw; + int tmpdiv = div << 1; + + //(66 - (((A8 - 50) / 50) ^ 0.63) * 50) / 132 + float sawfact = (66.0f - (powf((fsaw - 50.0f) / 50.0f, 0.63f) * 50.0f)) / 132.0f; + sawtable[f][rsaw] = (int)(sawfact * (float)tmpdiv) >> 16; + //synth->printDebug("F %d divtable %d saw %d sawtable %d", f, div>>16, rsaw, sawtable[f][rsaw]); + } +} + +static void initDep(int f) { + for (int dep = 0; dep < 5; dep++) { + if (dep == 0) { + fildeptable[dep][f] = 256; + timekeytable[dep][f] = 256; + } else { + float depfac = 3000.0f; + float ff, tempdep; + depfac = (float)depexp[dep]; + + ff = ((float)f - (float)MIDDLEC) / depfac; + tempdep = powf(2, ff) * 256.0f; + fildeptable[dep][f] = (int)tempdep; + + ff = (float)(exp(tkcatconst[dep] * ((float)MIDDLEC-(float)f)) * tkcatmult[dep]); + timekeytable[dep][f] = (int)(ff * 256.0f); + } + } + //synth->printDebug("F %d d1 %x d2 %x d3 %x d4 %x d5 %x", f, fildeptable[0][f],fildeptable[1][f],fildeptable[2][f],fildeptable[3][f],fildeptable[4][f]); +} + +void TableInitialiser::initWave(Synth *synth, File *file, bool reading, bool writing, int f, float freq, float rate, double ampsize, Bit32s div) { + double sd = (2.0*PI)/((((float)div/65536.0)) * 4.0); + waveformsize[0][f] = div >> 14; + waveformsize[1][f] = div >> 14; + waveformsize[2][f] = div >> 14; + waveformsize[3][f] = div >> 13; + for (int i = 0; i < 4; i++) + waveforms[i][f] = new Bit16s[waveformsize[i][f]]; + if (reading) { + for (int i = 0; i < 4; i++) { + size_t len = waveformsize[i][f] * sizeof(Bit16s); + if (file->read(waveforms[i][f], len) != len) { + synth->printDebug("Error reading wave file cache!"); + break; + } + } + } else { + float dumbfire; + double saw = 0.0f; + double sa = 0.0; + int fa = 0; + memset(tmpforms, 0,sizeof(tmpforms)); + + while (sa <= (2.0 * PI)) { + float sqp; + + if (sa < PI) { + sqp = -1; + sqp = (float)(sqp + (0.25 * (sa/PI))); + } else { + sqp=1; + sqp = (float)(sqp - (0.25 * ((sa-PI)/PI))); + } + + saw = 0; + for (int sinus = 1; sinus * freq < rate; sinus++) { + double fsinus = (double)sinus; + saw += (1 / fsinus) * sin(fsinus * sa); + } + + dumbfire = (float)(sa / 2); + + //This works pretty good + tmpforms[0][fa] = (Bit16s)(saw * -ampsize / 2); + tmpforms[1][fa] = (Bit16s)(saw * ampsize / 2); + + tmpforms[2][fa] = (Bit16s)(cos(dumbfire) * -ampsize); + tmpforms[3][fa * 2] = (Bit16s)(cos(sa - PI) * -ampsize); + tmpforms[3][fa * 2 + 1] = (Bit16s)(cos((sa + (sd / 2)) - PI) * -ampsize); + + fa++; + sa += sd; + } + + //synth->printDebug("f num %d freq %f and fa %d, div>>13=%d", f, freq, fa, div>>13); + + for (int i = 0; i < 4; i++) + memcpy(waveforms[i][f], tmpforms[i], waveformsize[i][f] * sizeof (Bit16s)); + + if (writing) { + for (int i = 0; i < 4; i++) { + int len = waveformsize[i][f] * sizeof(Bit16s); + if (!file->write(waveforms[i][f], len)) { + synth->printDebug("Error writing waveform cache file"); + break; + } + } + } + } +} + +static void initFiltTable(int f, float freq, float rate) { + for (int tr = 0; tr <= 200; tr++) { + float ftr = (float)tr; + + // Verified exact on MT-32 + if (tr > 100) + ftr = 100.0f + (powf((ftr - 100.0f) / 100.0f, 3.0f) * 100.0f); + + // I think this is the one + float brsq = powf(10.0f, (tr / 50.0f) - 1.0f); + filttable[0][f][tr] = (int)((freq * brsq) / (rate / 2) * FILTERGRAN); + if (filttable[0][f][tr]>=((FILTERGRAN*15)/16)) + filttable[0][f][tr] = ((FILTERGRAN*15)/16); + + float brsa = powf(10.0f, ((tr / 55.0f) - 1.0f)) / 2.0f; + filttable[1][f][tr] = (int)((freq * brsa) / (rate / 2) * FILTERGRAN); + if (filttable[1][f][tr]>=((FILTERGRAN*15)/16)) + filttable[1][f][tr] = ((FILTERGRAN*15)/16); + } +} + +static void initNFiltTable(int f, float freq, float rate) { + for (int cf = 0; cf <= 100; cf++) { + float cfmult = (float)cf; + + for (int tf = 0;tf <= 100; tf++) { + float tfadd = (float)(tf - 0); + if (tfadd < 0) + tfadd = 0; + + float freqsum = expf((cfmult + tfadd) / 30.0f) / 4.0f; + + nfilttable[f][cf][tf] = (int)((freq * freqsum) / (rate / 2)*FILTERGRAN); + if (nfilttable[f][cf][tf] >= ((FILTERGRAN * 15) / 16)) + nfilttable[f][cf][tf] = ((FILTERGRAN * 15) / 16); + } + } +} + +void TableInitialiser::initNotes(Synth *synth, sampleFormat pcms[54], float rate) { + char filename[32]; + int intRate = (int)rate; + sprintf(filename, "waveformcache-%d.raw", intRate); + + File *file = NULL; + bool reading = false, writing = false; + char header[16]; + strncpy(header, "MT32WAVE", 8); + int pos = 8; + // Version... + for (int i = 0; i < 4; i++) + header[pos++] = 0; + header[pos++] = (char)(intRate & 0xFF); + header[pos++] = (char)((intRate >> 8) & 0xFF); + header[pos++] = (char)((intRate >> 16) & 0xFF); + header[pos] = (char)((intRate >> 24) & 0xFF); +#if WAVECACHEMODE < 2 + file = synth->openFile(filename, File::OpenMode_read); + if (file != NULL) { + char fileHeader[16]; + if (file->read(fileHeader, 16) == 16) { + if (memcmp(fileHeader, header, 16) == 0) { + reading = true; + } else { + synth->printDebug("Header of %s does not match expected - will generate", filename); + } + } else { + synth->printDebug("Error reading 16 bytes of %s - will generate", filename); + } + } else { + synth->printDebug("Unable to open %s for reading - will generate", filename); + } +#endif +#if WAVECACHEMODE == 0 || WAVECACHEMODE == 2 + if (!reading) { + file = synth->openFile(filename, File::OpenMode_write); + if (file != NULL) { + if (file->write(header, 16) == 16) { + writing = true; + } else { + synth->printDebug("Error writing 16-byte header to %s - won't continue saving", filename); + } + } else { + synth->printDebug("Unable to open %s for writing - won't be created", filename); + } + } +#endif + + double ampsize = WGAMP; + for (int f = 12; f < 109; f++) { + float freq = (float)(TUNING * pow(2.0, ((double)f - 69.0) / 12.0)); + freqtable[f] = (Bit16s)freq; + divtable[f] = (int)(rate / freq); + smalldivtable[f] = divtable[f] << 8; + divtable[f] = divtable[f] << 16; + + initSaw(f, divtable[f]); + initDep(f); + + //synth->printDebug("F %d sd %f div %d", f, sd, divtable[f]); + initWave(synth, file, reading, writing, f, freq, rate, ampsize, divtable[f]); + + // Create the pitch tables + + float tuner = (32000.0f / rate) * 65536.0f; + for (int pc = 0; pc < 54; pc++) + wavtabler[pc][f] = (int)(tuner * (freq / pcms[pc].tune)); + for (int lp = 0; lp < 9; lp++) { + for (int ln = 0; ln < 10; ln++) { + // FIXME:KG: Surely this needs to be adjusted for the rate? + looptabler[lp][ln][f] = (int)((float)LoopPatternTuning[lp][ln] * (freq / 220.0f)); + } + } + + initFiltTable(f, freq, rate); + initNFiltTable(f, freq, rate); + } + if (file != NULL) + synth->closeFile(file); +} + +void TableInitialiser::initMT32Tables(Synth *synth, sampleFormat pcms[54], float sampleRate) { + if (sampleRate <= 0.0f) { + synth->printDebug("Bad sampleRate (%d <= 0.0f)", sampleRate); + } + if (initialisedSampleRate == 0.0f) { + initMT32ConstantTables(synth); + initFiltCoeff(sampleRate); + initEnvelopes(sampleRate); + initialisedSampleRate = sampleRate; + } + // This always needs to be done, to allocate the waveforms + initNotes(synth, pcms, sampleRate); +} + +} diff --git a/backends/midi/mt32/tables.h b/backends/midi/mt32/tables.h new file mode 100644 index 0000000000..1f4fdab8b2 --- /dev/null +++ b/backends/midi/mt32/tables.h @@ -0,0 +1,97 @@ +/* Copyright (c) 2003-2004 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef MT32EMU_TABLES_H +#define MT32EMU_TABLES_H + +// Mathematical constants +#ifndef PI +#define PI 3.1415926535897932384626433832795 +#endif +#ifndef LN +#define LN 2.30258509 +#endif + +// Filter settings +#define FILTERGRAN 512 + +#define MIDDLEC 60 + +#define NUMNOTES 128 // MIDI supports 128 notes/keys + +// Amplitude of waveform generator +#define WGAMP (7168) +//#define WGAMP (8192) + +namespace MT32Emu { + +class Synth; + +extern Bit16s smallnoise[MAX_SAMPLE_OUTPUT]; + +// Some optimization stuff +extern Bit32s keytable[217]; +extern Bit32s divtable[NUMNOTES]; +extern Bit32s smalldivtable[NUMNOTES]; +extern Bit32u wavtabler[54][NUMNOTES]; +extern Bit32u looptabler[9][10][NUMNOTES]; +extern Bit16s sintable[65536]; +extern Bit32u lfotable[101]; +extern Bit32s penvtable[16][101]; +extern Bit32s filveltable[128][101]; +extern Bit32s veltkeytable[5][128]; +extern Bit32s pulsetable[101]; +extern Bit32s sawtable[NUMNOTES][101]; +extern Bit32s ampbiastable[13][128]; +extern Bit32s fbiastable[15][128]; +extern float filtcoeff[FILTERGRAN][31][8]; +extern Bit32s finetable[201]; +extern Bit32u lfoptable[101][101]; +extern Bit32s ampveltable[128][64]; +extern Bit32s pwveltable[15][128]; +extern Bit32s envtimetable[101]; +extern Bit32s decaytimetable[101]; +extern Bit32s lasttimetable[101]; +extern Bit32s voltable[128]; +extern float ResonInv[31]; + +extern Bit16s freqtable[NUMNOTES]; +extern Bit32s fildeptable[5][NUMNOTES]; +extern Bit32s timekeytable[5][NUMNOTES]; +extern int filttable[2][NUMNOTES][201]; +extern int nfilttable[NUMNOTES][101][101]; + +extern const Bit8s LoopPatterns[9][10]; + +extern Bit16s *waveforms[4][NUMNOTES]; +extern Bit32u waveformsize[4][NUMNOTES]; + +class TableInitialiser { + void initMT32ConstantTables(Synth *synth); + void initWave(Synth *synth, File *file, bool reading, bool writing, int f, float freq, float rate, double ampsize, Bit32s div); + void initNotes(Synth *synth, sampleFormat pcms[54], float rate); +public: + void initMT32Tables(Synth *synth, sampleFormat pcms[54], float sampleRate); +}; + +} + +#endif |