MT32: Update MT-32 emulator to latest Munt code

Several changes against original code were made. They were intentionally
kept to the minimum
This commit is contained in:
Eugene Sandulenko 2012-02-09 11:20:45 +02:00
parent 06b5299461
commit 030e155eeb
48 changed files with 6954 additions and 6333 deletions

View File

@ -84,42 +84,6 @@ public:
int getRate() const { return _outputRate; }
};
class MT32File : public MT32Emu::File {
Common::File _in;
Common::DumpFile _out;
public:
bool open(const char *filename, OpenMode mode) {
if (mode == OpenMode_read)
return _in.open(filename);
else
return _out.open(filename);
}
void close() {
_in.close();
_out.close();
}
size_t read(void *in, size_t size) {
return _in.read(in, size);
}
bool readBit8u(MT32Emu::Bit8u *in) {
byte b = _in.readByte();
if (_in.eos())
return false;
*in = b;
return true;
}
size_t write(const void *in, size_t size) {
return _out.write(in, size);
}
bool writeBit8u(MT32Emu::Bit8u out) {
_out.writeByte(out);
return !_out.err();
}
bool isEOF() {
return _in.isOpen() && _in.eos();
}
};
static int eatSystemEvents() {
Common::Event event;
Common::EventManager *eventMan = g_system->getEventManager();
@ -206,9 +170,9 @@ static void drawMessage(int offset, const Common::String &text) {
g_system->updateScreen();
}
static MT32Emu::File *MT32_OpenFile(void *userData, const char *filename, MT32Emu::File::OpenMode mode) {
MT32File *file = new MT32File();
if (!file->open(filename, mode)) {
static Common::File *MT32_OpenFile(void *userData, const char *filename) {
Common::File *file = new Common::File();
if (!file->open(filename)) {
delete file;
return NULL;
}

View File

@ -0,0 +1,237 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mt32emu.h"
#include "AReverbModel.h"
using namespace MT32Emu;
// Default reverb settings for modes 0-2
static const unsigned int NUM_ALLPASSES = 6;
static const unsigned int NUM_DELAYS = 5;
static const Bit32u MODE_0_ALLPASSES[] = {729, 78, 394, 994, 1250, 1889};
static const Bit32u MODE_0_DELAYS[] = {846, 4, 1819, 778, 346};
static const float MODE_0_TIMES[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.9f};
static const float MODE_0_LEVELS[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 1.01575f};
static const Bit32u MODE_1_ALLPASSES[] = {176, 809, 1324, 1258};
static const Bit32u MODE_1_DELAYS[] = {2262, 124, 974, 2516, 356};
static const float MODE_1_TIMES[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.95f};
static const float MODE_1_LEVELS[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 1.01575f};
static const Bit32u MODE_2_ALLPASSES[] = {78, 729, 994, 389};
static const Bit32u MODE_2_DELAYS[] = {846, 4, 1819, 778, 346};
static const float MODE_2_TIMES[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f};
static const float MODE_2_LEVELS[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f};
const AReverbSettings AReverbModel::REVERB_MODE_0_SETTINGS = {MODE_0_ALLPASSES, MODE_0_DELAYS, MODE_0_TIMES, MODE_0_LEVELS, 0.687770909f, 0.5f, 0.5f};
const AReverbSettings AReverbModel::REVERB_MODE_1_SETTINGS = {MODE_1_ALLPASSES, MODE_1_DELAYS, MODE_1_TIMES, MODE_1_LEVELS, 0.712025098f, 0.375f, 0.625f};
const AReverbSettings AReverbModel::REVERB_MODE_2_SETTINGS = {MODE_2_ALLPASSES, MODE_2_DELAYS, MODE_2_TIMES, MODE_2_LEVELS, 0.939522749f, 0.0f, 0.0f};
RingBuffer::RingBuffer(Bit32u newsize) {
index = 0;
size = newsize;
buffer = new float[size];
}
RingBuffer::~RingBuffer() {
delete[] buffer;
buffer = NULL;
size = 0;
}
float RingBuffer::next() {
index++;
if (index >= size) {
index = 0;
}
return buffer[index];
}
bool RingBuffer::isEmpty() {
if (buffer == NULL) return true;
float *buf = buffer;
float total = 0;
for (Bit32u i = 0; i < size; i++) {
total += (*buf < 0 ? -*buf : *buf);
buf++;
}
return ((total / size) < .0002 ? true : false);
}
void RingBuffer::mute() {
float *buf = buffer;
for (Bit32u i = 0; i < size; i++) {
*buf++ = 0;
}
}
AllpassFilter::AllpassFilter(Bit32u useSize) : RingBuffer(useSize) {
}
Delay::Delay(Bit32u useSize) : RingBuffer(useSize) {
}
float AllpassFilter::process(float in) {
// This model corresponds to the allpass filter implementation in the real CM-32L device
// found from sample analysis
float out;
out = next();
// store input - feedback / 2
buffer[index] = in - 0.5f * out;
// return buffer output + feedforward / 2
return out + 0.5f * buffer[index];
}
float Delay::process(float in) {
// Implements a very simple delay
float out;
out = next();
// store input
buffer[index] = in;
// return buffer output
return out;
}
AReverbModel::AReverbModel(const AReverbSettings *useSettings) : allpasses(NULL), delays(NULL), currentSettings(useSettings) {
}
AReverbModel::~AReverbModel() {
close();
}
void AReverbModel::open(unsigned int /*sampleRate*/) {
// FIXME: filter sizes must be multiplied by sample rate to 32000Hz ratio
// IIR filter values depend on sample rate as well
allpasses = new AllpassFilter*[NUM_ALLPASSES];
for (Bit32u i = 0; i < NUM_ALLPASSES; i++) {
allpasses[i] = new AllpassFilter(currentSettings->allpassSizes[i]);
}
delays = new Delay*[NUM_DELAYS];
for (Bit32u i = 0; i < NUM_DELAYS; i++) {
delays[i] = new Delay(currentSettings->delaySizes[i]);
}
mute();
}
void AReverbModel::close() {
if (allpasses != NULL) {
for (Bit32u i = 0; i < NUM_ALLPASSES; i++) {
if (allpasses[i] != NULL) {
delete allpasses[i];
allpasses[i] = NULL;
}
}
delete[] allpasses;
allpasses = NULL;
}
if (delays != NULL) {
for (Bit32u i = 0; i < NUM_DELAYS; i++) {
if (delays[i] != NULL) {
delete delays[i];
delays[i] = NULL;
}
}
delete[] delays;
delays = NULL;
}
}
void AReverbModel::mute() {
for (Bit32u i = 0; i < NUM_ALLPASSES; i++) {
allpasses[i]->mute();
}
for (Bit32u i = 0; i < NUM_DELAYS; i++) {
delays[i]->mute();
}
filterhist1 = 0;
filterhist2 = 0;
combhist = 0;
}
void AReverbModel::setParameters(Bit8u time, Bit8u level) {
// FIXME: wetLevel definitely needs ramping when changed
// Although, most games don't set reverb level during MIDI playback
decayTime = currentSettings->decayTimes[time];
wetLevel = currentSettings->wetLevels[level];
}
bool AReverbModel::isActive() const {
bool bActive = false;
for (Bit32u i = 0; i < NUM_ALLPASSES; i++) {
bActive |= !allpasses[i]->isEmpty();
}
for (Bit32u i = 0; i < NUM_DELAYS; i++) {
bActive |= !delays[i]->isEmpty();
}
return bActive;
}
void AReverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) {
// Three series allpass filters followed by a delay, fourth allpass filter and another delay
float dry, link, outL1, outL2, outR1, outR2;
for (unsigned long i = 0; i < numSamples; i++) {
dry = *inLeft + *inRight;
// Implementation of 2-stage IIR single-pole low-pass filter
// found at the entrance of reverb processing on real devices
filterhist1 += (dry - filterhist1) * currentSettings->filtVal;
filterhist2 += (filterhist1 - filterhist2) * currentSettings->filtVal;
link = allpasses[0]->process(-filterhist2);
link = allpasses[1]->process(link);
// this implements a comb filter cross-linked with the fourth allpass filter
link += combhist * decayTime;
link = allpasses[2]->process(link);
link = delays[0]->process(link);
outL1 = link;
link = allpasses[3]->process(link);
link = delays[1]->process(link);
outR1 = link;
link = allpasses[4]->process(link);
link = delays[2]->process(link);
outL2 = link;
link = allpasses[5]->process(link);
link = delays[3]->process(link);
outR2 = link;
link = delays[4]->process(link);
// comb filter end point
combhist = combhist * currentSettings->damp1 + link * currentSettings->damp2;
*outLeft = (outL1 + outL2) * wetLevel;
*outRight = (outR1 + outR2) * wetLevel;
inLeft++;
inRight++;
outLeft++;
outRight++;
}
}

View File

@ -0,0 +1,86 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_A_REVERB_MODEL_H
#define MT32EMU_A_REVERB_MODEL_H
namespace MT32Emu {
struct AReverbSettings {
const Bit32u *allpassSizes;
const Bit32u *delaySizes;
const float *decayTimes;
const float *wetLevels;
float filtVal;
float damp1;
float damp2;
};
class RingBuffer {
protected:
float *buffer;
Bit32u size;
Bit32u index;
public:
RingBuffer(Bit32u size);
virtual ~RingBuffer();
float next();
bool isEmpty();
void mute();
};
class AllpassFilter : public RingBuffer {
public:
AllpassFilter(Bit32u size);
float process(float in);
};
class Delay : public RingBuffer {
public:
Delay(Bit32u size);
float process(float in);
};
class AReverbModel : public ReverbModel {
AllpassFilter **allpasses;
Delay **delays;
const AReverbSettings *currentSettings;
float decayTime;
float wetLevel;
float filterhist1, filterhist2;
float combhist;
void mute();
public:
AReverbModel(const AReverbSettings *newSettings);
~AReverbModel();
void open(unsigned int sampleRate);
void close();
void setParameters(Bit8u time, Bit8u level);
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
bool isActive() const;
static const AReverbSettings REVERB_MODE_0_SETTINGS;
static const AReverbSettings REVERB_MODE_1_SETTINGS;
static const AReverbSettings REVERB_MODE_2_SETTINGS;
};
// Default reverb settings for modes 0-2
}
#endif

View File

@ -0,0 +1,150 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//#include <cmath>
//#include <cstring>
#include "mt32emu.h"
#include "DelayReverb.h"
using namespace MT32Emu;
// CONFIRMED: The values below are found via analysis of digital samples. Checked with all time and level combinations.
// Obviously:
// rightDelay = (leftDelay - 2) * 2 + 2
// echoDelay = rightDelay - 1
// Leaving these separate in case it's useful for work on other reverb modes...
const Bit32u REVERB_TIMINGS[8][3]= {
// {leftDelay, rightDelay, feedbackDelay}
{402, 802, 801},
{626, 1250, 1249},
{962, 1922, 1921},
{1490, 2978, 2977},
{2258, 4514, 4513},
{3474, 6946, 6945},
{5282, 10562, 10561},
{8002, 16002, 16001}
};
const float REVERB_FADE[8] = {0.0f, -0.049400051f, -0.08220577f, -0.131861118f, -0.197344907f, -0.262956344f, -0.345162114f, -0.509508615f};
const float REVERB_FEEDBACK67 = -0.629960524947437f; // = -EXP2F(-2 / 3)
const float REVERB_FEEDBACK = -0.682034520443118f; // = -EXP2F(-53 / 96)
const float LPF_VALUE = 0.594603558f; // = EXP2F(-0.75f)
DelayReverb::DelayReverb() {
buf = NULL;
sampleRate = 0;
setParameters(0, 0);
}
DelayReverb::~DelayReverb() {
delete[] buf;
}
void DelayReverb::open(unsigned int newSampleRate) {
if (newSampleRate != sampleRate || buf == NULL) {
sampleRate = newSampleRate;
delete[] buf;
// If we ever need a speedup, set bufSize to EXP2F(ceil(log2(bufSize))) and use & instead of % to find buf indexes
bufSize = 16384 * sampleRate / 32000;
buf = new float[bufSize];
recalcParameters();
// mute buffer
bufIx = 0;
if (buf != NULL) {
for (unsigned int i = 0; i < bufSize; i++) {
buf[i] = 0.0f;
}
}
}
// FIXME: IIR filter value depends on sample rate as well
}
void DelayReverb::close() {
delete[] buf;
buf = NULL;
}
// This method will always trigger a flush of the buffer
void DelayReverb::setParameters(Bit8u newTime, Bit8u newLevel) {
time = newTime;
level = newLevel;
recalcParameters();
}
void DelayReverb::recalcParameters() {
// Number of samples between impulse and eventual appearance on the left channel
delayLeft = REVERB_TIMINGS[time][0] * sampleRate / 32000;
// Number of samples between impulse and eventual appearance on the right channel
delayRight = REVERB_TIMINGS[time][1] * sampleRate / 32000;
// Number of samples between a response and that response feeding back/echoing
delayFeedback = REVERB_TIMINGS[time][2] * sampleRate / 32000;
if (time < 6) {
feedback = REVERB_FEEDBACK;
} else {
feedback = REVERB_FEEDBACK67;
}
// Fading speed, i.e. amplitude ratio of neighbor responses
fade = REVERB_FADE[level];
}
void DelayReverb::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) {
if (buf == NULL) {
return;
}
for (unsigned int sampleIx = 0; sampleIx < numSamples; sampleIx++) {
// The ring buffer write index moves backwards; reads are all done with positive offsets.
Bit32u bufIxPrev = (bufIx + 1) % bufSize;
Bit32u bufIxLeft = (bufIx + delayLeft) % bufSize;
Bit32u bufIxRight = (bufIx + delayRight) % bufSize;
Bit32u bufIxFeedback = (bufIx + delayFeedback) % bufSize;
// Attenuated input samples and feedback response are directly added to the current ring buffer location
float sample = fade * (inLeft[sampleIx] + inRight[sampleIx]) + feedback * buf[bufIxFeedback];
// Single-pole IIR filter found on real devices
buf[bufIx] = buf[bufIxPrev] + (sample - buf[bufIxPrev]) * LPF_VALUE;
outLeft[sampleIx] = buf[bufIxLeft];
outRight[sampleIx] = buf[bufIxRight];
bufIx = (bufSize + bufIx - 1) % bufSize;
}
}
bool DelayReverb::isActive() const {
// Quick hack: Return true iff all samples in the left buffer are the same and
// all samples in the right buffers are the same (within the sample output threshold).
if (buf == NULL) {
return false;
}
float last = buf[0] * 8192.0f;
for (unsigned int i = 1; i < bufSize; i++) {
float s = (buf[i] * 8192.0f);
if (fabs(s - last) > 1.0f) {
return true;
}
}
return false;
}

View File

@ -0,0 +1,53 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_DELAYREVERB_H
#define MT32EMU_DELAYREVERB_H
namespace MT32Emu {
class DelayReverb : public ReverbModel {
private:
Bit8u time;
Bit8u level;
unsigned int sampleRate;
Bit32u bufSize;
Bit32u bufIx;
float *buf;
Bit32u delayLeft;
Bit32u delayRight;
Bit32u delayFeedback;
float fade;
float feedback;
void recalcParameters();
public:
DelayReverb();
~DelayReverb();
void open(unsigned int sampleRate);
void close();
void setParameters(Bit8u time, Bit8u level);
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
bool isActive() const;
};
}
#endif

View File

@ -0,0 +1,78 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mt32emu.h"
#include "FreeverbModel.h"
#include "freeverb.h"
using namespace MT32Emu;
FreeverbModel::FreeverbModel(float useScaleTuning, float useFiltVal, float useWet, Bit8u useRoom, float useDamp) {
freeverb = NULL;
scaleTuning = useScaleTuning;
filtVal = useFiltVal;
wet = useWet;
room = useRoom;
damp = useDamp;
}
FreeverbModel::~FreeverbModel() {
delete freeverb;
}
void FreeverbModel::open(unsigned int /*sampleRate*/) {
// FIXME: scaleTuning must be multiplied by sample rate to 32000Hz ratio
// IIR filter values depend on sample rate as well
if (freeverb == NULL) {
freeverb = new revmodel(scaleTuning);
}
freeverb->mute();
// entrance Lowpass filter factor
freeverb->setfiltval(filtVal);
// decay speed of high frequencies in the wet signal
freeverb->setdamp(damp);
}
void FreeverbModel::close() {
delete freeverb;
freeverb = NULL;
}
void FreeverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) {
freeverb->process(inLeft, inRight, outLeft, outRight, numSamples);
}
void FreeverbModel::setParameters(Bit8u time, Bit8u level) {
// wet signal level
// FIXME: need to implement some sort of reverb level ramping
freeverb->setwet((float)level / 7.0f * wet);
// wet signal decay speed
static float roomTable[] = {
0.25f, 0.37f, 0.54f, 0.71f, 0.78f, 0.86f, 0.93f, 1.00f,
-1.00f, -0.50f, 0.00f, 0.30f, 0.51f, 0.64f, 0.77f, 0.90f,
0.50f, 0.57f, 0.70f, 0.77f, 0.85f, 0.93f, 0.96f, 1.01f};
freeverb->setroomsize(roomTable[8 * room + time]);
}
bool FreeverbModel::isActive() const {
// FIXME: Not bothering to do this properly since we'll be replacing Freeverb soon...
return false;
}

View File

@ -0,0 +1,44 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_FREEVERB_MODEL_H
#define MT32EMU_FREEVERB_MODEL_H
class revmodel;
namespace MT32Emu {
class FreeverbModel : public ReverbModel {
revmodel *freeverb;
float scaleTuning;
float filtVal;
float wet;
Bit8u room;
float damp;
public:
FreeverbModel(float useScaleTuning, float useFiltVal, float useWet, Bit8u useRoom, float useDamp);
~FreeverbModel();
void open(unsigned int sampleRate);
void close();
void setParameters(Bit8u time, Bit8u level);
void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples);
bool isActive() const;
};
}
#endif

View File

