Updated playptmod, and now playptmod is unclipped and supports indefinite looping

CQTexperiment
Chris Moeller 2014-03-08 20:09:30 -08:00
parent ec40c5041c
commit 0b42254e4b
3 changed files with 161 additions and 136 deletions

View File

@ -1,5 +1,5 @@
/*
** - --=playptmod v1.05+ - 8bitbubsy 2010-2013=-- -
** - --=playptmod v1.10d - 8bitbubsy 2010-2014=-- -
** This is the native Win32 API version, no DLL needed in you
** production zip/rar whatever.
**
@ -62,6 +62,8 @@
**
*/
#define _USE_MATH_DEFINES // visual studio
#include "playptmod.h"
#include "blip_buf.h"
@ -70,16 +72,12 @@
#include <stdlib.h> // malloc(), calloc(), free()
#include <math.h> // floorf(), sinf()
#ifndef M_PI
#define M_PI 3.141592653589793
#endif
#define HI_NYBBLE(x) ((x) >> 4)
#define LO_NYBBLE(x) ((x) & 0x0F)
#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
#define DENORMAL_OFFSET 1E-10f
#define PT_MIN_PERIOD 108
#define PT_MAX_PERIOD 907
#define PT_MIN_PERIOD 78
#define PT_MAX_PERIOD 937
#define MAX_CHANNELS 32
#define MOD_ROWS 64
#define MOD_SAMPLES 31
@ -211,14 +209,12 @@ typedef struct
typedef struct paula_filter_state
{
float LED[4];
float high[2];
} Filter;
typedef struct paula_filter_coefficients
{
float LED;
float LEDFb;
float high;
} FilterC;
typedef struct voice_data
@ -282,7 +278,6 @@ typedef struct
float *frequencyTable;
float *extendedFrequencyTable;
unsigned char *sinusTable;
int *panTable;
int minPeriod;
int maxPeriod;
int loopCounter;
@ -384,7 +379,7 @@ static float calcRcCoeff(float sampleRate, float cutOffFreq)
if (cutOffFreq >= (sampleRate / 2.0f))
return (1.0f);
return ((2.0f * 3.141592f) * cutOffFreq / sampleRate);
return (2.0f * (float)(M_PI) * cutOffFreq / sampleRate);
}
static BUF *bufopen(const unsigned char *bufToCopy, unsigned long bufferSize)
@ -445,49 +440,28 @@ static void bufseek(BUF *_SrcBuf, long _Offset, int _Origin)
static inline int periodToNote(player *p, char finetune, short period)
{
char l;
char m;
char h;
unsigned char i;
unsigned char maxNotes;
short *tablePointer;
if (p->minPeriod == PT_MIN_PERIOD)
{
l = 0;
h = 35;
maxNotes = 36;
tablePointer = (short *)&rawAmigaPeriods[finetune * 37];
while (h >= l)
{
m = (h + l) / 2;
if (tablePointer[m] == period)
break;
else if (tablePointer[m] > period)
l = m + 1;
else
h = m - 1;
}
}
else
{
l = 0;
h = 83;
maxNotes = 84;
tablePointer = (short *)&extendedRawPeriods[finetune * 85];
while (h >= l)
{
m = (h + l) / 2;
if (tablePointer[m] == period)
break;
else if (tablePointer[m] > period)
l = m + 1;
else
h = m - 1;
}
}
return (m);
for (i = 0; i < maxNotes; ++i)
{
if (period >= tablePointer[i])
break;
}
return i;
}
static void mixerSwapChSource(player *p, int ch, const char *src, int length, int loopStart, int loopLength, int step)
@ -496,7 +470,7 @@ static void mixerSwapChSource(player *p, int ch, const char *src, int length, in
p->v[ch].newData = src;
p->v[ch].newLength = length;
p->v[ch].newLoopLength = loopLength;
p->v[ch].newLoopEnd = loopLength + loopStart;
p->v[ch].newLoopEnd = loopStart + loopLength;
p->v[ch].newStep = step;
}
@ -511,24 +485,47 @@ static void mixerSetChSource(player *p, int ch, const char *src, int length, int
p->v[ch].loopLength = loopLength;
p->v[ch].step = step;
if (p->v[ch].index > 0)
if (offset > 0)
{
if (p->v[ch].loopLength > 2)
if (loopLength > (2 * step))
{
if (p->v[ch].index >= p->v[ch].loopEnd)
if (offset >= p->v[ch].loopEnd)
p->v[ch].index = 0;
}
else if (p->v[ch].index >= p->v[ch].length)
else if (offset >= length)
{
p->v[ch].data = NULL;
}
}
}
// adejr: these sin/cos approximations both use a 0..1
// parameter range and have 'normalized' (1/2 = 0db) coefficients
//
// the coefficients are for LERP(x, x * x, 0.224) * sqrt(2)
// max_error is minimized with 0.224 = 0.0013012886
static inline float sinApx(float x)
{
x = x * (2.0f - x);
return (x * 1.09742972f + x * x * 0.31678383f);
}
static inline float cosApx(float x)
{
x = (1.0f - x) * (1.0f + x);
return (x * 1.09742972f + x * x * 0.31678383f);
}
static void mixerSetChPan(player *p, int ch, int pan)
{
p->v[ch].panL = p->panTable[256 - pan];
p->v[ch].panR = p->panTable[pan];
float newpan;
if (pan == 255) pan = 256; // 100% R pan fix for 8-bit pan
newpan = pan * (1.0f / 256.0f);
p->v[ch].panL = (int)(256.0f * cosApx(newpan));
p->v[ch].panR = (int)(256.0f * sinApx(newpan));
}
static void mixerSetChVol(player *p, int ch, int vol)
@ -572,9 +569,9 @@ static void mixerSetChRate(player *p, int ch, float rate)
p->v[ch].rate = rate;
}
static void outputAudio(player *p, short *target, int numSamples)
static void outputAudio(player *p, int *target, int numSamples)
{
short *out;
int *out;
int i;
int j;
int step;
@ -651,17 +648,18 @@ static void outputAudio(player *p, short *target, int numSamples)
if (p->v[i].newLoopLength <= (2 * step))
{
p->v[i].data = NULL;
continue;
}
p->v[i].index = p->v[i].loopEnd - p->v[i].loopLength;
p->v[i].data = p->v[i].newData;
p->v[i].length = p->v[i].newLength;
p->v[i].loopEnd = p->v[i].newLoopEnd;
p->v[i].loopLength = p->v[i].newLoopLength;
p->v[i].frac = 0.0f;
step = p->v[i].step = p->v[i].newStep;
p->v[i].index = p->v[i].loopEnd - p->v[i].loopLength;
}
else
{
@ -678,13 +676,17 @@ static void outputAudio(player *p, short *target, int numSamples)
if (p->v[i].newLoopLength <= 2)
{
p->v[i].data = NULL;
break;
continue;
}
p->v[i].index = 0;
p->v[i].data = p->v[i].newData;
p->v[i].length = p->v[i].newLength;
p->v[i].loopEnd = p->v[i].newLoopEnd;
p->v[i].loopLength = p->v[i].newLoopLength;
p->v[i].frac = 0.0f;
step = p->v[i].step = p->v[i].newStep;
}
else
@ -695,15 +697,6 @@ static void outputAudio(player *p, short *target, int numSamples)
}
}
}
else if (p->v[i].swapSampleFlag == true)
{
p->v[i].swapSampleFlag = false;
p->v[i].data = p->v[i].newData;
p->v[i].length = p->v[i].newLength;
p->v[i].loopEnd = p->v[i].newLoopEnd;
p->v[i].loopLength = p->v[i].newLoopLength;
p->v[i].step = p->v[i].newStep;
}
if ((j < numSamples) && (p->v[i].data == NULL))
{
@ -732,9 +725,9 @@ static void outputAudio(player *p, short *target, int numSamples)
float downscale;
if (p->numChans <= 4)
downscale = 1.0f / (96.0f * 256.0f);
downscale = 1.0f / 192.0f;
else
downscale = 1.0f / (160.0f * 256.0f);
downscale = 1.0f / 256.0f;
for (i = 0; i < numSamples; ++i)
{
@ -754,22 +747,16 @@ static void outputAudio(player *p, short *target, int numSamples)
R = p->filter.LED[3];
}
L -= p->filter.high[0];
R -= p->filter.high[1];
p->filter.high[0] += (p->filterC.high * L + DENORMAL_OFFSET);
p->filter.high[1] += (p->filterC.high * R + DENORMAL_OFFSET);
L *= downscale;
R *= downscale;
L = CLAMP(L, -32768.0f, 32767.0f);
R = CLAMP(R, -32768.0f, 32767.0f);
L = CLAMP(L, INT_MIN, INT_MAX);
R = CLAMP(R, INT_MIN, INT_MAX);
if ( out )
{
*out++ = (short)(int)(L);
*out++ = (short)(int)(R);
*out++ = (int)(L);
*out++ = (int)(R);
}
}
}
@ -828,8 +815,11 @@ static int playptmod_LoadMTM(player *p, BUF *fmodule)
{
if (p->source->head.pan[i] <= 15)
{
p->source->head.pan[i] -= (p->source->head.pan[i] & 8) / 8;
p->source->head.pan[i] = (((int)p->source->head.pan[i]) * 255) / 14;
// 8bitbubsy: WTF no, just do << 4 then if (p == 255) p = 256 in mixer
//p->source->head.pan[i] -= (p->source->head.pan[i] & 8) / 8;
//p->source->head.pan[i] = (((int)p->source->head.pan[i]) * 255) / 14;
p->source->head.pan[i] <<= 4;
p->source->head.volume[i] = 64;
}
else
@ -1311,7 +1301,6 @@ int playptmod_LoadMem(void *_p, const unsigned char *buf, unsigned long bufLengt
else if (note->param & 0x0F)
{
note->command = 0x01;
note->param &= 0x0F;
}
}
}
@ -1771,28 +1760,12 @@ static void efxTremoloControl(player *p, mod_channel *ch)
static void efxKarplusStrong(player *p, mod_channel *ch)
{
char *sampleLoopData;
unsigned int loopLength;
unsigned int loopLengthCounter;
MODULE_SAMPLE *s;
// WTF !
// KarplusStrong sucks, since some MODs
// used E8x for other effects instead.
if (ch->sample > 0)
{
s = &p->source->samples[ch->sample - 1];
(void)ch;
sampleLoopData = p->source->sampleData + s->offset + s->loopStart;
loopLength = s->loopLength - 2;
loopLengthCounter = loopLength;
while (loopLengthCounter--)
{
*sampleLoopData = (*sampleLoopData + *(sampleLoopData + 1)) / 2;
sampleLoopData++;
}
*sampleLoopData = (*sampleLoopData + *(sampleLoopData - loopLength)) / 2;
}
}
static void efxRetrigNote(player *p, mod_channel *ch)
@ -2274,6 +2247,7 @@ static void fxSampleOffset(player *p, mod_channel *ch)
ch->offsetTemp = ch->param * 256;
ch->offset += ch->offsetTemp;
ch->offsetBugNotAdded = false;
}
}
@ -2414,13 +2388,13 @@ static void processEffects(player *p, mod_channel *ch)
static void fxPan(player *p, mod_channel *ch)
{
if (p->modTick == 0)
mixerSetChPan(p, ch->chanIndex, (int)((float)ch->param * (256.0f / 255.0f)));
mixerSetChPan(p, ch->chanIndex, ch->param);
}
static void efxPan(player *p, mod_channel *ch)
{
if (p->modTick == 0)
mixerSetChPan(p, ch->chanIndex, (int)((float)LO_NYBBLE(ch->param) * (256.0f / 15.0f)));
mixerSetChPan(p, ch->chanIndex, LO_NYBBLE(ch->param) << 4);
}
void playptmod_Stop(void *_p)
@ -2483,11 +2457,18 @@ static void fetchPatternData(player *p, mod_channel *ch)
ch->fineTune = LO_NYBBLE(ch->param);
}
tempNote = periodToNote(p, 0, note->period);
ch->noNote = false;
ch->tempPeriod = (p->minPeriod == PT_MIN_PERIOD) ? rawAmigaPeriods[(ch->fineTune * 37) + tempNote] : extendedRawPeriods[(ch->fineTune * 85) + tempNote];
ch->flags |= FLAG_NOTE;
tempNote = periodToNote(p, 0, note->period);
if ((p->minPeriod == PT_MIN_PERIOD) && (tempNote == 36)) // PT/NT/STK/UST only
{
ch->noNote = true;
mixerSetChSource(p, ch->chanIndex, NULL, 0, 0, 0, 0, 0);
}
else
{
ch->noNote = false;
ch->tempPeriod = (p->minPeriod == PT_MIN_PERIOD) ? rawAmigaPeriods[(ch->fineTune * 37) + tempNote] : extendedRawPeriods[(ch->fineTune * 85) + tempNote];
ch->flags |= FLAG_NOTE;
}
}
else
{
@ -2722,7 +2703,7 @@ static int pulsateSamples(player *p, int samples)
return samples;
}
void playptmod_Render(void *_p, short *target, int length)
void playptmod_Render(void *_p, int *target, int length)
{
player *p = (player *)_p;
@ -2742,6 +2723,39 @@ void playptmod_Render(void *_p, short *target, int length)
}
}
void playptmod_Render16(void *_p, short *target, int length)
{
player *p = (player *)_p;
int tempBuffer[512];
int * temp = ( target ) ? tempBuffer : 0;
if (p->modulePlaying == true)
{
static const int soundBufferSamples = soundBufferSize / 4;
while (length)
{
int i, tempSamples = CLAMP(length, 0, soundBufferSamples);
tempSamples = CLAMP(length, 0, 256);
tempSamples = pulsateSamples(p, tempSamples);
length -= tempSamples;
outputAudio(p, temp, tempSamples);
if ( target )
for (i = 0; i < tempSamples * 2; ++i)
{
int s = tempBuffer[ i ] >> 8;
s = CLAMP(s, -32768, 32767);
target[ i ] = (short)s;
}
if ( target ) target += (tempSamples * 2);
}
}
}
void *playptmod_Create(int samplingFrequency)
{
player *p = (player *) calloc(1, sizeof(player));
@ -2755,8 +2769,10 @@ void *playptmod_Create(int samplingFrequency)
for (i = 0; i < 32; ++i)
p->sinusTable[i] = (unsigned char)floorf(255.0f * sinf(((float)i * 3.141592f) / 32.0f));
p->frequencyTable = (float *)malloc(sizeof (float) * 908);
for (i = 108; i <= 907; ++i) // 0..107 will never be looked up, junk is OK
p->frequencyTable = (float *)malloc(sizeof (float) * 938);
p->frequencyTable[0] = (float)samplingFrequency / 7093790.0f;
for (i = 1; i <= 937; ++i)
p->frequencyTable[i] = (float)samplingFrequency / (7093790.0f / (2.0f * (float)i));
for (j = 0; j < 16; ++j)
@ -2772,21 +2788,11 @@ void *playptmod_Create(int samplingFrequency)
for (i = 14; i <= 1712; ++i) // 0..14 will never be looked up, junk is OK
p->extendedFrequencyTable[i] = (float)samplingFrequency / (7093790.0f / (2.0f * (float)i));
p->panTable = (int *)malloc(sizeof (int) * 257);
norm = 1.0f / sinf(1.0f / (M_PI / 2));
for (i = 0; i <= 256; ++i)
{
float pan = (float)i * 1.0f / 256.0f;
// -3dB pan law ( pan = sin x/half_pi )
p->panTable[i] = 256.0f * sinf((pan) / (M_PI / 2)) * norm;
}
p->mixBufferL = (float *)malloc(soundBufferSize * sizeof (float));
p->mixBufferR = (float *)malloc(soundBufferSize * sizeof (float));
p->filterC.LED = calcRcCoeff((float)samplingFrequency, 3090.0f);
p->filterC.LEDFb = 0.125f + 0.125f / (1.0f - p->filterC.LED);
p->filterC.high = calcRcCoeff((float)p->soundFrequency, 5.2f);
p->useLEDFilter = false;
@ -2883,8 +2889,7 @@ void playptmod_Free(void *_p)
}
free(p->mixBufferL);
free(p->mixBufferR);
free(p->panTable);
free(p->mixBufferR);;
free(p->sinusTable);
free(p->frequencyTable);
free(p->extendedFrequencyTable);

