/***************************************************************************** * * File: SN76489.cpp * * Project: Osmose emulator. * * Description: This class will implements SN76489 functionnality. * * Author: Vedder Bruno * Date: 18/11/2004, 18h00 * * URL: http://bcz.emu-france.com/ *****************************************************************************/ #include "SN76489.h" #include "Bits.h" #define TO_PERIOD (22050.0*32.0)/3579454.0 //#define TRACE_ALL_WRITE_OPERATION /*--------------------------------------------------------------*/ /* Constructor. */ /*--------------------------------------------------------------*/ SN76489::SN76489() { reset(); } /*-------------------------------------------------------------*/ /* This method handles write operation on the PSG port. */ /*-------------------------------------------------------------*/ void SN76489::writePort(unsigned char value) { unsigned char channel; #ifdef TRACE_ALL_WRITE_OPERATION cout << "SN76489 Written with value 0x" << hex << setfill('0') << setw(2) << (int)value << "." << endl; #endif if (value & BIT7) // Latch { lastRegister = (value >> 4) & 0x7; channel= lastRegister/2; if (value & BIT4) { volume[channel] = value & 0xF; #ifdef SN76489_VERBOSE cout << "Volume of channel " << dec << (int)channel << " set to " << (int) volume[channel] << "." << endl; #endif } else { latch = value & 0xF; if (channel == 3) { // Like said in maxim's SN76489 documentation, LFSR is reset // when noise register is written. LFSR = PERIODIC_NOISE_FEEDBACK; freqDiv[channel] = latch; if (latch & BIT2) { whiteNoise = true; #ifdef SN76489_VERBOSE cout << "Noise generator set to WhiteNoise mode." << endl; #endif } else { whiteNoise = false; #ifdef SN76489_VERBOSE cout << "Noise generator set to Periodic mode." << endl; #endif } } } } else // Data byte write { channel = lastRegister/2; if (lastRegister & BIT0) // If true, it's volume register. { volume[channel] = value & 0xF; #ifdef SN76489_VERBOSE cout << "Volume of channel " << dec << (int)channel << " set to " << (int) volume[channel] << "." << endl; #endif } else { if (channel !=3) { freqDiv[channel] = ((value & 0x3F) << 4) | latch; #ifdef SN76489_VERBOSE cout << "Tone of channel " << dec << (int)channel << " set to " << (int) freqDiv[channel] << "." << endl; #endif } else { // Channel 3: writing 4bits 'tone' register. freqDiv[channel] = latch; if (channel == 3) { if (latch & BIT2) { whiteNoise = true; #ifdef SN76489_VERBOSE cout << "Noise generator set to WhiteNoise mode." << endl; #endif } else { whiteNoise = false; #ifdef SN76489_VERBOSE cout << "Noise generator set to Periodic mode." << endl; #endif } } } } } } /*-------------------------------------------------------------*/ /* This method resets the PSG to it's intial values */ /*-------------------------------------------------------------*/ void SN76489::reset() { latch = 0; lastRegister = 0; snd_genera_ind = 0; snd_played_ind = 0; total_generated = 0; total_played = 0; whiteNoise = true; // Default Periodic Noise. LFSR = WHITE_NOISE_FEEDBACK; for (int i=0; i<4; i++) { volume[i] = 0xF; l_volume[i] = 0xF; period[i] = 0; count[i] = 0; } for (int i=0; i total_generated) { memset(s, 0, (total_played+(len/2) - total_generated)); // cout << "Sound filled with silence !" << endl; return; } for (int i = 0; i < len/2; i++) { *dst = snd_buffer[snd_played_ind++]; dst++; total_played++; if (snd_played_ind>=SND_BUFFER_SIZE) { snd_played_ind = 0; } } } /*-------------------------------------------------------------*/ /* This method returns parity of the given value. */ /* Given by Maxim's SN76489 Documentation. */ /*-------------------------------------------------------------*/ int SN76489::parity(int val) { val^=val>>8; val^=val>>4; val^=val>>2; val^=val>>1; return val; } /*-------------------------------------------------------------*/ /* This method will generate 16bit/22050khz wave depending */ /* on 4 channels of the SN76489. It's called by audio callback */ /* routine. Note that sample are updated value per value. */ /* To ensure sound synchronisation, this method will skip */ /* buffer filling, to avoid, data not played to be overwritten.*/ /* (At 22050hz, the emu should provide 367.5 sample every */ /* 1/60th of a second. Actually the emu provide 368 sample. */ /* So the buffer is filled more quickly than played. Overwrite */ /* occurs 22050/(0.5*60) = after 735 seconds. */ /* In this case, it returns false. Frequency is not modified */ /* sample generation simply continue where it was. */ /*-------------------------------------------------------------*/ bool SN76489::update_sound_buffer(short *value) { short amp; short snd = 0; // Avoid buffer overwriting. if (total_generated >= (total_played+SND_BUFFER_SIZE)) { return false; } for (int c=0; c < 4;c++) { if (count[c] == 0) // Start New wave. { //amp = 3840 - (volume[c]*256); // 0 to 15 amp = volTable[volume[c]]; if (c<3) { double d; float p = (float)(freqDiv[c]*TO_PERIOD); // Get new tone. count [c] = (unsigned short)(p); // Are we playing the same Tone ? If yes, we can apply // Fractionnal part correction. if (count[c] == p_count[c]) { c_fract[c] += fract[c]; if (c_fract[c]>1.0) { count[c] = p_count[c] +1; c_fract[c] -= 1.0; } } else // We are playing a new tone. { fract[c] = modf(p, &d); c_fract[c] = fract[c]; p_count[c] = count[c]; } } else // count = 0, channel 3. { LFSR=(LFSR>>1) | ((whiteNoise ?parity(LFSR & 0x9):LFSR & 1)<<15); // Noise generator case, on 2 freq divisor low bits. switch(freqDiv[3] & 3) { case 0: // 0x10 * TO_PERIOD = 3.154 count[3] = 3; fract[3] = 0.15400058f; break; case 1: // 0x20 * TO_PERIOD = 6.30800115 count[3] = 6; fract[3] = 0.30800115f; break; case 2: // 0x40 * TO_PERIOD = 12.6160023 count[3] = 12; fract[3] = 0.6160023f; break; case 3: count[3] = p_count[2]; fract[3] = fract[2]; break; } // Are we playing the same 'tone' ? if (count[3] == p_count[3]) { c_fract[3] += fract[3]; if (c_fract[3]>1.0) { count[3] = p_count[3] +1; c_fract[3] -= 1.0; } } else { c_fract[3] = fract[3]; p_count[3] = count[3]; } } period[c] = count[c]; l_volume[c] = amp; } // Put value in our sound buffer. // If c < 3 we are generation sound of Tone channel. if (c < 3) { if (count[c]>= period[c]/2) { snd += l_volume[c]; } else { snd += -l_volume[c]; } } else // Noise channel. { // Noise generator case. if (LFSR & 1) { snd += l_volume[c]; } } if (count[c] != 0) { count[c]--; } } // SDL_LockAudio(); snd_buffer[snd_genera_ind++] = snd; // SDL_UnlockAudio(); total_generated++; if (snd_genera_ind>=SND_BUFFER_SIZE) { snd_genera_ind=0; } *value = snd; return true; } /*-------------------------------------------------------------*/ /* This method is only used on emulator startup. It returns */ /* true when generated samples are at least our buffer size. */ /* This avoid playing silence on emulator startup. */ /*-------------------------------------------------------------*/ bool SN76489::bufferFull() { if (total_generated >= SND_BUFFER_SIZE -1) { return true; } else { return false; } } /*-------------------------------------------------------------*/ /* This method is heavily based on Maxim's SN76489 code. */ /* If precalculate output volume level for a given gain. */ /* This avoid to get linear volume level, and probably sounds */ /* more accurate like that ;-) */ /*-------------------------------------------------------------*/ #define MAX_OUTPUT 0x7FFF void SN76489::setGain(unsigned short gain) { int i; double out; out = MAX_OUTPUT / 4; while (gain-- > 0) out *= 1.023292992; for (i = 0;i < 15;i++) { if (out > MAX_OUTPUT / 4) volTable[i] = MAX_OUTPUT / 4; else volTable[i] = (int)out; out /= 1.258925412; } volTable[15] = 0; }