text: /***************************************************************************
   *   Copyright (C) 2007 by Sindre Aamås                                    *
   *   aamas@stud.ntnu.no                                                    *
   *                                                                         *
   *   This program is free software; you can redistribute it and/or modify  *
   *   it under the terms of the GNU General Public License version 2 as     *
   *   published by the Free Software Foundation.                            *
   *                                                                         *
   *   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 version 2 for more details.                *
   *                                                                         *
   *   You should have received a copy of the GNU General Public License     *
   *   version 2 along with this program; if not, write to the               *
   *   Free Software Foundation, Inc.,                                       *
   *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
   ***************************************************************************/
  #include "sound.h"
  #include "savestate.h"
  #include <cstring>
  #include <algorithm>
  
  /*
  	Frame Sequencer
  
  	Step    Length Ctr  Vol Env     Sweep
  	- - - - - - - - - - - - - - - - - - - -
  	0       Clock       -           Clock
  S	1       -           Clock       -
  	2       Clock       -           -
  	3       -           -           -
  	4       Clock       -           Clock
  	5       -           -           -
  	6       Clock       -           -
  	7       -           -           -
  	- - - - - - - - - - - - - - - - - - - -
  	Rate    256 Hz      64 Hz       128 Hz
  
  S) start step on sound power on.
  */
  
  namespace gambatte {
  
  PSG::PSG()
  : buffer(0),
    lastUpdate(0),
    soVol(0),
    rsum(0x8000), // initialize to 0x8000 to prevent borrows from high word, xor away later
    bufferPos(0),
    enabled(false)
  {
  }
  
  void PSG::init(const bool cgb) {
  	ch1.init(cgb);
  	ch2.init(cgb);
  	ch3.init(cgb);
  	ch4.init(cgb);
  }
  
  void PSG::reset() {
  	ch1.reset();
  	ch2.reset();
  	ch3.reset();
  	ch4.reset();
  }
  
  void PSG::setStatePtrs(SaveState &state) {
  	ch3.setStatePtrs(state);
  }
  
  void PSG::loadState(const SaveState &state) {
  	ch1.loadState(state);
  	ch2.loadState(state);
  	ch3.loadState(state);
  	ch4.loadState(state);
  	
  	lastUpdate = state.cpu.cycleCounter;
  	set_so_volume(state.mem.ioamhram.get()[0x124]);
  	map_so(state.mem.ioamhram.get()[0x125]);
  	enabled = state.mem.ioamhram.get()[0x126] >> 7 & 1;
  }
  
  void PSG::accumulate_channels(const unsigned long cycles) {
  	uint_least32_t *const buf = buffer + bufferPos;
  	
  	std::memset(buf, 0, cycles * sizeof(uint_least32_t));
  	ch1.update(buf, soVol, cycles);
  	ch2.update(buf, soVol, cycles);
  	ch3.update(buf, soVol, cycles);
  	ch4.update(buf, soVol, cycles);
  }
  
  void PSG::generate_samples(const unsigned long cycleCounter, const unsigned doubleSpeed) {
  	const unsigned long cycles = (cycleCounter - lastUpdate) >> (1 + doubleSpeed);
  	lastUpdate += cycles << (1 + doubleSpeed);
  
  	if (cycles)
  		accumulate_channels(cycles);
  	
  	bufferPos += cycles;
  }
  
  void PSG::resetCounter(const unsigned long newCc, const unsigned long oldCc, const unsigned doubleSpeed) {
  	generate_samples(oldCc, doubleSpeed);
  	lastUpdate = newCc - (oldCc - lastUpdate);
  }
  
  unsigned PSG::fillBuffer() {
  	uint_least32_t sum = rsum;
  	uint_least32_t *b = buffer;
  	unsigned n = bufferPos;
  	
  	if (unsigned n2 = n >> 3) {
  		n -= n2 << 3;
  		
  		do {
  			sum += b[0];
  			b[0] = sum ^ 0x8000;
  			sum += b[1];
  			b[1] = sum ^ 0x8000;
  			sum += b[2];
  			b[2] = sum ^ 0x8000;
  			sum += b[3];
  			b[3] = sum ^ 0x8000;
  			sum += b[4];
  			b[4] = sum ^ 0x8000;
  			sum += b[5];
  			b[5] = sum ^ 0x8000;
  			sum += b[6];
  			b[6] = sum ^ 0x8000;
  			sum += b[7];
  			b[7] = sum ^ 0x8000;
  			
  			b += 8;
  		} while (--n2);
  	}
  	
  	while (n--) {
  		sum += *b;
  		*b++ = sum ^ 0x8000; // xor away the initial rsum value of 0x8000 (which prevents borrows from the high word) from the low word
  	}
  	
  	rsum = sum;
  	
  	return bufferPos;
  }
  
  #ifdef WORDS_BIGENDIAN
  static const unsigned long so1Mul = 0x00000001;
  static const unsigned long so2Mul = 0x00010000;
  #else
  static const unsigned long so1Mul = 0x00010000;
  static const unsigned long so2Mul = 0x00000001;
  #endif
  
  void PSG::set_so_volume(const unsigned nr50) {
  	soVol = (((nr50 & 0x7) + 1) * so1Mul + ((nr50 >> 4 & 0x7) + 1) * so2Mul) * 64;
  }
  
  void PSG::map_so(const unsigned nr51) {
  	const unsigned long tmp = nr51 * so1Mul + (nr51 >> 4) * so2Mul;
  	
  	ch1.setSo((tmp      & 0x00010001) * 0xFFFF);
  	ch2.setSo((tmp >> 1 & 0x00010001) * 0xFFFF);
  	ch3.setSo((tmp >> 2 & 0x00010001) * 0xFFFF);
  	ch4.setSo((tmp >> 3 & 0x00010001) * 0xFFFF);
  }
  
  unsigned PSG::getStatus() const {
  	return ch1.isActive() | ch2.isActive() << 1 | ch3.isActive() << 2 | ch4.isActive() << 3;
  }
  
  // the buffer and position are not saved, as they're set and flushed on each runfor() call
  SYNCFUNC(PSG)
  {
  	SSS(ch1);
  	SSS(ch2);
  	SSS(ch3);
  	SSS(ch4);
  	NSS(lastUpdate);
  	NSS(soVol);
  	NSS(rsum);
  	NSS(enabled);
  }
  
  }
  