diff options
author | Max Horn | 2011-02-09 01:09:01 +0000 |
---|---|---|
committer | Max Horn | 2011-02-09 01:09:01 +0000 |
commit | 42ab839dd6c8a1570b232101eb97f4e54de57935 (patch) | |
tree | 3b763d8913a87482b793e0348c88b9a5f40eecc9 /audio/softsynth/mt32 | |
parent | 386203a3d6ce1abf457c9110d695408ec5f01b85 (diff) | |
download | scummvm-rg350-42ab839dd6c8a1570b232101eb97f4e54de57935.tar.gz scummvm-rg350-42ab839dd6c8a1570b232101eb97f4e54de57935.tar.bz2 scummvm-rg350-42ab839dd6c8a1570b232101eb97f4e54de57935.zip |
AUDIO: Rename sound/ dir to audio/
svn-id: r55850
Diffstat (limited to 'audio/softsynth/mt32')
-rw-r--r-- | audio/softsynth/mt32/freeverb.cpp | 310 | ||||
-rw-r--r-- | audio/softsynth/mt32/freeverb.h | 244 | ||||
-rw-r--r-- | audio/softsynth/mt32/i386.cpp | 849 | ||||
-rw-r--r-- | audio/softsynth/mt32/i386.h | 49 | ||||
-rw-r--r-- | audio/softsynth/mt32/module.mk | 14 | ||||
-rw-r--r-- | audio/softsynth/mt32/mt32_file.cpp | 70 | ||||
-rw-r--r-- | audio/softsynth/mt32/mt32_file.h | 52 | ||||
-rw-r--r-- | audio/softsynth/mt32/mt32emu.h | 70 | ||||
-rw-r--r-- | audio/softsynth/mt32/part.cpp | 633 | ||||
-rw-r--r-- | audio/softsynth/mt32/part.h | 113 | ||||
-rw-r--r-- | audio/softsynth/mt32/partial.cpp | 968 | ||||
-rw-r--r-- | audio/softsynth/mt32/partial.h | 148 | ||||
-rw-r--r-- | audio/softsynth/mt32/partialManager.cpp | 272 | ||||
-rw-r--r-- | audio/softsynth/mt32/partialManager.h | 56 | ||||
-rw-r--r-- | audio/softsynth/mt32/structures.h | 284 | ||||
-rw-r--r-- | audio/softsynth/mt32/synth.cpp | 1198 | ||||
-rw-r--r-- | audio/softsynth/mt32/synth.h | 300 | ||||
-rw-r--r-- | audio/softsynth/mt32/tables.cpp | 757 | ||||
-rw-r--r-- | audio/softsynth/mt32/tables.h | 116 |
19 files changed, 6503 insertions, 0 deletions
diff --git a/audio/softsynth/mt32/freeverb.cpp b/audio/softsynth/mt32/freeverb.cpp new file mode 100644 index 0000000000..c62d4f2cf3 --- /dev/null +++ b/audio/softsynth/mt32/freeverb.cpp @@ -0,0 +1,310 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Comb filter implementation +// +// Written by +// http://www.dreampoint.co.uk +// This code is public domain + +#include "audio/softsynth/mt32/freeverb.h" + +comb::comb() { + filterstore = 0; + bufidx = 0; +} + +void comb::setbuffer(float *buf, int size) { + buffer = buf; + bufsize = size; +} + +void comb::mute() { + for (int i = 0; i < bufsize; i++) + buffer[i] = 0; +} + +void comb::setdamp(float val) { + damp1 = val; + damp2 = 1 - val; +} + +float comb::getdamp() { + return damp1; +} + +void comb::setfeedback(float val) { + feedback = val; +} + +float comb::getfeedback() { + return feedback; +} + +// Allpass filter implementation + +allpass::allpass() { + bufidx = 0; +} + +void allpass::setbuffer(float *buf, int size) { + buffer = buf; + bufsize = size; +} + +void allpass::mute() { + for (int i = 0; i < bufsize; i++) + buffer[i] = 0; +} + +void allpass::setfeedback(float val) { + feedback = val; +} + +float allpass::getfeedback() { + return feedback; +} + +// Reverb model implementation + +revmodel::revmodel() { + // Tie the components to their buffers + combL[0].setbuffer(bufcombL1,combtuningL1); + combR[0].setbuffer(bufcombR1,combtuningR1); + combL[1].setbuffer(bufcombL2,combtuningL2); + combR[1].setbuffer(bufcombR2,combtuningR2); + combL[2].setbuffer(bufcombL3,combtuningL3); + combR[2].setbuffer(bufcombR3,combtuningR3); + combL[3].setbuffer(bufcombL4,combtuningL4); + combR[3].setbuffer(bufcombR4,combtuningR4); + combL[4].setbuffer(bufcombL5,combtuningL5); + combR[4].setbuffer(bufcombR5,combtuningR5); + combL[5].setbuffer(bufcombL6,combtuningL6); + combR[5].setbuffer(bufcombR6,combtuningR6); + combL[6].setbuffer(bufcombL7,combtuningL7); + combR[6].setbuffer(bufcombR7,combtuningR7); + combL[7].setbuffer(bufcombL8,combtuningL8); + combR[7].setbuffer(bufcombR8,combtuningR8); + allpassL[0].setbuffer(bufallpassL1,allpasstuningL1); + allpassR[0].setbuffer(bufallpassR1,allpasstuningR1); + allpassL[1].setbuffer(bufallpassL2,allpasstuningL2); + allpassR[1].setbuffer(bufallpassR2,allpasstuningR2); + allpassL[2].setbuffer(bufallpassL3,allpasstuningL3); + allpassR[2].setbuffer(bufallpassR3,allpasstuningR3); + allpassL[3].setbuffer(bufallpassL4,allpasstuningL4); + allpassR[3].setbuffer(bufallpassR4,allpasstuningR4); + + // Set default values + allpassL[0].setfeedback(0.5f); + allpassR[0].setfeedback(0.5f); + allpassL[1].setfeedback(0.5f); + allpassR[1].setfeedback(0.5f); + allpassL[2].setfeedback(0.5f); + allpassR[2].setfeedback(0.5f); + allpassL[3].setfeedback(0.5f); + allpassR[3].setfeedback(0.5f); + setmode(initialmode); + setwet(initialwet); + setroomsize(initialroom); + setdry(initialdry); + setdamp(initialdamp); + setwidth(initialwidth); + + // Buffer will be full of rubbish - so we MUST mute them + mute(); +} + +void revmodel::mute() { + int i; + + if (getmode() >= freezemode) + return; + + for (i = 0; i < numcombs; i++) { + combL[i].mute(); + combR[i].mute(); + } + + for (i = 0; i < numallpasses; i++) { + allpassL[i].mute(); + allpassR[i].mute(); + } +} + +void revmodel::processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip) { + float outL, outR, input; + + while (numsamples-- > 0) { + int i; + + outL = outR = 0; + input = (*inputL + *inputR) * gain; + + // Accumulate comb filters in parallel + for (i = 0; i < numcombs; i++) { + outL += combL[i].process(input); + outR += combR[i].process(input); + } + + // Feed through allpasses in series + for (i = 0; i < numallpasses; i++) { + outL = allpassL[i].process(outL); + outR = allpassR[i].process(outR); + } + + // Calculate output REPLACING anything already there + *outputL = outL * wet1 + outR * wet2 + *inputL * dry; + *outputR = outR * wet1 + outL * wet2 + *inputR * dry; + + // Increment sample pointers, allowing for interleave (if any) + inputL += skip; + inputR += skip; + outputL += skip; + outputR += skip; + } +} + +void revmodel::processmix(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip) { + float outL, outR, input; + + while (numsamples-- > 0) { + int i; + + outL = outR = 0; + input = (*inputL + *inputR) * gain; + + // Accumulate comb filters in parallel + for (i = 0; i < numcombs; i++) { + outL += combL[i].process(input); + outR += combR[i].process(input); + } + + // Feed through allpasses in series + for (i = 0; i < numallpasses; i++) { + outL = allpassL[i].process(outL); + outR = allpassR[i].process(outR); + } + + // Calculate output MIXING with anything already there + *outputL += outL * wet1 + outR * wet2 + *inputL * dry; + *outputR += outR * wet1 + outL * wet2 + *inputR * dry; + + // Increment sample pointers, allowing for interleave (if any) + inputL += skip; + inputR += skip; + outputL += skip; + outputR += skip; + } +} + +void revmodel::update() { + // Recalculate internal values after parameter change + + int i; + + wet1 = wet * (width / 2 + 0.5f); + wet2 = wet * ((1 - width) / 2); + + if (mode >= freezemode) { + roomsize1 = 1; + damp1 = 0; + gain = muted; + } else { + roomsize1 = roomsize; + damp1 = damp; + gain = fixedgain; + } + + for (i = 0; i < numcombs; i++) { + combL[i].setfeedback(roomsize1); + combR[i].setfeedback(roomsize1); + } + + for (i = 0; i < numcombs; i++) { + combL[i].setdamp(damp1); + combR[i].setdamp(damp1); + } +} + +// The following get/set functions are not inlined, because +// speed is never an issue when calling them, and also +// because as you develop the reverb model, you may +// wish to take dynamic action when they are called. + +void revmodel::setroomsize(float value) { + roomsize = (value * scaleroom) + offsetroom; + update(); +} + +float revmodel::getroomsize() { + return (roomsize - offsetroom) / scaleroom; +} + +void revmodel::setdamp(float value) { + damp = value * scaledamp; + update(); +} + +float revmodel::getdamp() { + return damp / scaledamp; +} + +void revmodel::setwet(float value) { + wet = value * scalewet; + update(); +} + +float revmodel::getwet() { + return wet / scalewet; +} + +void revmodel::setdry(float value) { + dry = value * scaledry; +} + +float revmodel::getdry() { + return dry / scaledry; +} + +void revmodel::setwidth(float value) { + width = value; + update(); +} + +float revmodel::getwidth() { + return width; +} + +void revmodel::setmode(float value) { + mode = value; + update(); +} + +float revmodel::getmode() { + if (mode >= freezemode) + return 1; + else + return 0; +} diff --git a/audio/softsynth/mt32/freeverb.h b/audio/softsynth/mt32/freeverb.h new file mode 100644 index 0000000000..8310aca3e3 --- /dev/null +++ b/audio/softsynth/mt32/freeverb.h @@ -0,0 +1,244 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Macro for killing denormalled numbers +// +// Written by Jezar at Dreampoint, June 2000 +// http://www.dreampoint.co.uk +// Based on IS_DENORMAL macro by Jon Watte +// This code is public domain + +#ifndef FREEVERB_H +#define FREEVERB_H + +// FIXME: Fix this really ugly hack +inline float undenormalise(void *sample) { + if (((*(unsigned int*)sample) & 0x7f800000) == 0) + return 0.0f; + return *(float*)sample; +} + +// Comb filter class declaration + +class comb { +public: + comb(); + void setbuffer(float *buf, int size); + inline float process(float inp); + void mute(); + void setdamp(float val); + float getdamp(); + void setfeedback(float val); + float getfeedback(); +private: + float feedback; + float filterstore; + float damp1; + float damp2; + float *buffer; + int bufsize; + int bufidx; +}; + + +// Big to inline - but crucial for speed + +inline float comb::process(float input) { + float output; + + output = buffer[bufidx]; + undenormalise(&output); + + filterstore = (output * damp2) + (filterstore * damp1); + undenormalise(&filterstore); + + buffer[bufidx] = input + (filterstore * feedback); + + if (++bufidx >= bufsize) + bufidx = 0; + + return output; +} + +// Allpass filter declaration + +class allpass { +public: + allpass(); + void setbuffer(float *buf, int size); + inline float process(float inp); + void mute(); + void setfeedback(float val); + float getfeedback(); +private: + float feedback; + float *buffer; + int bufsize; + int bufidx; +}; + + +// Big to inline - but crucial for speed + +inline float allpass::process(float input) { + float output; + float bufout; + + bufout = buffer[bufidx]; + undenormalise(&bufout); + + output = -input + bufout; + buffer[bufidx] = input + (bufout * feedback); + + if (++bufidx >= bufsize) + bufidx = 0; + + return output; +} + + +// Reverb model tuning values + +const int numcombs = 8; +const int numallpasses = 4; +const float muted = 0; +const float fixedgain = 0.015f; +const float scalewet = 3; +const float scaledry = 2; +const float scaledamp = 0.4f; +const float scaleroom = 0.28f; +const float offsetroom = 0.7f; +const float initialroom = 0.5f; +const float initialdamp = 0.5f; +const float initialwet = 1 / scalewet; +const float initialdry = 0; +const float initialwidth = 1; +const float initialmode = 0; +const float freezemode = 0.5f; +const int stereospread = 23; + +// These values assume 44.1KHz sample rate +// they will probably be OK for 48KHz sample rate +// but would need scaling for 96KHz (or other) sample rates. +// The values were obtained by listening tests. +const int combtuningL1 = 1116; +const int combtuningR1 = 1116 + stereospread; +const int combtuningL2 = 1188; +const int combtuningR2 = 1188 + stereospread; +const int combtuningL3 = 1277; +const int combtuningR3 = 1277 + stereospread; +const int combtuningL4 = 1356; +const int combtuningR4 = 1356 + stereospread; +const int combtuningL5 = 1422; +const int combtuningR5 = 1422 + stereospread; +const int combtuningL6 = 1491; +const int combtuningR6 = 1491 + stereospread; +const int combtuningL7 = 1557; +const int combtuningR7 = 1557 + stereospread; +const int combtuningL8 = 1617; +const int combtuningR8 = 1617 + stereospread; +const int allpasstuningL1 = 556; +const int allpasstuningR1 = 556 + stereospread; +const int allpasstuningL2 = 441; +const int allpasstuningR2 = 441 + stereospread; +const int allpasstuningL3 = 341; +const int allpasstuningR3 = 341 + stereospread; +const int allpasstuningL4 = 225; +const int allpasstuningR4 = 225 + stereospread; + + +// Reverb model declaration + +class revmodel { +public: + revmodel(); + void mute(); + void processmix(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip); + void processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip); + void setroomsize(float value); + float getroomsize(); + void setdamp(float value); + float getdamp(); + void setwet(float value); + float getwet(); + void setdry(float value); + float getdry(); + void setwidth(float value); + float getwidth(); + void setmode(float value); + float getmode(); +private: + void update(); + + float gain; + float roomsize, roomsize1; + float damp, damp1; + float wet, wet1, wet2; + float dry; + float width; + float mode; + + // The following are all declared inline + // to remove the need for dynamic allocation + // with its subsequent error-checking messiness + + // Comb filters + comb combL[numcombs]; + comb combR[numcombs]; + + // Allpass filters + allpass allpassL[numallpasses]; + allpass allpassR[numallpasses]; + + // Buffers for the combs + float bufcombL1[combtuningL1]; + float bufcombR1[combtuningR1]; + float bufcombL2[combtuningL2]; + float bufcombR2[combtuningR2]; + float bufcombL3[combtuningL3]; + float bufcombR3[combtuningR3]; + float bufcombL4[combtuningL4]; + float bufcombR4[combtuningR4]; + float bufcombL5[combtuningL5]; + float bufcombR5[combtuningR5]; + float bufcombL6[combtuningL6]; + float bufcombR6[combtuningR6]; + float bufcombL7[combtuningL7]; + float bufcombR7[combtuningR7]; + float bufcombL8[combtuningL8]; + float bufcombR8[combtuningR8]; + + // Buffers for the allpasses + float bufallpassL1[allpasstuningL1]; + float bufallpassR1[allpasstuningR1]; + float bufallpassL2[allpasstuningL2]; + float bufallpassR2[allpasstuningR2]; + float bufallpassL3[allpasstuningL3]; + float bufallpassR3[allpasstuningR3]; + float bufallpassL4[allpasstuningL4]; + float bufallpassR4[allpasstuningR4]; +}; + +#endif diff --git a/audio/softsynth/mt32/i386.cpp b/audio/softsynth/mt32/i386.cpp new file mode 100644 index 0000000000..f092189d76 --- /dev/null +++ b/audio/softsynth/mt32/i386.cpp @@ -0,0 +1,849 @@ +/* Copyright (c) 2003-2005 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 MT32EMU_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__( + "pushl %%ebx \n" \ + "movl $1, %%eax \n" \ + "cpuid \n" \ + "movl %%edx, %0 \n" \ + "popl %%ebx \n" \ + : "=r"(result) : : "eax", "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__( + "pushl %%ebx \n" \ + "movl $0x80000001, %%eax \n" \ + "cpuid \n" \ + "movl %%edx, %0 \n" \ + "popl %%ebx \n" \ + : "=r"(result) : : "eax", "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) + : "memory" +#ifdef __SSE__ + , "xmm1", "xmm2", "xmm3" +#endif + ); + + 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, %%edi \n" \ + "movq 0(%%edi), %%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, %%edi \n" \ + "movq 0(%%edi), %%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, %%edi \n" \ + "addl $8, %%eax \n" \ + " \n" \ + "movq 0(%%edi), %%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, %%edi \n" \ + "movq 0(%%edi), %%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", "edi", "memory" +#ifdef __MMX__ + , "mm1", "mm2", "mm3" +#endif + ); + + 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", "memory" +#ifdef __MMX__ + , "mm1", "mm2", "mm3" +#endif + ); +} + +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", "memory" +#ifdef __MMX__ + , "mm1", "mm2" +#endif + ); +} + +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", "memory" +#ifdef __MMX__ + , "mm1", "mm2", "mm3" +#endif + ); +} + +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", "memory" +#ifdef __MMX__ + , "mm1", "mm2" +#endif + ); +} + +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" \ + "pushl %%ebx \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" \ + "popl %%ebx \n" \ + : : "g"(quadlen), "g"(leftvol), "g"(rightvol), "g"(partialBuf), "g"(p1buf) + : "eax", "ecx", "edx", "edi", "esi", "memory" +#ifdef __MMX__ + , "mm1", "mm2", "mm3" +#endif + ); +} + +#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) { + 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 + return output; +} + +float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr) { + 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 + return output; +} + +#if MT32EMU_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/audio/softsynth/mt32/i386.h b/audio/softsynth/mt32/i386.h new file mode 100644 index 0000000000..e8644411cd --- /dev/null +++ b/audio/softsynth/mt32/i386.h @@ -0,0 +1,49 @@ +/* Copyright (c) 2003-2005 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 MT32EMU_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); +float iir_filter_3dnow(float input,float *hist1_ptr, float *coef_ptr); +float iir_filter_normal(float input,float *hist1_ptr, float *coef_ptr); + +#if MT32EMU_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/audio/softsynth/mt32/module.mk b/audio/softsynth/mt32/module.mk new file mode 100644 index 0000000000..a8329bc98c --- /dev/null +++ b/audio/softsynth/mt32/module.mk @@ -0,0 +1,14 @@ +MODULE := audio/softsynth/mt32 + +MODULE_OBJS := \ + mt32_file.o \ + i386.o \ + part.o \ + partial.o \ + partialManager.o \ + synth.o \ + tables.o \ + freeverb.o + +# Include common rules +include $(srcdir)/rules.mk diff --git a/audio/softsynth/mt32/mt32_file.cpp b/audio/softsynth/mt32/mt32_file.cpp new file mode 100644 index 0000000000..cdf9fa13f6 --- /dev/null +++ b/audio/softsynth/mt32/mt32_file.cpp @@ -0,0 +1,70 @@ +/* Copyright (c) 2003-2005 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" + +namespace MT32Emu { + +bool File::readBit16u(Bit16u *in) { + Bit8u b[2]; + if (read(&b[0], 2) != 2) + return false; + *in = ((b[0] << 8) | b[1]); + return true; +} + +bool File::readBit32u(Bit32u *in) { + Bit8u b[4]; + if (read(&b[0], 4) != 4) + return false; + *in = ((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]); + return true; +} + +bool File::writeBit16u(Bit16u out) { + if (!writeBit8u((Bit8u)((out & 0xFF00) >> 8))) { + return false; + } + if (!writeBit8u((Bit8u)(out & 0x00FF))) { + return false; + } + return true; +} + +bool File::writeBit32u(Bit32u out) { + if (!writeBit8u((Bit8u)((out & 0xFF000000) >> 24))) { + return false; + } + if (!writeBit8u((Bit8u)((out & 0x00FF0000) >> 16))) { + return false; + } + if (!writeBit8u((Bit8u)((out & 0x0000FF00) >> 8))) { + return false; + } + if (!writeBit8u((Bit8u)(out & 0x000000FF))) { + return false; + } + return true; +} + +} // End of namespace MT32Emu + diff --git a/audio/softsynth/mt32/mt32_file.h b/audio/softsynth/mt32/mt32_file.h new file mode 100644 index 0000000000..e6641660ee --- /dev/null +++ b/audio/softsynth/mt32/mt32_file.h @@ -0,0 +1,52 @@ +/* Copyright (c) 2003-2005 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 "common/scummsys.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 *in, size_t size) = 0; + virtual bool readBit8u(Bit8u *in) = 0; + virtual bool readBit16u(Bit16u *in); + virtual bool readBit32u(Bit32u *in); + virtual size_t write(const void *out, size_t size) = 0; + virtual bool writeBit8u(Bit8u out) = 0; + // Note: May write a single byte to the file before failing + virtual bool writeBit16u(Bit16u out); + // Note: May write some (<4) bytes to the file before failing + virtual bool writeBit32u(Bit32u out); + virtual bool isEOF() = 0; +}; + +} // End of namespace MT32Emu + +#endif diff --git a/audio/softsynth/mt32/mt32emu.h b/audio/softsynth/mt32/mt32emu.h new file mode 100644 index 0000000000..6eedf04bc0 --- /dev/null +++ b/audio/softsynth/mt32/mt32emu.h @@ -0,0 +1,70 @@ +/* Copyright (c) 2003-2005 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 + +// Debugging +// Show the instruments played +#define MT32EMU_MONITOR_INSTRUMENTS 1 +// Shows number of partials MT-32 is playing, and on which parts +#define MT32EMU_MONITOR_PARTIALS 0 +// Determines how the waveform cache file is handled (must be regenerated after sampling rate change) +#define MT32EMU_WAVECACHEMODE 0 // Load existing cache if possible, otherwise generate and save cache +//#define MT32EMU_WAVECACHEMODE 1 // Load existing cache if possible, otherwise generate but don't save cache +//#define MT32EMU_WAVECACHEMODE 2 // Ignore existing cache, generate and save cache +//#define MT32EMU_WAVECACHEMODE 3 // Ignore existing cache, generate but don't save cache + +// Configuration +// The maximum number of partials playing simultaneously +#define MT32EMU_MAX_PARTIALS 32 +// The maximum number of notes playing simultaneously per part. +// No point making it more than MT32EMU_MAX_PARTIALS, since each note needs at least one partial. +#define MT32EMU_MAX_POLY 32 +// This calculates the exact frequencies of notes as they are played, instead of offsetting from pre-cached semitones. Potentially very slow. +#define MT32EMU_ACCURATENOTES 0 + +#if (defined (_MSC_VER) && defined(_M_IX86)) +#define MT32EMU_HAVE_X86 +#elif defined(__GNUC__) +#if __GNUC__ >= 3 && defined(__i386__) +#define MT32EMU_HAVE_X86 +#endif +#endif + +#ifdef MT32EMU_HAVE_X86 +#define MT32EMU_USE_MMX 1 +#else +#define MT32EMU_USE_MMX 0 +#endif + +#include "freeverb.h" + +#include "structures.h" +#include "i386.h" +#include "mt32_file.h" +#include "tables.h" +#include "partial.h" +#include "partialManager.h" +#include "part.h" +#include "synth.h" + +#endif diff --git a/audio/softsynth/mt32/part.cpp b/audio/softsynth/mt32/part.cpp new file mode 100644 index 0000000000..eb087f7ea0 --- /dev/null +++ b/audio/softsynth/mt32/part.cpp @@ -0,0 +1,633 @@ +/* Copyright (c) 2003-2005 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" + +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 float floatKeyfollow[17] = { + -1.0f, -1.0f/2.0f, -1.0f/4.0f, 0.0f, + 1.0f/8.0f, 1.0f/4.0f, 3.0f/8.0f, 1.0f/2.0f, 5.0f/8.0f, 3.0f/4.0f, 7.0f/8.0f, 1.0f, + 5.0f/4.0f, 3.0f/2.0f, 2.0f, + 1.0009765625f, 1.0048828125f +}; + +//FIXME:KG: Put this dpoly stuff somewhere better +bool dpoly::isActive() const { + return partials[0] != NULL || partials[1] != NULL || partials[2] != NULL || partials[3] != NULL; +} + +Bit32u dpoly::getAge() const { + for (int i = 0; i < 4; i++) { + if (partials[i] != NULL) { + return partials[i]->age; + } + } + return 0; +} + +RhythmPart::RhythmPart(Synth *useSynth, unsigned int usePartNum): Part(useSynth, usePartNum) { + strcpy(name, "Rhythm"); + rhythmTemp = &synth->mt32ram.rhythmSettings[0]; + refresh(); +} + +Part::Part(Synth *useSynth, unsigned int usePartNum) { + this->synth = useSynth; + this->partNum = usePartNum; + patchCache[0].dirty = true; + holdpedal = false; + patchTemp = &synth->mt32ram.patchSettings[partNum]; + if (usePartNum == 8) { + // Nasty hack for rhythm + timbreTemp = NULL; + } else { + sprintf(name, "Part %d", partNum + 1); + timbreTemp = &synth->mt32ram.timbreSettings[partNum]; + } + currentInstr[0] = 0; + currentInstr[10] = 0; + expression = 127; + volumeMult = 0; + volumesetting.leftvol = 32767; + volumesetting.rightvol = 32767; + bend = 0.0f; + memset(polyTable,0,sizeof(polyTable)); + memset(patchCache, 0, sizeof(patchCache)); +} + +void Part::setHoldPedal(bool pedalval) { + if (holdpedal && !pedalval) { + holdpedal = false; + stopPedalHold(); + } else { + holdpedal = pedalval; + } +} + +void RhythmPart::setBend(unsigned int midiBend) { + synth->printDebug("%s: Setting bend (%d) not supported on rhythm", name, midiBend); + return; +} + +void Part::setBend(unsigned int midiBend) { + // FIXME:KG: Slightly unbalanced increments, but I wanted min -1.0, centre 0.0 and max 1.0 + if (midiBend <= 0x2000) { + bend = ((signed int)midiBend - 0x2000) / (float)0x2000; + } else { + bend = ((signed int)midiBend - 0x2000) / (float)0x1FFF; + } + // Loop through all partials to update their bend + for (int i = 0; i < MT32EMU_MAX_POLY; i++) { + for (int j = 0; j < 4; j++) { + if (polyTable[i].partials[j] != NULL) { + polyTable[i].partials[j]->setBend(bend); + } + } + } +} + +void RhythmPart::setModulation(unsigned int midiModulation) { + synth->printDebug("%s: Setting modulation (%d) not supported on rhythm", name, midiModulation); +} + +void Part::setModulation(unsigned int midiModulation) { + // 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 * midiModulation) >> 7; + //patchCache[t].lfoperiod = lfotable[newrate]; + patchCache[t].lfodepth = newrate; + //FIXME:KG: timbreTemp->partial[t].lfo.depth = + } + } +} + +void RhythmPart::refresh() { + updateVolume(); + // (Re-)cache all the mapped timbres ahead of time + for (unsigned int drumNum = 0; drumNum < synth->controlROMMap->rhythmSettingsCount; drumNum++) { + int drumTimbreNum = rhythmTemp[drumNum].timbre; + if (drumTimbreNum >= 127) // 94 on MT-32 + continue; + Bit16s pan = rhythmTemp[drumNum].panpot; // They use R-L 0-14... + // FIXME:KG: Panning cache should be backed up to partials using it, too + if (pan < 7) { + drumPan[drumNum].leftvol = pan * 4681; + drumPan[drumNum].rightvol = 32767; + } else { + drumPan[drumNum].rightvol = (14 - pan) * 4681; + drumPan[drumNum].leftvol = 32767; + } + PatchCache *cache = drumCache[drumNum]; + backupCacheToPartials(cache); + for (int t = 0; t < 4; t++) { + // Common parameters, stored redundantly + cache[t].dirty = true; + cache[t].pitchShift = 0.0f; + cache[t].benderRange = 0.0f; + cache[t].pansetptr = &drumPan[drumNum]; + cache[t].reverb = rhythmTemp[drumNum].reverbSwitch > 0; + } + } +} + +void Part::refresh() { + updateVolume(); + backupCacheToPartials(patchCache); + for (int t = 0; t < 4; t++) { + // Common parameters, stored redundantly + patchCache[t].dirty = true; + patchCache[t].pitchShift = (patchTemp->patch.keyShift - 24) + (patchTemp->patch.fineTune - 50) / 100.0f; + patchCache[t].benderRange = patchTemp->patch.benderRange; + patchCache[t].pansetptr = &volumesetting; + patchCache[t].reverb = patchTemp->patch.reverbSwitch > 0; + } + memcpy(currentInstr, timbreTemp->common.name, 10); +} + +const char *Part::getCurrentInstr() const { + return ¤tInstr[0]; +} + +void RhythmPart::refreshTimbre(unsigned int absTimbreNum) { + for (int m = 0; m < 85; m++) { + if (rhythmTemp[m].timbre == absTimbreNum - 128) + drumCache[m][0].dirty = true; + } +} + +void Part::refreshTimbre(unsigned int absTimbreNum) { + if (getAbsTimbreNum() == absTimbreNum) { + memcpy(currentInstr, timbreTemp->common.name, 10); + patchCache[0].dirty = true; + } +} + +int Part::fixBiaslevel(int srcpnt, int *dir) { + int noteat = srcpnt & 0x3F; + int outnote; + if (srcpnt < 64) + *dir = 0; + else + *dir = 1; + outnote = 33 + noteat; + //synth->printDebug("Bias note %d, dir %d", outnote, *dir); + + return outnote; +} + +int Part::fixKeyfollow(int srckey) { + if (srckey>=0 && srckey<=16) { + 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}; + return keyfix[srckey]; + } else { + //LOG(LOG_ERROR|LOG_MISC,"Missed key: %d", srckey); + return 256; + } +} + +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(const PatchParam *patch) { + patchTemp->patch = *patch; +} + +void RhythmPart::setTimbre(TimbreParam * /*timbre*/) { + synth->printDebug("%s: Attempted to call setTimbre() - doesn't make sense for rhythm", name); +} + +void Part::setTimbre(TimbreParam *timbre) { + *timbreTemp = *timbre; +} + +unsigned int RhythmPart::getAbsTimbreNum() const { + synth->printDebug("%s: Attempted to call getAbsTimbreNum() - doesn't make sense for rhythm", name); + return 0; +} + +unsigned int Part::getAbsTimbreNum() const { + return (patchTemp->patch.timbreGroup * 64) + patchTemp->patch.timbreNum; +} + +void RhythmPart::setProgram(unsigned int patchNum) { + synth->printDebug("%s: Attempt to set program (%d) on rhythm is invalid", name, patchNum); +} + +void Part::setProgram(unsigned int patchNum) { + setPatch(&synth->mt32ram.patches[patchNum]); + setTimbre(&synth->mt32ram.timbres[getAbsTimbreNum()].timbre); + + refresh(); + + allSoundOff(); //FIXME:KG: Is this correct? +} + +void Part::backupCacheToPartials(PatchCache cache[4]) { + // check if any partials are still playing with the old patch cache + // 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. + // We delay this until now to avoid a copy operation with every note played + for (int m = 0; m < MT32EMU_MAX_POLY; m++) { + for (int i = 0; i < 4; i++) { + Partial *partial = polyTable[m].partials[i]; + if (partial != NULL && partial->patchCache == &cache[i]) { + partial->cachebackup = cache[i]; + partial->patchCache = &partial->cachebackup; + } + } + } +} + +void Part::cacheTimbre(PatchCache cache[4], const TimbreParam *timbre) { + backupCacheToPartials(cache); + int partialCount = 0; + for (int t = 0; t < 4; t++) { + cache[t].PCMPartial = false; + if (((timbre->common.pmute >> t) & 0x1) == 1) { + cache[t].playPartial = true; + partialCount++; + } else { + cache[t].playPartial = false; + continue; + } + + // Calculate and cache common parameters + + cache[t].pcm = timbre->partial[t].wg.pcmwave; + cache[t].useBender = (timbre->partial[t].wg.bender == 1); + + switch (t) { + case 0: + cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct12] & 0x2) ? true : false; + cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct12]; + cache[t].structurePosition = 0; + cache[t].structurePair = 1; + break; + case 1: + cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct12] & 0x1) ? true : false; + cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct12]; + cache[t].structurePosition = 1; + cache[t].structurePair = 0; + break; + case 2: + cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct34] & 0x2) ? true : false; + cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct34]; + cache[t].structurePosition = 0; + cache[t].structurePair = 3; + break; + case 3: + cache[t].PCMPartial = (PartialStruct[(int)timbre->common.pstruct34] & 0x1) ? true : false; + cache[t].structureMix = PartialMixStruct[(int)timbre->common.pstruct34]; + cache[t].structurePosition = 1; + cache[t].structurePair = 2; + break; + default: + break; + } + + cache[t].waveform = timbre->partial[t].wg.waveform; + cache[t].pulsewidth = timbre->partial[t].wg.pulsewid; + cache[t].pwsens = timbre->partial[t].wg.pwvelo; + if (timbre->partial[t].wg.keyfollow > 16) { + synth->printDebug("Bad keyfollow value in timbre!"); + cache[t].pitchKeyfollow = 1.0f; + } else { + cache[t].pitchKeyfollow = floatKeyfollow[timbre->partial[t].wg.keyfollow]; + } + + cache[t].pitch = timbre->partial[t].wg.coarse + (timbre->partial[t].wg.fine - 50) / 100.0f + 24.0f; + cache[t].pitchEnv = timbre->partial[t].env; + cache[t].pitchEnv.sensitivity = (char)((float)cache[t].pitchEnv.sensitivity * 1.27f); + cache[t].pitchsustain = cache[t].pitchEnv.level[3]; + + // Calculate and cache TVA envelope stuff + cache[t].ampEnv = timbre->partial[t].tva; + cache[t].ampEnv.level = (char)((float)cache[t].ampEnv.level * 1.27f); + + cache[t].ampbias[0] = fixBiaslevel(cache[t].ampEnv.biaspoint1, &cache[t].ampdir[0]); + cache[t].ampblevel[0] = 12 - cache[t].ampEnv.biaslevel1; + cache[t].ampbias[1] = fixBiaslevel(cache[t].ampEnv.biaspoint2, &cache[t].ampdir[1]); + cache[t].ampblevel[1] = 12 - cache[t].ampEnv.biaslevel2; + cache[t].ampdepth = cache[t].ampEnv.envvkf * cache[t].ampEnv.envvkf; + + // Calculate and cache filter stuff + cache[t].filtEnv = timbre->partial[t].tvf; + cache[t].filtkeyfollow = fixKeyfollow(cache[t].filtEnv.keyfollow); + cache[t].filtEnv.envdepth = (char)((float)cache[t].filtEnv.envdepth * 1.27); + cache[t].tvfbias = fixBiaslevel(cache[t].filtEnv.biaspoint, &cache[t].tvfdir); + cache[t].tvfblevel = cache[t].filtEnv.biaslevel; + cache[t].filtsustain = cache[t].filtEnv.envlevel[3]; + + // Calculate and cache LFO stuff + cache[t].lfodepth = timbre->partial[t].lfo.depth; + cache[t].lfoperiod = synth->tables.lfoPeriod[(int)timbre->partial[t].lfo.rate]; + cache[t].lforate = timbre->partial[t].lfo.rate; + cache[t].modsense = timbre->partial[t].lfo.modsense; + } + for (int t = 0; t < 4; t++) { + // Common parameters, stored redundantly + cache[t].dirty = false; + cache[t].partialCount = partialCount; + cache[t].sustain = (timbre->common.nosustain == 0); + } + //synth->printDebug("Res 1: %d 2: %d 3: %d 4: %d", cache[0].waveform, cache[1].waveform, cache[2].waveform, cache[3].waveform); + +#if MT32EMU_MONITOR_INSTRUMENTS == 1 + synth->printDebug("%s (%s): Recached timbre", name, currentInstr); + for (int i = 0; i < 4; i++) { + synth->printDebug(" %d: play=%s, pcm=%s (%d), wave=%d", i, cache[i].playPartial ? "YES" : "NO", cache[i].PCMPartial ? "YES" : "NO", timbre->partial[i].wg.pcmwave, timbre->partial[i].wg.waveform); + } +#endif +} + +const char *Part::getName() const { + return name; +} + +void Part::updateVolume() { + volumeMult = synth->tables.volumeMult[patchTemp->outlevel * expression / 127]; +} + +int Part::getVolume() const { + // FIXME: Use the mappings for this in the control ROM + return patchTemp->outlevel * 127 / 100; +} + +void Part::setVolume(int midiVolume) { + // FIXME: Use the mappings for this in the control ROM + patchTemp->outlevel = (Bit8u)(midiVolume * 100 / 127); + updateVolume(); + synth->printDebug("%s (%s): Set volume to %d", name, currentInstr, midiVolume); +} + +void Part::setExpression(int midiExpression) { + expression = midiExpression; + updateVolume(); +} + +void RhythmPart::setPan(unsigned int midiPan) +{ + // FIXME:KG: This is unchangeable for drums (they always use drumPan), is that correct? + synth->printDebug("%s: Setting pan (%d) not supported on rhythm", name, midiPan); +} + +void Part::setPan(unsigned int midiPan) { + // FIXME:KG: Tweaked this a bit so that we have a left 100%, centre and right 100% + // (But this makes the range somewhat skewed) + // Check against the real thing + // NOTE: Panning is inverted compared to GM. + if (midiPan < 64) { + volumesetting.leftvol = (Bit16s)(midiPan * 512); + volumesetting.rightvol = 32767; + } else if (midiPan == 64) { + volumesetting.leftvol = 32767; + volumesetting.rightvol = 32767; + } else { + volumesetting.rightvol = (Bit16s)((127 - midiPan) * 520); + volumesetting.leftvol = 32767; + } + patchTemp->panpot = (Bit8u)(midiPan * 14 / 127); + //synth->printDebug("%s (%s): Set pan to %d", name, currentInstr, panpot); +} + +void RhythmPart::playNote(unsigned int key, int vel) { + if (key < 24 || key > 108)/*> 87 on MT-32)*/ { + synth->printDebug("%s: Attempted to play invalid key %d", name, key); + return; + } + int drumNum = key - 24; + int drumTimbreNum = rhythmTemp[drumNum].timbre; + if (drumTimbreNum >= 127) { // 94 on MT-32 + synth->printDebug("%s: Attempted to play unmapped key %d", name, key); + return; + } + int absTimbreNum = drumTimbreNum + 128; + TimbreParam *timbre = &synth->mt32ram.timbres[absTimbreNum].timbre; + memcpy(currentInstr, timbre->common.name, 10); +#if MT32EMU_MONITOR_INSTRUMENTS == 1 + synth->printDebug("%s (%s): starting poly (drum %d, timbre %d) - Vel %d Key %d", name, currentInstr, drumNum, absTimbreNum, vel, key); +#endif + if (drumCache[drumNum][0].dirty) { + cacheTimbre(drumCache[drumNum], timbre); + } + playPoly(drumCache[drumNum], key, MIDDLEC, vel); +} + +void Part::playNote(unsigned int key, int vel) { + int freqNum = key; + if (freqNum < 12) { + synth->printDebug("%s (%s): Attempted to play invalid key %d < 12; moving up by octave", name, currentInstr, key); + freqNum += 12; + } else if (freqNum > 108) { + synth->printDebug("%s (%s): Attempted to play invalid key %d > 108; moving down by octave", name, currentInstr, key); + while (freqNum > 108) { + freqNum -= 12; + } + } + // 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? + for (unsigned int i = 0; i < MT32EMU_MAX_POLY; i++) { + if (polyTable[i].isActive() && (polyTable[i].key == key)) { + //AbortPoly(&polyTable[i]); + stopNote(key); + break; + } + } +#if MT32EMU_MONITOR_INSTRUMENTS == 1 + synth->printDebug("%s (%s): starting poly - Vel %d Key %d", name, currentInstr, vel, key); +#endif + if (patchCache[0].dirty) { + cacheTimbre(patchCache, timbreTemp); + } + playPoly(patchCache, key, freqNum, vel); +} + +void Part::playPoly(const PatchCache cache[4], unsigned int key, int freqNum, int vel) { + unsigned int needPartials = cache[0].partialCount; + unsigned int freePartials = synth->partialManager->getFreePartialCount(); + + if (freePartials < needPartials) { + if (!synth->partialManager->freePartials(needPartials - freePartials, partNum)) { + synth->printDebug("%s (%s): Insufficient free partials to play key %d (vel=%d); needed=%d, free=%d", name, currentInstr, key, vel, needPartials, synth->partialManager->getFreePartialCount()); + return; + } + } + // Find free poly + int m; + for (m = 0; m < MT32EMU_MAX_POLY; m++) { + if (!polyTable[m].isActive()) { + break; + } + } + if (m == MT32EMU_MAX_POLY) { + synth->printDebug("%s (%s): No free poly to play key %d (vel %d)", name, currentInstr, key, vel); + return; + } + + dpoly *tpoly = &polyTable[m]; + + tpoly->isPlaying = true; + tpoly->key = key; + tpoly->isDecay = false; + tpoly->freqnum = freqNum; + tpoly->vel = vel; + tpoly->pedalhold = false; + + bool allnull = true; + for (int x = 0; x < 4; x++) { + if (cache[x].playPartial) { + tpoly->partials[x] = synth->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); + + tpoly->sustain = cache[0].sustain; + tpoly->volumeptr = &volumeMult; + + for (int x = 0; x < 4; x++) { + if (tpoly->partials[x] != NULL) { + tpoly->partials[x]->startPartial(tpoly, &cache[x], tpoly->partials[cache[x].structurePair]); + tpoly->partials[x]->setBend(bend); + } + } +} + +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::allNotesOff() { + // Note: Unchecked on real MT-32, but the MIDI specification states that all notes off (0x7B) + // should treat the hold pedal as usual. + // All *sound* off (0x78) should stop notes immediately regardless of the hold pedal. + // The latter controller is not implemented on the MT-32 (according to the docs). + for (int q = 0; q < MT32EMU_MAX_POLY; q++) { + dpoly *tpoly = &polyTable[q]; + if (tpoly->isPlaying) { + if (holdpedal) + tpoly->pedalhold = true; + else if (tpoly->sustain) + startDecayPoly(tpoly); + } + } +} + +void Part::allSoundOff() { + for (int q = 0; q < MT32EMU_MAX_POLY; q++) { + dpoly *tpoly = &polyTable[q]; + if (tpoly->isPlaying) { + startDecayPoly(tpoly); + } + } +} + +void Part::stopPedalHold() { + for (int q = 0; q < MT32EMU_MAX_POLY; q++) { + dpoly *tpoly; + tpoly = &polyTable[q]; + if (tpoly->isActive() && tpoly->pedalhold) + stopNote(tpoly->key); + } +} + +void Part::stopNote(unsigned int key) { + // Non-sustaining instruments ignore stop commands. + // They die away eventually anyway + +#if MT32EMU_MONITOR_INSTRUMENTS == 1 + synth->printDebug("%s (%s): stopping key %d", name, currentInstr, key); +#endif + + if (key != 255) { + for (int q = 0; q < MT32EMU_MAX_POLY; q++) { + dpoly *tpoly = &polyTable[q]; + if (tpoly->isPlaying && tpoly->key == key) { + if (holdpedal) + tpoly->pedalhold = true; + else if (tpoly->sustain) + startDecayPoly(tpoly); + } + } + return; + } + + // Find oldest poly... yes, the MT-32 can be reconfigured to kill different poly first + // This is simplest + int oldest = -1; + Bit32u oldage = 0; + + for (int q = 0; q < MT32EMU_MAX_POLY; 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/audio/softsynth/mt32/part.h b/audio/softsynth/mt32/part.h new file mode 100644 index 0000000000..54c4999653 --- /dev/null +++ b/audio/softsynth/mt32/part.h @@ -0,0 +1,113 @@ +/* Copyright (c) 2003-2005 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 + +namespace MT32Emu { + +class PartialManager; +class Synth; + +class Part { +private: + // Pointers to the areas of the MT-32's memory dedicated to this part (for parts 1-8) + MemParams::PatchTemp *patchTemp; + TimbreParam *timbreTemp; + + // 0=Part 1, .. 7=Part 8, 8=Rhythm + unsigned int partNum; + + bool holdpedal; + + StereoVolume volumesetting; + + PatchCache patchCache[4]; + + float bend; // -1.0 .. +1.0 + + dpoly polyTable[MT32EMU_MAX_POLY]; + + void abortPoly(dpoly *poly); + + static int fixKeyfollow(int srckey); + static int fixBiaslevel(int srcpnt, int *dir); + + void setPatch(const PatchParam *patch); + +protected: + Synth *synth; + char name[8]; // "Part 1".."Part 8", "Rhythm" + char currentInstr[11]; + int expression; + Bit32u volumeMult; + + void updateVolume(); + void backupCacheToPartials(PatchCache cache[4]); + void cacheTimbre(PatchCache cache[4], const TimbreParam *timbre); + void playPoly(const PatchCache cache[4], unsigned int key, int freqNum, int vel); + const char *getName() const; + +public: + Part(Synth *synth, unsigned int usePartNum); + virtual ~Part() {} + virtual void playNote(unsigned int key, int vel); + void stopNote(unsigned int key); + void allNotesOff(); + void allSoundOff(); + int getVolume() const; + void setVolume(int midiVolume); + void setExpression(int midiExpression); + virtual void setPan(unsigned int midiPan); + virtual void setBend(unsigned int midiBend); + virtual void setModulation(unsigned int midiModulation); + virtual void setProgram(unsigned int midiProgram); + void setHoldPedal(bool pedalval); + void stopPedalHold(); + virtual void refresh(); + virtual void refreshTimbre(unsigned int absTimbreNum); + virtual void setTimbre(TimbreParam *timbre); + virtual unsigned int getAbsTimbreNum() const; + const char *getCurrentInstr() const; +}; + +class RhythmPart: public Part { + // Pointer to the area of the MT-32's memory dedicated to rhythm + const MemParams::RhythmTemp *rhythmTemp; + + // This caches the timbres/settings in use by the rhythm part + PatchCache drumCache[85][4]; + StereoVolume drumPan[85]; +public: + RhythmPart(Synth *synth, unsigned int usePartNum); + void refresh(); + void refreshTimbre(unsigned int timbreNum); + void setTimbre(TimbreParam *timbre); + void playNote(unsigned int key, int vel); + unsigned int getAbsTimbreNum() const; + void setPan(unsigned int midiPan); + void setBend(unsigned int midiBend); + void setModulation(unsigned int midiModulation); + void setProgram(unsigned int patchNum); +}; + +} +#endif diff --git a/audio/softsynth/mt32/partial.cpp b/audio/softsynth/mt32/partial.cpp new file mode 100644 index 0000000000..5ba9ef6145 --- /dev/null +++ b/audio/softsynth/mt32/partial.cpp @@ -0,0 +1,968 @@ +/* Copyright (c) 2003-2005 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 <math.h> +#include <string.h> + +#include "mt32emu.h" + +#if defined(MACOSX) || defined(SOLARIS) || defined(__MINGW32__) +// Older versions of Mac OS X didn't supply a powf function, so using it +// will cause a binary incompatibility when trying to run a binary built +// on a newer OS X release on an olderr one. And Solaris 8 doesn't provide +// powf, floorf, fabsf etc. at all. +// Cross-compiled MinGW32 toolchains suffer from a cross-compile bug in +// libstdc++. math/stubs.o should be empty, but it comes with a symbol for +// powf, resulting in a linker error because of multiple definitions. +// Hence we re-define them here. The only potential drawback is that it +// might be a little bit slower this way. +#define powf(x,y) ((float)pow(x,y)) +#define floorf(x) ((float)floor(x)) +#define fabsf(x) ((float)fabs(x)) +#endif + +#define FIXEDPOINT_UDIV(x, y, point) (((x) << (point)) / ((y))) +#define FIXEDPOINT_SDIV(x, y, point) (((x) * (1 << point)) / ((y))) +#define FIXEDPOINT_UMULT(x, y, point) (((x) * (y)) >> point) +#define FIXEDPOINT_SMULT(x, y, point) (((x) * (y)) / (1 << point)) + +using namespace MT32Emu; + +Partial::Partial(Synth *useSynth) { + this->synth = useSynth; + ownerPart = -1; + poly = NULL; + pair = NULL; +#if MT32EMU_ACCURATENOTES == 1 + for (int i = 0; i < 3; i++) { + noteLookupStorage.waveforms[i] = new Bit16s[65536]; + } + noteLookup = ¬eLookupStorage; +#endif +} + +Partial::~Partial() { +#if MT32EMU_ACCURATENOTES == 1 + for (int i = 0; i < 3; i++) { + delete[] noteLookupStorage.waveforms[i]; + } + delete[] noteLookupStorage.wavTable; +#endif +} + +int Partial::getOwnerPart() const { + return ownerPart; +} + +bool Partial::isActive() { + return ownerPart > -1; +} + +const dpoly *Partial::getDpoly() const { + return this->poly; +} + +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; + } + } + if (pair != NULL) { + pair->pair = NULL; + } + } +} + +void Partial::initKeyFollow(int key) { + // Setup partial keyfollow + // Note follow relative to middle C + + // Calculate keyfollow for pitch +#if 1 + float rel = key == -1 ? 0.0f : (key - MIDDLEC); + float newPitch = rel * patchCache->pitchKeyfollow + patchCache->pitch + patchCache->pitchShift; + //FIXME:KG: Does it truncate the keyfollowed pitch to a semitone (towards MIDDLEC)? + //int newKey = (int)(rel * patchCache->pitchKeyfollow); + //float newPitch = newKey + patchCache->pitch + patchCache->pitchShift; +#else + float rel = key == -1 ? 0.0f : (key + patchCache->pitchShift - MIDDLEC); + float newPitch = rel * patchCache->pitchKeyfollow + patchCache->pitch; +#endif +#if MT32EMU_ACCURATENOTES == 1 + noteVal = newPitch; + synth->printDebug("key=%d, pitch=%f, pitchKeyfollow=%f, pitchShift=%f, newPitch=%f", key, (double)patchCache->pitch, (double)patchCache->pitchKeyfollow, (double)patchCache->pitchShift, (double)newPitch); +#else + float newPitchInt; + float newPitchFract = modff(newPitch, &newPitchInt); + if (newPitchFract > 0.5f) { + newPitchInt += 1.0f; + newPitchFract -= 1.0f; + } + noteVal = (int)newPitchInt; + fineShift = (int)(powf(2.0f, newPitchFract / 12.0f) * 4096.0f); + synth->printDebug("key=%d, pitch=%f, pitchKeyfollow=%f, pitchShift=%f, newPitch=%f, noteVal=%d, fineShift=%d", key, (double)patchCache->pitch, (double)patchCache->pitchKeyfollow, (double)patchCache->pitchShift, (double)newPitch, noteVal, fineShift); +#endif + // FIXME:KG: Raise/lower by octaves until in the supported range. + while (noteVal > HIGHEST_NOTE) // FIXME:KG: see tables.cpp: >108? + noteVal -= 12; + while (noteVal < LOWEST_NOTE) // FIXME:KG: see tables.cpp: <12? + noteVal += 12; + // Calculate keyfollow for filter + int keyfollow = ((key - MIDDLEC) * patchCache->filtkeyfollow) / 4096; + if (keyfollow > 108) + keyfollow = 108; + else if (keyfollow < -108) + keyfollow = -108; + filtVal = synth->tables.tvfKeyfollowMult[keyfollow + 108]; + realVal = synth->tables.tvfKeyfollowMult[(noteVal - MIDDLEC) + 108]; +} + +int Partial::getKey() const { + if (poly == NULL) { + return -1; + } else { + return poly->key; + } +} + +void Partial::startPartial(dpoly *usePoly, const 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->structureMix; + structurePosition = patchCache->structurePosition; + + play = true; + initKeyFollow(poly->freqnum); // Initialises noteVal, filtVal and realVal +#if MT32EMU_ACCURATENOTES == 0 + noteLookup = &synth->tables.noteLookups[noteVal - LOWEST_NOTE]; +#else + Tables::initNote(synth, ¬eLookupStorage, noteVal, (float)synth->myProp.sampleRate, synth->masterTune, synth->pcmWaves, NULL); +#endif + keyLookup = &synth->tables.keyLookups[poly->freqnum - 12]; + + if (patchCache->PCMPartial) { + pcmNum = patchCache->pcm; + if (synth->controlROMMap->pcmCount > 128) { + // CM-32L, etc. support two "banks" of PCMs, selectable by waveform type parameter. + if (patchCache->waveform > 1) { + pcmNum += 128; + } + } + pcmWave = &synth->pcmWaves[pcmNum]; + } else { + pcmWave = NULL; + } + + lfoPos = 0; + pulsewidth = patchCache->pulsewidth + synth->tables.pwVelfollowAdd[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; + } + ampEnvVal = 0; + pitchEnvVal = 0; + pitchSustain = false; + loopPos = 0; + partialOff.pcmoffset = partialOff.pcmplace = 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]; + Bit32u volume = *poly->volumeptr; + while (length--) { + Bit32s envval; + Bit32s sample = 0; + if (!envs[EnvelopeType_amp].sustaining) { + if (envs[EnvelopeType_amp].count <= 0) { + Bit32u ampval = getAmpEnvelope(); + if (!play) { + deactivate(); + break; + } + if (ampval > 100) { + ampval = 100; + } + + ampval = synth->tables.volumeMult[ampval]; + ampval = FIXEDPOINT_UMULT(ampval, synth->tables.tvaVelfollowMult[poly->vel][(int)patchCache->ampEnv.velosens], 8); + //if (envs[EnvelopeType_amp].sustaining) + ampEnvVal = ampval; + } + --envs[EnvelopeType_amp].count; + } + + unsigned int lfoShift = 0x1000; + if (pitchSustain) { + // Calculate LFO position + // LFO does not kick in completely until pitch envelope sustains + if (patchCache->lfodepth > 0) { + lfoPos++; + if (lfoPos >= patchCache->lfoperiod) + lfoPos = 0; + int lfoatm = FIXEDPOINT_UDIV(lfoPos, patchCache->lfoperiod, 16); + int lfoatr = synth->tables.sintable[lfoatm]; + lfoShift = synth->tables.lfoShift[patchCache->lfodepth][lfoatr]; + } + } else { + // Calculate Pitch envelope + envval = getPitchEnvelope(); + int pd = patchCache->pitchEnv.depth; + pitchEnvVal = synth->tables.pitchEnvVal[pd][envval]; + } + + int delta; + + // Wrap positions or end if necessary + if (patchCache->PCMPartial) { + // PCM partial + + delta = noteLookup->wavTable[pcmNum]; + int len = pcmWave->len; + if (partialOff.pcmplace >= len) { + if (pcmWave->loop) { + //partialOff.pcmplace = partialOff.pcmoffset = 0; + partialOff.pcmplace %= len; + } else { + play = false; + deactivate(); + break; + } + } + } else { + // Synthesis partial + delta = 0x10000; + partialOff.pcmplace %= (Bit16u)noteLookup->div2; + } + + // Build delta for position of next sample + // Fix delta code + Bit32u tdelta = delta; +#if MT32EMU_ACCURATENOTES == 0 + tdelta = FIXEDPOINT_UMULT(tdelta, fineShift, 12); +#endif + tdelta = FIXEDPOINT_UMULT(tdelta, pitchEnvVal, 12); + tdelta = FIXEDPOINT_UMULT(tdelta, lfoShift, 12); + tdelta = FIXEDPOINT_UMULT(tdelta, bendShift, 12); + delta = (int)tdelta; + + // Get waveform - either PCM or synthesized sawtooth or square + if (ampEnvVal > 0) { + if (patchCache->PCMPartial) { + // Render PCM sample + int ra, rb, dist; + Bit32u taddr; + Bit32u pcmAddr = pcmWave->addr; + if (delta < 0x10000) { + // Linear sound interpolation + taddr = pcmAddr + partialOff.pcmplace; + ra = synth->pcmROMData[taddr]; + taddr++; + if (taddr == pcmAddr + pcmWave->len) { + // Past end of PCM + if (pcmWave->loop) { + rb = synth->pcmROMData[pcmAddr]; + } else { + rb = 0; + } + } else { + rb = synth->pcmROMData[taddr]; + } + dist = rb - ra; + sample = (ra + ((dist * (Bit32s)(partialOff.pcmoffset >> 8)) >> 8)); + } else { + // 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 = pcmAddr + partialOff.pcmplace; + ra = synth->pcmROMData[taddr++]; + for (int ix = 0; ix < idelta - 1; ix++) { + if (taddr == pcmAddr + pcmWave->len) { + // Past end of PCM + if (pcmWave->loop) { + taddr = pcmAddr; + } else { + // Behave as if all subsequent samples were 0 + break; + } + } + ra += synth->pcmROMData[taddr++]; + } + sample = ra / idelta; + } + } else { + // Render synthesised sample + int toff = partialOff.pcmplace; + int minorplace = partialOff.pcmoffset >> 14; + Bit32s filterInput; + Bit32s filtval = getFiltEnvelope(); + + //synth->printDebug("Filtval: %d", filtval); + + if ((patchCache->waveform & 1) == 0) { + // Square waveform. Made by combining two pregenerated bandlimited + // sawtooth waveforms + Bit32u ofsA = ((toff << 2) + minorplace) % noteLookup->waveformSize[0]; + int width = FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[pulsewidth], 7); + Bit32u ofsB = (ofsA + width) % noteLookup->waveformSize[0]; + Bit16s pa = noteLookup->waveforms[0][ofsA]; + Bit16s pb = noteLookup->waveforms[0][ofsB]; + filterInput = pa - pb; + // Non-bandlimited squarewave + /* + ofs = FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[patchCache->pulsewidth], 8); + if (toff < ofs) + sample = 1 * WGAMP; + else + sample = -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 + int waveoff = (toff << 2) + minorplace; + if (toff < noteLookup->sawTable[pulsewidth]) + filterInput = noteLookup->waveforms[1][waveoff % noteLookup->waveformSize[1]]; + else + filterInput = noteLookup->waveforms[2][waveoff % noteLookup->waveformSize[2]]; + // This is the correct way + // Seems slow to me (though bandlimited) -- doesn't seem to + // sound any better though + /* + //int pw = (patchCache->pulsewidth * pulsemod[filtval]) >> 8; + + Bit32u ofs = toff % (noteLookup->div2 >> 1); + + Bit32u ofs3 = toff + FIXEDPOINT_UMULT(noteLookup->div2, synth->tables.pwFactor[patchCache->pulsewidth], 9); + ofs3 = ofs3 % (noteLookup->div2 >> 1); + + pa = noteLookup->waveforms[0][ofs]; + pb = noteLookup->waveforms[0][ofs3]; + sample = ((pa - pb) * noteLookup->waveforms[2][toff]) / 2; + */ + } + + //Very exact filter + if (filtval > ((FILTERGRAN * 15) / 16)) + filtval = ((FILTERGRAN * 15) / 16); + sample = (Bit32s)(floorf((synth->iirFilter)((float)filterInput, &history[0], synth->tables.filtCoeff[filtval][(int)patchCache->filtEnv.resonance])) / synth->tables.resonanceFactor[patchCache->filtEnv.resonance]); + if (sample < -32768) { + synth->printDebug("Overdriven amplitude for %d: %d:=%d < -32768", patchCache->waveform, filterInput, sample); + sample = -32768; + } + else if (sample > 32767) { + synth->printDebug("Overdriven amplitude for %d: %d:=%d > 32767", patchCache->waveform, filterInput, sample); + sample = 32767; + } + } + } + + // Add calculated delta to our waveform offset + Bit32u absOff = ((partialOff.pcmplace << 16) | partialOff.pcmoffset); + absOff += delta; + partialOff.pcmplace = (Bit16u)((absOff & 0xFFFF0000) >> 16); + partialOff.pcmoffset = (Bit16u)(absOff & 0xFFFF); + + // Put volume envelope over generated sample + sample = FIXEDPOINT_SMULT(sample, ampEnvVal, 9); + sample = FIXEDPOINT_SMULT(sample, volume, 7); + envs[EnvelopeType_amp].envpos++; + envs[EnvelopeType_pitch].envpos++; + envs[EnvelopeType_filt].envpos++; + + *partialBuf++ = (Bit16s)sample; + } + // We may have deactivated and broken out of the loop before the end of the buffer, + // if so then fill the remainder with 0s. + if (++length > 0) + memset(partialBuf, 0, length * 2); + return &myBuffer[0]; +} + +void Partial::setBend(float factor) { + if (!patchCache->useBender || factor == 0.0f) { + bendShift = 4096; + return; + } + // NOTE:KG: We can't do this smoothly with lookup tables, unless we use several MB. + // FIXME:KG: Bend should be influenced by pitch key-follow too, according to docs. + float bendSemitones = factor * patchCache->benderRange; // -24 .. 24 + float mult = powf(2.0f, bendSemitones / 12.0f); + synth->printDebug("setBend(): factor=%f, benderRange=%f, semitones=%f, mult=%f\n", (double)factor, (double)patchCache->benderRange, (double)bendSemitones, (double)mult); + bendShift = (int)(mult * 4096.0f); +} + +Bit16s *Partial::mixBuffers(Bit16s * buf1, Bit16s *buf2, int len) { + if (buf1 == NULL) + return buf2; + if (buf2 == NULL) + return buf1; + + Bit16s *outBuf = buf1; +#if MT32EMU_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; +} + +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 MT32EMU_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) / 8192.0f; + b = ((float)*buf2) / 8192.0f; + a = (a * b) + a; + if (a > 1.0f) + a = 1.0f; + if (a < -1.0f) + a = -1.0f; + *buf1 = (Bit16s)(a * 8192.0f); + buf1++; + buf2++; + //buf1[i] = (Bit16s)(((Bit32s)buf1[i] * (Bit32s)buf2[i]) >> 10) + buf1[i]; + } + return outBuf; +} + +Bit16s *Partial::mixBuffersRing(Bit16s * buf1, Bit16s *buf2, int len) { + if (buf1 == NULL) { + return NULL; + } + if (buf2 == NULL) { + return NULL; + } + + Bit16s *outBuf = buf1; +#if MT32EMU_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.0f) + a = 1.0f; + if (a < -1.0f) + a = -1.0f; + *buf1 = (Bit16s)(a * 8192.0f); + buf1++; + buf2++; + } + return outBuf; +} + +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++; + } + } +} + +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; + } + + Bit16s *pairBuf = NULL; + // Check for dependant partial + 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 = synth->tables.noiseBuf; + } + + 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 = myBuf; + p1buf = pairBuf; + } + + //synth->printDebug("mixType: %d", mixType); + + Bit16s *mixedBuf; + switch (mixType) { + case 0: + // Standard sound mix + mixedBuf = mixBuffers(p1buf, p2buf, length); + break; + + case 1: + // Ring modulation with sound mix + mixedBuf = mixBuffersRingMix(p1buf, p2buf, length); + break; + + case 2: + // Ring modulation alone + mixedBuf = mixBuffersRing(p1buf, p2buf, length); + break; + + case 3: + // 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; + + default: + mixedBuf = mixBuffers(p1buf, p2buf, length); + break; + } + + if (mixedBuf == NULL) + return false; + + Bit16s leftvol, rightvol; + leftvol = patchCache->pansetptr->leftvol; + rightvol = patchCache->pansetptr->rightvol; + +#if MT32EMU_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) >> 15); + *partialBuf++ = (Bit16s)(((Bit32s)*mixedBuf * (Bit32s)rightvol) >> 15); + mixedBuf++; + } + return true; +} + +Bit32s Partial::getFiltEnvelope() { + int reshigh; + + int cutoff, depth; + + EnvelopeStatus *tStat = &envs[EnvelopeType_filt]; + + if (tStat->decaying) { + reshigh = tStat->envbase; + reshigh = (reshigh + ((tStat->envdist * tStat->envpos) / tStat->envsize)); + if (tStat->envpos >= tStat->envsize) + reshigh = 0; + } else { + if (tStat->envstat==4) { + reshigh = patchCache->filtsustain; + if (!poly->sustain) { + startDecay(EnvelopeType_filt, 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 = synth->tables.envTime[(int)patchCache->filtEnv.envtime[tStat->envstat]]; + } else { + Bit32u envTime = (int)patchCache->filtEnv.envtime[tStat->envstat]; + if (tStat->envstat > 1) { + int envDiff = abs(patchCache->filtEnv.envlevel[tStat->envstat] - patchCache->filtEnv.envlevel[tStat->envstat - 1]); + if (envTime > synth->tables.envDeltaMaxTime[envDiff]) { + envTime = synth->tables.envDeltaMaxTime[envDiff]; + } + } + + tStat->envsize = (synth->tables.envTime[envTime] * keyLookup->envTimeMult[(int)patchCache->filtEnv.envtkf]) >> 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; + } + + 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 = FIXEDPOINT_UMULT(depth, synth->tables.tvfVelfollowMult[poly->vel][(int)patchCache->filtEnv.envsense], 8); + + int bias = patchCache->tvfbias; + int dist; + + if (bias != 0) { + //FIXME:KG: Is this really based on pitch (as now), or key pressed? + //synth->printDebug("Cutoff before %d", cutoff); + if (patchCache->tvfdir == 0) { + if (noteVal < bias) { + dist = bias - noteVal; + cutoff = FIXEDPOINT_UMULT(cutoff, synth->tables.tvfBiasMult[patchCache->tvfblevel][dist], 8); + } + } else { + // > Bias + if (noteVal > bias) { + dist = noteVal - bias; + cutoff = FIXEDPOINT_UMULT(cutoff, synth->tables.tvfBiasMult[patchCache->tvfblevel][dist], 8); + } + + } + //synth->printDebug("Cutoff after %d", cutoff); + } + + depth = (depth * keyLookup->envDepthMult[patchCache->filtEnv.envdkf]) >> 8; + reshigh = (reshigh * depth) >> 7; + + Bit32s tmp; + + cutoff *= filtVal; + cutoff /= realVal; //FIXME:KG: With filter keyfollow 0, this makes no sense. What's correct? + + reshigh *= filtVal; + reshigh /= realVal; //FIXME:KG: As above for cutoff + + if (patchCache->waveform == 1) { + reshigh = (reshigh * 65) / 100; + } + + if (cutoff > 100) + cutoff = 100; + else if (cutoff < 0) + cutoff = 0; + if (reshigh > 100) + reshigh = 100; + else if (reshigh < 0) + reshigh = 0; + tmp = noteLookup->nfiltTable[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 patchCache->reverb; +} + +Bit32u Partial::getAmpEnvelope() { + Bit32s tc; + + EnvelopeStatus *tStat = &envs[EnvelopeType_amp]; + + if (!play) + return 0; + + if (tStat->decaying) { + tc = tStat->envbase; + 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; + if (tStat->envstat == 4) { + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + tc = patchCache->ampEnv.envlevel[3]; + if (!poly->sustain) + startDecay(EnvelopeType_amp, tc); + else + tStat->sustaining = true; + goto PastCalc; + } + Bit8u targetLevel = patchCache->ampEnv.envlevel[tStat->envstat]; + tStat->envdist = targetLevel - tStat->envbase; + Bit32u envTime = patchCache->ampEnv.envtime[tStat->envstat]; + if (targetLevel == 0) { + tStat->envsize = synth->tables.envDecayTime[envTime]; + } else { + int envLevelDelta = abs(tStat->envdist); + if (envTime > synth->tables.envDeltaMaxTime[envLevelDelta]) { + envTime = synth->tables.envDeltaMaxTime[envLevelDelta]; + } + tStat->envsize = synth->tables.envTime[envTime]; + } + + // Time keyfollow is used by all sections of the envelope (confirmed on CM-32L) + tStat->envsize = FIXEDPOINT_UMULT(tStat->envsize, keyLookup->envTimeMult[(int)patchCache->ampEnv.envtkf], 8); + + switch (tStat->envstat) { + case 0: + //Spot for velocity time follow + //Only used for first attack + tStat->envsize = FIXEDPOINT_UMULT(tStat->envsize, synth->tables.envTimeVelfollowMult[(int)patchCache->ampEnv.envvkf][poly->vel], 8); + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + break; + case 1: + case 2: + case 3: + //synth->printDebug("Envstat %d, size %d", tStat->envstat, tStat->envsize); + break; + default: + synth->printDebug("Invalid TVA envelope number %d hit!", tStat->envstat); + break; + } + + tStat->envsize++; + + 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->ampEnv.level) / 100; + } + + // Prevlevel storage is bottle neck + tStat->prevlevel = tc; + + //Bias level crap stuff now + + for (int i = 0; i < 2; i++) { + if (patchCache->ampblevel[i]!=0) { + int bias = patchCache->ampbias[i]; + if (patchCache->ampdir[i]==0) { + // < Bias + if (noteVal < bias) { + int dist = bias - noteVal; + tc = FIXEDPOINT_UMULT(tc, synth->tables.tvaBiasMult[patchCache->ampblevel[i]][dist], 8); + } + } else { + // > Bias + if (noteVal > bias) { + int dist = noteVal - bias; + tc = FIXEDPOINT_UMULT(tc, synth->tables.tvaBiasMult[patchCache->ampblevel[i]][dist], 8); + } + } + } + } + if (tc < 0) { + synth->printDebug("*** ERROR: tc < 0 (%d) at getAmpEnvelope()", tc); + tc = 0; + } + return (Bit32u)tc; +} + +Bit32s Partial::getPitchEnvelope() { + EnvelopeStatus *tStat = &envs[EnvelopeType_pitch]; + + 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(EnvelopeType_pitch, tc); + } else { + if ((tStat->envstat==-1) || (tStat->envpos >= tStat->envsize)) { + tStat->envstat++; + + tStat->envbase = patchCache->pitchEnv.level[tStat->envstat]; + + Bit32u envTime = patchCache->pitchEnv.time[tStat->envstat]; + int envDiff = abs(patchCache->pitchEnv.level[tStat->envstat] - patchCache->pitchEnv.level[tStat->envstat + 1]); + if (envTime > synth->tables.envDeltaMaxTime[envDiff]) { + envTime = synth->tables.envDeltaMaxTime[envDiff]; + } + + tStat->envsize = (synth->tables.envTime[envTime] * keyLookup->envTimeMult[(int)patchCache->pitchEnv.timekeyfollow]) >> 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(EnvelopeType_amp, envs[EnvelopeType_amp].prevlevel); + startDecay(EnvelopeType_filt, envs[EnvelopeType_filt].prevlevel); + startDecay(EnvelopeType_pitch, envs[EnvelopeType_pitch].prevlevel); + pitchSustain = false; +} + +void Partial::startDecay(EnvelopeType envnum, Bit32s startval) { + EnvelopeStatus *tStat = &envs[envnum]; + + tStat->sustaining = false; + tStat->decaying = true; + tStat->envpos = 0; + tStat->envbase = startval; + + switch (envnum) { + case EnvelopeType_amp: + tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->ampEnv.envtime[4]], keyLookup->envTimeMult[(int)patchCache->ampEnv.envtkf], 8); + tStat->envdist = -startval; + break; + case EnvelopeType_filt: + tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->filtEnv.envtime[4]], keyLookup->envTimeMult[(int)patchCache->filtEnv.envtkf], 8); + tStat->envdist = -startval; + break; + case EnvelopeType_pitch: + tStat->envsize = FIXEDPOINT_UMULT(synth->tables.envDecayTime[(int)patchCache->pitchEnv.time[3]], keyLookup->envTimeMult[(int)patchCache->pitchEnv.timekeyfollow], 8); + tStat->envdist = patchCache->pitchEnv.level[4] - startval; + break; + default: + break; + } + tStat->envsize++; +} diff --git a/audio/softsynth/mt32/partial.h b/audio/softsynth/mt32/partial.h new file mode 100644 index 0000000000..93d8bcd985 --- /dev/null +++ b/audio/softsynth/mt32/partial.h @@ -0,0 +1,148 @@ +/* Copyright (c) 2003-2005 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_PARTIAL_H +#define MT32EMU_PARTIAL_H + +namespace MT32Emu { + +class Synth; +struct NoteLookup; + +enum EnvelopeType { + EnvelopeType_amp = 0, + EnvelopeType_filt = 1, + EnvelopeType_pitch = 2 +}; + +struct EnvelopeStatus { + Bit32s envpos; + Bit32s envstat; + Bit32s envbase; + Bit32s envdist; + Bit32s envsize; + + bool sustaining; + bool decaying; + Bit32s prevlevel; + + Bit32s counter; + Bit32s count; +}; + +// Class definition of MT-32 partials. 32 in all. +class Partial { +private: + Synth *synth; + + int ownerPart; // -1 if unassigned + int mixType; + int structurePosition; // 0 or 1 of a structure pair + bool useNoisePair; + + Bit16s myBuffer[MAX_SAMPLE_OUTPUT]; + + // Keyfollowed note value +#if MT32EMU_ACCURATENOTES == 1 + NoteLookup noteLookupStorage; + float noteVal; +#else + int noteVal; + int fineShift; +#endif + const NoteLookup *noteLookup; // LUTs for this noteVal + const KeyLookup *keyLookup; // LUTs for the clamped (12..108) key + + // Keyfollowed filter values + int realVal; + int filtVal; + + // Only used for PCM partials + int pcmNum; + PCMWaveEntry *pcmWave; + + int pulsewidth; + + Bit32u lfoPos; + soundaddr partialOff; + + Bit32u ampEnvVal; + Bit32u pitchEnvVal; + + float history[32]; + + bool pitchSustain; + + int loopPos; + + dpoly *poly; + + int bendShift; + + 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); + + Bit32s getFiltEnvelope(); + Bit32u getAmpEnvelope(); + Bit32s getPitchEnvelope(); + + void initKeyFollow(int freqNum); + +public: + const PatchCache *patchCache; + EnvelopeStatus envs[3]; + bool play; + + PatchCache cachebackup; + + Partial *pair; + bool alreadyOutputed; + Bit32u age; + + Partial(Synth *synth); + ~Partial(); + + int getOwnerPart() const; + int getKey() const; + const dpoly *getDpoly() const; + bool isActive(); + void activate(int part); + void deactivate(void); + void startPartial(dpoly *usePoly, const PatchCache *useCache, Partial *pairPartial); + void startDecay(EnvelopeType envnum, Bit32s startval); + void startDecayAll(); + void setBend(float factor); + bool shouldReverb(); + + // Returns true only if data written to buffer + // 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(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/audio/softsynth/mt32/partialManager.cpp b/audio/softsynth/mt32/partialManager.cpp new file mode 100644 index 0000000000..3d3b6302db --- /dev/null +++ b/audio/softsynth/mt32/partialManager.cpp @@ -0,0 +1,272 @@ +/* Copyright (c) 2003-2005 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 "mt32emu.h" + +using namespace MT32Emu; + +PartialManager::PartialManager(Synth *useSynth) { + this->synth = useSynth; + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) + partialTable[i] = new Partial(synth); +} + +PartialManager::~PartialManager(void) { + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) + delete partialTable[i]; +} + +void PartialManager::getPerPartPartialUsage(int usage[9]) { + memset(usage, 0, 9 * sizeof (int)); + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialTable[i]->isActive()) + usage[partialTable[i]->getOwnerPart()]++; + } +} + +void PartialManager::clearAlreadyOutputed() { + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) + partialTable[i]->alreadyOutputed = false; +} + +void PartialManager::ageAll() { + for (int i = 0; i < MT32EMU_MAX_PARTIALS; 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 < MT32EMU_MAX_PARTIALS; i++) { + partialTable[i]->deactivate(); + } +} + +unsigned int PartialManager::setReserve(Bit8u *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 < MT32EMU_MAX_PARTIALS; 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 < MT32EMU_MAX_PARTIALS; 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.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.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.system.reserveSettings[selectPart]) { + oldest = -1; + oldnum = -1; + found = false; + for (i = 0; i < MT32EMU_MAX_PARTIALS; 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 < MT32EMU_MAX_PARTIALS; 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 < MT32EMU_MAX_PARTIALS; 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) { + Bit32u prior = 0; + int priornum = -1; + + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialReserveTable[i] == partNum && partialTable[i]->isActive() && partialTable[i]->getOwnerPart() != partNum) { + /* + if (mt32ram.system.reserveSettings[partialTable[i]->ownerPart] < prior) { + prior = mt32ram.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) { + Bit32u oldest = 0; + int oldlist = -1; + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialTable[i]->getOwnerPart() == partNum && partialTable[i]->isActive()) { + if (partialTable[i]->age >= oldest) { + oldest = partialTable[i]->age; + oldlist = i; + } + } + } + if (oldlist != -1) { + partialTable[oldlist]->deactivate(); + --needed; + } else { + break; + } + } + return needed == 0; +} + +const Partial *PartialManager::getPartial(unsigned int partialNum) const { + if (partialNum > MT32EMU_MAX_PARTIALS - 1) + return NULL; + return partialTable[partialNum]; +} diff --git a/audio/softsynth/mt32/partialManager.h b/audio/softsynth/mt32/partialManager.h new file mode 100644 index 0000000000..b10f93ff02 --- /dev/null +++ b/audio/softsynth/mt32/partialManager.h @@ -0,0 +1,56 @@ +/* Copyright (c) 2003-2005 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 + +namespace MT32Emu { + +class Synth; + +class PartialManager { +private: + Synth *synth; // Only used for sending debug output + + Partial *partialTable[MT32EMU_MAX_PARTIALS]; + Bit32s partialReserveTable[MT32EMU_MAX_PARTIALS]; + 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(Bit8u *rset); + void deactivateAll(); + void ageAll(); + bool produceOutput(int i, Bit16s *buffer, Bit32u bufferLength); + bool shouldReverb(int i); + void clearAlreadyOutputed(); + void getPerPartPartialUsage(int usage[9]); + const Partial *getPartial(unsigned int partialNum) const; +}; + +} + +#endif diff --git a/audio/softsynth/mt32/structures.h b/audio/softsynth/mt32/structures.h new file mode 100644 index 0000000000..ef58c1d20f --- /dev/null +++ b/audio/softsynth/mt32/structures.h @@ -0,0 +1,284 @@ +/* Copyright (c) 2003-2005 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_STRUCTURES_H +#define MT32EMU_STRUCTURES_H + +namespace MT32Emu { + +const unsigned int MAX_SAMPLE_OUTPUT = 4096; + +// MT32EMU_MEMADDR() converts from sysex-padded, MT32EMU_SYSEXMEMADDR converts to it +// Roland provides documentation using the sysex-padded addresses, so we tend to use that in code and output +#define MT32EMU_MEMADDR(x) ((((x) & 0x7f0000) >> 2) | (((x) & 0x7f00) >> 1) | ((x) & 0x7f)) +#define MT32EMU_SYSEXMEMADDR(x) ((((x) & 0x1FC000) << 2) | (((x) & 0x3F80) << 1) | ((x) & 0x7f)) + +#ifdef _MSC_VER +#define MT32EMU_ALIGN_PACKED __declspec(align(1)) +typedef unsigned __int64 Bit64u; +typedef signed __int64 Bit64s; +#else +#define MT32EMU_ALIGN_PACKED __attribute__((packed)) +typedef unsigned long long Bit64u; +typedef signed long long Bit64s; +#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; + +// The following structures represent the MT-32's memory +// Since sysex allows this memory to be written to in blocks of bytes, +// we keep this packed so that we can copy data into the various +// banks directly +#if defined(_MSC_VER) || defined (__MINGW32__) +#pragma pack(push, 1) +#else +#pragma pack(1) +#endif + +struct TimbreParam { + struct commonParam { + char name[10]; + Bit8u pstruct12; // 1&2 0-12 (1-13) + Bit8u pstruct34; // #3&4 0-12 (1-13) + Bit8u pmute; // 0-15 (0000-1111) + Bit8u nosustain; // 0-1(Normal, No sustain) + } MT32EMU_ALIGN_PACKED common; + + struct partialParam { + struct wgParam { + Bit8u coarse; // 0-96 (C1,C#1-C9) + Bit8u fine; // 0-100 (-50 to +50 (cents?)) + Bit8u keyfollow; // 0-16 (-1,-1/2,0,1,1/8,1/4,3/8,1/2,5/8,3/4,7/8,1,5/4,3/2,2.s1,s2) + Bit8u bender; // 0,1 (ON/OFF) + Bit8u waveform; // MT-32: 0-1 (SQU/SAW); LAPC-I: WG WAVEFORM/PCM BANK 0 - 3 (SQU/1, SAW/1, SQU/2, SAW/2) + Bit8u pcmwave; // 0-127 (1-128) + Bit8u pulsewid; // 0-100 + Bit8u pwvelo; // 0-14 (-7 - +7) + } MT32EMU_ALIGN_PACKED wg; + + struct envParam { + Bit8u depth; // 0-10 + Bit8u sensitivity; // 1-100 + Bit8u timekeyfollow; // 0-4 + Bit8u time[4]; // 1-100 + Bit8u level[5]; // 1-100 (-50 - +50) + } MT32EMU_ALIGN_PACKED env; + + struct lfoParam { + Bit8u rate; // 0-100 + Bit8u depth; // 0-100 + Bit8u modsense; // 0-100 + } MT32EMU_ALIGN_PACKED lfo; + + struct tvfParam { + Bit8u cutoff; // 0-100 + Bit8u resonance; // 0-30 + Bit8u keyfollow; // 0-16 (-1,-1/2,1/4,0,1,1/8,1/4,3/8,1/2,5/8,3/2,7/8,1,5/4,3/2,2,s1,s2) + Bit8u biaspoint; // 0-127 (<1A-<7C >1A-7C) + Bit8u biaslevel; // 0-14 (-7 - +7) + Bit8u envdepth; // 0-100 + Bit8u envsense; // 0-100 + Bit8u envdkf; // DEPTH KEY FOLL0W 0-4 + Bit8u envtkf; // TIME KEY FOLLOW 0-4 + Bit8u envtime[5]; // 1-100 + Bit8u envlevel[4]; // 1-100 + } MT32EMU_ALIGN_PACKED tvf; + + struct tvaParam { + Bit8u level; // 0-100 + Bit8u velosens; // 0-100 + Bit8u biaspoint1; // 0-127 (<1A-<7C >1A-7C) + Bit8u biaslevel1; // 0-12 (-12 - 0) + Bit8u biaspoint2; // 0-127 (<1A-<7C >1A-7C) + Bit8u biaslevel2; // 0-12 (-12 - 0) + Bit8u envtkf; // TIME KEY FOLLOW 0-4 + Bit8u envvkf; // VELOS KEY FOLL0W 0-4 + Bit8u envtime[5]; // 1-100 + Bit8u envlevel[4]; // 1-100 + } MT32EMU_ALIGN_PACKED tva; + } MT32EMU_ALIGN_PACKED partial[4]; +} MT32EMU_ALIGN_PACKED; + +struct PatchParam { + Bit8u timbreGroup; // TIMBRE GROUP 0-3 (group A, group B, Memory, Rhythm) + Bit8u timbreNum; // TIMBRE NUMBER 0-63 + Bit8u keyShift; // KEY SHIFT 0-48 (-24 - +24 semitones) + Bit8u fineTune; // FINE TUNE 0-100 (-50 - +50 cents) + Bit8u benderRange; // BENDER RANGE 0-24 + Bit8u assignMode; // ASSIGN MODE 0-3 (POLY1, POLY2, POLY3, POLY4) + Bit8u reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON) + Bit8u dummy; // (DUMMY) +} MT32EMU_ALIGN_PACKED; + +struct MemParams { + // NOTE: The MT-32 documentation only specifies PatchTemp areas for parts 1-8. + // The LAPC-I documentation specified an additional area for rhythm at the end, + // where all parameters but fine tune, assign mode and output level are ignored + struct PatchTemp { + PatchParam patch; + Bit8u outlevel; // OUTPUT LEVEL 0-100 + Bit8u panpot; // PANPOT 0-14 (R-L) + Bit8u dummyv[6]; + } MT32EMU_ALIGN_PACKED; + + PatchTemp patchSettings[9]; + + struct RhythmTemp { + Bit8u timbre; // TIMBRE 0-94 (M1-M64,R1-30,OFF) + Bit8u outlevel; // OUTPUT LEVEL 0-100 + Bit8u panpot; // PANPOT 0-14 (R-L) + Bit8u reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON) + } MT32EMU_ALIGN_PACKED; + + RhythmTemp rhythmSettings[85]; + + TimbreParam timbreSettings[8]; + + PatchParam patches[128]; + + // NOTE: There are only 30 timbres in the "rhythm" bank for MT-32; the additional 34 are for LAPC-I and above + struct PaddedTimbre { + TimbreParam timbre; + Bit8u padding[10]; + } MT32EMU_ALIGN_PACKED; + + PaddedTimbre timbres[64 + 64 + 64 + 64]; // Group A, Group B, Memory, Rhythm + + struct SystemArea { + Bit8u masterTune; // MASTER TUNE 0-127 432.1-457.6Hz + Bit8u reverbMode; // REVERB MODE 0-3 (room, hall, plate, tap delay) + Bit8u reverbTime; // REVERB TIME 0-7 (1-8) + Bit8u reverbLevel; // REVERB LEVEL 0-7 (1-8) + Bit8u reserveSettings[9]; // PARTIAL RESERVE (PART 1) 0-32 + Bit8u chanAssign[9]; // MIDI CHANNEL (PART1) 0-16 (1-16,OFF) + Bit8u masterVol; // MASTER VOLUME 0-100 + } MT32EMU_ALIGN_PACKED; + + SystemArea system; +}; + +#if defined(_MSC_VER) || defined (__MINGW32__) +#pragma pack(pop) +#else +#pragma pack() +#endif + +struct PCMWaveEntry { + Bit32u addr; + Bit32u len; + double tune; + bool loop; +}; + +struct soundaddr { + Bit16u pcmplace; + Bit16u pcmoffset; +}; + +struct StereoVolume { + Bit16s leftvol; + Bit16s rightvol; +}; + +// This is basically a per-partial, pre-processed combination of timbre and patch/rhythm settings +struct PatchCache { + bool playPartial; + bool PCMPartial; + int pcm; + char waveform; + int pulsewidth; + int pwsens; + + float pitch; + + int lfodepth; + int lforate; + Bit32u lfoperiod; + int modsense; + + float pitchKeyfollow; + + int filtkeyfollow; + + int tvfbias; + int tvfblevel; + int tvfdir; + + int ampbias[2]; + int ampblevel[2]; + int ampdir[2]; + + int ampdepth; + int amplevel; + + bool useBender; + float benderRange; // 0.0, 1.0, .., 24.0 (semitones) + + TimbreParam::partialParam::envParam pitchEnv; + TimbreParam::partialParam::tvaParam ampEnv; + TimbreParam::partialParam::tvfParam filtEnv; + + Bit32s pitchsustain; + Bit32s filtsustain; + + Bit32u structureMix; + int structurePosition; + int structurePair; + + // The following fields are actually common to all partials in the timbre + bool dirty; + Bit32u partialCount; + bool sustain; + float pitchShift; + bool reverb; + const StereoVolume *pansetptr; +}; + +class Partial; // Forward reference for class defined in partial.h + +struct dpoly { + bool isPlaying; + + unsigned int key; + int freqnum; + int vel; + + bool isDecay; + + const Bit32u *volumeptr; + + Partial *partials[4]; + + bool pedalhold; // This marks keys that have been released on the keyboard, but are being held by the pedal + bool sustain; + + bool isActive() const; + Bit32u getAge() const; +}; + +} + +#endif diff --git a/audio/softsynth/mt32/synth.cpp b/audio/softsynth/mt32/synth.cpp new file mode 100644 index 0000000000..16460795a5 --- /dev/null +++ b/audio/softsynth/mt32/synth.cpp @@ -0,0 +1,1198 @@ +/* Copyright (c) 2003-2005 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 <math.h> +#include <string.h> +#include <stdlib.h> + +#include "mt32emu.h" + +#include "common/str.h" + +#if defined(MACOSX) || defined(SOLARIS) || defined(__MINGW32__) +// Older versions of Mac OS X didn't supply a powf function, so using it +// will cause a binary incompatibility when trying to run a binary built +// on a newer OS X release on an olderr one. And Solaris 8 doesn't provide +// powf, floorf, fabsf etc. at all. +// Cross-compiled MinGW32 toolchains suffer from a cross-compile bug in +// libstdc++. math/stubs.o should be empty, but it comes with a symbol for +// powf, resulting in a linker error because of multiple definitions. +// Hence we re-define them here. The only potential drawback is that it +// might be a little bit slower this way. +#define powf(x,y) ((float)pow(x,y)) +#define floorf(x) ((float)floor(x)) +#define fabsf(x) ((float)fabs(x)) +#endif + +namespace MT32Emu { + +const int MAX_SYSEX_SIZE = 512; + +const ControlROMMap ControlROMMaps[5] = { + // ID IDc IDbytes PCMmap PCMc tmbrA tmbrAO, tmbrB tmbrBO, tmbrR trC rhythm rhyC rsrv panpot prog + {0x4014, 22, "\000 ver1.04 14 July 87 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x73A6, 85, 0x57C7, 0x57D0, 0x57E2}, // MT-32 revision 0 + {0x4014, 22, "\000 ver1.06 31 Aug, 87 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x7414, 85, 0x57D9, 0x57E2, 0x57F4}, // MT-32 revision 0 + {0x4010, 22, "\000 ver1.07 10 Oct, 87 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x73fe, 85, 0x57B1, 0x57BA, 0x57CC}, // MT-32 revision 1 + {0x4010, 22, "\000verX.XX 30 Sep, 88 ", 0x3000, 128, 0x8000, 0x0000, 0xC000, 0x4000, 0x3200, 30, 0x741C, 85, 0x57E5, 0x57EE, 0x5800}, // MT-32 Blue Ridge mod + {0x2205, 22, "\000CM32/LAPC1.02 891205", 0x8100, 256, 0x8000, 0x8000, 0x8080, 0x8000, 0x8500, 64, 0x8580, 85, 0x4F93, 0x4F9C, 0x4FAE} // CM-32L + // (Note that all but CM-32L ROM actually have 86 entries for rhythmTemp) +}; + +float iir_filter_normal(float input, float *hist1_ptr, float *coef_ptr) { + float *hist2_ptr; + float output,new_hist; + + 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 = output - *hist1_ptr * (*coef_ptr++); + new_hist = output - *hist2_ptr * (*coef_ptr++); // poles + + output = new_hist + *hist1_ptr * (*coef_ptr++); + 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 + + output = new_hist + *hist1_ptr * (*coef_ptr++); + output = output + *hist2_ptr * (*coef_ptr++); // zeros + + *hist2_ptr++ = *hist1_ptr; + *hist1_ptr++ = new_hist; + + return(output); +} + +Bit8u Synth::calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum) { + for (unsigned int i = 0; i < len; i++) { + checksum = checksum + data[i]; + } + checksum = checksum & 0x7f; + if (checksum) + checksum = 0x80 - checksum; + return checksum; +} + +Synth::Synth() { + isOpen = false; + reverbModel = NULL; + partialManager = NULL; + memset(parts, 0, sizeof(parts)); +} + +Synth::~Synth() { + close(); // Make sure we're closed and everything is freed +} + +int Synth::report(ReportType type, const void *data) { + if (myProp.report != NULL) { + return myProp.report(myProp.userData, type, data); + } + return 0; +} + +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"); + } + va_end(ap); +} + +void Synth::initReverb(Bit8u newRevMode, Bit8u newRevTime, Bit8u newRevLevel) { + // FIXME:KG: I don't think it's necessary to recreate the reverbModel... Just set the parameters + delete reverbModel; + reverbModel = new revmodel(); + + switch (newRevMode) { + case 0: + reverbModel->setroomsize(.1f); + reverbModel->setdamp(.75f); + break; + case 1: + reverbModel->setroomsize(.5f); + reverbModel->setdamp(.5f); + break; + case 2: + reverbModel->setroomsize(.5f); + reverbModel->setdamp(.1f); + break; + case 3: + reverbModel->setroomsize(1.0f); + reverbModel->setdamp(.75f); + break; + default: + reverbModel->setroomsize(.1f); + reverbModel->setdamp(.5f); + break; + } + reverbModel->setdry(1); + reverbModel->setwet((float)newRevLevel / 8.0f); + reverbModel->setwidth((float)newRevTime / 8.0f); +} + +File *Synth::openFile(const char *filename, File::OpenMode mode) { + // It should never happen that openFile is NULL in our use case. + // Just to cover the case where something is horrible wrong we + // use an assert here. + assert(myProp.openFile != NULL); + return myProp.openFile(myProp.userData, filename, mode); +} + +void Synth::closeFile(File *file) { + if (myProp.closeFile != NULL) { + myProp.closeFile(myProp.userData, file); + } else { + file->close(); + delete file; + } +} + +bool Synth::loadPreset(File *file) { + bool inSys = false; + Bit8u sysexBuf[MAX_SYSEX_SIZE]; + Bit16u syslen = 0; + bool rc = true; + for (;;) { + Bit8u c; + if (!file->readBit8u(&c)) { + if (!file->isEOF()) { + rc = false; + } + break; + } + sysexBuf[syslen] = c; + if (inSys) { + syslen++; + if (c == 0xF7) { + playSysex(&sysexBuf[0], syslen); + inSys = false; + syslen = 0; + } else if (syslen == MAX_SYSEX_SIZE) { + printDebug("MAX_SYSEX_SIZE (%d) exceeded while processing preset, ignoring message", MAX_SYSEX_SIZE); + inSys = false; + syslen = 0; + } + } else if (c == 0xF0) { + syslen++; + inSys = true; + } + } + return rc; +} + +bool Synth::loadControlROM(const char *filename) { + File *file = openFile(filename, File::OpenMode_read); // ROM File + if (file == NULL) { + return false; + } + bool rc = (file->read(controlROMData, CONTROL_ROM_SIZE) == CONTROL_ROM_SIZE); + + closeFile(file); + if (!rc) + return rc; + + // Control ROM successfully loaded, now check whether it's a known type + controlROMMap = NULL; + for (unsigned int i = 0; i < sizeof (ControlROMMaps) / sizeof (ControlROMMaps[0]); i++) { + if (memcmp(&controlROMData[ControlROMMaps[i].idPos], ControlROMMaps[i].idBytes, ControlROMMaps[i].idLen) == 0) { + controlROMMap = &ControlROMMaps[i]; + return true; + } + } + return false; +} + +bool Synth::loadPCMROM(const char *filename) { + File *file = openFile(filename, File::OpenMode_read); // ROM File + if (file == NULL) { + return false; + } + bool rc = true; + int i; + for (i = 0; i < pcmROMSize; i++) { + Bit8u s; + if (!file->readBit8u(&s)) { + if (!file->isEOF()) { + rc = false; + } + break; + } + Bit8u c; + if (!file->readBit8u(&c)) { + if (!file->isEOF()) { + rc = false; + } else { + printDebug("PCM ROM file has an odd number of bytes! Ignoring last"); + } + break; + } + + short e; + int bit; + int u; + int order[16] = {0, 9, 1 ,2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 8}; + + e = 0; + for (u = 0; u < 15; u++) { + if (order[u] < 8) + bit = (s >> (7 - order[u])) & 0x1; + 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)); + */ + + // File is companded (dB?), convert to linear PCM + // MINDB = -96 + // MAXDB = -15 + float testval; + testval = (float)((~e) & 0x7fff); + testval = -(testval / 400.00f); + //testval = -(testval / 341.32291666666666666666666666667); + float vol = powf(8, testval / 20) * 32767.0f; + + if (e > 0) + vol = -vol; + + pcmROMData[i] = (Bit16s)vol; + } + if (i != pcmROMSize) { + printDebug("PCM ROM file is too short (expected %d, got %d)", pcmROMSize, i); + rc = false; + } + closeFile(file); + return rc; +} + +bool Synth::initPCMList(Bit16u mapAddress, Bit16u count) { + ControlROMPCMStruct *tps = (ControlROMPCMStruct *)&controlROMData[mapAddress]; + for (int i = 0; i < count; i++) { + int rAddr = tps[i].pos * 0x800; + int rLenExp = (tps[i].len & 0x70) >> 4; + int rLen = 0x800 << rLenExp; + bool rLoop = (tps[i].len & 0x80) != 0; + //Bit8u rFlag = tps[i].len & 0x0F; + Bit16u rTuneOffset = (tps[i].pitchMSB << 8) | tps[i].pitchLSB; + // The number below is confirmed to a reasonable degree of accuracy on CM-32L + double STANDARDFREQ = 442.0; + float rTune = (float)(STANDARDFREQ * pow(2.0, (0x5000 - rTuneOffset) / 4056.0 - 9.0 / 12.0)); + //printDebug("%f,%d,%d", (double)pTune, tps[i].pitchCoarse, tps[i].pitchFine); + if (rAddr + rLen > pcmROMSize) { + printDebug("Control ROM error: Wave map entry %d points to invalid PCM address 0x%04X, length 0x%04X", i, rAddr, rLen); + return false; + } + pcmWaves[i].addr = rAddr; + pcmWaves[i].len = rLen; + pcmWaves[i].loop = rLoop; + pcmWaves[i].tune = rTune; + } + return false; +} + +bool Synth::initRhythmTimbre(int timbreNum, const Bit8u *mem, unsigned int memLen) { + if (memLen < sizeof(TimbreParam::commonParam)) { + return false; + } + TimbreParam *timbre = &mt32ram.timbres[timbreNum].timbre; + memcpy(&timbre->common, mem, 14); + unsigned int memPos = 14; + char drumname[11]; + memset(drumname, 0, 11); + memcpy(drumname, timbre->common.name, 10); + for (int t = 0; t < 4; t++) { + if (((timbre->common.pmute >> t) & 0x1) == 0x1) { + if (memPos + 58 >= memLen) { + return false; + } + memcpy(&timbre->partial[t], mem + memPos, 58); + memPos += 58; + } + } + return true; +} + +bool Synth::initRhythmTimbres(Bit16u mapAddress, Bit16u count) { + const Bit8u *drumMap = &controlROMData[mapAddress]; + int timbreNum = 192; + for (Bit16u i = 0; i < count * 2; i += 2) { + Bit16u address = (drumMap[i + 1] << 8) | drumMap[i]; + /* + // This check is nonsensical when the control ROM is the full 64KB addressable by 16-bit absolute pointers (which it is) + if (address >= CONTROL_ROM_SIZE) { + printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre address 0x%04x", i, address); + return false; + } + */ + if (!initRhythmTimbre(timbreNum++, &controlROMData[address], CONTROL_ROM_SIZE - address)) { + printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre 0x%04x", i, address); + return false; + } + } + return true; +} + +bool Synth::initTimbres(Bit16u mapAddress, Bit16u offset, int startTimbre) { + for (Bit16u i = mapAddress; i < mapAddress + 0x80; i += 2) { + Bit16u address = (controlROMData[i + 1] << 8) | controlROMData[i]; + if (address + sizeof(TimbreParam) > CONTROL_ROM_SIZE) { + printDebug("Control ROM error: Timbre map entry 0x%04x points to invalid timbre address 0x%04x", i, address); + return false; + } + address = address + offset; + TimbreParam *timbre = &mt32ram.timbres[startTimbre++].timbre; + memcpy(timbre, &controlROMData[address], sizeof(TimbreParam)); + } + return true; +} + +bool Synth::open(SynthProperties &useProp) { + if (isOpen) + return false; + + myProp = useProp; + if (useProp.baseDir != NULL) { + myProp.baseDir = new char[strlen(useProp.baseDir) + 1]; + strcpy(myProp.baseDir, useProp.baseDir); + } + + // This is to help detect bugs + memset(&mt32ram, '?', sizeof(mt32ram)); + + printDebug("Loading Control ROM"); + if (!loadControlROM("CM32L_CONTROL.ROM")) { + if (!loadControlROM("MT32_CONTROL.ROM")) { + printDebug("Init Error - Missing or invalid MT32_CONTROL.ROM"); + report(ReportType_errorControlROM, NULL); + return false; + } + } + + // 512KB PCM ROM for MT-32, etc. + // 1MB PCM ROM for CM-32L, LAPC-I, CM-64, CM-500 + // Note that the size below is given in samples (16-bit), not bytes + pcmROMSize = controlROMMap->pcmCount == 256 ? 512 * 1024 : 256 * 1024; + pcmROMData = new Bit16s[pcmROMSize]; + + printDebug("Loading PCM ROM"); + if (!loadPCMROM("CM32L_PCM.ROM")) { + if (!loadPCMROM("MT32_PCM.ROM")) { + printDebug("Init Error - Missing MT32_PCM.ROM"); + report(ReportType_errorPCMROM, NULL); + return false; + } + } + + printDebug("Initialising Timbre Bank A"); + if (!initTimbres(controlROMMap->timbreAMap, controlROMMap->timbreAOffset, 0)) { + return false; + } + + printDebug("Initialising Timbre Bank B"); + if (!initTimbres(controlROMMap->timbreBMap, controlROMMap->timbreBOffset, 64)) { + return false; + } + + printDebug("Initialising Timbre Bank R"); + if (!initRhythmTimbres(controlROMMap->timbreRMap, controlROMMap->timbreRCount)) { + return false; + } + + printDebug("Initialising Timbre Bank M"); + // CM-64 seems to initialise all bytes in this bank to 0. + memset(&mt32ram.timbres[128], 0, sizeof (mt32ram.timbres[128]) * 64); + + partialManager = new PartialManager(this); + + pcmWaves = new PCMWaveEntry[controlROMMap->pcmCount]; + + printDebug("Initialising PCM List"); + initPCMList(controlROMMap->pcmTable, controlROMMap->pcmCount); + + printDebug("Initialising Rhythm Temp"); + memcpy(mt32ram.rhythmSettings, &controlROMData[controlROMMap->rhythmSettings], controlROMMap->rhythmSettingsCount * 4); + + printDebug("Initialising Patches"); + for (Bit8u i = 0; i < 128; i++) { + PatchParam *patch = &mt32ram.patches[i]; + patch->timbreGroup = i / 64; + patch->timbreNum = i % 64; + patch->keyShift = 24; + patch->fineTune = 50; + patch->benderRange = 12; + patch->assignMode = 0; + patch->reverbSwitch = 1; + patch->dummy = 0; + } + + printDebug("Initialising System"); + // The MT-32 manual claims that "Standard pitch" is 442Hz. + mt32ram.system.masterTune = 0x40; // Confirmed on CM-64 as 0x4A, but SCUMM games use 0x40 and we don't want to initialise twice + mt32ram.system.reverbMode = 0; // Confirmed + mt32ram.system.reverbTime = 5; // Confirmed + mt32ram.system.reverbLevel = 3; // Confirmed + memcpy(mt32ram.system.reserveSettings, &controlROMData[controlROMMap->reserveSettings], 9); // Confirmed + for (Bit8u i = 0; i < 9; i++) { + // This is the default: {1, 2, 3, 4, 5, 6, 7, 8, 9} + // An alternative configuration can be selected by holding "Master Volume" + // and pressing "PART button 1" on the real MT-32's frontpanel. + // The channel assignment is then {0, 1, 2, 3, 4, 5, 6, 7, 9} + mt32ram.system.chanAssign[i] = i + 1; + } + mt32ram.system.masterVol = 100; // Confirmed + if (!refreshSystem()) + return false; + + for (int i = 0; i < 8; i++) { + mt32ram.patchSettings[i].outlevel = 80; + mt32ram.patchSettings[i].panpot = controlROMData[controlROMMap->panSettings + i]; + memset(mt32ram.patchSettings[i].dummyv, 0, sizeof(mt32ram.patchSettings[i].dummyv)); + parts[i] = new Part(this, i); + parts[i]->setProgram(controlROMData[controlROMMap->programSettings + i]); + } + parts[8] = new RhythmPart(this, 8); + + // For resetting mt32 mid-execution + mt32default = mt32ram; + + iirFilter = &iir_filter_normal; + +#ifdef MT32EMU_HAVE_X86 + bool availableSSE = DetectSIMD(); + bool available3DNow = Detect3DNow(); + + if (availableSSE) + report(ReportType_availableSSE, NULL); + if (available3DNow) + report(ReportType_available3DNow, NULL); + + if (available3DNow) { + printDebug("Detected and using SIMD (AMD 3DNow) extensions"); + iirFilter = &iir_filter_3dnow; + report(ReportType_using3DNow, NULL); + } else if (availableSSE) { + printDebug("Detected and using SIMD (Intel SSE) extensions"); + iirFilter = &iir_filter_sse; + report(ReportType_usingSSE, NULL); + } +#endif + + isOpen = true; + isEnabled = false; + + printDebug("*** Initialisation complete ***"); + return true; +} + +void Synth::close(void) { + if (!isOpen) + return; + + tables.freeNotes(); + if (partialManager != NULL) { + delete partialManager; + partialManager = NULL; + } + + if (reverbModel != NULL) { + delete reverbModel; + reverbModel = NULL; + } + + for (int i = 0; i < 9; i++) { + if (parts[i] != NULL) { + delete parts[i]; + parts[i] = NULL; + } + } + if (myProp.baseDir != NULL) { + delete myProp.baseDir; + myProp.baseDir = NULL; + } + + delete[] pcmWaves; + delete[] pcmROMData; + isOpen = false; +} + +void Synth::playMsg(Bit32u msg) { + // FIXME: Implement active sensing + unsigned char code = (unsigned char)((msg & 0x0000F0) >> 4); + unsigned char chan = (unsigned char) (msg & 0x00000F); + unsigned char note = (unsigned char)((msg & 0x00FF00) >> 8); + unsigned char velocity = (unsigned char)((msg & 0xFF0000) >> 16); + isEnabled = true; + + //printDebug("Playing chan %d, code 0x%01x note: 0x%02x", chan, code, note); + + signed 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; + } + playMsgOnPart(part, code, note, velocity); +} + +void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity) { + Bit32u bend; + + //printDebug("Synth::playMsg(0x%02x)",msg); + switch (code) { + case 0x8: + //printDebug("Note OFF - Part %d", part); + // The MT-32 ignores velocity for note off + parts[part]->stopNote(note); + break; + 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 { + parts[part]->playNote(note, velocity); + } + break; + case 0xB: // Control change + switch (note) { + case 0x01: // Modulation + //printDebug("Modulation: %d", velocity); + parts[part]->setModulation(velocity); + break; + case 0x07: // Set volume + //printDebug("Volume set: %d", velocity); + parts[part]->setVolume(velocity); + break; + case 0x0A: // Pan + //printDebug("Pan set: %d", velocity); + parts[part]->setPan(velocity); + break; + case 0x0B: + //printDebug("Expression set: %d", velocity); + parts[part]->setExpression(velocity); + break; + case 0x40: // Hold (sustain) pedal + //printDebug("Hold pedal set: %d", velocity); + parts[part]->setHoldPedal(velocity>=64); + break; + + case 0x79: // Reset all controllers + //printDebug("Reset all controllers"); + //FIXME: Check for accuracy against real thing + parts[part]->setVolume(100); + parts[part]->setExpression(127); + parts[part]->setPan(64); + parts[part]->setBend(0x2000); + parts[part]->setHoldPedal(false); + break; + + case 0x7B: // All notes off + //printDebug("All notes off"); + parts[part]->allNotesOff(); + break; + + default: + printDebug("Unknown MIDI Control code: 0x%02x - vel 0x%02x", note, velocity); + break; + } + + break; + case 0xC: // Program change + //printDebug("Program change %01x", note); + parts[part]->setProgram(note); + break; + case 0xE: // Pitch bender + bend = (velocity << 7) | (note); + //printDebug("Pitch bender %02x", bend); + parts[part]->setBend(bend); + break; + default: + printDebug("Unknown Midi code: 0x%01x - %02x - %02x", code, note, velocity); + break; + } + + //midiOutShortMsg(m_out, msg); +} + +void Synth::playSysex(const Bit8u *sysex, Bit32u len) { + if (len < 2) { + printDebug("playSysex: Message is too short for sysex (%d bytes)", len); + } + if (sysex[0] != 0xF0) { + printDebug("playSysex: Message lacks start-of-sysex (0xF0)"); + return; + } + // Due to some programs (e.g. Java) sending buffers with junk at the end, we have to go through and find the end marker rather than relying on len. + Bit32u endPos; + for (endPos = 1; endPos < len; endPos++) + { + if (sysex[endPos] == 0xF7) + break; + } + if (endPos == len) { + printDebug("playSysex: Message lacks end-of-sysex (0xf7)"); + return; + } + playSysexWithoutFraming(sysex + 1, endPos - 1); +} + +void Synth::playSysexWithoutFraming(const Bit8u *sysex, Bit32u len) { + if (len < 4) { + printDebug("playSysexWithoutFraming: Message is too short (%d bytes)!", len); + return; + } + if (sysex[0] != SYSEX_MANUFACTURER_ROLAND) { + 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] == SYSEX_MDL_D50) { + printDebug("playSysexWithoutFraming: Header is intended for model 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] != SYSEX_MDL_MT32) { + printDebug("playSysexWithoutFraming: Header not intended for model MT-32: %02x %02x %02x %02x", (int)sysex[0], (int)sysex[1], (int)sysex[2], (int)sysex[3]); + return; + } + playSysexWithoutHeader(sysex[1], sysex[3], sysex + 4, len - 4); +} + +void Synth::playSysexWithoutHeader(unsigned char device, unsigned char command, const 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 (len < 4) { + printDebug("playSysexWithoutHeader: Message is too short (%d bytes)!", len); + return; + } + unsigned char checksum = calcSysexChecksum(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 + switch (command) { + case SYSEX_CMD_DT1: + writeSysex(device, sysex, len); + break; + case SYSEX_CMD_RQ1: + readSysex(device, sysex, len); + break; + default: + printDebug("playSysexWithoutFraming: Unsupported command %02x", command); + return; + } +} + +void Synth::readSysex(unsigned char /*device*/, const Bit8u * /*sysex*/, Bit32u /*len*/) { +} + +const MemoryRegion memoryRegions[8] = { + {MR_PatchTemp, MT32EMU_MEMADDR(0x030000), sizeof(MemParams::PatchTemp), 9}, + {MR_RhythmTemp, MT32EMU_MEMADDR(0x030110), sizeof(MemParams::RhythmTemp), 85}, + {MR_TimbreTemp, MT32EMU_MEMADDR(0x040000), sizeof(TimbreParam), 8}, + {MR_Patches, MT32EMU_MEMADDR(0x050000), sizeof(PatchParam), 128}, + {MR_Timbres, MT32EMU_MEMADDR(0x080000), sizeof(MemParams::PaddedTimbre), 64 + 64 + 64 + 64}, + {MR_System, MT32EMU_MEMADDR(0x100000), sizeof(MemParams::SystemArea), 1}, + {MR_Display, MT32EMU_MEMADDR(0x200000), MAX_SYSEX_SIZE - 1, 1}, + {MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1} +}; + +const int NUM_REGIONS = sizeof(memoryRegions) / sizeof(MemoryRegion); + +void Synth::writeSysex(unsigned char device, const Bit8u *sysex, Bit32u len) { + Bit32u addr = (sysex[0] << 16) | (sysex[1] << 8) | (sysex[2]); + addr = MT32EMU_MEMADDR(addr); + sysex += 3; + len -= 3; + //printDebug("Sysex addr: 0x%06x", MT32EMU_SYSEXMEMADDR(addr)); + // NOTE: Please keep both lower and upper bounds in each check, for ease of reading + + // Process channel-specific sysex by converting it to device-global + if (device < 0x10) { + printDebug("WRITE-CHANNEL: Channel %d temp area 0x%06x", device, MT32EMU_SYSEXMEMADDR(addr)); + if (/*addr >= MT32EMU_MEMADDR(0x000000) && */addr < MT32EMU_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 += MT32EMU_MEMADDR(0x030000) + offset; + } else if (/*addr >= 0x010000 && */ addr < MT32EMU_MEMADDR(0x020000)) { + addr += MT32EMU_MEMADDR(0x030110) - MT32EMU_MEMADDR(0x010000); + } else if (/*addr >= 0x020000 && */ addr < MT32EMU_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); + } + addr += MT32EMU_MEMADDR(0x040000) - MT32EMU_MEMADDR(0x020000) + offset; + } else { + printDebug("PlaySysexWithoutHeader: Invalid channel %d address 0x%06x", device, MT32EMU_SYSEXMEMADDR(addr)); + return; + } + } + + // Process device-global sysex (possibly converted from channel-specific sysex above) + for (;;) { + // Find the appropriate memory region + int regionNum; + const MemoryRegion *region = NULL; // Initialised to please compiler + for (regionNum = 0; regionNum < NUM_REGIONS; regionNum++) { + region = &memoryRegions[regionNum]; + if (region->contains(addr)) { + writeMemoryRegion(region, addr, region->getClampedLen(addr, len), sysex); + break; + } + } + if (regionNum == NUM_REGIONS) { + printDebug("Sysex write to unrecognised address %06x, len %d", MT32EMU_SYSEXMEMADDR(addr), len); + break; + } + Bit32u next = region->next(addr, len); + if (next == 0) { + break; + } + addr += next; + sysex += next; + len -= next; + } +} + +void Synth::readMemory(Bit32u addr, Bit32u len, Bit8u *data) { + int regionNum; + const MemoryRegion *region = NULL; + for (regionNum = 0; regionNum < NUM_REGIONS; regionNum++) { + region = &memoryRegions[regionNum]; + if (region->contains(addr)) { + readMemoryRegion(region, addr, len, data); + break; + } + } +} + +void Synth::readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data) { + unsigned int first = region->firstTouched(addr); + //unsigned int last = region->lastTouched(addr, len); + unsigned int off = region->firstTouchedOffset(addr); + len = region->getClampedLen(addr, len); + + unsigned int m; + + switch (region->type) { + case MR_PatchTemp: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.patchSettings[first])[off + m]; + break; + case MR_RhythmTemp: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.rhythmSettings[first])[off + m]; + break; + case MR_TimbreTemp: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.timbreSettings[first])[off + m]; + break; + case MR_Patches: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.patches[first])[off + m]; + break; + case MR_Timbres: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.timbres[first])[off + m]; + break; + case MR_System: + for (m = 0; m < len; m++) + data[m] = ((Bit8u *)&mt32ram.system)[m + off]; + break; + default: + for (m = 0; m < len; m += 2) { + data[m] = 0xff; + if (m + 1 < len) { + data[m+1] = (Bit8u)region->type; + } + } + // TODO: Don't care about the others ATM + break; + } + +} + +void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data) { + unsigned int first = region->firstTouched(addr); + unsigned int last = region->lastTouched(addr, len); + unsigned int off = region->firstTouchedOffset(addr); + switch (region->type) { + case MR_PatchTemp: + for (unsigned int m = 0; m < len; m++) { + ((Bit8u *)&mt32ram.patchSettings[first])[off + m] = data[m]; + } + //printDebug("Patch temp: Patch %d, offset %x, len %d", off/16, off % 16, len); + + for (unsigned int i = first; i <= last; i++) { + int absTimbreNum = mt32ram.patchSettings[i].patch.timbreGroup * 64 + mt32ram.patchSettings[i].patch.timbreNum; + char timbreName[11]; + memcpy(timbreName, mt32ram.timbres[absTimbreNum].timbre.common.name, 10); + timbreName[10] = 0; + printDebug("WRITE-PARTPATCH (%d-%d@%d..%d): %d; timbre=%d (%s), outlevel=%d", first, last, off, off + len, i, absTimbreNum, timbreName, mt32ram.patchSettings[i].outlevel); + if (parts[i] != NULL) { + if (i != 8) { + // Note: Confirmed on CM-64 that we definitely *should* update the timbre here, + // but only in the case that the sysex actually writes to those values + if (i == first && off > 2) { + printDebug(" (Not updating timbre, since those values weren't touched)"); + } else { + parts[i]->setTimbre(&mt32ram.timbres[parts[i]->getAbsTimbreNum()].timbre); + } + } + parts[i]->refresh(); + } + } + break; + case MR_RhythmTemp: + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.rhythmSettings[first])[off + m] = data[m]; + for (unsigned int i = first; i <= last; i++) { + int timbreNum = mt32ram.rhythmSettings[i].timbre; + char timbreName[11]; + if (timbreNum < 94) { + memcpy(timbreName, mt32ram.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)", first, last, off, off + len, i, mt32ram.rhythmSettings[i].outlevel, mt32ram.rhythmSettings[i].panpot, mt32ram.rhythmSettings[i].reverbSwitch, mt32ram.rhythmSettings[i].timbre, timbreName); + } + if (parts[8] != NULL) { + parts[8]->refresh(); + } + break; + case MR_TimbreTemp: + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.timbreSettings[first])[off + m] = data[m]; + for (unsigned int i = first; i <= last; i++) { + char instrumentName[11]; + memcpy(instrumentName, mt32ram.timbreSettings[i].common.name, 10); + instrumentName[10] = 0; + printDebug("WRITE-PARTTIMBRE (%d-%d@%d..%d): timbre=%d (%s)", first, last, off, off + len, i, instrumentName); + if (parts[i] != NULL) { + parts[i]->refresh(); + } + } + break; + case MR_Patches: + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.patches[first])[off + m] = data[m]; + for (unsigned int i = first; i <= last; i++) { + PatchParam *patch = &mt32ram.patches[i]; + int patchAbsTimbreNum = patch->timbreGroup * 64 + patch->timbreNum; + char instrumentName[11]; + memcpy(instrumentName, mt32ram.timbres[patchAbsTimbreNum].timbre.common.name, 10); + instrumentName[10] = 0; + Bit8u *n = (Bit8u *)patch; + printDebug("WRITE-PATCH (%d-%d@%d..%d): %d; timbre=%d (%s) %02X%02X%02X%02X%02X%02X%02X%02X", first, last, off, off + len, i, patchAbsTimbreNum, instrumentName, n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7]); + // 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.patchSettings[part].patch.timbreGroup * 64 + mt32ram.patchSettings[part].patch.timbreNum; + if (parts[part]->getAbsTimbreNum() == patchAbsTimbreNum) { + parts[part]->setPatch(patch); + parts[part]->RefreshPatch(); + } + } + } + */ + } + break; + case MR_Timbres: + // Timbres + first += 128; + last += 128; + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.timbres[first])[off + m] = data[m]; + for (unsigned int i = first; i <= last; i++) { + char instrumentName[11]; + memcpy(instrumentName, mt32ram.timbres[i].timbre.common.name, 10); + instrumentName[10] = 0; + printDebug("WRITE-TIMBRE (%d-%d@%d..%d): %d; name=\"%s\"", first, last, 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? + for (unsigned int part = 0; part < 9; part++) { + if (parts[part] != NULL) { + parts[part]->refreshTimbre(i); + } + } + } + break; + case MR_System: + for (unsigned int m = 0; m < len; m++) + ((Bit8u *)&mt32ram.system)[m + off] = data[m]; + + report(ReportType_devReconfig, NULL); + + printDebug("WRITE-SYSTEM:"); + refreshSystem(); + break; + case MR_Display: + char buf[MAX_SYSEX_SIZE]; + memcpy(&buf, &data[0], len); + buf[len] = 0; + printDebug("WRITE-LCD: %s", buf); + report(ReportType_lcdMessage, buf); + break; + case MR_Reset: + printDebug("RESET"); + report(ReportType_devReset, NULL); + partialManager->deactivateAll(); + mt32ram = mt32default; + for (int i = 0; i < 9; i++) { + parts[i]->refresh(); + } + isEnabled = false; + break; + } +} + +bool Synth::refreshSystem() { + memset(chantable, -1, sizeof(chantable)); + + for (unsigned int i = 0; i < 9; i++) { + //LOG(LOG_MISC|LOG_ERROR,"Part %d set to MIDI channel %d",i,mt32ram.system.chanAssign[i]); + if (mt32ram.system.chanAssign[i] == 16 && parts[i] != NULL) { + parts[i]->allSoundOff(); + } else { + chantable[(int)mt32ram.system.chanAssign[i]] = (char)i; + } + } + //FIXME:KG: This is just an educated guess. + // The LAPC-I documentation claims a range of 427.5Hz-452.6Hz (similar to what we have here) + // The MT-32 documentation claims a range of 432.1Hz-457.6Hz + masterTune = 440.0f * powf(2.0f, (mt32ram.system.masterTune - 64.0f) / (128.0f * 12.0f)); + printDebug(" Master Tune: %f", (double)masterTune); + printDebug(" Reverb: mode=%d, time=%d, level=%d", mt32ram.system.reverbMode, mt32ram.system.reverbTime, mt32ram.system.reverbLevel); + report(ReportType_newReverbMode, &mt32ram.system.reverbMode); + report(ReportType_newReverbTime, &mt32ram.system.reverbTime); + report(ReportType_newReverbLevel, &mt32ram.system.reverbLevel); + + if (myProp.useDefaultReverb) { + initReverb(mt32ram.system.reverbMode, mt32ram.system.reverbTime, mt32ram.system.reverbLevel); + } else { + initReverb(myProp.reverbType, myProp.reverbTime, mt32ram.system.reverbLevel); + } + + Bit8u *rset = mt32ram.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.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.system.masterVol); + masterVolume = (Bit16u)(mt32ram.system.masterVol * 32767 / 100); + if (!tables.init(this, pcmWaves, (float)myProp.sampleRate, masterTune)) { + report(ReportType_errorSampleRate, NULL); + return false; + } + return true; +} + +bool Synth::dumpTimbre(File *file, const TimbreParam *timbre, Bit32u address) { + // Sysex header + if (!file->writeBit8u(0xF0)) + return false; + if (!file->writeBit8u(0x41)) + return false; + if (!file->writeBit8u(0x10)) + return false; + if (!file->writeBit8u(0x16)) + return false; + if (!file->writeBit8u(0x12)) + return false; + + char lsb = (char)(address & 0x7f); + char isb = (char)((address >> 7) & 0x7f); + char msb = (char)(((address >> 14) & 0x7f) | 0x08); + + //Address + if (!file->writeBit8u(msb)) + return false; + if (!file->writeBit8u(isb)) + return false; + if (!file->writeBit8u(lsb)) + return false; + + //Data + if (file->write(timbre, 246) != 246) + return false; + + //Checksum + unsigned char checksum = calcSysexChecksum((const Bit8u *)timbre, 246, msb + isb + lsb); + if (!file->writeBit8u(checksum)) + return false; + + //End of sysex + if (!file->writeBit8u(0xF7)) + return false; + return true; +} + +int Synth::dumpTimbres(const char *filename, int start, int len) { + File *file = openFile(filename, File::OpenMode_write); + if (file == NULL) + return -1; + + for (int timbreNum = start; timbreNum < start + len; timbreNum++) { + int useaddr = (timbreNum - start) * 256; + TimbreParam *timbre = &mt32ram.timbres[timbreNum].timbre; + if (!dumpTimbre(file, timbre, useaddr)) + break; + } + closeFile(file); + return 0; +} + +void ProduceOutput1(Bit16s *useBuf, Bit16s *stream, Bit32u len, Bit16s volume) { +#if MT32EMU_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 + int end = len * 2; + while (end--) { + *stream = *stream + (Bit16s)(((Bit32s)*useBuf++ * (Bit32s)volume)>>15); + stream++; + } +} + +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; + } +} + +void Synth::doRender(Bit16s *stream, Bit32u len) { + partialManager->ageAll(); + + if (myProp.useReverb) { + for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (partialManager->shouldReverb(i)) { + if (partialManager->produceOutput(i, &tmpBuffer[0], len)) { + ProduceOutput1(&tmpBuffer[0], stream, len, masterVolume); + } + } + } + Bit32u m = 0; + for (unsigned int i = 0; i < len; i++) { + sndbufl[i] = (float)stream[m] / 32767.0f; + m++; + sndbufr[i] = (float)stream[m] / 32767.0f; + m++; + } + reverbModel->processreplace(sndbufl, sndbufr, outbufl, outbufr, len, 1); + m=0; + for (unsigned int i = 0; i < len; i++) { + stream[m] = (Bit16s)(outbufl[i] * 32767.0f); + m++; + stream[m] = (Bit16s)(outbufr[i] * 32767.0f); + m++; + } + for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; 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 < MT32EMU_MAX_PARTIALS; i++) { + if (partialManager->produceOutput(i, &tmpBuffer[0], len)) + ProduceOutput1(&tmpBuffer[0], stream, len, masterVolume); + } + } + + partialManager->clearAlreadyOutputed(); + +#if MT32EMU_MONITOR_PARTIALS == 1 + samplepos += len; + if (samplepos > myProp.SampleRate * 5) { + samplepos = 0; + 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], MT32EMU_MAX_PARTIALS - partialManager->GetFreePartialCount()); + } +#endif +} + +const Partial *Synth::getPartial(unsigned int partialNum) const { + return partialManager->getPartial(partialNum); +} + +const Part *Synth::getPart(unsigned int partNum) const { + if (partNum > 8) + return NULL; + return parts[partNum]; +} + +} diff --git a/audio/softsynth/mt32/synth.h b/audio/softsynth/mt32/synth.h new file mode 100644 index 0000000000..3fc303d322 --- /dev/null +++ b/audio/softsynth/mt32/synth.h @@ -0,0 +1,300 @@ +/* Copyright (c) 2003-2005 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_SYNTH_H +#define MT32EMU_SYNTH_H + +#include "common/scummsys.h" + +class revmodel; + +namespace MT32Emu { + +class File; +class TableInitialiser; +class Partial; +class PartialManager; +class Part; + +enum ReportType { + // Errors + ReportType_errorControlROM = 1, + ReportType_errorPCMROM, + ReportType_errorSampleRate, + + // Progress + ReportType_progressInit, + + // HW spec + ReportType_availableSSE, + ReportType_available3DNow, + ReportType_usingSSE, + ReportType_using3DNow, + + // General info + ReportType_lcdMessage, + ReportType_devReset, + ReportType_devReconfig, + ReportType_newReverbMode, + ReportType_newReverbTime, + ReportType_newReverbLevel +}; + +struct SynthProperties { + // Sample rate to use in mixing + int sampleRate; + + // Flag to activate reverb. True = use reverb, False = no reverb + bool useReverb; + // True to use software set reverb settings, False to set reverb settings in + // following parameters + bool useDefaultReverb; + // When not using the default settings, this specifies one of the 4 reverb types + // 1 = Room 2 = Hall 3 = Plate 4 = Tap + unsigned char reverbType; + // This specifies the delay time, from 0-7 (not sure of the actual MT-32's measurement) + unsigned char reverbTime; + // This specifies the reverb level, from 0-7 (not sure of the actual MT-32's measurement) + unsigned char reverbLevel; + // The name of the directory in which the ROM and data files are stored (with trailing slash/backslash) + // Not used if "openFile" is set. May be NULL in any case. + char *baseDir; + // This is used as the first argument to all callbacks + void *userData; + // Callback for reporting various errors and information. May be NULL + int (*report)(void *userData, ReportType type, const void *reportData); + // Callback for debug messages, in vprintf() format + void (*printDebug)(void *userData, const char *fmt, va_list list); + // Callback for providing an implementation of File, opened and ready for use + // May be NULL, in which case a default implementation will be used. + 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 +// function +typedef void (*recalcStatusCallback)(int percDone); + +// This external function recreates the base waveform file (waveforms.raw) using a specifed +// 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(char * baseDir, int sampRate, recalcStatusCallback callBack); + +typedef float (*iir_filter_type)(float input,float *hist1_ptr, float *coef_ptr); + +const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41; + +const Bit8u SYSEX_MDL_MT32 = 0x16; +const Bit8u SYSEX_MDL_D50 = 0x14; + +const Bit8u SYSEX_CMD_RQ1 = 0x11; // Request data #1 +const Bit8u SYSEX_CMD_DT1 = 0x12; // Data set 1 +const Bit8u SYSEX_CMD_WSD = 0x40; // Want to send data +const Bit8u SYSEX_CMD_RQD = 0x41; // Request data +const Bit8u SYSEX_CMD_DAT = 0x42; // Data set +const Bit8u SYSEX_CMD_ACK = 0x43; // Acknowledge +const Bit8u SYSEX_CMD_EOD = 0x45; // End of data +const Bit8u SYSEX_CMD_ERR = 0x4E; // Communications error +const Bit8u SYSEX_CMD_RJC = 0x4F; // Rejection + +const unsigned int CONTROL_ROM_SIZE = 64 * 1024; + +struct ControlROMPCMStruct +{ + Bit8u pos; + Bit8u len; + Bit8u pitchLSB; + Bit8u pitchMSB; +}; + +struct ControlROMMap { + Bit16u idPos; + Bit16u idLen; + const char *idBytes; + Bit16u pcmTable; + Bit16u pcmCount; + Bit16u timbreAMap; + Bit16u timbreAOffset; + Bit16u timbreBMap; + Bit16u timbreBOffset; + Bit16u timbreRMap; + Bit16u timbreRCount; + Bit16u rhythmSettings; + Bit16u rhythmSettingsCount; + Bit16u reserveSettings; + Bit16u panSettings; + Bit16u programSettings; +}; + +enum MemoryRegionType { + MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset +}; + +class MemoryRegion { +public: + MemoryRegionType type; + Bit32u startAddr, entrySize, entries; + + int lastTouched(Bit32u addr, Bit32u len) const { + return (offset(addr) + len - 1) / entrySize; + } + int firstTouchedOffset(Bit32u addr) const { + return offset(addr) % entrySize; + } + int firstTouched(Bit32u addr) const { + return offset(addr) / entrySize; + } + Bit32u regionEnd() const { + return startAddr + entrySize * entries; + } + bool contains(Bit32u addr) const { + return addr >= startAddr && addr < regionEnd(); + } + int offset(Bit32u addr) const { + return addr - startAddr; + } + Bit32u getClampedLen(Bit32u addr, Bit32u len) const { + if (addr + len > regionEnd()) + return regionEnd() - addr; + return len; + } + Bit32u next(Bit32u addr, Bit32u len) const { + if (addr + len > regionEnd()) { + return regionEnd() - addr; + } + return 0; + } +}; + + +class Synth { +friend class Part; +friend class RhythmPart; +friend class Partial; +friend class Tables; +private: + bool isEnabled; + + iir_filter_type iirFilter; + + PCMWaveEntry *pcmWaves; // Array + + const ControlROMMap *controlROMMap; + Bit8u controlROMData[CONTROL_ROM_SIZE]; + Bit16s *pcmROMData; + int pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM + + Bit8s chantable[32]; + + #if MT32EMU_MONITOR_PARTIALS == 1 + static Bit32s samplepos = 0; + #endif + + Tables tables; + + MemParams mt32ram, mt32default; + + revmodel *reverbModel; + + float masterTune; + Bit16u masterVolume; + + 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 loadPreset(File *file); + void initReverb(Bit8u newRevMode, Bit8u newRevTime, Bit8u newRevLevel); + void doRender(Bit16s * stream, Bit32u len); + + void playAddressedSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); + void readSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); + void writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data); + void readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data); + + bool loadControlROM(const char *filename); + bool loadPCMROM(const char *filename); + bool dumpTimbre(File *file, const TimbreParam *timbre, Bit32u addr); + int dumpTimbres(const char *filename, int start, int len); + + bool initPCMList(Bit16u mapAddress, Bit16u count); + bool initRhythmTimbres(Bit16u mapAddress, Bit16u count); + bool initTimbres(Bit16u mapAddress, Bit16u offset, int startTimbre); + bool initRhythmTimbre(int drumNum, const Bit8u *mem, unsigned int memLen); + bool refreshSystem(); + +protected: + int report(ReportType type, const void *reportData); + File *openFile(const char *filename, File::OpenMode mode); + void closeFile(File *file); + void printDebug(const char *fmt, ...) GCC_PRINTF(2, 3); + +public: + static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum); + + Synth(); + ~Synth(); + + // Used to initialise the MT-32. Must be called before any other function. + // Returns true if initialization was sucessful, otherwise returns false. + bool open(SynthProperties &useProp); + + // Closes the MT-32 and deallocates any memory used by the synthesizer + void close(void); + + // Sends a 4-byte MIDI message to the MT-32 for immediate playback + void playMsg(Bit32u msg); + void playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity); + + // Sends a string of Sysex commands to the MT-32 for immediate interpretation + // The length is in bytes + void playSysex(const Bit8u *sysex, Bit32u len); + void playSysexWithoutFraming(const Bit8u *sysex, Bit32u len); + void playSysexWithoutHeader(unsigned char device, unsigned char command, const Bit8u *sysex, Bit32u len); + void writeSysex(unsigned char channel, const Bit8u *sysex, Bit32u len); + + // 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 render(Bit16s * stream, Bit32u len); + + const Partial *getPartial(unsigned int partialNum) const; + + void readMemory(Bit32u addr, Bit32u len, Bit8u *data); + + // partNum should be 0..7 for Part 1..8, or 8 for Rhythm + const Part *getPart(unsigned int partNum) const; +}; + +} + +#endif diff --git a/audio/softsynth/mt32/tables.cpp b/audio/softsynth/mt32/tables.cpp new file mode 100644 index 0000000000..eba4d2a520 --- /dev/null +++ b/audio/softsynth/mt32/tables.cpp @@ -0,0 +1,757 @@ +/* Copyright (c) 2003-2005 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" + +#if defined(MACOSX) || defined(SOLARIS) || defined(__MINGW32__) +// Older versions of Mac OS X didn't supply a powf function, so using it +// will cause a binary incompatibility when trying to run a binary built +// on a newer OS X release on an olderr one. And Solaris 8 doesn't provide +// powf, floorf, fabsf etc. at all. +// Cross-compiled MinGW32 toolchains suffer from a cross-compile bug in +// libstdc++. math/stubs.o should be empty, but it comes with a symbol for +// powf, resulting in a linker error because of multiple definitions. +// Hence we re-define them here. The only potential drawback is that it +// might be a little bit slower this way. +#define powf(x,y) ((float)pow(x,y)) +#define floorf(x) ((float)floor(x)) +#define fabsf(x) ((float)fabs(x)) +#endif + +#define FIXEDPOINT_MAKE(x, point) ((Bit32u)((1 << point) * x)) + +namespace MT32Emu { + +//Amplitude time velocity follow exponential coefficients +static const double tvcatconst[5] = {0.0, 0.002791309, 0.005942882, 0.012652792, 0.026938637}; +static const double tvcatmult[5] = {1.0, 1.072662811, 1.169129367, 1.288579123, 1.229630539}; + +// 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}; + +// 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(DOUBLE_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; +} + +void Tables::initFiltCoeff(float samplerate) { + for (int j = 0; j < FILTERGRAN; j++) { + for (int res = 0; res < 31; res++) { + float tres = resonanceFactor[res]; + initFilter((float)samplerate, (((float)(j+1.0)/FILTERGRAN)) * ((float)samplerate/2), filtCoeff[j][res], tres); + } + } +} + +void Tables::initEnvelopes(float samplerate) { + for (int lf = 0; lf <= 100; lf++) { + float elf = (float)lf; + + // General envelope + // This formula fits observation of the CM-32L by +/- 0.03s or so for the second time value in the filter, + // when all other times were 0 and all levels were 100. Note that variations occur depending on the level + // delta of the section, which we're not fully emulating. + float seconds = powf(2.0f, (elf / 8.0f) + 7.0f) / 32768.0f; + int samples = (int)(seconds * samplerate); + envTime[lf] = samples; + + // Cap on envelope times depending on the level delta + if (elf == 0) { + envDeltaMaxTime[lf] = 63; + } else { + float cap = 11.0f * (float)log(elf) + 64; + if (cap > 100.0f) { + cap = 100.0f; + } + envDeltaMaxTime[lf] = (int)cap; + } + + + // This (approximately) represents the time durations when the target level is 0. + // Not sure why this is a special case, but it's seen to be from the real thing. + seconds = powf(2, (elf / 8.0f) + 6) / 32768.0f; + envDecayTime[lf] = (int)(seconds * samplerate); + + // I am certain of this: Verified by hand LFO log + lfoPeriod[lf] = (Bit32u)(((float)samplerate) / (powf(1.088883372f, (float)lf) * 0.021236044f)); + } +} + +void Tables::initMT32ConstantTables(Synth *synth) { + int lf; + synth->printDebug("Initialising Pitch Tables"); + for (lf = -108; lf <= 108; lf++) { + tvfKeyfollowMult[lf + 108] = (int)(256 * powf(2.0f, (float)(lf / 24.0f))); + //synth->printDebug("KT %d = %d", f, keytable[f+108]); + } + + for (int res = 0; res < 31; res++) { + resonanceFactor[res] = powf((float)res / 30.0f, 5.0f) + 1.0f; + } + + 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; + envTimeVelfollowMult[dep][velt] = (int)tempdep; + //if ((velt % 16) == 0) { + // synth->printDebug("Key %d, depth %d, factor %d", velt, dep, (int)tempdep); + //} + } else + envTimeVelfollowMult[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; + pwVelfollowAdd[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) + synth->tables.tvfVelfollowMult[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)); + synth->tables.tvfVelfollowMult[velt][dep] = (int)(fbase * 256.0); + } + //synth->printDebug("Filvel dep %d velt %d = %x", dep, velt, filveltable[velt][dep]); + } + } + + for (lf = 0; lf < 128; lf++) { + float veloFract = lf / 127.0f; + for (int velsens = 0; velsens <= 100; velsens++) { + float sensFract = (velsens - 50) / 50.0f; + if (velsens < 50) { + tvaVelfollowMult[lf][velsens] = FIXEDPOINT_MAKE(1.0f / powf(2.0f, veloFract * -sensFract * 127.0f / 20.0f), 8); + } else { + tvaVelfollowMult[lf][velsens] = FIXEDPOINT_MAKE(1.0f / powf(2.0f, (1.0f - veloFract) * sensFract * 127.0f / 20.0f), 8); + } + } + } + + for (lf = 0; lf <= 100; lf++) { + // Converts the 0-100 range used by the MT-32 to volume multiplier + volumeMult[lf] = FIXEDPOINT_MAKE(powf((float)lf / 100.0f, FLOAT_LN), 7); + } + + for (lf = 0; lf <= 100; lf++) { + float mv = lf / 100.0f; + float pt = mv - 0.5f; + if (pt < 0) + pt = 0; + + // Original (CC version) + //pwFactor[lf] = (int)(pt * 210.04f) + 128; + + // Approximation from sample comparison + pwFactor[lf] = (int)(pt * 179.0f) + 128; + } + + for (unsigned int i = 0; i < MAX_SAMPLE_OUTPUT; i++) { + int myRand; + myRand = rand(); + //myRand = ((myRand - 16383) * 7168) >> 16; + // This one is slower but works with all values of RAND_MAX + myRand = (int)((myRand - RAND_MAX / 2) / (float)RAND_MAX * (7168 / 2)); + //FIXME:KG: Original ultimately set the lowest two bits to 0, for no obvious reason + noiseBuf[i] = (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, (double)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 = (float)exp(0.713619942f * tlf) / 407.4945111f; + + if (depat < 50) + finalval = 4096.0f * powf(2, -lfp); + else + finalval = 4096.0f * powf(2, lfp); + pval = (int)finalval; + + pitchEnvVal[lf][depat] = pval; + //synth->printDebug("lf %d depat %d pval %d tlf %f lfp %f", lf,depat,pval, (double)tlf, (double)lfp); + } else { + pitchEnvVal[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; + + lfoShift[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; + tvaBiasMult[lf][distval] = 256; + } else { + /* + amplog = powf(1.431817011f, (float)lf) / FLOAT_PI; + dval = ((128.0f - (float)distval) / 128.0f); + amplog = exp(amplog); + dval = powf(amplog, dval) / amplog; + tvaBiasMult[lf][distval] = (int)(dval * 256.0); + */ + // Lets assume for a second it's linear + + // Distance of full volume reduction + amplog = (float)(12.0f / (float)lf) * 24.0f; + if (distval > amplog) { + tvaBiasMult[lf][distval] = 0; + } else { + dval = (amplog - (float)distval) / amplog; + tvaBiasMult[lf][distval] = (int)(dval * 256.0f); + } + } + //synth->printDebug("Ampbias lf %d distval %d = %f (%x) %f", lf, distval, (double)dval, tvaBiasMult[lf][distval],(double)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; + tvfBiasMult[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 = (float)exp(amplog); + dval = powf(amplog,dval)/amplog; + if (lf < 8) { + tvfBiasMult[lf][distval] = (int)(dval * 256.0f); + } else { + dval = powf(dval, 0.3333333f); + if (dval < 0.01f) + dval = 0.01f; + dval = 1 / dval; + tvfBiasMult[lf][distval] = (int)(dval * 256.0f); + } + } + //synth->printDebug("Fbias lf %d distval %d = %f (%x) %f", lf, distval, (double)dval, tvfBiasMult[lf][distval],(double)amplog); + } + } +} + +// Per-note table initialisation follows + +static void initSaw(NoteLookup *noteLookup, Bit32s div2) { + int tmpdiv = div2 << 16; + for (int rsaw = 0; rsaw <= 100; rsaw++) { + float fsaw; + if (rsaw < 50) + fsaw = 50.0f; + else + fsaw = (float)rsaw; + + //(66 - (((A8 - 50) / 50) ^ 0.63) * 50) / 132 + float sawfact = (66.0f - (powf((fsaw - 50.0f) / 50.0f, 0.63f) * 50.0f)) / 132.0f; + noteLookup->sawTable[rsaw] = (int)(sawfact * (float)tmpdiv) >> 16; + //synth->printDebug("F %d divtable %d saw %d sawtable %d", f, div, rsaw, sawtable[f][rsaw]); + } +} + +static void initDep(KeyLookup *keyLookup, float f) { + for (int dep = 0; dep < 5; dep++) { + if (dep == 0) { + keyLookup->envDepthMult[dep] = 256; + keyLookup->envTimeMult[dep] = 256; + } else { + float depfac = 3000.0f; + float ff, tempdep; + depfac = (float)depexp[dep]; + + ff = (f - (float)MIDDLEC) / depfac; + tempdep = powf(2, ff) * 256.0f; + keyLookup->envDepthMult[dep] = (int)tempdep; + + ff = (float)(exp(tkcatconst[dep] * ((float)MIDDLEC - f)) * tkcatmult[dep]); + keyLookup->envTimeMult[dep] = (int)(ff * 256.0f); + } + } + //synth->printDebug("F %f d1 %x d2 %x d3 %x d4 %x d5 %x", (double)f, noteLookup->fildepTable[0], noteLookup->fildepTable[1], noteLookup->fildepTable[2], noteLookup->fildepTable[3], noteLookup->fildepTable[4]); +} + +Bit16s Tables::clampWF(Synth *synth, const char *n, float ampVal, double input) { + Bit32s x = (Bit32s)(input * ampVal); + if (x < -ampVal - 1) { + synth->printDebug("%s==%d<-WGAMP-1!", n, x); + x = (Bit32s)(-ampVal - 1); + } else if (x > ampVal) { + synth->printDebug("%s==%d>WGAMP!", n, x); + x = (Bit32s)ampVal; + } + return (Bit16s)x; +} + +File *Tables::initWave(Synth *synth, NoteLookup *noteLookup, float ampVal, float div2, File *file) { + int iDiv2 = (int)div2; + noteLookup->waveformSize[0] = iDiv2 << 1; + noteLookup->waveformSize[1] = iDiv2 << 1; + noteLookup->waveformSize[2] = iDiv2 << 2; + for (int i = 0; i < 3; i++) { + if (noteLookup->waveforms[i] == NULL) { + noteLookup->waveforms[i] = new Bit16s[noteLookup->waveformSize[i]]; + } + } + if (file != NULL) { + for (int i = 0; i < 3 && file != NULL; i++) { + size_t len = noteLookup->waveformSize[i]; + for (unsigned int j = 0; j < len; j++) { + if (!file->readBit16u((Bit16u *)¬eLookup->waveforms[i][j])) { + synth->printDebug("Error reading wave file cache!"); + file->close(); + file = NULL; + break; + } + } + } + } + if (file == NULL) { + double sd = DOUBLE_PI / div2; + + for (int fa = 0; fa < (iDiv2 << 1); fa++) { + // sa ranges from 0 to 2PI + double sa = fa * sd; + + // Calculate a sample for the bandlimited sawtooth wave + double saw = 0.0; + int sincs = iDiv2 >> 1; + double sinus = 1.0; + for (int sincNum = 1; sincNum <= sincs; sincNum++) { + saw += sin(sinus * sa) / sinus; + sinus++; + } + + // This works pretty well + // Multiplied by 0.84 so that the spikes caused by bandlimiting don't overdrive the amplitude + noteLookup->waveforms[0][fa] = clampWF(synth, "saw", ampVal, -saw / (0.5 * DOUBLE_PI) * 0.84); + noteLookup->waveforms[1][fa] = clampWF(synth, "cos", ampVal, -cos(sa / 2.0)); + noteLookup->waveforms[2][fa * 2] = clampWF(synth, "cosoff_0", ampVal, -cos(sa - DOUBLE_PI)); + noteLookup->waveforms[2][fa * 2 + 1] = clampWF(synth, "cosoff_1", ampVal, -cos((sa + (sd / 2)) - DOUBLE_PI)); + } + } + return file; +} + +static void initFiltTable(NoteLookup *noteLookup, 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); + noteLookup->filtTable[0][tr] = (int)((freq * brsq) / (rate / 2) * FILTERGRAN); + if (noteLookup->filtTable[0][tr]>=((FILTERGRAN*15)/16)) + noteLookup->filtTable[0][tr] = ((FILTERGRAN*15)/16); + + float brsa = powf(10.0f, ((tr / 55.0f) - 1.0f)) / 2.0f; + noteLookup->filtTable[1][tr] = (int)((freq * brsa) / (rate / 2) * FILTERGRAN); + if (noteLookup->filtTable[1][tr]>=((FILTERGRAN*15)/16)) + noteLookup->filtTable[1][tr] = ((FILTERGRAN*15)/16); + } +} + +static void initNFiltTable(NoteLookup *noteLookup, 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; + + //float freqsum = exp((cfmult + tfadd) / 30.0f) / 4.0f; + //float freqsum = 0.15f * exp(0.45f * ((cfmult + tfadd) / 10.0f)); + + float freqsum = powf(2.0f, ((cfmult + tfadd) - 40.0f) / 16.0f); + + noteLookup->nfiltTable[cf][tf] = (int)((freq * freqsum) / (rate / 2) * FILTERGRAN); + if (noteLookup->nfiltTable[cf][tf] >= ((FILTERGRAN * 15) / 16)) + noteLookup->nfiltTable[cf][tf] = ((FILTERGRAN * 15) / 16); + } + } +} + +File *Tables::initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float masterTune, PCMWaveEntry *pcmWaves, File *file) { + float freq = (float)(masterTune * pow(2.0, ((double)note - MIDDLEA) / 12.0)); + float div2 = rate * 2.0f / freq; + noteLookup->div2 = (int)div2; + + if (noteLookup->div2 == 0) + noteLookup->div2 = 1; + + initSaw(noteLookup, noteLookup->div2); + + //synth->printDebug("Note %f; freq=%f, div=%f", (double)note, (double)freq, (double) rate / freq); + file = initWave(synth, noteLookup, WGAMP, div2, file); + + // Create the pitch tables + if (noteLookup->wavTable == NULL) + noteLookup->wavTable = new Bit32u[synth->controlROMMap->pcmCount]; + double rateMult = 32000.0 / rate; + double tuner = freq * 65536.0f; + for (int pc = 0; pc < synth->controlROMMap->pcmCount; pc++) { + noteLookup->wavTable[pc] = (int)(tuner / pcmWaves[pc].tune * rateMult); + } + + initFiltTable(noteLookup, freq, rate); + initNFiltTable(noteLookup, freq, rate); + return file; +} + +bool Tables::initNotes(Synth *synth, PCMWaveEntry *pcmWaves, float rate, float masterTune) { + const char *NoteNames[12] = { + "C ", "C#", "D ", "D#", "E ", "F ", "F#", "G ", "G#", "A ", "A#", "B " + }; + char filename[64]; + int intRate = (int)rate; + char version[4] = {0, 0, 0, 5}; + sprintf(filename, "waveformcache-%d-%.2f.raw", intRate, (double)masterTune); + + File *file = NULL; + char header[20]; + memcpy(header, "MT32WAVE", 8); + int pos = 8; + // Version... + for (int i = 0; i < 4; i++) + header[pos++] = version[i]; + header[pos++] = (char)((intRate >> 24) & 0xFF); + header[pos++] = (char)((intRate >> 16) & 0xFF); + header[pos++] = (char)((intRate >> 8) & 0xFF); + header[pos++] = (char)(intRate & 0xFF); + int intTuning = (int)masterTune; + header[pos++] = (char)((intTuning >> 8) & 0xFF); + header[pos++] = (char)(intTuning & 0xFF); + header[pos++] = 0; + header[pos] = (char)((masterTune - intTuning) * 10); +#if MT32EMU_WAVECACHEMODE < 2 + bool reading = false; + file = synth->openFile(filename, File::OpenMode_read); + if (file != NULL) { + char fileHeader[20]; + if (file->read(fileHeader, 20) == 20) { + if (memcmp(fileHeader, header, 20) == 0) { + Bit16u endianCheck; + if (file->readBit16u(&endianCheck)) { + if (endianCheck == 1) { + reading = true; + } else { + synth->printDebug("Endian check in %s does not match expected", filename); + } + } else { + synth->printDebug("Unable to read endian check in %s", filename); + } + } else { + synth->printDebug("Header of %s does not match expected", filename); + } + } else { + synth->printDebug("Error reading 16 bytes of %s", filename); + } + if (!reading) { + file->close(); + file = NULL; + } + } else { + synth->printDebug("Unable to open %s for reading", filename); + } +#endif + + float progress = 0.0f; + bool abort = false; + synth->report(ReportType_progressInit, &progress); + for (int f = LOWEST_NOTE; f <= HIGHEST_NOTE; f++) { + synth->printDebug("Initialising note %s%d", NoteNames[f % 12], (f / 12) - 2); + NoteLookup *noteLookup = ¬eLookups[f - LOWEST_NOTE]; + file = initNote(synth, noteLookup, (float)f, rate, masterTune, pcmWaves, file); + progress = (f - LOWEST_NOTE + 1) / (float)NUM_NOTES; + abort = synth->report(ReportType_progressInit, &progress) != 0; + if (abort) + break; + } + +#if MT32EMU_WAVECACHEMODE == 0 || MT32EMU_WAVECACHEMODE == 2 + if (file == NULL) { + file = synth->openFile(filename, File::OpenMode_write); + if (file != NULL) { + if (file->write(header, 20) == 20 && file->writeBit16u(1)) { + for (int f = 0; f < NUM_NOTES; f++) { + for (int i = 0; i < 3 && file != NULL; i++) { + int len = noteLookups[f].waveformSize[i]; + for (int j = 0; j < len; j++) { + if (!file->writeBit16u(noteLookups[f].waveforms[i][j])) { + synth->printDebug("Error writing waveform cache file"); + file->close(); + file = NULL; + break; + } + } + } + } + } 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 + + if (file != NULL) + synth->closeFile(file); + return !abort; +} + +void Tables::freeNotes() { + for (int t = 0; t < 3; t++) { + for (int m = 0; m < NUM_NOTES; m++) { + if (noteLookups[m].waveforms[t] != NULL) { + delete[] noteLookups[m].waveforms[t]; + noteLookups[m].waveforms[t] = NULL; + noteLookups[m].waveformSize[t] = 0; + } + if (noteLookups[m].wavTable != NULL) { + delete[] noteLookups[m].wavTable; + noteLookups[m].wavTable = NULL; + } + } + } + initialisedMasterTune = 0.0f; +} + +Tables::Tables() { + initialisedSampleRate = 0.0f; + initialisedMasterTune = 0.0f; + memset(¬eLookups, 0, sizeof(noteLookups)); +} + +bool Tables::init(Synth *synth, PCMWaveEntry *pcmWaves, float sampleRate, float masterTune) { + if (sampleRate <= 0.0f) { + synth->printDebug("Bad sampleRate (%f <= 0.0f)", (double)sampleRate); + return false; + } + if (initialisedSampleRate == 0.0f) { + initMT32ConstantTables(synth); + } + if (initialisedSampleRate != sampleRate) { + initFiltCoeff(sampleRate); + initEnvelopes(sampleRate); + for (int key = 12; key <= 108; key++) { + initDep(&keyLookups[key - 12], (float)key); + } + } + if (initialisedSampleRate != sampleRate || initialisedMasterTune != masterTune) { + freeNotes(); + if (!initNotes(synth, pcmWaves, sampleRate, masterTune)) { + return false; + } + initialisedSampleRate = sampleRate; + initialisedMasterTune = masterTune; + } + return true; +} + +} diff --git a/audio/softsynth/mt32/tables.h b/audio/softsynth/mt32/tables.h new file mode 100644 index 0000000000..d9af5114b2 --- /dev/null +++ b/audio/softsynth/mt32/tables.h @@ -0,0 +1,116 @@ +/* Copyright (c) 2003-2005 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 + +namespace MT32Emu { + +// Mathematical constants +const double DOUBLE_PI = 3.1415926535897932384626433832795; +const double DOUBLE_LN = 2.3025850929940456840179914546844; +const float FLOAT_PI = 3.1415926535897932384626433832795f; +const float FLOAT_LN = 2.3025850929940456840179914546844f; + +// Filter settings +const int FILTERGRAN = 512; + +// Amplitude of waveform generator +// FIXME: This value is the amplitude possible whilst avoiding +// overdriven values immediately after filtering when playing +// back SQ3MT.MID. Needs to be checked. +const int WGAMP = 12382; + +const int MIDDLEC = 60; +const int MIDDLEA = 69; // By this I mean "A above middle C" + +// FIXME:KG: may only need to do 12 to 108 +// 12..108 is the range allowed by note on commands, but the key can be modified by pitch keyfollow +// and adjustment for timbre pitch, so the results can be outside that range. +// Should we move it (by octave) into the 12..108 range, or keep it in 0..127 range, +// or something else altogether? +const int LOWEST_NOTE = 12; +const int HIGHEST_NOTE = 127; +const int NUM_NOTES = HIGHEST_NOTE - LOWEST_NOTE + 1; // Number of slots for note LUT + +class Synth; + +struct NoteLookup { + Bit32u div2; + Bit32u *wavTable; + Bit32s sawTable[101]; + int filtTable[2][201]; + int nfiltTable[101][101]; + Bit16s *waveforms[3]; + Bit32u waveformSize[3]; +}; + +struct KeyLookup { + Bit32s envTimeMult[5]; // For envelope time adjustment for key pressed + Bit32s envDepthMult[5]; +}; + +class Tables { + float initialisedSampleRate; + float initialisedMasterTune; + void initMT32ConstantTables(Synth *synth); + static Bit16s clampWF(Synth *synth, const char *n, float ampVal, double input); + static File *initWave(Synth *synth, NoteLookup *noteLookup, float ampsize, float div2, File *file); + bool initNotes(Synth *synth, PCMWaveEntry *pcmWaves, float rate, float tuning); + void initEnvelopes(float sampleRate); + void initFiltCoeff(float samplerate); +public: + // Constant LUTs + Bit32s tvfKeyfollowMult[217]; + Bit32s tvfVelfollowMult[128][101]; + Bit32s tvfBiasMult[15][128]; + Bit32u tvaVelfollowMult[128][101]; + Bit32s tvaBiasMult[13][128]; + Bit16s noiseBuf[MAX_SAMPLE_OUTPUT]; + Bit16s sintable[65536]; + Bit32s pitchEnvVal[16][101]; + Bit32s envTimeVelfollowMult[5][128]; + Bit32s pwVelfollowAdd[15][128]; + float resonanceFactor[31]; + Bit32u lfoShift[101][101]; + Bit32s pwFactor[101]; + Bit32s volumeMult[101]; + + // LUTs varying with sample rate + Bit32u envTime[101]; + Bit32u envDeltaMaxTime[101]; + Bit32u envDecayTime[101]; + Bit32u lfoPeriod[101]; + float filtCoeff[FILTERGRAN][31][8]; + + // Various LUTs for each note and key + NoteLookup noteLookups[NUM_NOTES]; + KeyLookup keyLookups[97]; + + Tables(); + bool init(Synth *synth, PCMWaveEntry *pcmWaves, float sampleRate, float masterTune); + File *initNote(Synth *synth, NoteLookup *noteLookup, float note, float rate, float tuning, PCMWaveEntry *pcmWaves, File *file); + void freeNotes(); +}; + +} + +#endif |