View File

@ -23,7 +23,8 @@ int playptmod_Load(void *p, const char *filename);
void playptmod_Play(void *p, unsigned int startOrder);
void playptmod_Stop(void *p);
void playptmod_Render(void *p, signed short *target, int length);
void playptmod_Render(void *p, signed int *target, int length);
void playptmod_Render16(void *p, signed short *target, int length);
void playptmod_Mute(void *p, int channel, int mute);

View File

@ -10,6 +10,8 @@
#import "Logging.h"
#import "PlaylistController.h"
@implementation ptmodDecoder
BOOL probe_length( unsigned long * intro_length, unsigned long * loop_length, int test_vblank, const void * src, unsigned long size, unsigned int subsong )
@ -135,9 +137,9 @@ BOOL probe_length( unsigned long * intro_length, unsigned long * loop_length, in
[NSNumber numberWithInt:0], @"bitrate",
[NSNumber numberWithFloat:44100], @"sampleRate",
[NSNumber numberWithDouble:totalFrames], @"totalFrames",
[NSNumber numberWithInt:16], @"bitsPerSample", //Samples are short
[NSNumber numberWithBool:NO], @"floatingPoint",
[NSNumber numberWithInt:2], @"channels", //output from gme_play is in stereo
[NSNumber numberWithInt:32], @"bitsPerSample",
[NSNumber numberWithBool:YES], @"floatingPoint",
[NSNumber numberWithInt:2], @"channels",
[NSNumber numberWithBool:YES], @"seekable",
@"host", @"endian",
nil];
@ -145,7 +147,9 @@ BOOL probe_length( unsigned long * intro_length, unsigned long * loop_length, in
- (int)readAudio:(void *)buf frames:(UInt32)frames
{
if ( framesRead >= totalFrames )
BOOL repeat_one = IsRepeatOneSet();
if ( !repeat_one && framesRead >= totalFrames )
return 0;
if ( !ptmod )
@ -158,27 +162,27 @@ BOOL probe_length( unsigned long * intro_length, unsigned long * loop_length, in
while ( total < frames ) {
int framesToRender = 512;
if ( framesToRender > totalFrames - framesRead )
framesToRender = totalFrames - framesRead;
framesToRender = (int)(totalFrames - framesRead);
if ( framesToRender > frames - total )
framesToRender = frames - total;
int16_t * sampleBuf = ( int16_t * ) buf + total * 2;
int32_t * sampleBuf = ( int32_t * ) buf + total * 2;
playptmod_Render( ptmod, sampleBuf, framesToRender );
if ( framesRead + framesToRender > framesLength ) {
if ( !repeat_one && framesRead + framesToRender > framesLength ) {
long fadeStart = ( framesLength > framesRead ) ? framesLength : framesRead;
long fadeEnd = ( framesRead + framesToRender < totalFrames ) ? framesRead + framesToRender : totalFrames;
const long fadeTotal = totalFrames - framesLength;
for ( long fadePos = fadeStart; fadePos < fadeEnd; ++fadePos ) {
const long scale = ( fadeTotal - ( fadePos - framesLength ) );
const long offset = fadePos - framesRead;
int16_t * samples = sampleBuf + offset * 2;
samples[ 0 ] = samples[ 0 ] * scale / fadeTotal;
samples[ 1 ] = samples[ 1 ] * scale / fadeTotal;
int32_t * samples = sampleBuf + offset * 2;
samples[ 0 ] = (int32_t)(samples[ 0 ] * scale / fadeTotal);
samples[ 1 ] = (int32_t)(samples[ 1 ] * scale / fadeTotal);
}
framesToRender = fadeEnd - framesRead;
framesToRender = (int)(fadeEnd - framesRead);
}
if ( !framesToRender )
@ -188,6 +192,14 @@ BOOL probe_length( unsigned long * intro_length, unsigned long * loop_length, in
framesRead += framesToRender;
}
for ( int i = 0; i < total; ++i )
{
int32_t * sampleIn = ( int32_t * ) buf + i * 2;
float * sampleOut = ( float * ) buf + i * 2;
sampleOut[ 0 ] = sampleIn[ 0 ] * (1.0f / 16777216.0f);
sampleOut[ 1 ] = sampleIn[ 1 ] * (1.0f / 16777216.0f);
}
return total;
}
@ -200,7 +212,14 @@ BOOL probe_length( unsigned long * intro_length, unsigned long * loop_length, in
return 0;
}
playptmod_Render( ptmod, NULL, frame - framesRead );
while ( framesRead < frame )
{
int frames_todo = INT_MAX;
if ( frames_todo > frame - framesRead )
frames_todo = (int)( frame - framesRead );
playptmod_Render( ptmod, NULL, frames_todo );
framesRead += frames_todo;
}
framesRead = frame;