2012-02-10 07:51:41 +01:00
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
2013-07-07 15:29:33 +02:00
* Copyright ( C ) 2011 , 2012 , 2013 Dean Beeler , Jerome Fisher , Sergey V . Mikayev
2010-01-21 19:15:34 +00:00
*
2012-02-10 07:51:41 +01:00
* 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 .
2010-01-21 19:15:34 +00:00
*
2012-02-10 07:51:41 +01:00
* 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 .
2010-01-21 19:15:34 +00:00
*
2012-02-10 07:51:41 +01:00
* 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/>.
2010-01-21 19:15:34 +00:00
*/
2012-02-10 07:51:41 +01:00
//#include <cmath>
//#include <cstdlib>
//#include <cstring>
2010-01-21 19:15:34 +00:00
# include "mt32emu.h"
2012-02-10 07:51:41 +01:00
# include "mmath.h"
2010-01-21 19:15:34 +00:00
2012-11-18 19:07:46 +01:00
namespace MT32Emu {
2010-01-21 19:15:34 +00:00
2012-02-10 07:51:41 +01:00
# 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 ) :
2013-07-07 15:29:33 +02:00
synth ( useSynth ) , debugPartialNum ( useDebugPartialNum ) , sampleNum ( 0 ) {
// Initialisation of tva, tvp and tvf uses 'this' pointer
// and thus should not be in the initializer list to avoid a compiler warning
tva = new TVA ( this , & ampRamp ) ;
tvp = new TVP ( this ) ;
tvf = new TVF ( this , & cutoffModifierRamp ) ;
2010-01-21 19:15:34 +00:00
ownerPart = - 1 ;
poly = NULL ;
pair = NULL ;
}
Partial : : ~ Partial ( ) {
2012-02-10 07:51:41 +01:00
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 ;
2010-01-21 19:15:34 +00:00
}
int Partial : : getOwnerPart ( ) const {
return ownerPart ;
}
2012-02-10 07:51:41 +01:00
bool Partial : : isActive ( ) const {
2010-01-21 19:15:34 +00:00
return ownerPart > - 1 ;
}
2012-02-10 07:51:41 +01:00
const Poly * Partial : : getPoly ( ) const {
return poly ;
2010-01-21 19:15:34 +00:00
}
void Partial : : activate ( int part ) {
// This just marks the partial as being assigned to a part
ownerPart = part ;
}
void Partial : : deactivate ( ) {
2012-02-10 07:51:41 +01:00
if ( ! isActive ( ) ) {
return ;
}
2010-01-21 19:15:34 +00:00
ownerPart = - 1 ;
if ( poly ! = NULL ) {
2012-02-10 07:51:41 +01:00
poly - > partialDeactivated ( this ) ;
2010-01-21 19:15:34 +00:00
}
2012-02-10 07:51:41 +01:00
# if MT32EMU_MONITOR_PARTIALS > 2
synth - > printDebug ( " [+%lu] [Partial %d] Deactivated " , sampleNum , debugPartialNum ) ;
synth - > printPartialUsage ( sampleNum ) ;
2010-01-21 19:15:34 +00:00
# endif
2013-07-07 15:29:33 +02:00
if ( isRingModulatingSlave ( ) ) {
pair - > la32Pair . deactivate ( LA32PartialPair : : SLAVE ) ;
2010-01-21 19:15:34 +00:00
} else {
2013-07-07 15:29:33 +02:00
la32Pair . deactivate ( LA32PartialPair : : MASTER ) ;
if ( hasRingModulatingSlave ( ) ) {
pair - > deactivate ( ) ;
pair = NULL ;
}
}
if ( pair ! = NULL ) {
pair - > pair = NULL ;
2010-01-21 19:15:34 +00:00
}
}
2012-02-10 07:51:41 +01:00
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 " ) ;
2010-01-21 19:15:34 +00:00
return ;
}
2012-02-10 07:51:41 +01:00
patchCache = usePatchCache ;
2010-01-21 19:15:34 +00:00
poly = usePoly ;
mixType = patchCache - > structureMix ;
structurePosition = patchCache - > structurePosition ;
2012-02-10 07:51:41 +01:00
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 ;
2010-01-21 19:15:34 +00:00
2012-11-18 19:07:46 +01:00
// SEMI-CONFIRMED: From sample analysis:
// Found that timbres with 3 or 4 partials (i.e. one using two partial pairs) are mixed in two different ways.
// Either partial pairs are added or subtracted, it depends on how the partial pairs are allocated.
// It seems that partials are grouped into quarters and if the partial pairs are allocated in different quarters the subtraction happens.
// Though, this matters little for the majority of timbres, it becomes crucial for timbres which contain several partials that sound very close.
// In this case that timbre can sound totally different depending of the way it is mixed up.
// Most easily this effect can be displayed with the help of a special timbre consisting of several identical square wave partials (3 or 4).
// Say, it is 3-partial timbre. Just play any two notes simultaneously and the polys very probably are mixed differently.
// Moreover, the partial allocator retains the last partial assignment it did and all the subsequent notes will sound the same as the last released one.
// The situation is better with 4-partial timbres since then a whole quarter is assigned for each poly. However, if a 3-partial timbre broke the normal
// whole-quarter assignment or after some partials got aborted, even 4-partial timbres can be found sounding differently.
// This behaviour is also confirmed with two more special timbres: one with identical sawtooth partials, and one with PCM wave 02.
// For my personal taste, this behaviour rather enriches the sounding and should be emulated.
// Also, the current partial allocator model probably needs to be refined.
if ( debugPartialNum & 8 ) {
stereoVolume . leftVol = - stereoVolume . leftVol ;
stereoVolume . rightVol = - stereoVolume . rightVol ;
}
2010-01-21 19:15:34 +00:00
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 ;
}
2012-02-10 07:51:41 +01:00
// CONFIRMED: pulseWidthVal calculation is based on information from Mok
2012-11-18 19:07:46 +01:00
pulseWidthVal = ( poly - > getVelocity ( ) - 64 ) * ( patchCache - > srcPartial . wg . pulseWidthVeloSensitivity - 7 ) + Tables : : getInstance ( ) . pulseWidth100To255 [ patchCache - > srcPartial . wg . pulseWidth ] ;
2012-02-10 07:51:41 +01:00
if ( pulseWidthVal < 0 ) {
pulseWidthVal = 0 ;
} else if ( pulseWidthVal > 255 ) {
pulseWidthVal = 255 ;
2010-01-21 19:15:34 +00:00
}
pair = pairPartial ;
alreadyOutputed = false ;
2012-02-10 07:51:41 +01:00
tva - > reset ( part , patchCache - > partialParam , rhythmTemp ) ;
tvp - > reset ( part , patchCache - > partialParam ) ;
tvf - > reset ( patchCache - > partialParam , tvp - > getBasePitch ( ) ) ;
2013-07-07 15:29:33 +02:00
LA32PartialPair : : PairType pairType ;
LA32PartialPair * useLA32Pair ;
if ( isRingModulatingSlave ( ) ) {
pairType = LA32PartialPair : : SLAVE ;
useLA32Pair = & pair - > la32Pair ;
} else {
pairType = LA32PartialPair : : MASTER ;
la32Pair . init ( hasRingModulatingSlave ( ) , mixType = = 1 ) ;
useLA32Pair = & la32Pair ;
}
if ( isPCM ( ) ) {
useLA32Pair - > initPCM ( pairType , & synth - > pcmROMData [ pcmWave - > addr ] , pcmWave - > len , pcmWave - > loop ) ;
} else {
useLA32Pair - > initSynth ( pairType , ( patchCache - > waveform & 1 ) ! = 0 , pulseWidthVal , patchCache - > srcPartial . tvf . resonance + 1 ) ;
}
if ( ! hasRingModulatingSlave ( ) ) {
la32Pair . deactivate ( LA32PartialPair : : SLAVE ) ;
}
// Temporary integration hack
stereoVolume . leftVol / = 8192.0f ;
stereoVolume . rightVol / = 8192.0f ;
2010-01-21 19:15:34 +00:00
}
2013-07-07 15:29:33 +02:00
Bit32u Partial : : getAmpValue ( ) {
// 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.
// TODO: The tests above were performed using the float model, to be refined
Bit32u ampRampVal = 67117056 - ampRamp . nextValue ( ) ;
if ( ampRamp . checkInterrupt ( ) ) {
tva - > handleInterrupt ( ) ;
2012-02-10 07:51:41 +01:00
}
2013-07-07 15:29:33 +02:00
return ampRampVal ;
2012-02-10 07:51:41 +01:00
}
2013-07-07 15:29:33 +02:00
Bit32u Partial : : getCutoffValue ( ) {
if ( isPCM ( ) ) {
return 0 ;
}
Bit32u cutoffModifierRampVal = cutoffModifierRamp . nextValue ( ) ;
if ( cutoffModifierRamp . checkInterrupt ( ) ) {
tvf - > handleInterrupt ( ) ;
}
return ( tvf - > getBaseCutoff ( ) < < 18 ) + cutoffModifierRampVal ;
}
unsigned long Partial : : generateSamples ( Bit16s * partialBuf , unsigned long length ) {
2010-01-21 19:15:34 +00:00
if ( ! isActive ( ) | | alreadyOutputed ) {
2012-02-10 07:51:41 +01:00
return 0 ;
2010-01-21 19:15:34 +00:00
}
if ( poly = = NULL ) {
2012-02-10 07:51:41 +01:00
synth - > printDebug ( " [Partial %d] *** ERROR: poly is NULL at Partial::generateSamples()! " , debugPartialNum ) ;
return 0 ;
2010-01-21 19:15:34 +00:00
}
alreadyOutputed = true ;
2012-02-10 07:51:41 +01:00
for ( sampleNum = 0 ; sampleNum < length ; sampleNum + + ) {
2013-07-07 15:29:33 +02:00
if ( ! tva - > isPlaying ( ) | | ! la32Pair . isActive ( LA32PartialPair : : MASTER ) ) {
2012-02-10 07:51:41 +01:00
deactivate ( ) ;
break ;
}
2013-07-07 15:29:33 +02:00
la32Pair . generateNextSample ( LA32PartialPair : : MASTER , getAmpValue ( ) , tvp - > nextPitch ( ) , getCutoffValue ( ) ) ;
if ( hasRingModulatingSlave ( ) ) {
la32Pair . generateNextSample ( LA32PartialPair : : SLAVE , pair - > getAmpValue ( ) , pair - > tvp - > nextPitch ( ) , pair - > getCutoffValue ( ) ) ;
if ( ! pair - > tva - > isPlaying ( ) | | ! la32Pair . isActive ( LA32PartialPair : : SLAVE ) ) {
pair - > deactivate ( ) ;
if ( mixType = = 2 ) {
deactivate ( ) ;
break ;
2012-02-10 07:51:41 +01:00
}
}
}
2013-07-07 15:29:33 +02:00
* partialBuf + + = la32Pair . nextOutSample ( ) ;
2010-01-21 19:15:34 +00:00
}
2012-02-10 07:51:41 +01:00
unsigned long renderedSamples = sampleNum ;
sampleNum = 0 ;
return renderedSamples ;
2010-01-21 19:15:34 +00:00
}
2012-02-10 07:51:41 +01:00
bool Partial : : hasRingModulatingSlave ( ) const {
return pair ! = NULL & & structurePosition = = 0 & & ( mixType = = 1 | | mixType = = 2 ) ;
2010-01-21 19:15:34 +00:00
}
2012-02-10 07:51:41 +01:00
bool Partial : : isRingModulatingSlave ( ) const {
return pair ! = NULL & & structurePosition = = 1 & & ( mixType = = 1 | | mixType = = 2 ) ;
}
2010-01-21 19:15:34 +00:00
2012-02-10 07:51:41 +01:00
bool Partial : : isPCM ( ) const {
return pcmWave ! = NULL ;
}
const ControlROMPCMStruct * Partial : : getControlROMPCMStruct ( ) const {
if ( pcmWave ! = NULL ) {
return pcmWave - > controlROMPCMStruct ;
2010-01-21 19:15:34 +00:00
}
2012-02-10 07:51:41 +01:00
return NULL ;
}
2010-01-21 19:15:34 +00:00
2012-02-10 07:51:41 +01:00
Synth * Partial : : getSynth ( ) const {
return synth ;
}
2010-01-21 19:15:34 +00:00
2012-02-10 07:51:41 +01:00
bool Partial : : produceOutput ( float * leftBuf , float * rightBuf , unsigned long length ) {
if ( ! isActive ( ) | | alreadyOutputed | | isRingModulatingSlave ( ) ) {
2010-01-21 19:15:34 +00:00
return false ;
}
2012-02-10 07:51:41 +01:00
if ( poly = = NULL ) {
synth - > printDebug ( " [Partial %d] *** ERROR: poly is NULL at Partial::produceOutput()! " , debugPartialNum ) ;
2010-01-21 19:15:34 +00:00
return false ;
}
2013-07-07 15:29:33 +02:00
unsigned long numGenerated = generateSamples ( myBuffer , length ) ;
2012-02-10 07:51:41 +01:00
for ( unsigned int i = 0 ; i < numGenerated ; i + + ) {
2013-07-07 15:29:33 +02:00
* leftBuf + + = myBuffer [ i ] * stereoVolume . leftVol ;
* rightBuf + + = myBuffer [ i ] * stereoVolume . rightVol ;
2010-01-21 19:15:34 +00:00
}
2013-07-07 15:29:33 +02:00
for ( ; numGenerated < length ; numGenerated + + ) {
2012-02-10 07:51:41 +01:00
* leftBuf + + = 0.0f ;
* rightBuf + + = 0.0f ;
2010-01-21 19:15:34 +00:00
}
2012-02-10 07:51:41 +01:00
return true ;
2010-01-21 19:15:34 +00:00
}
2012-02-10 07:51:41 +01:00
bool Partial : : shouldReverb ( ) {
if ( ! isActive ( ) ) {
return false ;
2010-01-21 19:15:34 +00:00
}
2012-02-10 07:51:41 +01:00
return patchCache - > reverb ;
2010-01-21 19:15:34 +00:00
}
2012-02-10 07:51:41 +01:00
void Partial : : startAbort ( ) {
// This is called when the partial manager needs to terminate partials for re-use by a new Poly.
tva - > startAbort ( ) ;
2010-01-21 19:15:34 +00:00
}
2012-02-10 07:51:41 +01:00
void Partial : : startDecayAll ( ) {
tva - > startDecay ( ) ;
tvp - > startDecay ( ) ;
tvf - > startDecay ( ) ;
2010-01-21 19:15:34 +00:00
}
2012-11-18 19:07:46 +01:00
}