@ -0,0 +1,150 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
Some notes on this class:
This emulates the LA-32's implementation of "ramps". A ramp in this context is a smooth transition from one value to another, handled entirely within the LA-32.
The LA-32 provides this feature for amplitude and filter cutoff values.
The 8095 starts ramps on the LA-32 by setting two values in memory-mapped registers:
(1) The target value (between 0 and 255) for the ramp to end on. This is represented by the "target" argument to startRamp().
(2) The speed at which that value should be approached. This is represented by the "increment" argument to startRamp().
Once the ramp target value has been hit, the LA-32 raises an interrupt.
Note that the starting point of the ramp is whatever internal value the LA-32 had when the registers were set. This is usually the end point of a previously completed ramp.
Our handling of the "target" and "increment" values is based on sample analysis and a little guesswork.
Here's what we're pretty confident about:
- The most significant bit of "increment" indicates the direction that the LA32's current internal value ("current" in our emulation) should change in.
Set means downward, clear means upward.
- The lower 7 bits of "increment" indicate how quickly "current" should be changed.
- If "increment" is 0, no change to "current" is made and no interrupt is raised. [SEMI-CONFIRMED by sample analysis]
- Otherwise, if the MSb is set:
- If "current" already corresponds to a value <= "target", "current" is set immediately to the equivalent of "target" and an interrupt is raised.
- Otherwise, "current" is gradually reduced (at a rate determined by the lower 7 bits of "increment"), and once it reaches the equivalent of "target" an interrupt is raised.
- Otherwise (the MSb is unset):
- If "current" already corresponds to a value >= "target", "current" is set immediately to the equivalent of "target" and an interrupt is raised.
- Otherwise, "current" is gradually increased (at a rate determined by the lower 7 bits of "increment"), and once it reaches the equivalent of "target" an interrupt is raised.
We haven't fully explored:
- Values when ramping between levels (though this is probably correct).
- Transition timing (may not be 100% accurate, especially for very fast ramps).
*/
//#include <cmath>
#include "mt32emu.h"
#include "LA32Ramp.h"
#include "mmath.h"
namespace MT32Emu {
// SEMI-CONFIRMED from sample analysis.
const int TARGET_MULT = 0x40000;
const unsigned int MAX_CURRENT = 0xFF * TARGET_MULT;
// We simulate the delay in handling "target was reached" interrupts by waiting
// this many samples before setting interruptRaised.
// FIXME: This should vary with the sample rate, but doesn't.
// SEMI-CONFIRMED: Since this involves asynchronous activity between the LA32
// and the 8095, a good value is hard to pin down.
// This one matches observed behaviour on a few digital captures I had handy,
// and should be double-checked. We may also need a more sophisticated delay
// scheme eventually.
const int INTERRUPT_TIME = 7;
LA32Ramp::LA32Ramp() :
current(0),
largeTarget(0),
largeIncrement(0),
interruptCountdown(0),
interruptRaised(false) {
}
void LA32Ramp::startRamp(Bit8u target, Bit8u increment) {
// CONFIRMED: From sample analysis, this appears to be very accurate.
// FIXME: We could use a table for this in future
if (increment == 0) {
largeIncrement = 0;
} else {
largeIncrement = (unsigned int)(EXP2F(((increment & 0x7F) + 24) / 8.0f) + 0.125f);
}
descending = (increment & 0x80) != 0;
if (descending) {
// CONFIRMED: From sample analysis, descending increments are slightly faster
largeIncrement++;
}
largeTarget = target * TARGET_MULT;
interruptCountdown = 0;
interruptRaised = false;
}
Bit32u LA32Ramp::nextValue() {
if (interruptCountdown > 0) {
if (--interruptCountdown == 0) {
interruptRaised = true;
}
} else if (largeIncrement != 0) {
// CONFIRMED from sample analysis: When increment is 0, the LA32 does *not* change the current value at all (and of course doesn't fire an interrupt).
if (descending) {
// Lowering current value
if (largeIncrement > current) {
current = largeTarget;
interruptCountdown = INTERRUPT_TIME;
} else {
current -= largeIncrement;
if (current <= largeTarget) {
current = largeTarget;
interruptCountdown = INTERRUPT_TIME;
}
}
} else {
// Raising current value
if (MAX_CURRENT - current < largeIncrement) {
current = largeTarget;
interruptCountdown = INTERRUPT_TIME;
} else {
current += largeIncrement;
if (current >= largeTarget) {
current = largeTarget;
interruptCountdown = INTERRUPT_TIME;
}
}
}
}
return current;
}
bool LA32Ramp::checkInterrupt() {
bool wasRaised = interruptRaised;
interruptRaised = false;
return wasRaised;
}
void LA32Ramp::reset() {
current = 0;
largeTarget = 0;
largeIncrement = 0;
descending = false;
interruptCountdown = 0;
interruptRaised = false;
}
}

View File

@ -0,0 +1,43 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_LA32RAMP_H
#define MT32EMU_LA32RAMP_H
namespace MT32Emu {
class LA32Ramp {
private:
Bit32u current;
unsigned int largeTarget;
unsigned int largeIncrement;
bool descending;
int interruptCountdown;
bool interruptRaised;
public:
LA32Ramp();
void startRamp(Bit8u target, Bit8u increment);
Bit32u nextValue();
bool checkInterrupt();
void reset();
};
}
#endif /* TVA_H_ */

View File

@ -0,0 +1,622 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//#include <cstdio>
//#include <cstring>
#include "mt32emu.h"
#include "PartialManager.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
};
RhythmPart::RhythmPart(Synth *useSynth, unsigned int usePartNum): Part(useSynth, usePartNum) {
strcpy(name, "Rhythm");
rhythmTemp = &synth->mt32ram.rhythmTemp[0];
refresh();
}
Part::Part(Synth *useSynth, unsigned int usePartNum) {
synth = useSynth;
partNum = usePartNum;
patchCache[0].dirty = true;
holdpedal = false;
patchTemp = &synth->mt32ram.patchTemp[partNum];
if (usePartNum == 8) {
// Nasty hack for rhythm
timbreTemp = NULL;
} else {
sprintf(name, "Part %d", partNum + 1);
timbreTemp = &synth->mt32ram.timbreTemp[partNum];
}
currentInstr[0] = 0;
currentInstr[10] = 0;
modulation = 0;
expression = 100;
pitchBend = 0;
activePartialCount = 0;
memset(patchCache, 0, sizeof(patchCache));
for (int i = 0; i < MT32EMU_MAX_POLY; i++) {
freePolys.push_front(new Poly(this));
}
}
Part::~Part() {
while (!activePolys.empty()) {
delete activePolys.front();
activePolys.pop_front();
}
while (!freePolys.empty()) {
delete freePolys.front();
freePolys.pop_front();
}
}
void Part::setDataEntryMSB(unsigned char midiDataEntryMSB) {
if (nrpn) {
// The last RPN-related control change was for an NRPN,
// which the real synths don't support.
return;
}
if (rpn != 0) {
// The RPN has been set to something other than 0,
// which is the only RPN that these synths support
return;
}
patchTemp->patch.benderRange = midiDataEntryMSB > 24 ? 24 : midiDataEntryMSB;
updatePitchBenderRange();
}
void Part::setNRPN() {
nrpn = true;
}
void Part::setRPNLSB(unsigned char midiRPNLSB) {
nrpn = false;
rpn = (rpn & 0xFF00) | midiRPNLSB;
}
void Part::setRPNMSB(unsigned char midiRPNMSB) {
nrpn = false;
rpn = (rpn & 0x00FF) | (midiRPNMSB << 8);
}
void Part::setHoldPedal(bool pressed) {
if (holdpedal && !pressed) {
holdpedal = false;
stopPedalHold();
} else {
holdpedal = pressed;
}
}
Bit32s Part::getPitchBend() const {
return pitchBend;
}
void Part::setBend(unsigned int midiBend) {
// CONFIRMED:
pitchBend = (((signed)midiBend - 8192) * pitchBenderRange) >> 14; // PORTABILITY NOTE: Assumes arithmetic shift
}
Bit8u Part::getModulation() const {
return modulation;
}
void Part::setModulation(unsigned int midiModulation) {
modulation = (Bit8u)midiModulation;
}
void Part::resetAllControllers() {
modulation = 0;
expression = 100;
pitchBend = 0;
setHoldPedal(false);
}
void Part::reset() {
resetAllControllers();
allSoundOff();
rpn = 0xFFFF;
}
void RhythmPart::refresh() {
// (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;
}
PatchCache *cache = drumCache[drumNum];
backupCacheToPartials(cache);
for (int t = 0; t < 4; t++) {
// Common parameters, stored redundantly
cache[t].dirty = true;
cache[t].reverb = rhythmTemp[drumNum].reverbSwitch > 0;
}
}
updatePitchBenderRange();
}
void Part::refresh() {
backupCacheToPartials(patchCache);
for (int t = 0; t < 4; t++) {
// Common parameters, stored redundantly
patchCache[t].dirty = true;
patchCache[t].reverb = patchTemp->patch.reverbSwitch > 0;
}
memcpy(currentInstr, timbreTemp->common.name, 10);
updatePitchBenderRange();
}
const char *Part::getCurrentInstr() const {
return &currentInstr[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;
}
}
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) {
#if MT32EMU_MONITOR_MIDI > 0
synth->printDebug("%s: Attempt to set program (%d) on rhythm is invalid", name, patchNum);
#else
patchNum = 0; // Just to avoid unused variable warning
#endif
}
void Part::setProgram(unsigned int patchNum) {
setPatch(&synth->mt32ram.patches[patchNum]);
holdpedal = false;
allSoundOff();
setTimbre(&synth->mt32ram.timbres[getAbsTimbreNum()].timbre);
refresh();
}
void Part::updatePitchBenderRange() {
pitchBenderRange = patchTemp->patch.benderRange * 683;
}
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 (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
(*polyIt)->backupCacheToPartials(cache);
}
}
void Part::cacheTimbre(PatchCache cache[4], const TimbreParam *timbre) {
backupCacheToPartials(cache);
int partialCount = 0;
for (int t = 0; t < 4; t++) {
if (((timbre->common.partialMute >> t) & 0x1) == 1) {
cache[t].playPartial = true;
partialCount++;
} else {
cache[t].playPartial = false;
continue;
}
// Calculate and cache common parameters
cache[t].srcPartial = timbre->partial[t];
cache[t].pcm = timbre->partial[t].wg.pcmWave;
switch (t) {
case 0:
cache[t].PCMPartial = (PartialStruct[(int)timbre->common.partialStructure12] & 0x2) ? true : false;
cache[t].structureMix = PartialMixStruct[(int)timbre->common.partialStructure12];
cache[t].structurePosition = 0;
cache[t].structurePair = 1;
break;
case 1:
cache[t].PCMPartial = (PartialStruct[(int)timbre->common.partialStructure12] & 0x1) ? true : false;
cache[t].structureMix = PartialMixStruct[(int)timbre->common.partialStructure12];
cache[t].structurePosition = 1;
cache[t].structurePair = 0;
break;
case 2:
cache[t].PCMPartial = (PartialStruct[(int)timbre->common.partialStructure34] & 0x2) ? true : false;
cache[t].structureMix = PartialMixStruct[(int)timbre->common.partialStructure34];
cache[t].structurePosition = 0;
cache[t].structurePair = 3;
break;
case 3:
cache[t].PCMPartial = (PartialStruct[(int)timbre->common.partialStructure34] & 0x1) ? true : false;
cache[t].structureMix = PartialMixStruct[(int)timbre->common.partialStructure34];
cache[t].structurePosition = 1;
cache[t].structurePair = 2;
break;
default:
break;
}
cache[t].partialParam = &timbre->partial[t];
cache[t].waveform = timbre->partial[t].wg.waveform;
}
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 > 0
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::setVolume(unsigned int midiVolume) {
// CONFIRMED: This calculation matches the table used in the control ROM
patchTemp->outputLevel = (Bit8u)(midiVolume * 100 / 127);
//synth->printDebug("%s (%s): Set volume to %d", name, currentInstr, midiVolume);
}
Bit8u Part::getVolume() const {
return patchTemp->outputLevel;
}
Bit8u Part::getExpression() const {
return expression;
}
void Part::setExpression(unsigned int midiExpression) {
// CONFIRMED: This calculation matches the table used in the control ROM
expression = (Bit8u)(midiExpression * 100 / 127);
}
void RhythmPart::setPan(unsigned int midiPan) {
// CONFIRMED: This does change patchTemp, but has no actual effect on playback.
#if MT32EMU_MONITOR_MIDI > 0
synth->printDebug("%s: Pointlessly setting pan (%d) on rhythm part", name, midiPan);
#endif
Part::setPan(midiPan);
}
void Part::setPan(unsigned int midiPan) {
// NOTE: Panning is inverted compared to GM.
// CM-32L: Divide by 8.5
patchTemp->panpot = (Bit8u)((midiPan << 3) / 68);
// FIXME: MT-32: Divide by 9
//patchTemp->panpot = (Bit8u)(midiPan / 9);
//synth->printDebug("%s (%s): Set pan to %d", name, currentInstr, panpot);
}
/**
* Applies key shift to a MIDI key and converts it into an internal key value in the range 12-108.
*/
unsigned int Part::midiKeyToKey(unsigned int midiKey) {
int key = midiKey + patchTemp->patch.keyShift;
if (key < 36) {
// After keyShift is applied, key < 36, so move up by octaves
while (key < 36) {
key += 12;
}
} else if (key > 132) {
// After keyShift is applied, key > 132, so move down by octaves
while (key > 132) {
key -= 12;
}
}
key -= 24;
return key;
}
void RhythmPart::noteOn(unsigned int midiKey, unsigned int velocity) {
if (midiKey < 24 || midiKey > 108) { /*> 87 on MT-32)*/
synth->printDebug("%s: Attempted to play invalid key %d (velocity %d)", name, midiKey, velocity);
return;
}
unsigned int key = midiKey;
unsigned 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 (velocity %d)", name, midiKey, velocity);
return;
}
// CONFIRMED: Two special cases described by Mok
if (drumTimbreNum == 64 + 6) {
noteOff(0);
key = 1;
} else if (drumTimbreNum == 64 + 7) {
// This noteOff(0) is not performed on MT-32, only LAPC-I
noteOff(0);
key = 0;
}
int absTimbreNum = drumTimbreNum + 128;
TimbreParam *timbre = &synth->mt32ram.timbres[absTimbreNum].timbre;
memcpy(currentInstr, timbre->common.name, 10);
if (drumCache[drumNum][0].dirty) {
cacheTimbre(drumCache[drumNum], timbre);
}
#if MT32EMU_MONITOR_INSTRUMENTS > 0
synth->printDebug("%s (%s): Start poly (drum %d, timbre %d): midiKey %u, key %u, velo %u, mod %u, exp %u, bend %u", name, currentInstr, drumNum, absTimbreNum, midiKey, key, velocity, modulation, expression, pitchBend);
#if MT32EMU_MONITOR_INSTRUMENTS > 1
// According to info from Mok, keyShift does not appear to affect anything on rhythm part on LAPC-I, but may do on MT-32 - needs investigation
synth->printDebug(" Patch: (timbreGroup %u), (timbreNum %u), (keyShift %u), fineTune %u, benderRange %u, assignMode %u, (reverbSwitch %u)", patchTemp->patch.timbreGroup, patchTemp->patch.timbreNum, patchTemp->patch.keyShift, patchTemp->patch.fineTune, patchTemp->patch.benderRange, patchTemp->patch.assignMode, patchTemp->patch.reverbSwitch);
synth->printDebug(" PatchTemp: outputLevel %u, (panpot %u)", patchTemp->outputLevel, patchTemp->panpot);
synth->printDebug(" RhythmTemp: timbre %u, outputLevel %u, panpot %u, reverbSwitch %u", rhythmTemp[drumNum].timbre, rhythmTemp[drumNum].outputLevel, rhythmTemp[drumNum].panpot, rhythmTemp[drumNum].reverbSwitch);
#endif
#endif
playPoly(drumCache[drumNum], &rhythmTemp[drumNum], midiKey, key, velocity);
}
void Part::noteOn(unsigned int midiKey, unsigned int velocity) {
unsigned int key = midiKeyToKey(midiKey);
if (patchCache[0].dirty) {
cacheTimbre(patchCache, timbreTemp);
}
#if MT32EMU_MONITOR_INSTRUMENTS > 0
synth->printDebug("%s (%s): Start poly: midiKey %u, key %u, velo %u, mod %u, exp %u, bend %u", name, currentInstr, midiKey, key, velocity, modulation, expression, pitchBend);
#if MT32EMU_MONITOR_INSTRUMENTS > 1
synth->printDebug(" Patch: timbreGroup %u, timbreNum %u, keyShift %u, fineTune %u, benderRange %u, assignMode %u, reverbSwitch %u", patchTemp->patch.timbreGroup, patchTemp->patch.timbreNum, patchTemp->patch.keyShift, patchTemp->patch.fineTune, patchTemp->patch.benderRange, patchTemp->patch.assignMode, patchTemp->patch.reverbSwitch);
synth->printDebug(" PatchTemp: outputLevel %u, panpot %u", patchTemp->outputLevel, patchTemp->panpot);
#endif
#endif
playPoly(patchCache, NULL, midiKey, key, velocity);
}
void Part::abortPoly(Poly *poly) {
if (poly->startAbort()) {
while (poly->isActive()) {
if (!synth->prerender()) {
synth->printDebug("%s (%s): Ran out of prerender space to abort poly gracefully", name, currentInstr);
poly->terminate();
break;
}
}
}
}
bool Part::abortFirstPoly(unsigned int key) {
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
Poly *poly = *polyIt;
if (poly->getKey() == key) {
abortPoly(poly);
return true;
}
}
return false;
}
bool Part::abortFirstPoly(PolyState polyState) {
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
Poly *poly = *polyIt;
if (poly->getState() == polyState) {
abortPoly(poly);
return true;
}
}
return false;
}
bool Part::abortFirstPolyPreferHeld() {
if (abortFirstPoly(POLY_Held)) {
return true;
}
return abortFirstPoly();
}
bool Part::abortFirstPoly() {
if (activePolys.empty()) {
return false;
}
abortPoly(activePolys.front());
return true;
}
void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhythmTemp, unsigned int midiKey, unsigned int key, unsigned int velocity) {
// CONFIRMED: Even in single-assign mode, we don't abort playing polys if the timbre to play is completely muted.
unsigned int needPartials = cache[0].partialCount;
if (needPartials == 0) {
synth->printDebug("%s (%s): Completely muted instrument", name, currentInstr);
return;
}
if ((patchTemp->patch.assignMode & 2) == 0) {
// Single-assign mode
abortFirstPoly(key);
}
if (!synth->partialManager->freePartials(needPartials, partNum)) {
#if MT32EMU_MONITOR_PARTIALS > 0
synth->printDebug("%s (%s): Insufficient free partials to play key %d (velocity %d); needed=%d, free=%d, assignMode=%d", name, currentInstr, midiKey, velocity, needPartials, synth->partialManager->getFreePartialCount(), patchTemp->patch.assignMode);
synth->printPartialUsage();
#endif
return;
}
if (freePolys.empty()) {
synth->printDebug("%s (%s): No free poly to play key %d (velocity %d)", name, currentInstr, midiKey, velocity);
return;
}
Poly *poly = freePolys.front();
freePolys.pop_front();
if (patchTemp->patch.assignMode & 1) {
// Priority to data first received
activePolys.push_front(poly);
} else {
activePolys.push_back(poly);
}
Partial *partials[4];
for (int x = 0; x < 4; x++) {
if (cache[x].playPartial) {
partials[x] = synth->partialManager->allocPartial(partNum);
activePartialCount++;
} else {
partials[x] = NULL;
}
}
poly->reset(key, velocity, cache[0].sustain, partials);
for (int x = 0; x < 4; x++) {
if (partials[x] != NULL) {
#if MT32EMU_MONITOR_PARTIALS > 2
synth->printDebug("%s (%s): Allocated partial %d", name, currentInstr, partials[x]->debugGetPartialNum());
#endif
partials[x]->startPartial(this, poly, &cache[x], rhythmTemp, partials[cache[x].structurePair]);
}
}
#if MT32EMU_MONITOR_PARTIALS > 1
synth->printPartialUsage();
#endif
}
void Part::allNotesOff() {
// The MIDI specification states - and Mok confirms - that all notes off (0x7B)
// should treat the hold pedal as usual.
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
Poly *poly = *polyIt;
// FIXME: This has special handling of key 0 in NoteOff that Mok has not yet confirmed
// applies to AllNotesOff.
poly->noteOff(holdpedal);
}
}
void Part::allSoundOff() {
// MIDI "All sound off" (0x78) should release notes immediately regardless of the hold pedal.
// This controller is not actually implemented by the synths, though (according to the docs and Mok) -
// we're only using this method internally.
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
Poly *poly = *polyIt;
poly->startDecay();
}
}
void Part::stopPedalHold() {
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
Poly *poly = *polyIt;
poly->stopPedalHold();
}
}
void RhythmPart::noteOff(unsigned int midiKey) {
stopNote(midiKey);
}
void Part::noteOff(unsigned int midiKey) {
stopNote(midiKeyToKey(midiKey));
}
void Part::stopNote(unsigned int key) {
#if MT32EMU_MONITOR_INSTRUMENTS > 0
synth->printDebug("%s (%s): stopping key %d", name, currentInstr, key);
#endif
for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
Poly *poly = *polyIt;
// Generally, non-sustaining instruments ignore note off. They die away eventually anyway.
// Key 0 (only used by special cases on rhythm part) reacts to note off even if non-sustaining or pedal held.
if (poly->getKey() == key && (poly->canSustain() || key == 0)) {
if (poly->noteOff(holdpedal && key != 0)) {
break;
}
}
}
}
const MemParams::PatchTemp *Part::getPatchTemp() const {
return patchTemp;
}
unsigned int Part::getActivePartialCount() const {
return activePartialCount;
}
unsigned int Part::getActiveNonReleasingPartialCount() const {
unsigned int activeNonReleasingPartialCount = 0;
for (Common::List<Poly *>::const_iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) {
Poly *poly = *polyIt;
if (poly->getState() != POLY_Releasing) {
activeNonReleasingPartialCount += poly->getActivePartialCount();
}
}
return activeNonReleasingPartialCount;
}
void Part::partialDeactivated(Poly *poly) {
activePartialCount--;
if (!poly->isActive()) {
activePolys.remove(poly);
freePolys.push_front(poly);
}
}
}

