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