cog/Frameworks/GME/gme/pwm.c

445 lines
11 KiB
C

/***************************************************************************
* Gens: PWM audio emulator. *
* *
* Copyright (c) 1999-2002 by Stéphane Dallongeville *
* Copyright (c) 2003-2004 by Stéphane Akhoun *
* Copyright (c) 2008-2009 by David Korth *
* *
* 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. *
***************************************************************************/
#include "pwm.h"
#include <string.h>
#include <stdlib.h>
#ifndef INLINE
#define INLINE static __inline
#endif
//#include "gens_core/mem/mem_sh2.h"
//#include "gens_core/cpu/sh2/sh2.h"
#define CHILLY_WILLY_SCALE 1
#if PWM_BUF_SIZE == 8
unsigned char PWM_FULL_TAB[PWM_BUF_SIZE * PWM_BUF_SIZE] =
{
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x80, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x80, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x40, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x40,
};
#elif PWM_BUF_SIZE == 4
unsigned char PWM_FULL_TAB[PWM_BUF_SIZE * PWM_BUF_SIZE] =
{
0x40, 0x00, 0x00, 0x80,
0x80, 0x40, 0x00, 0x00,
0x00, 0x80, 0x40, 0x00,
0x00, 0x00, 0x80, 0x40,
};
#else
#error PWM_BUF_SIZE must equal 4 or 8.
#endif /* PWM_BUF_SIZE */
typedef struct _pwm_chip
{
unsigned short PWM_FIFO_R[8];
unsigned short PWM_FIFO_L[8];
unsigned int PWM_RP_R;
unsigned int PWM_WP_R;
unsigned int PWM_RP_L;
unsigned int PWM_WP_L;
unsigned int PWM_Cycles;
unsigned int PWM_Cycle;
unsigned int PWM_Cycle_Cnt;
unsigned int PWM_Int;
unsigned int PWM_Int_Cnt;
unsigned int PWM_Mode;
//unsigned int PWM_Enable;
unsigned int PWM_Out_R;
unsigned int PWM_Out_L;
unsigned int PWM_Cycle_Tmp;
unsigned int PWM_Cycles_Tmp;
unsigned int PWM_Int_Tmp;
unsigned int PWM_FIFO_L_Tmp;
unsigned int PWM_FIFO_R_Tmp;
#if CHILLY_WILLY_SCALE
// TODO: Fix Chilly Willy's new scaling algorithm.
/* PWM scaling variables. */
int PWM_Offset;
int PWM_Scale;
//int PWM_Loudness;
#endif
int clock;
} pwm_chip;
#if CHILLY_WILLY_SCALE
// TODO: Fix Chilly Willy's new scaling algorithm.
#define PWM_Loudness 0
#endif
void PWM_Init(pwm_chip* chip);
void PWM_Recalc_Scale(pwm_chip* chip);
void PWM_Set_Cycle(pwm_chip* chip, unsigned int cycle);
void PWM_Set_Int(pwm_chip* chip, unsigned int int_time);
void PWM_Update(pwm_chip* chip, int **buf, int length);
/**
* PWM_Init(): Initialize the PWM audio emulator.
*/
void PWM_Init(pwm_chip* chip)
{
chip->PWM_Mode = 0;
chip->PWM_Out_R = 0;
chip->PWM_Out_L = 0;
memset(chip->PWM_FIFO_R, 0x00, sizeof(chip->PWM_FIFO_R));
memset(chip->PWM_FIFO_L, 0x00, sizeof(chip->PWM_FIFO_L));
chip->PWM_RP_R = 0;
chip->PWM_WP_R = 0;
chip->PWM_RP_L = 0;
chip->PWM_WP_L = 0;
chip->PWM_Cycle_Tmp = 0;
chip->PWM_Int_Tmp = 0;
chip->PWM_FIFO_L_Tmp = 0;
chip->PWM_FIFO_R_Tmp = 0;
//PWM_Loudness = 0;
PWM_Set_Cycle(chip, 0);
PWM_Set_Int(chip, 0);
}
#if CHILLY_WILLY_SCALE
// TODO: Fix Chilly Willy's new scaling algorithm.
void PWM_Recalc_Scale(pwm_chip* chip)
{
chip->PWM_Offset = (chip->PWM_Cycle / 2) + 1;
chip->PWM_Scale = 0x7FFF00 / chip->PWM_Offset;
}
#endif
void PWM_Set_Cycle(pwm_chip* chip, unsigned int cycle)
{
cycle--;
chip->PWM_Cycle = (cycle & 0xFFF);
chip->PWM_Cycle_Cnt = chip->PWM_Cycles;
#if CHILLY_WILLY_SCALE
// TODO: Fix Chilly Willy's new scaling algorithm.
PWM_Recalc_Scale(chip);
#endif
}
void PWM_Set_Int(pwm_chip* chip, unsigned int int_time)
{
int_time &= 0x0F;
if (int_time)
chip->PWM_Int = chip->PWM_Int_Cnt = int_time;
else
chip->PWM_Int = chip->PWM_Int_Cnt = 16;
}
void PWM_Clear_Timer(pwm_chip* chip)
{
chip->PWM_Cycle_Cnt = 0;
}
/**
* PWM_SHIFT(): Shift PWM data.
* @param src: Channel (L or R) with the source data.
* @param dest Channel (L or R) for the destination.
*/
#define PWM_SHIFT(src, dest) \
{ \
/* Make sure the source FIFO isn't empty. */ \
if (PWM_RP_##src != PWM_WP_##src) \
{ \
/* Get destination channel output from the source channel FIFO. */ \
PWM_Out_##dest = PWM_FIFO_##src[PWM_RP_##src]; \
\
/* Increment the source channel read pointer, resetting to 0 if it overflows. */ \
PWM_RP_##src = (PWM_RP_##src + 1) & (PWM_BUF_SIZE - 1); \
} \
}
/*static void PWM_Shift_Data(void)
{
switch (PWM_Mode & 0x0F)
{
case 0x01:
case 0x0D:
// Rx_LL: Right -> Ignore, Left -> Left
PWM_SHIFT(L, L);
break;
case 0x02:
case 0x0E:
// Rx_LR: Right -> Ignore, Left -> Right
PWM_SHIFT(L, R);
break;
case 0x04:
case 0x07:
// RL_Lx: Right -> Left, Left -> Ignore
PWM_SHIFT(R, L);
break;
case 0x05:
case 0x09:
// RR_LL: Right -> Right, Left -> Left
PWM_SHIFT(L, L);
PWM_SHIFT(R, R);
break;
case 0x06:
case 0x0A:
// RL_LR: Right -> Left, Left -> Right
PWM_SHIFT(L, R);
PWM_SHIFT(R, L);
break;
case 0x08:
case 0x0B:
// RR_Lx: Right -> Right, Left -> Ignore
PWM_SHIFT(R, R);
break;
case 0x00:
case 0x03:
case 0x0C:
case 0x0F:
default:
// Rx_Lx: Right -> Ignore, Left -> Ignore
break;
}
}
void PWM_Update_Timer(unsigned int cycle)
{
// Don't do anything if PWM is disabled in the Sound menu.
// Don't do anything if PWM isn't active.
if ((PWM_Mode & 0x0F) == 0x00)
return;
if (PWM_Cycle == 0x00 || (PWM_Cycle_Cnt > cycle))
return;
PWM_Shift_Data();
PWM_Cycle_Cnt += PWM_Cycle;
PWM_Int_Cnt--;
if (PWM_Int_Cnt == 0)
{
PWM_Int_Cnt = PWM_Int;
if (PWM_Mode & 0x0080)
{
// RPT => generate DREQ1 as well as INT
SH2_DMA1_Request(&M_SH2, 1);
SH2_DMA1_Request(&S_SH2, 1);
}
if (_32X_MINT & 1)
SH2_Interrupt(&M_SH2, 6);
if (_32X_SINT & 1)
SH2_Interrupt(&S_SH2, 6);
}
}*/
INLINE int PWM_Update_Scale(pwm_chip* chip, int PWM_In)
{
if (PWM_In == 0)
return 0;
// TODO: Chilly Willy's new scaling algorithm breaks drx's Sonic 1 32X (with PWM drums).
#ifdef CHILLY_WILLY_SCALE
//return (((PWM_In & 0xFFF) - chip->PWM_Offset) * chip->PWM_Scale) >> (8 - PWM_Loudness);
// Knuckles' Chaotix: Tachy Touch uses the values 0xF?? for negative values
// This small modification fixes the terrible pops.
PWM_In &= 0xFFF;
if (PWM_In & 0x800)
PWM_In |= ~0xFFF;
return ((PWM_In - chip->PWM_Offset) * chip->PWM_Scale) >> (8 - PWM_Loudness);
#else
const int PWM_adjust = ((chip->PWM_Cycle >> 1) + 1);
int PWM_Ret = ((chip->PWM_In & 0xFFF) - PWM_adjust);
// Increase PWM volume so it's audible.
PWM_Ret <<= (5+2);
// Make sure the PWM isn't oversaturated.
if (PWM_Ret > 32767)
PWM_Ret = 32767;
else if (PWM_Ret < -32768)
PWM_Ret = -32768;
return PWM_Ret;
#endif
}
void PWM_Update(pwm_chip* chip, int **buf, int length)
{
int tmpOutL;
int tmpOutR;
int i;
//if (!PWM_Enable)
// return;
if (chip->PWM_Out_L == 0 && chip->PWM_Out_R == 0)
{
memset(buf[0], 0x00, length * sizeof(int));
memset(buf[1], 0x00, length * sizeof(int));
return;
}
// New PWM scaling algorithm provided by Chilly Willy on the Sonic Retro forums.
tmpOutL = PWM_Update_Scale(chip, (int)chip->PWM_Out_L);
tmpOutR = PWM_Update_Scale(chip, (int)chip->PWM_Out_R);
for (i = 0; i < length; i ++)
{
buf[0][i] = tmpOutL;
buf[1][i] = tmpOutR;
}
}
void pwm_update(void *_chip, stream_sample_t **outputs, int samples)
{
pwm_chip *chip = (pwm_chip *) _chip;
PWM_Update(chip, outputs, samples);
}
void * device_start_pwm(int clock)
{
/* allocate memory for the chip */
//pwm_state *chip = get_safe_token(device);
pwm_chip *chip;
int rate;
chip = (pwm_chip *) malloc(sizeof(pwm_chip));
if (!chip) return chip;
rate = 22020; // that's the rate the PWM is mostly used
chip->clock = clock;
PWM_Init(chip);
/* allocate the stream */
//chip->stream = stream_create(device, 0, 2, device->clock / 384, chip, rf5c68_update);
return chip;
}
void device_stop_pwm(void *chip)
{
//pwm_chip *chip = &PWM_Chip[ChipID];
//free(chip->ram);
free(chip);
}
void device_reset_pwm(void *_chip)
{
pwm_chip *chip = (pwm_chip *) _chip;
PWM_Init(chip);
}
void pwm_chn_w(void *_chip, UINT8 Channel, UINT16 data)
{
pwm_chip *chip = (pwm_chip *) _chip;
if (chip->clock == 1)
{ // old-style commands
switch(Channel)
{
case 0x00:
chip->PWM_Out_L = data;
break;
case 0x01:
chip->PWM_Out_R = data;
break;
case 0x02:
PWM_Set_Cycle(chip, data);
break;
case 0x03:
chip->PWM_Out_L = data;
chip->PWM_Out_R = data;
break;
}
}
else
{
switch(Channel)
{
case 0x00/2: // control register
PWM_Set_Int(chip, data >> 8);
break;
case 0x02/2: // cycle register
PWM_Set_Cycle(chip, data);
break;
case 0x04/2: // l ch
chip->PWM_Out_L = data;
break;
case 0x06/2: // r ch
chip->PWM_Out_R = data;
if (! chip->PWM_Mode)
{
if (chip->PWM_Out_L == chip->PWM_Out_R)
{
// fixes these terrible pops when
// starting/stopping/pausing the song
chip->PWM_Offset = data;
chip->PWM_Mode = 0x01;
}
}
break;
case 0x08/2: // mono ch
chip->PWM_Out_L = data;
chip->PWM_Out_R = data;
if (! chip->PWM_Mode)
{
chip->PWM_Offset = data;
chip->PWM_Mode = 0x01;
}
break;
}
}
return;
}