133
audio/softsynth/mt32/Part.h Normal file
View File

@ -0,0 +1,133 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_PART_H
#define MT32EMU_PART_H
#include <common/list.h>
namespace MT32Emu {
class PartialManager;
class Synth;
class Part {
private:
// Direct pointer to sysex-addressable memory dedicated to this part (valid for parts 1-8, NULL for rhythm)
TimbreParam *timbreTemp;
// 0=Part 1, .. 7=Part 8, 8=Rhythm
unsigned int partNum;
bool holdpedal;
unsigned int activePartialCount;
PatchCache patchCache[4];
Common::List<Poly*> freePolys;
Common::List<Poly*> activePolys;
void setPatch(const PatchParam *patch);
unsigned int midiKeyToKey(unsigned int midiKey);
void abortPoly(Poly *poly);
bool abortFirstPoly(unsigned int key);
protected:
Synth *synth;
// Direct pointer into sysex-addressable memory
MemParams::PatchTemp *patchTemp;
char name[8]; // "Part 1".."Part 8", "Rhythm"
char currentInstr[11];
Bit8u modulation;
Bit8u expression;
Bit32s pitchBend;
bool nrpn;
Bit16u rpn;
Bit16u pitchBenderRange; // (patchTemp->patch.benderRange * 683) at the time of the last MIDI program change or MIDI data entry.
void backupCacheToPartials(PatchCache cache[4]);
void cacheTimbre(PatchCache cache[4], const TimbreParam *timbre);
void playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhythmTemp, unsigned int midiKey, unsigned int key, unsigned int velocity);
void stopNote(unsigned int key);
const char *getName() const;
public:
Part(Synth *synth, unsigned int usePartNum);
virtual ~Part();
void reset();
void setDataEntryMSB(unsigned char midiDataEntryMSB);
void setNRPN();
void setRPNLSB(unsigned char midiRPNLSB);
void setRPNMSB(unsigned char midiRPNMSB);
void resetAllControllers();
virtual void noteOn(unsigned int midiKey, unsigned int velocity);
virtual void noteOff(unsigned int midiKey);
void allNotesOff();
void allSoundOff();
Bit8u getVolume() const; // Internal volume, 0-100, exposed for use by ExternalInterface
void setVolume(unsigned int midiVolume);
Bit8u getModulation() const;
void setModulation(unsigned int midiModulation);
Bit8u getExpression() const;
void setExpression(unsigned int midiExpression);
virtual void setPan(unsigned int midiPan);
Bit32s getPitchBend() const;
void setBend(unsigned int midiBend);
virtual void setProgram(unsigned int midiProgram);
void setHoldPedal(bool pedalval);
void stopPedalHold();
void updatePitchBenderRange();
virtual void refresh();
virtual void refreshTimbre(unsigned int absTimbreNum);
virtual void setTimbre(TimbreParam *timbre);
virtual unsigned int getAbsTimbreNum() const;
const char *getCurrentInstr() const;
unsigned int getActivePartialCount() const;
unsigned int getActiveNonReleasingPartialCount() const;
const MemParams::PatchTemp *getPatchTemp() const;
// This should only be called by Poly
void partialDeactivated(Poly *poly);
// These are rather specialised, and should probably only be used by PartialManager
bool abortFirstPoly(PolyState polyState);
// Abort the first poly in PolyState_HELD, or if none exists, the first active poly in any state.
bool abortFirstPolyPreferHeld();
bool abortFirstPoly();
};
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];
public:
RhythmPart(Synth *synth, unsigned int usePartNum);
void refresh();
void refreshTimbre(unsigned int timbreNum);
void setTimbre(TimbreParam *timbre);
void noteOn(unsigned int key, unsigned int velocity);
void noteOff(unsigned int midiKey);
unsigned int getAbsTimbreNum() const;
void setPan(unsigned int midiPan);
void setProgram(unsigned int patchNum);
};
}
#endif

View File

@ -0,0 +1,557 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//#include <cmath>
//#include <cstdlib>
//#include <cstring>
#include "mt32emu.h"
#include "mmath.h"
using namespace MT32Emu;
#ifdef INACCURATE_SMOOTH_PAN
// Mok wanted an option for smoother panning, and we love Mok.
static const float PAN_NUMERATOR_NORMAL[] = {0.0f, 0.5f, 1.0f, 1.5f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.5f, 5.0f, 5.5f, 6.0f, 6.5f, 7.0f};
#else
// CONFIRMED by Mok: These NUMERATOR values (as bytes, not floats, obviously) are sent exactly like this to the LA32.
static const float PAN_NUMERATOR_NORMAL[] = {0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f, 4.0f, 4.0f, 5.0f, 5.0f, 6.0f, 6.0f, 7.0f};
#endif
static const float PAN_NUMERATOR_MASTER[] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f};
static const float PAN_NUMERATOR_SLAVE[] = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f};
Partial::Partial(Synth *useSynth, int useDebugPartialNum) :
synth(useSynth), debugPartialNum(useDebugPartialNum), sampleNum(0), tva(new TVA(this, &ampRamp)), tvp(new TVP(this)), tvf(new TVF(this, &cutoffModifierRamp)) {
ownerPart = -1;
poly = NULL;
pair = NULL;
}
Partial::~Partial() {
delete tva;
delete tvp;
delete tvf;
}
// Only used for debugging purposes
int Partial::debugGetPartialNum() const {
return debugPartialNum;
}
// Only used for debugging purposes
unsigned long Partial::debugGetSampleNum() const {
return sampleNum;
}
int Partial::getOwnerPart() const {
return ownerPart;
}
bool Partial::isActive() const {
return ownerPart > -1;
}
const Poly *Partial::getPoly() const {
return poly;
}
void Partial::activate(int part) {
// This just marks the partial as being assigned to a part
ownerPart = part;
}
void Partial::deactivate() {
if (!isActive()) {
return;
}
ownerPart = -1;
if (poly != NULL) {
poly->partialDeactivated(this);
if (pair != NULL) {
pair->pair = NULL;
}
}
#if MT32EMU_MONITOR_PARTIALS > 2
synth->printDebug("[+%lu] [Partial %d] Deactivated", sampleNum, debugPartialNum);
synth->printPartialUsage(sampleNum);
#endif
}
// DEPRECATED: This should probably go away eventually, it's currently only used as a kludge to protect our old assumptions that
// rhythm part notes were always played as key MIDDLEC.
int Partial::getKey() const {
if (poly == NULL) {
return -1;
} else if (ownerPart == 8) {
// FIXME: Hack, should go away after new pitch stuff is committed (and possibly some TVF changes)
return MIDDLEC;
} else {
return poly->getKey();
}
}
void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *usePatchCache, const MemParams::RhythmTemp *rhythmTemp, Partial *pairPartial) {
if (usePoly == NULL || usePatchCache == NULL) {
synth->printDebug("[Partial %d] *** Error: Starting partial for owner %d, usePoly=%s, usePatchCache=%s", debugPartialNum, ownerPart, usePoly == NULL ? "*** NULL ***" : "OK", usePatchCache == NULL ? "*** NULL ***" : "OK");
return;
}
patchCache = usePatchCache;
poly = usePoly;
mixType = patchCache->structureMix;
structurePosition = patchCache->structurePosition;
Bit8u panSetting = rhythmTemp != NULL ? rhythmTemp->panpot : part->getPatchTemp()->panpot;
float panVal;
if (mixType == 3) {
if (structurePosition == 0) {
panVal = PAN_NUMERATOR_MASTER[panSetting];
} else {
panVal = PAN_NUMERATOR_SLAVE[panSetting];
}
// Do a normal mix independent of any pair partial.
mixType = 0;
pairPartial = NULL;
} else {
panVal = PAN_NUMERATOR_NORMAL[panSetting];
}
// FIXME: Sample analysis suggests that the use of panVal is linear, but there are some some quirks that still need to be resolved.
stereoVolume.leftVol = panVal / 7.0f;
stereoVolume.rightVol = 1.0f - stereoVolume.leftVol;
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;
wavePos = 0.0f;
lastFreq = 0.0;
}
// CONFIRMED: pulseWidthVal calculation is based on information from Mok
pulseWidthVal = (poly->getVelocity() - 64) * (patchCache->srcPartial.wg.pulseWidthVeloSensitivity - 7) + synth->tables.pulseWidth100To255[patchCache->srcPartial.wg.pulseWidth];
if (pulseWidthVal < 0) {
pulseWidthVal = 0;
} else if (pulseWidthVal > 255) {
pulseWidthVal = 255;
}
pcmPosition = 0.0f;
pair = pairPartial;
alreadyOutputed = false;
tva->reset(part, patchCache->partialParam, rhythmTemp);
tvp->reset(part, patchCache->partialParam);
tvf->reset(patchCache->partialParam, tvp->getBasePitch());
}
float Partial::getPCMSample(unsigned int position) {
if (position >= pcmWave->len) {
if (!pcmWave->loop) {
return 0;
}
position = position % pcmWave->len;
}
return synth->pcmROMData[pcmWave->addr + position];
}
unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) {
if (!isActive() || alreadyOutputed) {
return 0;
}
if (poly == NULL) {
synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::generateSamples()!", debugPartialNum);
return 0;
}
alreadyOutputed = true;
// Generate samples
for (sampleNum = 0; sampleNum < length; sampleNum++) {
float sample = 0;
Bit32u ampRampVal = ampRamp.nextValue();
if (ampRamp.checkInterrupt()) {
tva->handleInterrupt();
}
if (!tva->isPlaying()) {
deactivate();
break;
}
// SEMI-CONFIRMED: From sample analysis:
// (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc.
// This gives results within +/- 2 at the output (before any DAC bitshifting)
// when sustaining at levels 156 - 255 with no modifiers.
// (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255.
// This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces
// positive amps, so negative still needs to be explored, as well as lower levels.
//
// Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing.
float amp = EXP2F((32772 - ampRampVal / 2048) / -2048.0f);
Bit16u pitch = tvp->nextPitch();
float freq = synth->tables.pitchToFreq[pitch];
if (patchCache->PCMPartial) {
// Render PCM waveform
int len = pcmWave->len;
int intPCMPosition = (int)pcmPosition;
if (intPCMPosition >= len && !pcmWave->loop) {
// We're now past the end of a non-looping PCM waveform so it's time to die.
deactivate();
break;
}
Bit32u pcmAddr = pcmWave->addr;
float positionDelta = freq * 2048.0f / synth->myProp.sampleRate;
// Linear interpolation
float firstSample = synth->pcmROMData[pcmAddr + intPCMPosition];
float nextSample = getPCMSample(intPCMPosition + 1);
sample = firstSample + (nextSample - firstSample) * (pcmPosition - intPCMPosition);
float newPCMPosition = pcmPosition + positionDelta;
if (pcmWave->loop) {
newPCMPosition = fmod(newPCMPosition, (float)pcmWave->len);
}
pcmPosition = newPCMPosition;
} else {
// Render synthesised waveform
wavePos *= lastFreq / freq;
lastFreq = freq;
Bit32u cutoffModifierRampVal = cutoffModifierRamp.nextValue();
if (cutoffModifierRamp.checkInterrupt()) {
tvf->handleInterrupt();
}
float cutoffModifier = cutoffModifierRampVal / 262144.0f;
// res corresponds to a value set in an LA32 register
Bit8u res = patchCache->srcPartial.tvf.resonance + 1;
// EXP2F(1.0f - (32 - res) / 4.0f);
float resAmp = synth->tables.resAmpMax[res];
// The cutoffModifier may not be supposed to be directly added to the cutoff -
// it may for example need to be multiplied in some way.
// The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4).
// More research is needed to be sure that this is correct, however.
float cutoffVal = tvf->getBaseCutoff() + cutoffModifier;
if (cutoffVal > 240.0f) {
cutoffVal = 240.0f;
}
// Wave length in samples
float waveLen = synth->myProp.sampleRate / freq;
// Init cosineLen
float cosineLen = 0.5f * waveLen;
if (cutoffVal > 128.0f) {
#if MT32EMU_ACCURATE_WG == 1
cosineLen *= EXP2F((cutoffVal - 128.0f) / -16.0f); // found from sample analysis
#else
cosineLen *= synth->tables.cutoffToCosineLen[Bit32u((cutoffVal - 128.0f) * 8.0f)];
#endif
}
// Start playing in center of first cosine segment
// relWavePos is shifted by a half of cosineLen
float relWavePos = wavePos + 0.5f * cosineLen;
if (relWavePos > waveLen) {
relWavePos -= waveLen;
}
float pulseLen = 0.5f;
if (pulseWidthVal > 128) {
pulseLen += synth->tables.pulseLenFactor[pulseWidthVal - 128];
}
pulseLen *= waveLen;
float lLen = pulseLen - cosineLen;
// Ignore pulsewidths too high for given freq
if (lLen < 0.0f) {
lLen = 0.0f;
}
// Ignore pulsewidths too high for given freq and cutoff
float hLen = waveLen - lLen - 2 * cosineLen;
if (hLen < 0.0f) {
hLen = 0.0f;
}
// Correct resAmp for cutoff in range 50..66
if ((cutoffVal >= 128.0f) && (cutoffVal < 144.0f)) {
#if MT32EMU_ACCURATE_WG == 1
resAmp *= sinf(FLOAT_PI * (cutoffVal - 128.0f) / 32.0f);
#else
resAmp *= synth->tables.sinf10[Bit32u(64 * (cutoffVal - 128.0f))];
#endif
}
// Produce filtered square wave with 2 cosine waves on slopes
// 1st cosine segment
if (relWavePos < cosineLen) {
#if MT32EMU_ACCURATE_WG == 1
sample = -cosf(FLOAT_PI * relWavePos / cosineLen);
#else
sample = -synth->tables.sinf10[Bit32u(2048.0f * relWavePos / cosineLen) + 1024];
#endif
} else
// high linear segment
if (relWavePos < (cosineLen + hLen)) {
sample = 1.f;
} else
// 2nd cosine segment
if (relWavePos < (2 * cosineLen + hLen)) {
#if MT32EMU_ACCURATE_WG == 1
sample = cosf(FLOAT_PI * (relWavePos - (cosineLen + hLen)) / cosineLen);
#else
sample = synth->tables.sinf10[Bit32u(2048.0f * (relWavePos - (cosineLen + hLen)) / cosineLen) + 1024];
#endif
} else {
// low linear segment
sample = -1.f;
}
if (cutoffVal < 128.0f) {
// Attenuate samples below cutoff 50
// Found by sample analysis
#if MT32EMU_ACCURATE_WG == 1
sample *= EXP2F(-0.125f * (128.0f - cutoffVal));
#else
sample *= synth->tables.cutoffToFilterAmp[Bit32u(cutoffVal * 8.0f)];
#endif
} else {
// Add resonance sine. Effective for cutoff > 50 only
float resSample = 1.0f;
// Now relWavePos counts from the middle of first cosine
relWavePos = wavePos;
// negative segments
if (!(relWavePos < (cosineLen + hLen))) {
resSample = -resSample;
relWavePos -= cosineLen + hLen;
}
// Resonance sine WG
#if MT32EMU_ACCURATE_WG == 1
resSample *= sinf(FLOAT_PI * relWavePos / cosineLen);
#else
resSample *= synth->tables.sinf10[Bit32u(2048.0f * relWavePos / cosineLen) & 4095];
#endif
// Resonance sine amp
float resAmpFade = EXP2F(-synth->tables.resAmpFadeFactor[res >> 2] * (relWavePos / cosineLen)); // seems to be exact
// Now relWavePos set negative to the left from center of any cosine
relWavePos = wavePos;
// negative segment
if (!(wavePos < (waveLen - 0.5f * cosineLen))) {
relWavePos -= waveLen;
} else
// positive segment
if (!(wavePos < (hLen + 0.5f * cosineLen))) {
relWavePos -= cosineLen + hLen;
}
// Fading to zero while within cosine segments to avoid jumps in the wave
// Sample analysis suggests that this window is very close to cosine
if (relWavePos < 0.5f * cosineLen) {
#if MT32EMU_ACCURATE_WG == 1
resAmpFade *= 0.5f * (1.0f - cosf(FLOAT_PI * relWavePos / (0.5f * cosineLen)));
#else
resAmpFade *= 0.5f * (1.0f + synth->tables.sinf10[Bit32s(2048.0f * relWavePos / (0.5f * cosineLen)) + 3072]);
#endif
}
sample += resSample * resAmp * resAmpFade;
}
// sawtooth waves
if ((patchCache->waveform & 1) != 0) {
#if MT32EMU_ACCURATE_WG == 1
sample *= cosf(FLOAT_2PI * wavePos / waveLen);
#else
sample *= synth->tables.sinf10[(Bit32u(4096.0f * wavePos / waveLen) & 4095) + 1024];
#endif
}
wavePos++;
// wavePos isn't supposed to be > waveLen
if (wavePos > waveLen) {
wavePos -= waveLen;
}
}
// Multiply sample with current TVA value
sample *= amp;
*partialBuf++ = sample;
}
unsigned long renderedSamples = sampleNum;
sampleNum = 0;
return renderedSamples;
}
float *Partial::mixBuffersRingMix(float *buf1, float *buf2, unsigned long len) {
if (buf1 == NULL) {
return NULL;
}
if (buf2 == NULL) {
return buf1;
}
while (len--) {
// FIXME: At this point we have no idea whether this is remotely correct...
*buf1 = *buf1 * *buf2 + *buf1;
buf1++;
buf2++;
}
return buf1;
}
float *Partial::mixBuffersRing(float *buf1, float *buf2, unsigned long len) {
if (buf1 == NULL) {
return NULL;
}
if (buf2 == NULL) {
return NULL;
}
while (len--) {
// FIXME: At this point we have no idea whether this is remotely correct...
*buf1 = *buf1 * *buf2;
buf1++;
buf2++;
}
return buf1;
}
bool Partial::hasRingModulatingSlave() const {
return pair != NULL && structurePosition == 0 && (mixType == 1 || mixType == 2);
}
bool Partial::isRingModulatingSlave() const {
return pair != NULL && structurePosition == 1 && (mixType == 1 || mixType == 2);
}
bool Partial::isPCM() const {
return pcmWave != NULL;
}
const ControlROMPCMStruct *Partial::getControlROMPCMStruct() const {
if (pcmWave != NULL) {
return pcmWave->controlROMPCMStruct;
}
return NULL;
}
Synth *Partial::getSynth() const {
return synth;
}
bool Partial::produceOutput(float *leftBuf, float *rightBuf, unsigned long length) {
if (!isActive() || alreadyOutputed || isRingModulatingSlave()) {
return false;
}
if (poly == NULL) {
synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::produceOutput()!", debugPartialNum);
return false;
}
float *partialBuf = &myBuffer[0];
unsigned long numGenerated = generateSamples(partialBuf, length);
if (mixType == 1 || mixType == 2) {
float *pairBuf;
unsigned long pairNumGenerated;
if (pair == NULL) {
pairBuf = NULL;
pairNumGenerated = 0;
} else {
pairBuf = &pair->myBuffer[0];
pairNumGenerated = pair->generateSamples(pairBuf, numGenerated);
// pair will have been set to NULL if it deactivated within generateSamples()
if (pair != NULL) {
if (!isActive()) {
pair->deactivate();
pair = NULL;
} else if (!pair->isActive()) {
pair = NULL;
}
}
}
if (pairNumGenerated > 0) {
if (mixType == 1) {
mixBuffersRingMix(partialBuf, pairBuf, pairNumGenerated);
} else {
mixBuffersRing(partialBuf, pairBuf, pairNumGenerated);
}
}
if (numGenerated > pairNumGenerated) {
if (mixType == 1) {
mixBuffersRingMix(partialBuf + pairNumGenerated, NULL, numGenerated - pairNumGenerated);
} else {
mixBuffersRing(partialBuf + pairNumGenerated, NULL, numGenerated - pairNumGenerated);
}
}
}
for (unsigned int i = 0; i < numGenerated; i++) {
*leftBuf++ = partialBuf[i] * stereoVolume.leftVol;
}
for (unsigned int i = 0; i < numGenerated; i++) {
*rightBuf++ = partialBuf[i] * stereoVolume.rightVol;
}
while (numGenerated < length) {
*leftBuf++ = 0.0f;
*rightBuf++ = 0.0f;
numGenerated++;
}
return true;
}
bool Partial::shouldReverb() {
if (!isActive()) {
return false;
}
return patchCache->reverb;
}
void Partial::startAbort() {
// This is called when the partial manager needs to terminate partials for re-use by a new Poly.
tva->startAbort();
}
void Partial::startDecayAll() {
tva->startDecay();
tvp->startDecay();
tvf->startDecay();
}

