1 //
2 //   Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
3 //
4 //   This program is free software; you can redistribute it and/or modify
5 //   it under the terms of the GNU General Public License version 2 as
6 //   published by the Free Software Foundation.
7 //
8 //   This program is distributed in the hope that it will be useful,
9 //   but WITHOUT ANY WARRANTY; without even the implied warranty of
10 //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 //   GNU General Public License version 2 for more details.
12 //
13 //   You should have received a copy of the GNU General Public License
14 //   version 2 along with this program; if not, write to the
15 //   Free Software Foundation, Inc.,
16 //   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17 //
18 
19 #include "duty_unit.h"
20 #include <algorithm>
21 
toOutState(unsigned duty,unsigned pos)22 static inline bool toOutState(unsigned duty, unsigned pos) {
23 	return 0x7EE18180 >> (duty * 8 + pos) & 1;
24 }
25 
toPeriod(unsigned freq)26 static inline unsigned toPeriod(unsigned freq) {
27 	return (2048 - freq) * 2;
28 }
29 
30 namespace gambatte {
31 
DutyUnit()32 DutyUnit::DutyUnit()
33 : nextPosUpdate_(counter_disabled)
34 , period_(4096)
35 , pos_(0)
36 , duty_(0)
37 , inc_(0)
38 , high_(false)
39 , enableEvents_(true)
40 {
41 }
42 
updatePos(unsigned long const cc)43 void DutyUnit::updatePos(unsigned long const cc) {
44 	if (cc >= nextPosUpdate_) {
45 		unsigned long const inc = (cc - nextPosUpdate_) / period_ + 1;
46 		nextPosUpdate_ += period_ * inc;
47 		pos_ += inc;
48 		pos_ &= 7;
49 		high_ = toOutState(duty_, pos_);
50 	}
51 }
52 
setCounter()53 void DutyUnit::setCounter() {
54 	static unsigned char const nextStateDistance[4 * 8] = {
55 		7, 6, 5, 4, 3, 2, 1, 1,
56 		1, 6, 5, 4, 3, 2, 1, 2,
57 		1, 4, 3, 2, 1, 4, 3, 2,
58 		1, 6, 5, 4, 3, 2, 1, 2
59 	};
60 
61 	if (enableEvents_ && nextPosUpdate_ != counter_disabled) {
62 		unsigned const npos = (pos_ + 1) & 7;
63 		counter_ = nextPosUpdate_;
64 		inc_ = nextStateDistance[duty_ * 8 + npos];
65 		if (toOutState(duty_, npos) == high_) {
66 			counter_ += period_ * inc_;
67 			inc_ = nextStateDistance[duty_ * 8 + ((npos + inc_) & 7)];
68 		}
69 	} else
70 		counter_ = counter_disabled;
71 }
72 
setFreq(unsigned newFreq,unsigned long cc)73 void DutyUnit::setFreq(unsigned newFreq, unsigned long cc) {
74 	updatePos(cc);
75 	period_ = toPeriod(newFreq);
76 	setCounter();
77 }
78 
event()79 void DutyUnit::event() {
80 	static unsigned char const inc[] = {
81 		1, 7,
82 		2, 6,
83 		4, 4,
84 		6, 2,
85 	};
86 
87 	high_ ^= true;
88 	counter_ += inc_ * period_;
89 	inc_ = inc[duty_ * 2 + high_];
90 }
91 
nr1Change(unsigned newNr1,unsigned long cc)92 void DutyUnit::nr1Change(unsigned newNr1, unsigned long cc) {
93 	updatePos(cc);
94 	duty_ = newNr1 >> 6;
95 	setCounter();
96 }
97 
nr3Change(unsigned newNr3,unsigned long cc)98 void DutyUnit::nr3Change(unsigned newNr3, unsigned long cc) {
99 	setFreq((freq() & 0x700) | newNr3, cc);
100 }
101 
nr4Change(unsigned const newNr4,unsigned long const cc)102 void DutyUnit::nr4Change(unsigned const newNr4, unsigned long const cc) {
103 	setFreq((newNr4 << 8 & 0x700) | (freq() & 0xFF), cc);
104 
105 	if (newNr4 & 0x80) {
106 		nextPosUpdate_ = (cc & ~1ul) + period_ + 4;
107 		setCounter();
108 	}
109 }
110 
reset()111 void DutyUnit::reset() {
112 	pos_ = 0;
113 	high_ = false;
114 	nextPosUpdate_ = counter_disabled;
115 	setCounter();
116 }
117 
saveState(SaveState::SPU::Duty & dstate,unsigned long const cc)118 void DutyUnit::saveState(SaveState::SPU::Duty &dstate, unsigned long const cc) {
119 	updatePos(cc);
120 	setCounter();
121 	dstate.nextPosUpdate = nextPosUpdate_;
122 	dstate.nr3 = freq() & 0xFF;
123 	dstate.pos = pos_;
124 	dstate.high = high_;
125 }
126 
loadState(SaveState::SPU::Duty const & dstate,unsigned const nr1,unsigned const nr4,unsigned long const cc)127 void DutyUnit::loadState(SaveState::SPU::Duty const &dstate,
128 		unsigned const nr1, unsigned const nr4, unsigned long const cc) {
129 	nextPosUpdate_ = std::max(dstate.nextPosUpdate, cc);
130 	pos_ = dstate.pos & 7;
131 	high_ = dstate.high;
132 	duty_ = nr1 >> 6;
133 	period_ = toPeriod((nr4 << 8 & 0x700) | dstate.nr3);
134 	enableEvents_ = true;
135 	setCounter();
136 }
137 
resetCounters(unsigned long const oldCc)138 void DutyUnit::resetCounters(unsigned long const oldCc) {
139 	if (nextPosUpdate_ == counter_disabled)
140 		return;
141 
142 	updatePos(oldCc);
143 	nextPosUpdate_ -= counter_max;
144 	setCounter();
145 }
146 
killCounter()147 void DutyUnit::killCounter() {
148 	enableEvents_ = false;
149 	setCounter();
150 }
151 
reviveCounter(unsigned long const cc)152 void DutyUnit::reviveCounter(unsigned long const cc) {
153 	updatePos(cc);
154 	enableEvents_ = true;
155 	setCounter();
156 }
157 
158 }
159