/*************************************************************************** * 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 #include #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; }