View File

@ -0,0 +1,119 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_PARTIAL_H
#define MT32EMU_PARTIAL_H
namespace MT32Emu {
class Synth;
class Part;
class TVA;
struct ControlROMPCMStruct;
struct StereoVolume {
float leftVol;
float rightVol;
};
// A partial represents one of up to four waveform generators currently playing within a poly.
class Partial {
private:
Synth *synth;
const int debugPartialNum; // Only used for debugging
// Number of the sample currently being rendered by generateSamples(), or 0 if no run is in progress
// This is only kept available for debugging purposes.
unsigned long sampleNum;
int ownerPart; // -1 if unassigned
int mixType;
int structurePosition; // 0 or 1 of a structure pair
StereoVolume stereoVolume;
// Distance in (possibly fractional) samples from the start of the current pulse
float wavePos;
float lastFreq;
float myBuffer[MAX_SAMPLES_PER_RUN];
// Only used for PCM partials
int pcmNum;
// FIXME: Give this a better name (e.g. pcmWaveInfo)
PCMWaveEntry *pcmWave;
// Final pulse width value, with velfollow applied, matching what is sent to the LA32.
// Range: 0-255
int pulseWidthVal;
float pcmPosition;
Poly *poly;
LA32Ramp ampRamp;
LA32Ramp cutoffModifierRamp;
float *mixBuffersRingMix(float *buf1, float *buf2, unsigned long len);
float *mixBuffersRing(float *buf1, float *buf2, unsigned long len);
float getPCMSample(unsigned int position);
public:
const PatchCache *patchCache;
TVA *tva;
TVP *tvp;
TVF *tvf;
PatchCache cachebackup;
Partial *pair;
bool alreadyOutputed;
Partial(Synth *synth, int debugPartialNum);
~Partial();
int debugGetPartialNum() const;
unsigned long debugGetSampleNum() const;
int getOwnerPart() const;
int getKey() const;
const Poly *getPoly() const;
bool isActive() const;
void activate(int part);
void deactivate(void);
void startPartial(const Part *part, Poly *usePoly, const PatchCache *useCache, const MemParams::RhythmTemp *rhythmTemp, Partial *pairPartial);
void startAbort();
void startDecayAll();
bool shouldReverb();
bool hasRingModulatingSlave() const;
bool isRingModulatingSlave() const;
bool isPCM() const;
const ControlROMPCMStruct *getControlROMPCMStruct() const;
Synth *getSynth() const;
// 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(float *leftBuf, float *rightBuf, unsigned long length);
// This function writes mono sample output to the provided buffer, and returns the number of samples written
unsigned long generateSamples(float *partialBuf, unsigned long length);
};
}
#endif

View File

@ -0,0 +1,250 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//#include <cstring>
#include "mt32emu.h"
#include "PartialManager.h"
using namespace MT32Emu;
PartialManager::PartialManager(Synth *useSynth, Part **useParts) {
synth = useSynth;
parts = useParts;
for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
partialTable[i] = new Partial(synth, i);
}
}
PartialManager::~PartialManager(void) {
for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
delete partialTable[i];
}
}
void PartialManager::clearAlreadyOutputed() {
for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
partialTable[i]->alreadyOutputed = false;
}
}
bool PartialManager::shouldReverb(int i) {
return partialTable[i]->shouldReverb();
}
bool PartialManager::produceOutput(int i, float *leftBuf, float *rightBuf, Bit32u bufferLength) {
return partialTable[i]->produceOutput(leftBuf, rightBuf, 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 <= 8; x++) {
numReservedPartialsForPart[x] = rset[x];
pr += rset[x];
}
return pr;
}
Partial *PartialManager::allocPartial(int partNum) {
Partial *outPartial = NULL;
// Get the first inactive partial
for (int partialNum = 0; partialNum < MT32EMU_MAX_PARTIALS; partialNum++) {
if (!partialTable[partialNum]->isActive()) {
outPartial = partialTable[partialNum];
break;
}
}
if (outPartial != NULL) {
outPartial->activate(partNum);
}
return outPartial;
}
unsigned int PartialManager::getFreePartialCount(void) {
int count = 0;
for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
if (!partialTable[i]->isActive()) {
count++;
}
}
return count;
}
// This function is solely used to gather data for debug output at the moment.
void PartialManager::getPerPartPartialUsage(unsigned int perPartPartialUsage[9]) {
memset(perPartPartialUsage, 0, 9 * sizeof(unsigned int));
for (unsigned int i = 0; i < MT32EMU_MAX_PARTIALS; i++) {
if (partialTable[i]->isActive()) {
perPartPartialUsage[partialTable[i]->getOwnerPart()]++;
}
}
}
// Finds the lowest-priority part that is exceeding its reserved partial allocation and has a poly
// in POLY_Releasing, then kills its first releasing poly.
// Parts with higher priority than minPart are not checked.
// Assumes that getFreePartials() has been called to make numReservedPartialsForPart up-to-date.
bool PartialManager::abortFirstReleasingPolyWhereReserveExceeded(int minPart) {
if (minPart == 8) {
// Rhythm is highest priority
minPart = -1;
}
for (int partNum = 7; partNum >= minPart; partNum--) {
int usePartNum = partNum == -1 ? 8 : partNum;
if (parts[usePartNum]->getActivePartialCount() > numReservedPartialsForPart[usePartNum]) {
// This part has exceeded its reserved partial count.
// If it has any releasing polys, kill its first one and we're done.
if (parts[usePartNum]->abortFirstPoly(POLY_Releasing)) {
return true;
}
}
}
return false;
}
// Finds the lowest-priority part that is exceeding its reserved partial allocation and has a poly, then kills
// its first poly in POLY_Held - or failing that, its first poly in any state.
// Parts with higher priority than minPart are not checked.
// Assumes that getFreePartials() has been called to make numReservedPartialsForPart up-to-date.
bool PartialManager::abortFirstPolyPreferHeldWhereReserveExceeded(int minPart) {
if (minPart == 8) {
// Rhythm is highest priority
minPart = -1;
}
for (int partNum = 7; partNum >= minPart; partNum--) {
int usePartNum = partNum == -1 ? 8 : partNum;
if (parts[usePartNum]->getActivePartialCount() > numReservedPartialsForPart[usePartNum]) {
// This part has exceeded its reserved partial count.
// If it has any polys, kill its first (preferably held) one and we're done.
if (parts[usePartNum]->abortFirstPolyPreferHeld()) {
return true;
}
}
}
return false;
}
bool PartialManager::freePartials(unsigned int needed, int partNum) {
// CONFIRMED: Barring bugs, this matches the real LAPC-I according to information from Mok.
// BUG: There's a bug in the LAPC-I implementation:
// When allocating for rhythm part, or when allocating for a part that is using fewer partials than it has reserved,
// held and playing polys on the rhythm part can potentially be aborted before releasing polys on the rhythm part.
// This bug isn't present on MT-32.
// I consider this to be a bug because I think that playing polys should always have priority over held polys,
// and held polys should always have priority over releasing polys.
// NOTE: This code generally aborts polys in parts (according to certain conditions) in the following order:
// 7, 6, 5, 4, 3, 2, 1, 0, 8 (rhythm)
// (from lowest priority, meaning most likely to have polys aborted, to highest priority, meaning least likely)
if (needed == 0) {
return true;
}
// Note that calling getFreePartialCount() also ensures that numReservedPartialsPerPart is up-to-date
if (getFreePartialCount() >= needed) {
return true;
}
// Note: These #ifdefs are temporary until we have proper "quirk" configuration.
// Also, the MT-32 version isn't properly confirmed yet.
#ifdef MT32EMU_QUIRK_FREE_PARTIALS_MT32
// On MT-32, we bail out before even killing releasing partials if the allocating part has exceeded its reserve and is configured for priority-to-earlier-polys.
if (parts[partNum]->getActiveNonReleasingPartialCount() + needed > numReservedPartialsForPart[partNum] && (synth->getPart(partNum)->getPatchTemp()->patch.assignMode & 1)) {
return false;
}
#endif
for (;;) {
#ifdef MT32EMU_QUIRK_FREE_PARTIALS_MT32
// Abort releasing polys in parts that have exceeded their partial reservation (working backwards from part 7, with rhythm last)
if (!abortFirstReleasingPolyWhereReserveExceeded(-1)) {
break;
}
#else
// Abort releasing polys in non-rhythm parts that have exceeded their partial reservation (working backwards from part 7)
if (!abortFirstReleasingPolyWhereReserveExceeded(0)) {
break;
}
#endif
if (getFreePartialCount() >= needed) {
return true;
}
}
if (parts[partNum]->getActiveNonReleasingPartialCount() + needed > numReservedPartialsForPart[partNum]) {
// With the new partials we're freeing for, we would end up using more partials than we have reserved.
if (synth->getPart(partNum)->getPatchTemp()->patch.assignMode & 1) {
// Priority is given to earlier polys, so just give up
return false;
}
// Only abort held polys in the target part and parts that have a lower priority
// (higher part number = lower priority, except for rhythm, which has the highest priority).
for (;;) {
if (!abortFirstPolyPreferHeldWhereReserveExceeded(partNum)) {
break;
}
if (getFreePartialCount() >= needed) {
return true;
}
}
if (needed > numReservedPartialsForPart[partNum]) {
return false;
}
} else {
// At this point, we're certain that we've reserved enough partials to play our poly.
// Check all parts from lowest to highest priority to see whether they've exceeded their
// reserve, and abort their polys until until we have enough free partials or they're within
// their reserve allocation.
for (;;) {
if (!abortFirstPolyPreferHeldWhereReserveExceeded(-1)) {
break;
}
if (getFreePartialCount() >= needed) {
return true;
}
}
}
// Abort polys in the target part until there are enough free partials for the new one
for (;;) {
if (!parts[partNum]->abortFirstPolyPreferHeld()) {
break;
}
if (getFreePartialCount() >= needed) {
return true;
}
}
// Aww, not enough partials for you.
return false;
}
const Partial *PartialManager::getPartial(unsigned int partialNum) const {
if (partialNum > MT32EMU_MAX_PARTIALS - 1) {
return NULL;
}
return partialTable[partialNum];
}

View File

@ -0,0 +1,54 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_PARTIALMANAGER_H
#define MT32EMU_PARTIALMANAGER_H
namespace MT32Emu {
class Synth;
class PartialManager {
private:
Synth *synth; // Only used for sending debug output
Part **parts;
Partial *partialTable[MT32EMU_MAX_PARTIALS];
Bit8u numReservedPartialsForPart[9];
bool abortFirstReleasingPolyWhereReserveExceeded(int minPart);
bool abortFirstPolyPreferHeldWhereReserveExceeded(int minPart);
public:
PartialManager(Synth *synth, Part **parts);
~PartialManager();
Partial *allocPartial(int partNum);
unsigned int getFreePartialCount(void);
void getPerPartPartialUsage(unsigned int perPartPartialUsage[9]);
bool freePartials(unsigned int needed, int partNum);
unsigned int setReserve(Bit8u *rset);
void deactivateAll();
bool produceOutput(int i, float *leftBuf, float *rightBuf, Bit32u bufferLength);
bool shouldReverb(int i);
void clearAlreadyOutputed();
const Partial *getPartial(unsigned int partialNum) const;
};
}
#endif

View File

@ -0,0 +1,174 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mt32emu.h"
namespace MT32Emu {
Poly::Poly(Part *usePart) {
part = usePart;
key = 255;
velocity = 255;
sustain = false;
activePartialCount = 0;
for (int i = 0; i < 4; i++) {
partials[i] = NULL;
}
state = POLY_Inactive;
}
void Poly::reset(unsigned int newKey, unsigned int newVelocity, bool newSustain, Partial **newPartials) {
if (isActive()) {
// FIXME: Throw out some big ugly debug output with a lot of exclamation marks - we should never get here
terminate();
}
key = newKey;
velocity = newVelocity;
sustain = newSustain;
activePartialCount = 0;
for (int i = 0; i < 4; i++) {
partials[i] = newPartials[i];
if (newPartials[i] != NULL) {
activePartialCount++;
state = POLY_Playing;
}
}
}
bool Poly::noteOff(bool pedalHeld) {
// Generally, non-sustaining instruments ignore note off. They die away eventually anyway.
// Key 0 (only used by special cases on rhythm part) reacts to note off even if non-sustaining or pedal held.
if (state == POLY_Inactive || state == POLY_Releasing) {
return false;
}
if (pedalHeld) {
state = POLY_Held;
} else {
startDecay();
}
return true;
}
bool Poly::stopPedalHold() {
if (state != POLY_Held) {
return false;
}
return startDecay();
}
bool Poly::startDecay() {
if (state == POLY_Inactive || state == POLY_Releasing) {
return false;
}
state = POLY_Releasing;
for (int t = 0; t < 4; t++) {
Partial *partial = partials[t];
if (partial != NULL) {
partial->startDecayAll();
}
}
return true;
}
bool Poly::startAbort() {
if (state == POLY_Inactive) {
return false;
}
for (int t = 0; t < 4; t++) {
Partial *partial = partials[t];
if (partial != NULL) {
partial->startAbort();
}
}
return true;
}
void Poly::terminate() {
if (state == POLY_Inactive) {
return;
}
for (int t = 0; t < 4; t++) {
Partial *partial = partials[t];
if (partial != NULL) {
partial->deactivate();
}
}
if (state != POLY_Inactive) {
// FIXME: Throw out lots of debug output - this should never happen
// (Deactivating the partials above should've made them each call partialDeactivated(), ultimately changing the state to POLY_Inactive)
state = POLY_Inactive;
}
}
void Poly::backupCacheToPartials(PatchCache cache[4]) {
for (int partialNum = 0; partialNum < 4; partialNum++) {
Partial *partial = partials[partialNum];
if (partial != NULL && partial->patchCache == &cache[partialNum]) {
partial->cachebackup = cache[partialNum];
partial->patchCache = &partial->cachebackup;
}
}
}
/**
* Returns the internal key identifier.
* For non-rhythm, this is within the range 12 to 108.
* For rhythm on MT-32, this is 0 or 1 (special cases) or within the range 24 to 87.
* For rhythm on devices with extended PCM sounds (e.g. CM-32L), this is 0, 1 or 24 to 108
*/
unsigned int Poly::getKey() const {
return key;
}
unsigned int Poly::getVelocity() const {
return velocity;
}
bool Poly::canSustain() const {
return sustain;
}
PolyState Poly::getState() const {
return state;
}
unsigned int Poly::getActivePartialCount() const {
return activePartialCount;
}
bool Poly::isActive() const {
return state != POLY_Inactive;
}
// This is called by Partial to inform the poly that the Partial has deactivated
void Poly::partialDeactivated(Partial *partial) {
for (int i = 0; i < 4; i++) {
if (partials[i] == partial) {
partials[i] = NULL;
activePartialCount--;
}
}
if (activePartialCount == 0) {
state = POLY_Inactive;
}
part->partialDeactivated(this);
}
}

View File

@ -0,0 +1,67 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_POLY_H
#define MT32EMU_POLY_H
namespace MT32Emu {
class Part;
enum PolyState {
POLY_Playing,
POLY_Held, // This marks keys that have been released on the keyboard, but are being held by the pedal
POLY_Releasing,
POLY_Inactive
};
class Poly {
private:
Part *part;
unsigned int key;
unsigned int velocity;
unsigned int activePartialCount;
bool sustain;
PolyState state;
Partial *partials[4];
public:
Poly(Part *part);
void reset(unsigned int key, unsigned int velocity, bool sustain, Partial **partials);
bool noteOff(bool pedalHeld);
bool stopPedalHold();
bool startDecay();
bool startAbort();
void terminate();
void backupCacheToPartials(PatchCache cache[4]);
unsigned int getKey() const;
unsigned int getVelocity() const;
bool canSustain() const;
PolyState getState() const;
unsigned int getActivePartialCount() const;
bool isActive() const;
void partialDeactivated(Partial *partial);
};
}
#endif /* POLY_H_ */

View File

@ -0,0 +1,217 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_STRUCTURES_H
#define MT32EMU_STRUCTURES_H
namespace MT32Emu {
// 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))
#else
#define MT32EMU_ALIGN_PACKED __attribute__((packed))
#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 partialStructure12; // 1 & 2 0-12 (1-13)
Bit8u partialStructure34; // 3 & 4 0-12 (1-13)
Bit8u partialMute; // 0-15 (0000-1111)
Bit8u noSustain; // ENV MODE 0-1 (Normal, No sustain)
} MT32EMU_ALIGN_PACKED common;
struct PartialParam {
struct WGParam {
Bit8u pitchCoarse; // 0-96 (C1,C#1-C9)
Bit8u pitchFine; // 0-100 (-50 to +50 (cents - confirmed by Mok))
Bit8u pitchKeyfollow; // 0-16 (-1, -1/2, -1/4, 0, 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, 1, 5/4, 3/2, 2, s1, s2)
Bit8u pitchBenderEnabled; // 0-1 (OFF, ON)
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 pulseWidth; // 0-100
Bit8u pulseWidthVeloSensitivity; // 0-14 (-7 - +7)
} MT32EMU_ALIGN_PACKED wg;
struct PitchEnvParam {
Bit8u depth; // 0-10
Bit8u veloSensitivity; // 0-100
Bit8u timeKeyfollow; // 0-4
Bit8u time[4]; // 0-100
Bit8u level[5]; // 0-100 (-50 - +50) // [3]: SUSTAIN LEVEL, [4]: END LEVEL
} MT32EMU_ALIGN_PACKED pitchEnv;
struct PitchLFOParam {
Bit8u rate; // 0-100
Bit8u depth; // 0-100
Bit8u modSensitivity; // 0-100
} MT32EMU_ALIGN_PACKED pitchLFO;
struct TVFParam {
Bit8u cutoff; // 0-100
Bit8u resonance; // 0-30
Bit8u keyfollow; // -1, -1/2, -1/4, 0, 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, 1, 5/4, 3/2, 2
Bit8u biasPoint; // 0-127 (<1A-<7C >1A-7C)
Bit8u biasLevel; // 0-14 (-7 - +7)
Bit8u envDepth; // 0-100
Bit8u envVeloSensitivity; // 0-100
Bit8u envDepthKeyfollow; // DEPTH KEY FOLL0W 0-4
Bit8u envTimeKeyfollow; // TIME KEY FOLLOW 0-4
Bit8u envTime[5]; // 0-100
Bit8u envLevel[4]; // 0-100 // [3]: SUSTAIN LEVEL
} MT32EMU_ALIGN_PACKED tvf;
struct TVAParam {
Bit8u level; // 0-100
Bit8u veloSensitivity; // 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 envTimeKeyfollow; // TIME KEY FOLLOW 0-4
Bit8u envTimeVeloSensitivity; // VELOS KEY FOLL0W 0-4
Bit8u envTime[5]; // 0-100
Bit8u envLevel[4]; // 0-100 // [3]: SUSTAIN LEVEL
} 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;
const unsigned int SYSTEM_MASTER_TUNE_OFF = 0;
const unsigned int SYSTEM_REVERB_MODE_OFF = 1;
const unsigned int SYSTEM_REVERB_TIME_OFF = 2;
const unsigned int SYSTEM_REVERB_LEVEL_OFF = 3;
const unsigned int SYSTEM_RESERVE_SETTINGS_START_OFF = 4;
const unsigned int SYSTEM_RESERVE_SETTINGS_END_OFF = 12;
const unsigned int SYSTEM_CHAN_ASSIGN_START_OFF = 13;
const unsigned int SYSTEM_CHAN_ASSIGN_END_OFF = 21;
const unsigned int SYSTEM_MASTER_VOL_OFF = 22;
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 outputLevel; // OUTPUT LEVEL 0-100
Bit8u panpot; // PANPOT 0-14 (R-L)
Bit8u dummyv[6];
} MT32EMU_ALIGN_PACKED patchTemp[9];
struct RhythmTemp {
Bit8u timbre; // TIMBRE 0-94 (M1-M64,R1-30,OFF); LAPC-I: 0-127 (M01-M64,R01-R63)
Bit8u outputLevel; // OUTPUT LEVEL 0-100
Bit8u panpot; // PANPOT 0-14 (R-L)
Bit8u reverbSwitch; // REVERB SWITCH 0-1 (OFF,ON)
} MT32EMU_ALIGN_PACKED rhythmTemp[85];
TimbreParam timbreTemp[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 timbres[64 + 64 + 64 + 64]; // Group A, Group B, Memory, Rhythm
struct System {
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 system;
};
#if defined(_MSC_VER) || defined (__MINGW32__)
#pragma pack(pop)
#else
#pragma pack()
#endif
struct ControlROMPCMStruct;
struct PCMWaveEntry {
Bit32u addr;
Bit32u len;
bool loop;
ControlROMPCMStruct *controlROMPCMStruct;
};
// 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;
Bit32u structureMix;
int structurePosition;
int structurePair;
// The following fields are actually common to all partials in the timbre
bool dirty;
Bit32u partialCount;
bool sustain;
bool reverb;
TimbreParam::PartialParam srcPartial;
// The following directly points into live sysex-addressable memory
const TimbreParam::PartialParam *partialParam;
};
class Partial; // Forward reference for class defined in partial.h
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,471 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_SYNTH_H
#define MT32EMU_SYNTH_H
//#include <cstdarg>
namespace MT32Emu {
class TableInitialiser;
class Partial;
class PartialManager;
class Part;
/**
* Methods for emulating the connection between the LA32 and the DAC, which involves
* some hacks in the real devices for doubling the volume.
* See also http://en.wikipedia.org/wiki/Roland_MT-32#Digital_overflow
*/
enum DACInputMode {
// Produces samples at double the volume, without tricks.
// * Nicer overdrive characteristics than the DAC hacks (it simply clips samples within range)
// * Higher quality than the real devices
DACInputMode_NICE,
// Produces samples that exactly match the bits output from the emulated LA32.
// * Nicer overdrive characteristics than the DAC hacks (it simply clips samples within range)
// * Much less likely to overdrive than any other mode.
// * Half the volume of any of the other modes, meaning its volume relative to the reverb
// output when mixed together directly will sound wrong.
// * Perfect for developers while debugging :)
DACInputMode_PURE,
// Re-orders the LA32 output bits as in early generation MT-32s (according to Wikipedia).
// Bit order at DAC (where each number represents the original LA32 output bit number, and XX means the bit is always low):
// 15 13 12 11 10 09 08 07 06 05 04 03 02 01 00 XX
DACInputMode_GENERATION1,
// Re-orders the LA32 output bits as in later generations (personally confirmed on my CM-32L - KG).
// Bit order at DAC (where each number represents the original LA32 output bit number):
// 15 13 12 11 10 09 08 07 06 05 04 03 02 01 00 14
DACInputMode_GENERATION2
};
enum ReportType {
// Errors
ReportType_errorControlROM = 1,
ReportType_errorPCMROM,
ReportType_errorSampleRate,
// Progress
ReportType_progressInit,
// HW spec
ReportType_availableSSE,
ReportType_available3DNow,
ReportType_usingSSE,
ReportType_using3DNow,
// General info
ReportType_lcdMessage,
ReportType_devReset,
ReportType_devReconfig,
ReportType_newReverbMode,
ReportType_newReverbTime,
ReportType_newReverbLevel
};
enum LoadResult {
LoadResult_OK,
LoadResult_NotFound,
LoadResult_Unreadable,
LoadResult_Invalid
};
struct SynthProperties {
// Sample rate to use in mixing
unsigned int sampleRate;
// Deprecated - ignored. Use Synth::setReverbEnabled() instead.
bool useReverb;
// Deprecated - ignored. Use Synth::setReverbOverridden() instead.
bool useDefaultReverb;
// Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead.
unsigned char reverbType;
// Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead.
unsigned char reverbTime;
// Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead.
unsigned char reverbLevel;
// The name of the directory in which the ROM and data files are stored (with trailing slash/backslash)
// Not used if "openFile" is set. May be NULL in any case.
const char *baseDir;
// This is used as the first argument to all callbacks
void *userData;
// Callback for reporting various errors and information. May be NULL
int (*report)(void *userData, ReportType type, const void *reportData);
// Callback for debug messages, in vprintf() format
void (*printDebug)(void *userData, const char *fmt, va_list list);
// Callback for providing an implementation of File, opened and ready for use
// May be NULL, in which case a default implementation will be used.
Common::File *(*openFile)(void *userData, const char *filename);
// Callback for closing a File. May be NULL, in which case the File will automatically be close()d/deleted.
void (*closeFile)(void *userData, Common::File *file);
};
// This is the specification of the Callback routine used when calling the RecalcWaveforms
// function
typedef void (*recalcStatusCallback)(int percDone);
typedef void (*FloatToBit16sFunc)(Bit16s *target, const float *source, Bit32u len, float outputGain);
const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41;
const Bit8u SYSEX_MDL_MT32 = 0x16;
const Bit8u SYSEX_MDL_D50 = 0x14;
const Bit8u SYSEX_CMD_RQ1 = 0x11; // Request data #1
const Bit8u SYSEX_CMD_DT1 = 0x12; // Data set 1
const Bit8u SYSEX_CMD_WSD = 0x40; // Want to send data
const Bit8u SYSEX_CMD_RQD = 0x41; // Request data
const Bit8u SYSEX_CMD_DAT = 0x42; // Data set
const Bit8u SYSEX_CMD_ACK = 0x43; // Acknowledge
const Bit8u SYSEX_CMD_EOD = 0x45; // End of data
const Bit8u SYSEX_CMD_ERR = 0x4E; // Communications error
const Bit8u SYSEX_CMD_RJC = 0x4F; // Rejection
const int MAX_SYSEX_SIZE = 512;
const unsigned int CONTROL_ROM_SIZE = 64 * 1024;
struct ControlROMPCMStruct {
Bit8u pos;
Bit8u len;
Bit8u pitchLSB;
Bit8u pitchMSB;
};
struct ControlROMMap {
Bit16u idPos;
Bit16u idLen;
const char *idBytes;
Bit16u pcmTable; // 4 * pcmCount bytes
Bit16u pcmCount;
Bit16u timbreAMap; // 128 bytes
Bit16u timbreAOffset;
bool timbreACompressed;
Bit16u timbreBMap; // 128 bytes
Bit16u timbreBOffset;
bool timbreBCompressed;
Bit16u timbreRMap; // 2 * timbreRCount bytes
Bit16u timbreRCount;
Bit16u rhythmSettings; // 4 * rhythmSettingsCount bytes
Bit16u rhythmSettingsCount;
Bit16u reserveSettings; // 9 bytes
Bit16u panSettings; // 8 bytes
Bit16u programSettings; // 8 bytes
Bit16u rhythmMaxTable; // 4 bytes
Bit16u patchMaxTable; // 16 bytes
Bit16u systemMaxTable; // 23 bytes
Bit16u timbreMaxTable; // 72 bytes
};
enum MemoryRegionType {
MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset
};
class MemoryRegion {
private:
Synth *synth;
Bit8u *realMemory;
Bit8u *maxTable;
public:
MemoryRegionType type;
Bit32u startAddr, entrySize, entries;
MemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable, MemoryRegionType useType, Bit32u useStartAddr, Bit32u useEntrySize, Bit32u useEntries) {
synth = useSynth;
realMemory = useRealMemory;
maxTable = useMaxTable;
type = useType;
startAddr = useStartAddr;
entrySize = useEntrySize;
entries = useEntries;
}
int lastTouched(Bit32u addr, Bit32u len) const {
return (offset(addr) + len - 1) / entrySize;
}
int firstTouchedOffset(Bit32u addr) const {
return offset(addr) % entrySize;
}
int firstTouched(Bit32u addr) const {
return offset(addr) / entrySize;
}
Bit32u regionEnd() const {
return startAddr + entrySize * entries;
}
bool contains(Bit32u addr) const {
return addr >= startAddr && addr < regionEnd();
}
int offset(Bit32u addr) const {
return addr - startAddr;
}
Bit32u getClampedLen(Bit32u addr, Bit32u len) const {
if (addr + len > regionEnd())
return regionEnd() - addr;
return len;
}
Bit32u next(Bit32u addr, Bit32u len) const {
if (addr + len > regionEnd()) {
return regionEnd() - addr;
}
return 0;
}
Bit8u getMaxValue(int off) const {
if (maxTable == NULL)
return 0xFF;
return maxTable[off % entrySize];
}
Bit8u *getRealMemory() const {
return realMemory;
}
bool isReadable() const {
return getRealMemory() != NULL;
}
void read(unsigned int entry, unsigned int off, Bit8u *dst, unsigned int len) const;
void write(unsigned int entry, unsigned int off, const Bit8u *src, unsigned int len, bool init = false) const;
};
class PatchTempMemoryRegion : public MemoryRegion {
public:
PatchTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_PatchTemp, MT32EMU_MEMADDR(0x030000), sizeof(MemParams::PatchTemp), 9) {}
};
class RhythmTempMemoryRegion : public MemoryRegion {
public:
RhythmTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_RhythmTemp, MT32EMU_MEMADDR(0x030110), sizeof(MemParams::RhythmTemp), 85) {}
};
class TimbreTempMemoryRegion : public MemoryRegion {
public:
TimbreTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_TimbreTemp, MT32EMU_MEMADDR(0x040000), sizeof(TimbreParam), 8) {}
};
class PatchesMemoryRegion : public MemoryRegion {
public:
PatchesMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Patches, MT32EMU_MEMADDR(0x050000), sizeof(PatchParam), 128) {}
};
class TimbresMemoryRegion : public MemoryRegion {
public:
TimbresMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Timbres, MT32EMU_MEMADDR(0x080000), sizeof(MemParams::PaddedTimbre), 64 + 64 + 64 + 64) {}
};
class SystemMemoryRegion : public MemoryRegion {
public:
SystemMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_System, MT32EMU_MEMADDR(0x100000), sizeof(MemParams::System), 1) {}
};
class DisplayMemoryRegion : public MemoryRegion {
public:
DisplayMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Display, MT32EMU_MEMADDR(0x200000), MAX_SYSEX_SIZE - 1, 1) {}
};
class ResetMemoryRegion : public MemoryRegion {
public:
ResetMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1) {}
};
class ReverbModel {
public:
virtual ~ReverbModel() {}
// After construction or a close(), open() will be called at least once before any other call (with the exception of close()).
virtual void open(unsigned int sampleRate) = 0;
// May be called multiple times without an open() in between.
virtual void close() = 0;
virtual void setParameters(Bit8u time, Bit8u level) = 0;
virtual void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) = 0;
virtual bool isActive() const = 0;
};
class Synth {
friend class Part;
friend class RhythmPart;
friend class Poly;
friend class Partial;
friend class Tables;
friend class MemoryRegion;
friend class TVA;
friend class TVF;
friend class TVP;
private:
PatchTempMemoryRegion *patchTempMemoryRegion;
RhythmTempMemoryRegion *rhythmTempMemoryRegion;
TimbreTempMemoryRegion *timbreTempMemoryRegion;
PatchesMemoryRegion *patchesMemoryRegion;
TimbresMemoryRegion *timbresMemoryRegion;
SystemMemoryRegion *systemMemoryRegion;
DisplayMemoryRegion *displayMemoryRegion;
ResetMemoryRegion *resetMemoryRegion;
Bit8u *paddedTimbreMaxTable;
bool isEnabled;
PCMWaveEntry *pcmWaves; // Array
const ControlROMMap *controlROMMap;
Bit8u controlROMData[CONTROL_ROM_SIZE];
float *pcmROMData;
int pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM
Bit8s chantable[32];
Bit32u renderedSampleCount;
Tables tables;
MemParams mt32ram, mt32default;
ReverbModel *reverbModels[4];
ReverbModel *reverbModel;
bool reverbEnabled;
bool reverbOverridden;
FloatToBit16sFunc la32FloatToBit16sFunc;
FloatToBit16sFunc reverbFloatToBit16sFunc;
float outputGain;
float reverbOutputGain;
bool isOpen;
PartialManager *partialManager;
Part *parts[9];
// FIXME: We can reorganise things so that we don't need all these separate tmpBuf, tmp and prerender buffers.
// This should be rationalised when things have stabilised a bit (if prerender buffers don't die in the mean time).
float tmpBufPartialLeft[MAX_SAMPLES_PER_RUN];
float tmpBufPartialRight[MAX_SAMPLES_PER_RUN];
float tmpBufMixLeft[MAX_SAMPLES_PER_RUN];
float tmpBufMixRight[MAX_SAMPLES_PER_RUN];
float tmpBufReverbOutLeft[MAX_SAMPLES_PER_RUN];
float tmpBufReverbOutRight[MAX_SAMPLES_PER_RUN];
Bit16s tmpNonReverbLeft[MAX_SAMPLES_PER_RUN];
Bit16s tmpNonReverbRight[MAX_SAMPLES_PER_RUN];
Bit16s tmpReverbDryLeft[MAX_SAMPLES_PER_RUN];
Bit16s tmpReverbDryRight[MAX_SAMPLES_PER_RUN];
Bit16s tmpReverbWetLeft[MAX_SAMPLES_PER_RUN];
Bit16s tmpReverbWetRight[MAX_SAMPLES_PER_RUN];
// These ring buffers are only used to simulate delays present on the real device.
// In particular, when a partial needs to be aborted to free it up for use by a new Poly,
// the controller will busy-loop waiting for the sound to finish.
Bit16s prerenderNonReverbLeft[MAX_PRERENDER_SAMPLES];
Bit16s prerenderNonReverbRight[MAX_PRERENDER_SAMPLES];
Bit16s prerenderReverbDryLeft[MAX_PRERENDER_SAMPLES];
Bit16s prerenderReverbDryRight[MAX_PRERENDER_SAMPLES];
Bit16s prerenderReverbWetLeft[MAX_PRERENDER_SAMPLES];
Bit16s prerenderReverbWetRight[MAX_PRERENDER_SAMPLES];
int prerenderReadIx;
int prerenderWriteIx;
SynthProperties myProp;
bool prerender();
void copyPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u pos, Bit32u len);
void checkPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u &pos, Bit32u &len);
void doRenderStreams(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u len);
void playAddressedSysex(unsigned char channel, const Bit8u *sysex, Bit32u len);
void readSysex(unsigned char channel, const Bit8u *sysex, Bit32u len) const;
void initMemoryRegions();
void deleteMemoryRegions();
MemoryRegion *findMemoryRegion(Bit32u addr);
void writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data);
void readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data);
LoadResult loadControlROM(const char *filename);
LoadResult loadPCMROM(const char *filename);
bool initPCMList(Bit16u mapAddress, Bit16u count);
bool initTimbres(Bit16u mapAddress, Bit16u offset, int timbreCount, int startTimbre, bool compressed);
bool initCompressedTimbre(int drumNum, const Bit8u *mem, unsigned int memLen);
void refreshSystemMasterTune();
void refreshSystemReverbParameters();
void refreshSystemReserveSettings();
void refreshSystemChanAssign(unsigned int firstPart, unsigned int lastPart);
void refreshSystemMasterVol();
void refreshSystem();
void reset();
unsigned int getSampleRate() const;
void printPartialUsage(unsigned long sampleOffset = 0);
protected:
int report(ReportType type, const void *reportData);
Common::File *openFile(const char *filename);
void closeFile(Common::File *file);
void printDebug(const char *fmt, ...);
public:
static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum);
Synth();
~Synth();
// Used to initialise the MT-32. Must be called before any other function.
// Returns true if initialization was sucessful, otherwise returns false.
bool open(SynthProperties &useProp);
// Closes the MT-32 and deallocates any memory used by the synthesizer
void close(void);
// Sends a 4-byte MIDI message to the MT-32 for immediate playback
void playMsg(Bit32u msg);
void playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity);
// Sends a string of Sysex commands to the MT-32 for immediate interpretation
// The length is in bytes
void playSysex(const Bit8u *sysex, Bit32u len);
void playSysexWithoutFraming(const Bit8u *sysex, Bit32u len);
void playSysexWithoutHeader(unsigned char device, unsigned char command, const Bit8u *sysex, Bit32u len);
void writeSysex(unsigned char channel, const Bit8u *sysex, Bit32u len);
void setReverbEnabled(bool reverbEnabled);
bool isReverbEnabled() const;
void setReverbOverridden(bool reverbOverridden);
bool isReverbOverridden() const;
void setDACInputMode(DACInputMode mode);
// Sets output gain factor. Applied to all output samples and unrelated with the synth's Master volume.
void setOutputGain(float);
// Sets output gain factor for the reverb wet output. setOutputGain() doesn't change reverb output gain.
void setReverbOutputGain(float);
// Renders samples to the specified output stream.
// The length is in frames, not bytes (in 16-bit stereo,
// one frame is 4 bytes).
void render(Bit16s *stream, Bit32u len);
// Renders samples to the specified output streams (any or all of which may be NULL).
void renderStreams(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u len);
// Returns true when there is at least one active partial, otherwise false.
bool hasActivePartials() const;
// Returns true if hasActivePartials() returns true, or reverb is (somewhat unreliably) detected as being active.
bool isActive() const;
const Partial *getPartial(unsigned int partialNum) const;
void readMemory(Bit32u addr, Bit32u len, Bit8u *data);
// partNum should be 0..7 for Part 1..8, or 8 for Rhythm
const Part *getPart(unsigned int partNum) const;
};
}
#endif

View File

@ -0,0 +1,365 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* This class emulates the calculations performed by the 8095 microcontroller in order to configure the LA-32's amplitude ramp for a single partial at each stage of its TVA envelope.
* Unless we introduced bugs, it should be pretty much 100% accurate according to Mok's specifications.
*/
//#include <cmath>
#include "mt32emu.h"
#include "mmath.h"
namespace MT32Emu {
// CONFIRMED: Matches a table in ROM - haven't got around to coming up with a formula for it yet.
static Bit8u biasLevelToAmpSubtractionCoeff[13] = {255, 187, 137, 100, 74, 54, 40, 29, 21, 15, 10, 5, 0};
TVA::TVA(const Partial *usePartial, LA32Ramp *useAmpRamp) :
partial(usePartial), ampRamp(useAmpRamp), system_(&usePartial->getSynth()->mt32ram.system) {
}
void TVA::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) {
target = newTarget;
phase = newPhase;
ampRamp->startRamp(newTarget, newIncrement);
#if MT32EMU_MONITOR_TVA >= 1
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVA,ramp,%d,%d,%d,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), (newIncrement & 0x80) ? -1 : 1, (newIncrement & 0x7F), newPhase);
#endif
}
void TVA::end(int newPhase) {
phase = newPhase;
playing = false;
#if MT32EMU_MONITOR_TVA >= 1
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVA,end,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), newPhase);
#endif
}
static int multBias(Bit8u biasLevel, int bias) {
return (bias * biasLevelToAmpSubtractionCoeff[biasLevel]) >> 5;
}
static int calcBiasAmpSubtraction(Bit8u biasPoint, Bit8u biasLevel, int key) {
if ((biasPoint & 0x40) == 0) {
int bias = biasPoint + 33 - key;
if (bias > 0) {
return multBias(biasLevel, bias);
}
} else {
int bias = biasPoint - 31 - key;
if (bias < 0) {
bias = -bias;
return multBias(biasLevel, bias);
}
}
return 0;
}
static int calcBiasAmpSubtractions(const TimbreParam::PartialParam *partialParam, int key) {
int biasAmpSubtraction1 = calcBiasAmpSubtraction(partialParam->tva.biasPoint1, partialParam->tva.biasLevel1, key);
if (biasAmpSubtraction1 > 255) {
return 255;
}
int biasAmpSubtraction2 = calcBiasAmpSubtraction(partialParam->tva.biasPoint2, partialParam->tva.biasLevel2, key);
if (biasAmpSubtraction2 > 255) {
return 255;
}
int biasAmpSubtraction = biasAmpSubtraction1 + biasAmpSubtraction2;
if (biasAmpSubtraction > 255) {
return 255;
}
return biasAmpSubtraction;
}
static int calcVeloAmpSubtraction(Bit8u veloSensitivity, unsigned int velocity) {
// FIXME:KG: Better variable names
int velocityMult = veloSensitivity - 50;
int absVelocityMult = velocityMult < 0 ? -velocityMult : velocityMult;
velocityMult = (signed)((unsigned)(velocityMult * ((signed)velocity - 64)) << 2);
return absVelocityMult - (velocityMult >> 8); // PORTABILITY NOTE: Assumes arithmetic shift
}
static int calcBasicAmp(const Tables *tables, const Partial *partial, const MemParams::System *system_, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, const MemParams::RhythmTemp *rhythmTemp, int biasAmpSubtraction, int veloAmpSubtraction, Bit8u expression) {
int amp = 155;
if (!partial->isRingModulatingSlave()) {
amp -= tables->masterVolToAmpSubtraction[system_->masterVol];
if (amp < 0) {
return 0;
}
amp -= tables->levelToAmpSubtraction[patchTemp->outputLevel];
if (amp < 0) {
return 0;
}
amp -= tables->levelToAmpSubtraction[expression];
if (amp < 0) {
return 0;
}
if (rhythmTemp != NULL) {
amp -= tables->levelToAmpSubtraction[rhythmTemp->outputLevel];
if (amp < 0) {
return 0;
}
}
}
amp -= biasAmpSubtraction;
if (amp < 0) {
return 0;
}
amp -= tables->levelToAmpSubtraction[partialParam->tva.level];
if (amp < 0) {
return 0;
}
amp -= veloAmpSubtraction;
if (amp < 0) {
return 0;
}
if (amp > 155) {
amp = 155;
}
amp -= partialParam->tvf.resonance >> 1;
if (amp < 0) {
return 0;
}
return amp;
}
int calcKeyTimeSubtraction(Bit8u envTimeKeyfollow, int key) {
if (envTimeKeyfollow == 0) {
return 0;
}
return (key - 60) >> (5 - envTimeKeyfollow); // PORTABILITY NOTE: Assumes arithmetic shift
}
void TVA::reset(const Part *newPart, const TimbreParam::PartialParam *newPartialParam, const MemParams::RhythmTemp *newRhythmTemp) {
part = newPart;
partialParam = newPartialParam;
patchTemp = newPart->getPatchTemp();
rhythmTemp = newRhythmTemp;
playing = true;
Tables *tables = &partial->getSynth()->tables;
int key = partial->getPoly()->getKey();
int velocity = partial->getPoly()->getVelocity();
keyTimeSubtraction = calcKeyTimeSubtraction(partialParam->tva.envTimeKeyfollow, key);
biasAmpSubtraction = calcBiasAmpSubtractions(partialParam, key);
veloAmpSubtraction = calcVeloAmpSubtraction(partialParam->tva.veloSensitivity, velocity);
int newTarget = calcBasicAmp(tables, partial, system_, partialParam, patchTemp, newRhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression());
int newPhase;
if (partialParam->tva.envTime[0] == 0) {
// Initially go to the TVA_PHASE_ATTACK target amp, and spend the next phase going from there to the TVA_PHASE_2 target amp
// Note that this means that velocity never affects time for this partial.
newTarget += partialParam->tva.envLevel[0];
newPhase = TVA_PHASE_ATTACK; // The first target used in nextPhase() will be TVA_PHASE_2
} else {
// Initially go to the base amp determined by TVA level, part volume, etc., and spend the next phase going from there to the full TVA_PHASE_ATTACK target amp.
newPhase = TVA_PHASE_BASIC; // The first target used in nextPhase() will be TVA_PHASE_ATTACK
}
ampRamp->reset();//currentAmp = 0;
// "Go downward as quickly as possible".
// Since the current value is 0, the LA32Ramp will notice that we're already at or below the target and trying to go downward,
// and therefore jump to the target immediately and raise an interrupt.
startRamp((Bit8u)newTarget, 0x80 | 127, newPhase);
}
void TVA::startAbort() {
startRamp(64, 0x80 | 127, TVA_PHASE_RELEASE);
}
void TVA::startDecay() {
if (phase >= TVA_PHASE_RELEASE) {
return;
}
Bit8u newIncrement;
if (partialParam->tva.envTime[4] == 0) {
newIncrement = 1;
} else {
newIncrement = -partialParam->tva.envTime[4];
}
// The next time nextPhase() is called, it will think TVA_PHASE_RELEASE has finished and the partial will be aborted
startRamp(0, newIncrement, TVA_PHASE_RELEASE);
}
void TVA::handleInterrupt() {
nextPhase();
}
void TVA::recalcSustain() {
// We get pinged periodically by the pitch code to recalculate our values when in sustain.
// This is done so that the TVA will respond to things like MIDI expression and volume changes while it's sustaining, which it otherwise wouldn't do.
// The check for envLevel[3] == 0 strikes me as slightly dumb. FIXME: Explain why
if (phase != TVA_PHASE_SUSTAIN || partialParam->tva.envLevel[3] == 0) {
return;
}
// We're sustaining. Recalculate all the values
Tables *tables = &partial->getSynth()->tables;
int newTarget = calcBasicAmp(tables, partial, system_, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression());
newTarget += partialParam->tva.envLevel[3];
// Since we're in TVA_PHASE_SUSTAIN at this point, we know that target has been reached and an interrupt fired, so we can rely on it being the current amp.
int targetDelta = newTarget - target;
// Calculate an increment to get to the new amp value in a short, more or less consistent amount of time
Bit8u newIncrement;
if (targetDelta >= 0) {
newIncrement = tables->envLogarithmicTime[(Bit8u)targetDelta] - 2;
} else {
newIncrement = (tables->envLogarithmicTime[(Bit8u)-targetDelta] - 2) | 0x80;
}
// Configure so that once the transition's complete and nextPhase() is called, we'll just re-enter sustain phase (or decay phase, depending on parameters at the time).
startRamp(newTarget, newIncrement, TVA_PHASE_SUSTAIN - 1);
}
bool TVA::isPlaying() const {
return playing;
}
int TVA::getPhase() const {
return phase;
}
void TVA::nextPhase() {
Tables *tables = &partial->getSynth()->tables;
if (phase >= TVA_PHASE_DEAD || !playing) {
partial->getSynth()->printDebug("TVA::nextPhase(): Shouldn't have got here with phase %d, playing=%s", phase, playing ? "true" : "false");
return;
}
int newPhase = phase + 1;
if (newPhase == TVA_PHASE_DEAD) {
end(newPhase);
return;
}
bool allLevelsZeroFromNowOn = false;
if (partialParam->tva.envLevel[3] == 0) {
if (newPhase == TVA_PHASE_4) {
allLevelsZeroFromNowOn = true;
} else if (partialParam->tva.envLevel[2] == 0) {
if (newPhase == TVA_PHASE_3) {
allLevelsZeroFromNowOn = true;
} else if (partialParam->tva.envLevel[1] == 0) {
if (newPhase == TVA_PHASE_2) {
allLevelsZeroFromNowOn = true;
} else if (partialParam->tva.envLevel[0] == 0) {
if (newPhase == TVA_PHASE_ATTACK) { // this line added, missing in ROM - FIXME: Add description of repercussions
allLevelsZeroFromNowOn = true;
}
}
}
}
}
int newTarget;
int newIncrement;
int envPointIndex = phase;
if (!allLevelsZeroFromNowOn) {
newTarget = calcBasicAmp(tables, partial, system_, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression());
if (newPhase == TVA_PHASE_SUSTAIN || newPhase == TVA_PHASE_RELEASE) {
if (partialParam->tva.envLevel[3] == 0) {
end(newPhase);
return;
}
if (!partial->getPoly()->canSustain()) {
newPhase = TVA_PHASE_RELEASE;
newTarget = 0;
newIncrement = -partialParam->tva.envTime[4];
if (newIncrement == 0) {
// We can't let the increment be 0, or there would be no emulated interrupt.
// So we do an "upward" increment, which should set the amp to 0 extremely quickly
// and cause an "interrupt" to bring us back to nextPhase().
newIncrement = 1;
}
} else {
newTarget += partialParam->tva.envLevel[3];
newIncrement = 0;
}
} else {
newTarget += partialParam->tva.envLevel[envPointIndex];
}
} else {
newTarget = 0;
}
if ((newPhase != TVA_PHASE_SUSTAIN && newPhase != TVA_PHASE_RELEASE) || allLevelsZeroFromNowOn) {
int envTimeSetting = partialParam->tva.envTime[envPointIndex];
if (newPhase == TVA_PHASE_ATTACK) {
envTimeSetting -= ((signed)partial->getPoly()->getVelocity() - 64) >> (6 - partialParam->tva.envTimeVeloSensitivity); // PORTABILITY NOTE: Assumes arithmetic shift
if (envTimeSetting <= 0 && partialParam->tva.envTime[envPointIndex] != 0) {
envTimeSetting = 1;
}
} else {
envTimeSetting -= keyTimeSubtraction;
}
if (envTimeSetting > 0) {
int targetDelta = newTarget - target;
if (targetDelta <= 0) {
if (targetDelta == 0) {
// target and newTarget are the same.
// We can't have an increment of 0 or we wouldn't get an emulated interrupt.
// So instead make the target one less than it really should be and set targetDelta accordingly.
targetDelta = -1;
newTarget--;
if (newTarget < 0) {
// Oops, newTarget is less than zero now, so let's do it the other way:
// Make newTarget one more than it really should've been and set targetDelta accordingly.
// FIXME (apparent bug in real firmware):
// This means targetDelta will be positive just below here where it's inverted, and we'll end up using envLogarithmicTime[-1], and we'll be setting newIncrement to be descending later on, etc..
targetDelta = 1;
newTarget = -newTarget;
}
}
targetDelta = -targetDelta;
newIncrement = tables->envLogarithmicTime[(Bit8u)targetDelta] - envTimeSetting;
if (newIncrement <= 0) {
newIncrement = 1;
}
newIncrement = newIncrement | 0x80;
} else {
// FIXME: The last 22 or so entries in this table are 128 - surely that fucks things up, since that ends up being -128 signed?
newIncrement = tables->envLogarithmicTime[(Bit8u)targetDelta] - envTimeSetting;
if (newIncrement <= 0) {
newIncrement = 1;
}
}
} else {
newIncrement = newTarget >= target ? (0x80 | 127) : 127;
}
// FIXME: What's the point of this? It's checked or set to non-zero everywhere above
if (newIncrement == 0) {
newIncrement = 1;
}
}
startRamp((Bit8u)newTarget, (Bit8u)newIncrement, newPhase);
}
}

View File

@ -0,0 +1,94 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_TVA_H
#define MT32EMU_TVA_H
namespace MT32Emu {
class Part;
// Note that when entering nextPhase(), newPhase is set to phase + 1, and the descriptions/names below refer to
// newPhase's value.
enum {
// In this phase, the base amp (as calculated in calcBasicAmp()) is targeted with an instant time.
// This phase is entered by reset() only if time[0] != 0.
TVA_PHASE_BASIC = 0,
// In this phase, level[0] is targeted within time[0], and velocity potentially affects time
TVA_PHASE_ATTACK = 1,
// In this phase, level[1] is targeted within time[1]
TVA_PHASE_2 = 2,
// In this phase, level[2] is targeted within time[2]
TVA_PHASE_3 = 3,
// In this phase, level[3] is targeted within time[3]
TVA_PHASE_4 = 4,
// In this phase, immediately goes to PHASE_RELEASE unless the poly is set to sustain.
// Aborts the partial if level[3] is 0.
// Otherwise level[3] is continued, no phase change will occur until some external influence (like pedal release)
TVA_PHASE_SUSTAIN = 5,
// In this phase, 0 is targeted within time[4] (the time calculation is quite different from the other phases)
TVA_PHASE_RELEASE = 6,
// It's PHASE_DEAD, Jim.
TVA_PHASE_DEAD = 7
};
class TVA {
private:
const Partial * const partial;
LA32Ramp *ampRamp;
const MemParams::System * const system_;
const Part *part;
const TimbreParam::PartialParam *partialParam;
const MemParams::PatchTemp *patchTemp;
const MemParams::RhythmTemp *rhythmTemp;
bool playing;
int biasAmpSubtraction;
int veloAmpSubtraction;
int keyTimeSubtraction;
Bit8u target;
int phase;
void startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase);
void end(int newPhase);
void nextPhase();
public:
TVA(const Partial *partial, LA32Ramp *ampRamp);
void reset(const Part *part, const TimbreParam::PartialParam *partialParam, const MemParams::RhythmTemp *rhythmTemp);
void handleInterrupt();
void recalcSustain();
void startDecay();
void startAbort();
bool isPlaying() const;
int getPhase() const;
};
}
#endif /* TVA_H_ */

View File

@ -0,0 +1,230 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//#include <cmath>
#include "mt32emu.h"
#include "mmath.h"
namespace MT32Emu {
// Note that when entering nextPhase(), newPhase is set to phase + 1, and the descriptions/names below refer to
// newPhase's value.
enum {
// When this is the target phase, level[0] is targeted within time[0]
// Note that this phase is always set up in reset(), not nextPhase()
PHASE_ATTACK = 1,
// When this is the target phase, level[1] is targeted within time[1]
PHASE_2 = 2,
// When this is the target phase, level[2] is targeted within time[2]
PHASE_3 = 3,
// When this is the target phase, level[3] is targeted within time[3]
PHASE_4 = 4,
// When this is the target phase, immediately goes to PHASE_RELEASE unless the poly is set to sustain.
// Otherwise level[3] is continued with increment 0 - no phase change will occur until some external influence (like pedal release)
PHASE_SUSTAIN = 5,
// 0 is targeted within time[4] (the time calculation is quite different from the other phases)
PHASE_RELEASE = 6,
// 0 is targeted with increment 0 (thus theoretically staying that way forever)
PHASE_DONE = 7
};
static int calcBaseCutoff(const TimbreParam::PartialParam *partialParam, Bit32u basePitch, unsigned int key) {
// This table matches the values used by a real LAPC-I.
static const Bit8s biasLevelToBiasMult[] = {85, 42, 21, 16, 10, 5, 2, 0, -2, -5, -10, -16, -21, -74, -85};
// These values represent unique options with no consistent pattern, so we have to use something like a table in any case.
// The table entries, when divided by 21, match approximately what the manual claims:
// -1, -1/2, -1/4, 0, 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, 1, 5/4, 3/2, 2, s1, s2
// Note that the entry for 1/8 is rounded to 2 (from 1/8 * 21 = 2.625), which seems strangely inaccurate compared to the others.
static const Bit8s keyfollowMult21[] = {-21, -10, -5, 0, 2, 5, 8, 10, 13, 16, 18, 21, 26, 32, 42, 21, 21};
int baseCutoff = keyfollowMult21[partialParam->tvf.keyfollow] - keyfollowMult21[partialParam->wg.pitchKeyfollow];
// baseCutoff range now: -63 to 63
baseCutoff *= (int)key - 60;
// baseCutoff range now: -3024 to 3024
int biasPoint = partialParam->tvf.biasPoint;
if ((biasPoint & 0x40) == 0) {
// biasPoint range here: 0 to 63
int bias = biasPoint + 33 - key; // bias range here: -75 to 84
if (bias > 0) {
bias = -bias; // bias range here: -1 to -84
baseCutoff += bias * biasLevelToBiasMult[partialParam->tvf.biasLevel]; // Calculation range: -7140 to 7140
// baseCutoff range now: -10164 to 10164
}
} else {
// biasPoint range here: 64 to 127
int bias = biasPoint - 31 - key; // bias range here: -75 to 84
if (bias < 0) {
baseCutoff += bias * biasLevelToBiasMult[partialParam->tvf.biasLevel]; // Calculation range: 6375 to 6375
// baseCutoff range now: -9399 to 9399
}
}
// baseCutoff range now: -10164 to 10164
baseCutoff += ((partialParam->tvf.cutoff << 4) - 800);
// baseCutoff range now: -10964 to 10964
if (baseCutoff >= 0) {
// FIXME: Potentially bad if baseCutoff ends up below -2056?
int pitchDeltaThing = (basePitch >> 4) + baseCutoff - 3584;
if (pitchDeltaThing > 0) {
baseCutoff -= pitchDeltaThing;
}
} else if (baseCutoff < -2048) {
baseCutoff = -2048;
}
baseCutoff += 2056;
baseCutoff >>= 4; // PORTABILITY NOTE: Hmm... Depends whether it could've been below -2056, but maybe arithmetic shift assumed?
if (baseCutoff > 255) {
baseCutoff = 255;
}
return (Bit8u)baseCutoff;
}
TVF::TVF(const Partial *usePartial, LA32Ramp *useCutoffModifierRamp) :
partial(usePartial), cutoffModifierRamp(useCutoffModifierRamp) {
}
void TVF::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) {
target = newTarget;
phase = newPhase;
cutoffModifierRamp->startRamp(newTarget, newIncrement);
#if MT32EMU_MONITOR_TVF >= 1
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVF,ramp,%d,%d,%d,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), newTarget, (newIncrement & 0x80) ? -1 : 1, (newIncrement & 0x7F), newPhase);
#endif
}
void TVF::reset(const TimbreParam::PartialParam *newPartialParam, unsigned int basePitch) {
partialParam = newPartialParam;
unsigned int key = partial->getPoly()->getKey();
unsigned int velocity = partial->getPoly()->getVelocity();
Tables *tables = &partial->getSynth()->tables;
baseCutoff = calcBaseCutoff(newPartialParam, basePitch, key);
#if MT32EMU_MONITOR_TVF >= 1
partial->getSynth()->printDebug("[+%lu] [Partial %d] TVF,base,%d", partial->debugGetSampleNum(), partial->debugGetPartialNum(), baseCutoff);
#endif
int newLevelMult = velocity * newPartialParam->tvf.envVeloSensitivity;
newLevelMult >>= 6;
newLevelMult += 109 - newPartialParam->tvf.envVeloSensitivity;
newLevelMult += ((signed)key - 60) >> (4 - newPartialParam->tvf.envDepthKeyfollow);
if (newLevelMult < 0) {
newLevelMult = 0;
}
newLevelMult *= newPartialParam->tvf.envDepth;
newLevelMult >>= 6;
if (newLevelMult > 255) {
newLevelMult = 255;
}
levelMult = newLevelMult;
if (newPartialParam->tvf.envTimeKeyfollow != 0) {
keyTimeSubtraction = ((signed)key - 60) >> (5 - newPartialParam->tvf.envTimeKeyfollow);
} else {
keyTimeSubtraction = 0;
}
int newTarget = (newLevelMult * newPartialParam->tvf.envLevel[0]) >> 8;
int envTimeSetting = newPartialParam->tvf.envTime[0] - keyTimeSubtraction;
int newIncrement;
if (envTimeSetting <= 0) {
newIncrement = (0x80 | 127);
} else {
newIncrement = tables->envLogarithmicTime[newTarget] - envTimeSetting;
if (newIncrement <= 0) {
newIncrement = 1;
}
}
cutoffModifierRamp->reset();
startRamp(newTarget, newIncrement, PHASE_2 - 1);
}
Bit8u TVF::getBaseCutoff() const {
return baseCutoff;
}
void TVF::handleInterrupt() {
nextPhase();
}
void TVF::startDecay() {
if (phase >= PHASE_RELEASE) {
return;
}
if (partialParam->tvf.envTime[4] == 0) {
startRamp(0, 1, PHASE_DONE - 1);
} else {
startRamp(0, -partialParam->tvf.envTime[4], PHASE_DONE - 1);
}
}
void TVF::nextPhase() {
Tables *tables = &partial->getSynth()->tables;
int newPhase = phase + 1;
switch (newPhase) {
case PHASE_DONE:
startRamp(0, 0, newPhase);
return;
case PHASE_SUSTAIN:
case PHASE_RELEASE:
// FIXME: Afaict newPhase should never be PHASE_RELEASE here. And if it were, this is an odd way to handle it.
if (!partial->getPoly()->canSustain()) {
phase = newPhase; // FIXME: Correct?
startDecay(); // FIXME: This should actually start decay even if phase is already 6. Does that matter?
return;
}
startRamp((levelMult * partialParam->tvf.envLevel[3]) >> 8, 0, newPhase);
return;
}
int envPointIndex = phase;
int envTimeSetting = partialParam->tvf.envTime[envPointIndex] - keyTimeSubtraction;
int newTarget = (levelMult * partialParam->tvf.envLevel[envPointIndex]) >> 8;
int newIncrement;
if (envTimeSetting > 0) {
int targetDelta = newTarget - target;
if (targetDelta == 0) {
if (newTarget == 0) {
targetDelta = 1;
newTarget = 1;
} else {
targetDelta = -1;
newTarget--;
}
}
newIncrement = tables->envLogarithmicTime[targetDelta < 0 ? -targetDelta : targetDelta] - envTimeSetting;
if (newIncrement <= 0) {
newIncrement = 1;
}
if (targetDelta < 0) {
newIncrement |= 0x80;
}
} else {
newIncrement = newTarget >= target ? (0x80 | 127) : 127;
}
startRamp(newTarget, newIncrement, newPhase);
}
}

View File

@ -0,0 +1,54 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_TVF_H
#define MT32EMU_TVF_H
namespace MT32Emu {
class TVF {
private:
const Partial * const partial;
LA32Ramp *cutoffModifierRamp;
const TimbreParam::PartialParam *partialParam;
Bit8u baseCutoff;
int keyTimeSubtraction;
unsigned int levelMult;
Bit8u target;
unsigned int phase;
void startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase);
void nextPhase();
public:
TVF(const Partial *partial, LA32Ramp *cutoffModifierRamp);
void reset(const TimbreParam::PartialParam *partialParam, Bit32u basePitch);
// Returns the base cutoff (without envelope modification).
// The base cutoff is calculated when reset() is called and remains static
// for the lifetime of the partial.
// Barring bugs, the number returned is confirmed accurate
// (based on specs from Mok).
Bit8u getBaseCutoff() const;
void handleInterrupt();
void startDecay();
};
}
#endif

View File

@ -0,0 +1,321 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//#include <cmath>
//#include <cstdlib>
#include "mt32emu.h"
namespace MT32Emu {
// FIXME: Add Explanation
static Bit16u lowerDurationToDivisor[] = {34078, 37162, 40526, 44194, 48194, 52556, 57312, 62499};
// These values represent unique options with no consistent pattern, so we have to use something like a table in any case.
// The table matches exactly what the manual claims (when divided by 8192):
// -1, -1/2, -1/4, 0, 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, 1, 5/4, 3/2, 2, s1, s2
// ...except for the last two entries, which are supposed to be "1 cent above 1" and "2 cents above 1", respectively. They can only be roughly approximated with this integer math.
static Bit16s pitchKeyfollowMult[] = {-8192, -4096, -2048, 0, 1024, 2048, 3072, 4096, 5120, 6144, 7168, 8192, 10240, 12288, 16384, 8198, 8226};
// Note: Keys < 60 use keyToPitchTable[60 - key], keys >= 60 use keyToPitchTable[key - 60].
// FIXME: This table could really be shorter, since we never use e.g. key 127.
static Bit16u keyToPitchTable[] = {
0, 341, 683, 1024, 1365, 1707, 2048, 2389,
2731, 3072, 3413, 3755, 4096, 4437, 4779, 5120,
5461, 5803, 6144, 6485, 6827, 7168, 7509, 7851,
8192, 8533, 8875, 9216, 9557, 9899, 10240, 10581,
10923, 11264, 11605, 11947, 12288, 12629, 12971, 13312,
13653, 13995, 14336, 14677, 15019, 15360, 15701, 16043,
16384, 16725, 17067, 17408, 17749, 18091, 18432, 18773,
19115, 19456, 19797, 20139, 20480, 20821, 21163, 21504,
21845, 22187, 22528, 22869
};
TVP::TVP(const Partial *usePartial) :
partial(usePartial), system_(&usePartial->getSynth()->mt32ram.system) {
unsigned int sampleRate = usePartial->getSynth()->myProp.sampleRate;
// We want to do processing 4000 times per second. FIXME: This is pretty arbitrary.
maxCounter = sampleRate / 4000;
// The timer runs at 500kHz. We only need to bother updating it every maxCounter samples, before we do processing.
// This is how much to increment it by every maxCounter samples.
processTimerIncrement = 500000 * maxCounter / sampleRate;
}
static Bit16s keyToPitch(unsigned int key) {
// We're using a table to do: return round_to_nearest_or_even((key - 60) * (4096.0 / 12.0))
// Banker's rounding is just slightly annoying to do in C++
int k = (int)key;
Bit16s pitch = keyToPitchTable[abs(k - 60)];
return key < 60 ? -pitch : pitch;
}
static inline Bit32s coarseToPitch(Bit8u coarse) {
return (coarse - 36) * 4096 / 12; // One semitone per coarse offset
}
static inline Bit32s fineToPitch(Bit8u fine) {
return (fine - 50) * 4096 / 1200; // One cent per fine offset
}
static Bit32u calcBasePitch(const Partial *partial, const TimbreParam::PartialParam *partialParam, const MemParams::PatchTemp *patchTemp, unsigned int key) {
Bit32s basePitch = keyToPitch(key);
basePitch = (basePitch * pitchKeyfollowMult[partialParam->wg.pitchKeyfollow]) >> 13; // PORTABILITY NOTE: Assumes arithmetic shift
basePitch += coarseToPitch(partialParam->wg.pitchCoarse);
basePitch += fineToPitch(partialParam->wg.pitchFine);
// NOTE:Mok: This is done on MT-32, but not LAPC-I:
//pitch += coarseToPitch(patchTemp->patch.keyShift + 12);
basePitch += fineToPitch(patchTemp->patch.fineTune);
const ControlROMPCMStruct *controlROMPCMStruct = partial->getControlROMPCMStruct();
if (controlROMPCMStruct != NULL) {
basePitch += (Bit32s)((((Bit32s)controlROMPCMStruct->pitchMSB) << 8) | (Bit32s)controlROMPCMStruct->pitchLSB);
} else {
if ((partialParam->wg.waveform & 1) == 0) {
basePitch += 37133; // This puts Middle C at around 261.64Hz (assuming no other modifications, masterTune of 64, etc.)
} else {
// Sawtooth waves are effectively double the frequency of square waves.
// Thus we add 4096 less than for square waves here, which results in halving the frequency.
basePitch += 33037;
}
}
if (basePitch < 0) {
basePitch = 0;
}
if (basePitch > 59392) {
basePitch = 59392;
}
return (Bit32u)basePitch;
}
static Bit32u calcVeloMult(Bit8u veloSensitivity, unsigned int velocity) {
if (veloSensitivity == 0 || veloSensitivity > 3) {
// Note that on CM-32L/LAPC-I veloSensitivity is never > 3, since it's clipped to 3 by the max tables.
return 21845; // aka floor(4096 / 12 * 64), aka ~64 semitones
}
// When velocity is 127, the multiplier is 21845, aka ~64 semitones (regardless of veloSensitivity).
// The lower the velocity, the lower the multiplier. The veloSensitivity determines the amount decreased per velocity value.
// The minimum multiplier (with velocity 0, veloSensitivity 3) is 170 (~half a semitone).
Bit32u veloMult = 32768;
veloMult -= (127 - velocity) << (5 + veloSensitivity);
veloMult *= 21845;
veloMult >>= 15;
return veloMult;
}
static Bit32s calcTargetPitchOffsetWithoutLFO(const TimbreParam::PartialParam *partialParam, int levelIndex, unsigned int velocity) {
int veloMult = calcVeloMult(partialParam->pitchEnv.veloSensitivity, velocity);
int targetPitchOffsetWithoutLFO = partialParam->pitchEnv.level[levelIndex] - 50;
targetPitchOffsetWithoutLFO = (Bit32s)(targetPitchOffsetWithoutLFO * veloMult) >> (16 - partialParam->pitchEnv.depth); // PORTABILITY NOTE: Assumes arithmetic shift
return targetPitchOffsetWithoutLFO;
}
void TVP::reset(const Part *usePart, const TimbreParam::PartialParam *usePartialParam) {
part = usePart;
partialParam = usePartialParam;
patchTemp = part->getPatchTemp();
unsigned int key = partial->getPoly()->getKey();
unsigned int velocity = partial->getPoly()->getVelocity();
// FIXME: We're using a per-TVP timer instead of a system-wide one for convenience.
timeElapsed = 0;
basePitch = calcBasePitch(partial, partialParam, patchTemp, key);
currentPitchOffset = calcTargetPitchOffsetWithoutLFO(partialParam, 0, velocity);
targetPitchOffsetWithoutLFO = currentPitchOffset;
phase = 0;
if (partialParam->pitchEnv.timeKeyfollow) {
timeKeyfollowSubtraction = (key - 60) >> (5 - partialParam->pitchEnv.timeKeyfollow); // PORTABILITY NOTE: Assumes arithmetic shift
} else {
timeKeyfollowSubtraction = 0;
}
lfoPitchOffset = 0;
counter = 0;
pitch = basePitch;
// These don't really need to be initialised, but it aids debugging.
pitchOffsetChangePerBigTick = 0;
targetPitchOffsetReachedBigTick = 0;
shifts = 0;
}
Bit32u TVP::getBasePitch() const {
return basePitch;
}
void TVP::updatePitch() {
Bit32s newPitch = basePitch + currentPitchOffset;
if (!partial->isPCM() || (partial->getControlROMPCMStruct()->len & 0x01) == 0) { // FIXME: Use !partial->pcmWaveEntry->unaffectedByMasterTune instead
// FIXME: masterTune recalculation doesn't really happen here, and there are various bugs not yet emulated
// 171 is ~half a semitone.
newPitch += ((system_->masterTune - 64) * 171) >> 6; // PORTABILITY NOTE: Assumes arithmetic shift.
}
if ((partialParam->wg.pitchBenderEnabled & 1) != 0) {
newPitch += part->getPitchBend();
}
if (newPitch < 0) {
newPitch = 0;
}
if (newPitch > 59392) {
newPitch = 59392;
}
pitch = (Bit16u)newPitch;
// FIXME: We're doing this here because that's what the CM-32L does - we should probably move this somewhere more appropriate in future.
partial->tva->recalcSustain();
}
void TVP::targetPitchOffsetReached() {
currentPitchOffset = targetPitchOffsetWithoutLFO + lfoPitchOffset;
switch (phase) {
case 3:
case 4:
{
int newLFOPitchOffset = (part->getModulation() * partialParam->pitchLFO.modSensitivity) >> 7;
newLFOPitchOffset = (newLFOPitchOffset + partialParam->pitchLFO.depth) << 1;
if (pitchOffsetChangePerBigTick > 0) {
// Go in the opposite direction to last time
newLFOPitchOffset = -newLFOPitchOffset;
}
lfoPitchOffset = newLFOPitchOffset;
int targetPitchOffset = targetPitchOffsetWithoutLFO + lfoPitchOffset;
setupPitchChange(targetPitchOffset, 101 - partialParam->pitchLFO.rate);
updatePitch();
break;
}
case 6:
updatePitch();
break;
default:
nextPhase();
}
}
void TVP::nextPhase() {
phase++;
int envIndex = phase == 6 ? 4 : phase;
targetPitchOffsetWithoutLFO = calcTargetPitchOffsetWithoutLFO(partialParam, envIndex, partial->getPoly()->getVelocity()); // pitch we'll reach at the end
int changeDuration = partialParam->pitchEnv.time[envIndex - 1];
changeDuration -= timeKeyfollowSubtraction;
if (changeDuration > 0) {
setupPitchChange(targetPitchOffsetWithoutLFO, changeDuration); // changeDuration between 0 and 112 now
updatePitch();
} else {
targetPitchOffsetReached();
}
}
// Shifts val to the left until bit 31 is 1 and returns the number of shifts
static Bit8u normalise(Bit32u &val) {
Bit8u leftShifts;
for (leftShifts = 0; leftShifts < 31; leftShifts++) {
if ((val & 0x80000000) != 0) {
break;
}
val = val << 1;
}
return leftShifts;
}
void TVP::setupPitchChange(int targetPitchOffset, Bit8u changeDuration) {
bool negativeDelta = targetPitchOffset < currentPitchOffset;
Bit32s pitchOffsetDelta = targetPitchOffset - currentPitchOffset;
if (pitchOffsetDelta > 32767 || pitchOffsetDelta < -32768) {
pitchOffsetDelta = 32767;
}
if (negativeDelta) {
pitchOffsetDelta = -pitchOffsetDelta;
}
// We want to maximise the number of bits of the Bit16s "pitchOffsetChangePerBigTick" we use in order to get the best possible precision later
Bit32u absPitchOffsetDelta = pitchOffsetDelta << 16;
Bit8u normalisationShifts = normalise(absPitchOffsetDelta); // FIXME: Double-check: normalisationShifts is usually between 0 and 15 here, unless the delta is 0, in which case it's 31
absPitchOffsetDelta = absPitchOffsetDelta >> 1; // Make room for the sign bit
changeDuration--; // changeDuration's now between 0 and 111
unsigned int upperDuration = changeDuration >> 3; // upperDuration's now between 0 and 13
shifts = normalisationShifts + upperDuration + 2;
Bit16u divisor = lowerDurationToDivisor[changeDuration & 7];
Bit16s newPitchOffsetChangePerBigTick = ((absPitchOffsetDelta & 0xFFFF0000) / divisor) >> 1; // Result now fits within 15 bits. FIXME: Check nothing's getting sign-extended incorrectly
if (negativeDelta) {
newPitchOffsetChangePerBigTick = -newPitchOffsetChangePerBigTick;
}
pitchOffsetChangePerBigTick = newPitchOffsetChangePerBigTick;
int currentBigTick = timeElapsed >> 8;
int durationInBigTicks = divisor >> (12 - upperDuration);
if (durationInBigTicks > 32767) {
durationInBigTicks = 32767;
}
// The result of the addition may exceed 16 bits, but wrapping is fine and intended here.
targetPitchOffsetReachedBigTick = currentBigTick + durationInBigTicks;
}
void TVP::startDecay() {
phase = 5;
lfoPitchOffset = 0;
targetPitchOffsetReachedBigTick = timeElapsed >> 8; // FIXME: Afaict there's no good reason for this - check
}
Bit16u TVP::nextPitch() {
// FIXME: Write explanation of counter and time increment
if (counter == 0) {
timeElapsed += processTimerIncrement;
timeElapsed = timeElapsed & 0x00FFFFFF;
process();
}
counter = (counter + 1) % maxCounter;
return pitch;
}
void TVP::process() {
if (phase == 0) {
targetPitchOffsetReached();
return;
}
if (phase == 5) {
nextPhase();
return;
}
if (phase > 7) {
updatePitch();
return;
}
Bit16s negativeBigTicksRemaining = (timeElapsed >> 8) - targetPitchOffsetReachedBigTick;
if (negativeBigTicksRemaining >= 0) {
// We've reached the time for a phase change
targetPitchOffsetReached();
return;
}
// FIXME: Write explanation for this stuff
int rightShifts = shifts;
if (rightShifts > 13) {
rightShifts -= 13;
negativeBigTicksRemaining = negativeBigTicksRemaining >> rightShifts; // PORTABILITY NOTE: Assumes arithmetic shift
rightShifts = 13;
}
int newResult = ((Bit32s)(negativeBigTicksRemaining * pitchOffsetChangePerBigTick)) >> rightShifts; // PORTABILITY NOTE: Assumes arithmetic shift
newResult += targetPitchOffsetWithoutLFO + lfoPitchOffset;
currentPitchOffset = newResult;
updatePitch();
}
}

View File

@ -0,0 +1,67 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_TVP_H
#define MT32EMU_TVP_H
namespace MT32Emu {
class TVP {
private:
const Partial * const partial;
const MemParams::System * const system_; // FIXME: Only necessary because masterTune calculation is done in the wrong place atm.
const Part *part;
const TimbreParam::PartialParam *partialParam;
const MemParams::PatchTemp *patchTemp;
int maxCounter;
int processTimerIncrement;
int counter;
Bit32u timeElapsed;
int phase;
Bit32u basePitch;
Bit32s targetPitchOffsetWithoutLFO;
Bit32s currentPitchOffset;
Bit16s lfoPitchOffset;
// In range -12 - 36
Bit8s timeKeyfollowSubtraction;
Bit16s pitchOffsetChangePerBigTick;
Bit16u targetPitchOffsetReachedBigTick;
unsigned int shifts;
Bit16u pitch;
void updatePitch();
void setupPitchChange(int targetPitchOffset, Bit8u changeDuration);
void targetPitchOffsetReached();
void nextPhase();
void process();
public:
TVP(const Partial *partial);
void reset(const Part *part, const TimbreParam::PartialParam *partialParam);
Bit32u getBasePitch() const;
Bit16u nextPitch();
void startDecay();
};
}
#endif

View File

@ -0,0 +1,119 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//#include <cmath>
//#include <cstdlib>
//#include <cstring>
#include "mt32emu.h"
#include "mmath.h"
using namespace MT32Emu;
Tables::Tables() {
initialised = false;
}
void Tables::init() {
if (initialised) {
return;
}
initialised = true;
int lf;
for (lf = 0; lf <= 100; lf++) {
// CONFIRMED:KG: This matches a ROM table found by Mok
float fVal = (2.0f - LOG10F((float)lf + 1.0f)) * 128.0f;
int val = (int)(fVal + 1.0);
if (val > 255) {
val = 255;
}
levelToAmpSubtraction[lf] = (Bit8u)val;
}
envLogarithmicTime[0] = 64;
for (lf = 1; lf <= 255; lf++) {
// CONFIRMED:KG: This matches a ROM table found by Mok
envLogarithmicTime[lf] = (Bit8u)ceil(64.0f + LOG2F((float)lf) * 8.0f);
}
#ifdef EMULATE_LAPC_I // Dummy #ifdef - we'll have runtime emulation mode selection in future.
// CONFIRMED: Based on a table found by Mok in the LAPC-I control ROM
// Note that this matches the MT-32 table, but with the values clamped to a maximum of 8.
memset(masterVolToAmpSubtraction, 8, 71);
memset(masterVolToAmpSubtraction + 71, 7, 3);
memset(masterVolToAmpSubtraction + 74, 6, 4);
memset(masterVolToAmpSubtraction + 78, 5, 3);
memset(masterVolToAmpSubtraction + 81, 4, 4);
memset(masterVolToAmpSubtraction + 85, 3, 3);
memset(masterVolToAmpSubtraction + 88, 2, 4);
memset(masterVolToAmpSubtraction + 92, 1, 4);
memset(masterVolToAmpSubtraction + 96, 0, 5);
#else
// CONFIRMED: Based on a table found by Mok in the MT-32 control ROM
masterVolToAmpSubtraction[0] = 255;
for (int masterVol = 1; masterVol <= 100; masterVol++) {
masterVolToAmpSubtraction[masterVol] = (int)(106.31 - 16.0f * LOG2F((float)masterVol));
}
#endif
for (int i = 0; i <= 100; i++) {
pulseWidth100To255[i] = (int)(i * 255 / 100.0f + 0.5f);
//synth->printDebug("%d: %d", i, pulseWidth100To255[i]);
}
// Ratio of negative segment to wave length
for (int i = 0; i < 128; i++) {
// Formula determined from sample analysis.
float pt = 0.5f / 127.0f * i;
pulseLenFactor[i] = (1.241857812f - pt) * pt; // seems to be 2 ^ (5 / 16) = 1.241857812f
}
for (int i = 0; i < 65536; i++) {
// Aka (slightly slower): EXP2F(pitchVal / 4096.0f - 16.0f) * 32000.0f
pitchToFreq[i] = EXP2F(i / 4096.0f - 1.034215715f);
}
// found from sample analysis
for (int i = 0; i < 1024; i++) {
cutoffToCosineLen[i] = EXP2F(i / -128.0f);
}
// found from sample analysis
for (int i = 0; i < 1024; i++) {
cutoffToFilterAmp[i] = EXP2F(-0.125f * (128.0f - i / 8.0f));
}
// found from sample analysis
for (int i = 0; i < 32; i++) {
resAmpMax[i] = EXP2F(1.0f - (32 - i) / 4.0f);
}
// found from sample analysis
resAmpFadeFactor[7] = 1.0f / 8.0f;
resAmpFadeFactor[6] = 2.0f / 8.0f;
resAmpFadeFactor[5] = 3.0f / 8.0f;
resAmpFadeFactor[4] = 5.0f / 8.0f;
resAmpFadeFactor[3] = 8.0f / 8.0f;
resAmpFadeFactor[2] = 12.0f / 8.0f;
resAmpFadeFactor[1] = 16.0f / 8.0f;
resAmpFadeFactor[0] = 31.0f / 8.0f;
for (int i = 0; i < 5120; i++) {
sinf10[i] = sin(FLOAT_PI * i / 2048.0f);
}
}

View File

@ -0,0 +1,64 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_TABLES_H
#define MT32EMU_TABLES_H
namespace MT32Emu {
const int MIDDLEC = 60;
class Synth;
class Tables {
bool initialised;
public:
// Constant LUTs
// CONFIRMED: This is used to convert several parameters to amp-modifying values in the TVA envelope:
// - PatchTemp.outputLevel
// - RhythmTemp.outlevel
// - PartialParam.tva.level
// - expression
// It's used to determine how much to subtract from the amp envelope's target value
Bit8u levelToAmpSubtraction[101];
// CONFIRMED: ...
Bit8u envLogarithmicTime[256];
// CONFIRMED: ...
Bit8u masterVolToAmpSubtraction[101];
// CONFIRMED:
Bit8u pulseWidth100To255[101];
float pulseLenFactor[128];
float pitchToFreq[65536];
float cutoffToCosineLen[1024];
float cutoffToFilterAmp[1024];
float resAmpMax[32];
float resAmpFadeFactor[8];
float sinf10[5120];
Tables();
void init();
};
}
#endif

View File

@ -1,245 +1,245 @@
/* 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.
*
*/
// Comb filter implementation
// Allpass filter implementation
//
// Written by
// Written by Jezar at Dreampoint, June 2000
// http://www.dreampoint.co.uk
// This code is public domain
#include "audio/softsynth/mt32/freeverb.h"
#include "freeverb.h"
comb::comb() {
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;
}
void allpass::deletebuffer()
{
delete[] buffer;
buffer = 0;
}
// Comb filter implementation
//
// Written by Jezar at Dreampoint, June 2000
// http://www.dreampoint.co.uk
// This code is public domain
comb::comb()
{
filterstore = 0;
bufidx = 0;
}
void comb::setbuffer(float *buf, int size) {
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::mute()
{
for (int i=0; i<bufsize; i++)
buffer[i]=0;
}
void comb::setdamp(float val) {
void comb::setdamp(float val)
{
damp1 = val;
damp2 = 1 - val;
damp2 = 1-val;
}
float comb::getdamp() {
float comb::getdamp()
{
return damp1;
}
void comb::setfeedback(float val) {
void comb::setfeedback(float val)
{
feedback = val;
}
float comb::getfeedback() {
float comb::getfeedback()
{
return feedback;
}
// Allpass filter implementation
allpass::allpass() {
bufidx = 0;
void comb::deletebuffer()
{
delete[] buffer;
buffer = 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
//
// Written by Jezar at Dreampoint, June 2000
// Modifications by Jerome Fisher, 2009, 2011
// http://www.dreampoint.co.uk
// This code is public domain
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);
revmodel::revmodel(float scaletuning)
{
int i;
int bufsize;
// Allocate buffers for the components
for (i = 0; i < numcombs; i++) {
bufsize = int(scaletuning * combtuning[i]);
combL[i].setbuffer(new float[bufsize], bufsize);
bufsize += int(scaletuning * stereospread);
combR[i].setbuffer(new float[bufsize], bufsize);
}
for (i = 0; i < numallpasses; i++) {
bufsize = int(scaletuning * allpasstuning[i]);
allpassL[i].setbuffer(new float[bufsize], bufsize);
allpassL[i].setfeedback(0.5f);
bufsize += int(scaletuning * stereospread);
allpassR[i].setbuffer(new float[bufsize], bufsize);
allpassR[i].setfeedback(0.5f);
}
// 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);
dry = initialdry;
wet = initialwet*scalewet;
damp = initialdamp*scaledamp;
roomsize = (initialroom*scaleroom) + offsetroom;
width = initialwidth;
mode = initialmode;
update();
// Buffer will be full of rubbish - so we MUST mute them
mute();
}
void revmodel::mute() {
revmodel::~revmodel()
{
int i;
for (i = 0; i < numcombs; i++) {
combL[i].deletebuffer();
combR[i].deletebuffer();
}
for (i = 0; i < numallpasses; i++) {
allpassL[i].deletebuffer();
allpassR[i].deletebuffer();
}
}
void revmodel::mute()
{
int i;
if (getmode() >= freezemode)
return;
for (i = 0; i < numcombs; i++) {
for (i=0;i<numcombs;i++)
{
combL[i].mute();
combR[i].mute();
}
for (i = 0; i < numallpasses; i++) {
for (i=0;i<numallpasses;i++)
{
allpassL[i].mute();
allpassR[i].mute();
}
// Init LPF history
filtprev1 = 0;
filtprev2 = 0;
}
void revmodel::processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip) {
float outL, outR, input;
void revmodel::process(const float *inputL, const float *inputR, float *outputL, float *outputR, long numsamples)
{
float outL,outR,input;
while (numsamples-- > 0) {
while (numsamples-- > 0)
{
int i;
outL = outR = 0;
input = (*inputL + *inputR) * gain;
// Implementation of 2-stage IIR single-pole low-pass filter
// found at the entrance of reverb processing on real devices
filtprev1 += (input - filtprev1) * filtval;
filtprev2 += (filtprev1 - filtprev2) * filtval;
input = filtprev2;
int s = -1;
// Accumulate comb filters in parallel
for (i = 0; i < numcombs; i++) {
outL += combL[i].process(input);
outR += combR[i].process(input);
for (i=0; i<numcombs; i++)
{
outL += s * combL[i].process(input);
outR += s * combR[i].process(input);
s = -s;
}
// Feed through allpasses in series
for (i = 0; i < numallpasses; i++) {
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;
*outputL = outL*wet1 + outR*wet2;
*outputR = outR*wet1 + outL*wet2;
inputL++;
inputR++;
outputL++;
outputR++;
}
}
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
void revmodel::update()
{
// Recalculate internal values after parameter change
int i;
wet1 = wet * (width / 2 + 0.5f);
wet2 = wet * ((1 - width) / 2);
wet1 = wet*(width/2 + 0.5f);
wet2 = wet*((1-width)/2);
if (mode >= freezemode) {
if (mode >= freezemode)
{
roomsize1 = 1;
damp1 = 0;
gain = muted;
} else {
}
else
{
roomsize1 = roomsize;
damp1 = damp;
gain = fixedgain;
}
for (i = 0; i < numcombs; i++) {
for (i=0; i<numcombs; i++)
{
combL[i].setfeedback(roomsize1);
combR[i].setfeedback(roomsize1);
}
for (i = 0; i < numcombs; i++) {
for (i=0; i<numcombs; i++)
{
combL[i].setdamp(damp1);
combR[i].setdamp(damp1);
}
@ -250,58 +250,75 @@ void revmodel::update() {
// 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;
void revmodel::setroomsize(float value)
{
roomsize = (value*scaleroom) + offsetroom;
update();
}
float revmodel::getroomsize() {
return (roomsize - offsetroom) / scaleroom;
float revmodel::getroomsize()
{
return (roomsize-offsetroom)/scaleroom;
}
void revmodel::setdamp(float value) {
damp = value * scaledamp;
void revmodel::setdamp(float value)
{
damp = value*scaledamp;
update();
}
float revmodel::getdamp() {
return damp / scaledamp;
float revmodel::getdamp()
{
return damp/scaledamp;
}
void revmodel::setwet(float value) {
wet = value * scalewet;
void revmodel::setwet(float value)
{
wet = value*scalewet;
update();
}
float revmodel::getwet() {
return wet / scalewet;
float revmodel::getwet()
{
return wet/scalewet;
}
void revmodel::setdry(float value) {
dry = value * scaledry;
void revmodel::setdry(float value)
{
dry = value*scaledry;
}
float revmodel::getdry() {
return dry / scaledry;
float revmodel::getdry()
{
return dry/scaledry;
}
void revmodel::setwidth(float value) {
void revmodel::setwidth(float value)
{
width = value;
update();
}
float revmodel::getwidth() {
float revmodel::getwidth()
{
return width;
}
void revmodel::setmode(float value) {
void revmodel::setmode(float value)
{
mode = value;
update();
}
float revmodel::getmode() {
float revmodel::getmode()
{
if (mode >= freezemode)
return 1;
else
return 0;
}
void revmodel::setfiltval(float value)
{
filtval = value;
}

View File

@ -1,24 +1,32 @@
/* 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.
#ifndef _freeverb_
#define _freeverb_
* 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.
// Reverb model tuning values
//
// Written by Jezar at Dreampoint, June 2000
// http://www.dreampoint.co.uk
// This code is public domain
* 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.
*
*/
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;
const int combtuning[] = {1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617};
const int allpasstuning[] = {556, 441, 341, 225};
// Macro for killing denormalled numbers
//
@ -27,215 +35,155 @@
// 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)
static inline float undenormalise(float x) {
union {
float f;
unsigned int i;
} u;
u.f = x;
if ((u.i & 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;
}
return x;
}
// Allpass filter declaration
//
// Written by Jezar at Dreampoint, June 2000
// http://www.dreampoint.co.uk
// This code is public domain
class allpass {
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;
allpass();
void setbuffer(float *buf, int size);
void deletebuffer();
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) {
inline float allpass::process(float input)
{
float output;
float bufout;
bufout = buffer[bufidx];
undenormalise(&bufout);
bufout = undenormalise(buffer[bufidx]);
output = -input + bufout;
buffer[bufidx] = input + (bufout * feedback);
buffer[bufidx] = input + (bufout*feedback);
if (++bufidx >= bufsize)
bufidx = 0;
if (++bufidx>=bufsize) bufidx = 0;
return output;
}
// Comb filter class declaration
//
// Written by Jezar at Dreampoint, June 2000
// http://www.dreampoint.co.uk
// This code is public domain
// Reverb model tuning values
class comb
{
public:
comb();
void setbuffer(float *buf, int size);
void deletebuffer();
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;
};
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;
// Big to inline - but crucial for speed
inline float comb::process(float input)
{
float output;
output = undenormalise(buffer[bufidx]);
filterstore = undenormalise((output*damp2) + (filterstore*damp1));
buffer[bufidx] = input + (filterstore*feedback);
if (++bufidx>=bufsize) bufidx = 0;
return output;
}
// Reverb model declaration
//
// Written by Jezar at Dreampoint, June 2000
// Modifications by Jerome Fisher, 2009
// http://www.dreampoint.co.uk
// This code is public domain
class revmodel {
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();
revmodel(float scaletuning);
~revmodel();
void mute();
void process(const float *inputL, const float *inputR, float *outputL, float *outputR, long numsamples);
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();
void setfiltval(float value);
private:
void update();
void update();
private:
float gain;
float roomsize,roomsize1;
float damp,damp1;
float wet,wet1,wet2;
float dry;
float width;
float mode;
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
// LPF stuff
float filtval;
float filtprev1;
float filtprev2;
// Comb filters
comb combL[numcombs];
comb combR[numcombs];
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
#endif//_freeverb_

View File

@ -1,849 +0,0 @@
/* 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

View File

@ -1,49 +0,0 @@
/* 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

View File

@ -0,0 +1,73 @@
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MT32EMU_MMATH_H
#define MT32EMU_MMATH_H
#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))
#define FIXEDPOINT_MAKE(x, point) ((Bit32u)((1 << point) * x))
namespace MT32Emu {
// Mathematical constants
const double DOUBLE_PI = 3.141592653589793;
const double DOUBLE_LN_10 = 2.302585092994046;
const float FLOAT_PI = 3.1415927f;
const float FLOAT_2PI = 6.2831853f;
const float FLOAT_LN_2 = 0.6931472f;
const float FLOAT_LN_10 = 2.3025851f;
static inline float POWF(float x, float y) {
return pow(x, y);
}
static inline float EXPF(float x) {
return exp(x);
}
static inline float EXP2F(float x) {
#ifdef __APPLE__
// on OSX exp2f() is 1.59 times faster than "exp() and the multiplication with FLOAT_LN_2"
return exp2f(x);
#else
return exp(FLOAT_LN_2 * x);
#endif
}
static inline float EXP10F(float x) {
return exp(FLOAT_LN_10 * x);
}
static inline float LOGF(float x) {
return log(x);
}
static inline float LOG2F(float x) {
return log(x) / FLOAT_LN_2;
}
static inline float LOG10F(float x) {
return log10(x);
}
}
#endif

View File

@ -1,13 +1,19 @@
MODULE := audio/softsynth/mt32
MODULE_OBJS := \
mt32_file.o \
i386.o \
part.o \
partial.o \
partialManager.o \
synth.o \
tables.o \
AReverbModel.o \
DelayReverb.o \
FreeverbModel.o \
LA32Ramp.o \
Part.o \
Partial.o \
PartialManager.o \
Poly.o \
Synth.o \
TVA.o \
TVF.o \
TVP.o \
Tables.o \
freeverb.o
# Include common rules

View File

@ -1,69 +0,0 @@
/* 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

View File

@ -1,52 +0,0 @@
/* 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

View File

@ -1,37 +1,70 @@
/* Copyright (c) 2003-2005 Various contributors
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
* Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
*
* 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:
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* 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.
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#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
// 0: Standard debug output is not stamped with the rendered sample count
// 1: Standard debug output is stamped with the rendered sample count
// NOTE: The "samplestamp" corresponds to the end of the last completed rendering run.
// This is important to bear in mind for debug output that occurs during a run.
#define MT32EMU_DEBUG_SAMPLESTAMPS 0
// 0: No debug output for initialisation progress
// 1: Debug output for initialisation progress
#define MT32EMU_MONITOR_INIT 0
// 0: No debug output for MIDI events
// 1: Debug output for weird MIDI events
#define MT32EMU_MONITOR_MIDI 0
// 0: No debug output for note on/off
// 1: Basic debug output for note on/off
// 2: Comprehensive debug output for note on/off
#define MT32EMU_MONITOR_INSTRUMENTS 0
// 0: No debug output for partial allocations
// 1: Show partial stats when an allocation fails
// 2: Show partial stats with every new poly
// 3: Show individual partial allocations/deactivations
#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
// 0: No debug output for sysex
// 1: Basic debug output for sysex
#define MT32EMU_MONITOR_SYSEX 0
// 0: No debug output for sysex writes to the timbre areas
// 1: Debug output with the name and location of newly-written timbres
// 2: Complete dump of timbre parameters for newly-written timbres
#define MT32EMU_MONITOR_TIMBRES 0
// 0: No TVA/TVF-related debug output.
// 1: Shows changes to TVA/TVF target, increment and phase.
#define MT32EMU_MONITOR_TVA 0
#define MT32EMU_MONITOR_TVF 0
// 0: Use LUTs to speedup WG
// 1: Use precise float math
#define MT32EMU_ACCURATE_WG 1
#define MT32EMU_USE_EXTINT 0
// Configuration
// The maximum number of partials playing simultaneously
@ -39,32 +72,43 @@
// 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
// If non-zero, deletes reverb buffers that are not in use to save memory.
// If zero, keeps reverb buffers for all modes around all the time to avoid allocating/freeing in the critical path.
#define MT32EMU_REDUCE_REVERB_MEMORY 1
#ifdef MT32EMU_HAVE_X86
#define MT32EMU_USE_MMX 1
#else
#define MT32EMU_USE_MMX 0
#endif
// 0: Use standard Freeverb
// 1: Use AReverb (currently not properly tuned)
#define MT32EMU_USE_AREVERBMODEL 0
#include "freeverb.h"
namespace MT32Emu
{
// The higher this number, the more memory will be used, but the more samples can be processed in one run -
// various parts of sample generation can be processed more efficiently in a single run.
// A run's maximum length is that given to Synth::render(), so giving a value here higher than render() is ever
// called with will give no gain (but simply waste the memory).
// Note that this value does *not* in any way impose limitations on the length given to render(), and has no effect
// on the generated audio.
// This value must be >= 1.
const unsigned int MAX_SAMPLES_PER_RUN = 4096;
#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"
// This determines the amount of memory available for simulating delays.
// If set too low, partials aborted to allow other partials to play will not end gracefully, but will terminate
// abruptly and potentially cause a pop/crackle in the audio output.
// This value must be >= 1.
const unsigned int MAX_PRERENDER_SAMPLES = 1024;
}
#include "Structures.h"
#include "common/file.h"
#include "Tables.h"
#include "Poly.h"
#include "LA32Ramp.h"
#include "TVA.h"
#include "TVP.h"
#include "TVF.h"
#include "Partial.h"
#include "Part.h"
#include "Synth.h"
#endif

View File

@ -1,633 +0,0 @@
/* 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, center 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 &currentInstr[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%, center 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]);
}
}
}

View File

@ -1,112 +0,0 @@
/* 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 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

View File

@ -1,968 +0,0 @@
/* 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 older 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 = &noteLookupStorage;
#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); // Initializes noteVal, filtVal and realVal
#if MT32EMU_ACCURATENOTES == 0
noteLookup = &synth->tables.noteLookups[noteVal - LOWEST_NOTE];
#else
Tables::initNote(synth, &noteLookupStorage, 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++;
}

View File

@ -1,148 +0,0 @@
/* 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

View File

@ -1,272 +0,0 @@
/* 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];
}

View File

@ -1,56 +0,0 @@
/* 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

View File

@ -1,284 +0,0 @@
/* 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

File diff suppressed because it is too large Load Diff

View File

@ -1,299 +0,0 @@
/* 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 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 initialize 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

View File

@ -1,761 +0,0 @@
/* 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.
*/
// FIXME: Avoid using rand
#define FORBIDDEN_SYMBOL_EXCEPTION_rand
#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 older 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("Initializing 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 *)&noteLookup->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("Initializing note %s%d", NoteNames[f % 12], (f / 12) - 2);
NoteLookup *noteLookup = &noteLookups[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;
}
}
}
initializedMasterTune = 0.0f;
}
Tables::Tables() {
initializedSampleRate = 0.0f;
initializedMasterTune = 0.0f;
memset(&noteLookups, 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 (initializedSampleRate == 0.0f) {
initMT32ConstantTables(synth);
}
if (initializedSampleRate != sampleRate) {
initFiltCoeff(sampleRate);
initEnvelopes(sampleRate);
for (int key = 12; key <= 108; key++) {
initDep(&keyLookups[key - 12], (float)key);
}
}
if (initializedSampleRate != sampleRate || initializedMasterTune != masterTune) {
freeNotes();
if (!initNotes(synth, pcmWaves, sampleRate, masterTune)) {
return false;
}
initializedSampleRate = sampleRate;
initializedMasterTune = masterTune;
}
return true;
}
}

View File

@ -1,116 +0,0 @@
/* 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 initializedSampleRate;
float initializedMasterTune;
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