1 //============================================================================
2 //
3 //   SSSS    tt          lll  lll
4 //  SS  SS   tt           ll   ll
5 //  SS     tttttt  eeee   ll   ll   aaaa
6 //   SSSS    tt   ee  ee  ll   ll      aa
7 //      SS   tt   eeeeee  ll   ll   aaaaa  --  "An Atari 2600 VCS Emulator"
8 //  SS  SS   tt   ee      ll   ll  aa  aa
9 //   SSSS     ttt  eeeee llll llll  aaaaa
10 //
11 // Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
12 // and the Stella Team
13 //
14 // See the file "License.txt" for information on usage and redistribution of
15 // this file, and for a DISCLAIMER OF ALL WARRANTIES.
16 //============================================================================
17 
18 #include "Audio.hxx"
19 #include "AudioQueue.hxx"
20 
21 #include <cmath>
22 
23 namespace {
24   constexpr double R_MAX = 30.;
25   constexpr double R = 1.;
26 
mixingTableEntry(uInt8 v,uInt8 vMax)27   Int16 mixingTableEntry(uInt8 v, uInt8 vMax)
28   {
29     return static_cast<Int16>(
30       floor(0x7fff * double(v) / double(vMax) * (R_MAX + R * double(vMax)) / (R_MAX + R * double(v)))
31     );
32   }
33 }
34 
35 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Audio()36 Audio::Audio()
37 {
38   for (uInt8 i = 0; i <= 0x1e; ++i) myMixingTableSum[i] = mixingTableEntry(i, 0x1e);
39   for (uInt8 i = 0; i <= 0x0f; ++i) myMixingTableIndividual[i] = mixingTableEntry(i, 0x0f);
40 
41   reset();
42 }
43 
44 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
reset()45 void Audio::reset()
46 {
47   myCounter = 0;
48   mySampleIndex = 0;
49 
50   myChannel0.reset();
51   myChannel1.reset();
52 }
53 
54 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setAudioQueue(const shared_ptr<AudioQueue> & queue)55 void Audio::setAudioQueue(const shared_ptr<AudioQueue>& queue)
56 {
57   myAudioQueue = queue;
58 
59   myCurrentFragment = myAudioQueue->enqueue();
60   mySampleIndex = 0;
61 }
62 
63 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
tick()64 void Audio::tick()
65 {
66   switch (myCounter) {
67     case 9:
68     case 81:
69       myChannel0.phase0();
70       myChannel1.phase0();
71 
72       break;
73 
74     case 37:
75     case 149:
76       phase1();
77       break;
78   }
79 
80   if (++myCounter == 228) myCounter = 0;
81 }
82 
83 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
phase1()84 void Audio::phase1()
85 {
86   uInt8 sample0 = myChannel0.phase1();
87   uInt8 sample1 = myChannel1.phase1();
88 
89   addSample(sample0, sample1);
90 #ifdef GUI_SUPPORT
91   if(myRewindMode)
92     mySamples.push_back(sample0 | (sample1 << 4));
93 #endif
94 }
95 
96 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
addSample(uInt8 sample0,uInt8 sample1)97 void Audio::addSample(uInt8 sample0, uInt8 sample1)
98 {
99   if(!myAudioQueue) return;
100 
101   if(myAudioQueue->isStereo()) {
102     myCurrentFragment[2 * mySampleIndex] = myMixingTableIndividual[sample0];
103     myCurrentFragment[2 * mySampleIndex + 1] = myMixingTableIndividual[sample1];
104   }
105   else {
106     myCurrentFragment[mySampleIndex] = myMixingTableSum[sample0 + sample1];
107   }
108 
109   if(++mySampleIndex == myAudioQueue->fragmentSize()) {
110     mySampleIndex = 0;
111     myCurrentFragment = myAudioQueue->enqueue(myCurrentFragment);
112   }
113 }
114 
115 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
channel0()116 AudioChannel& Audio::channel0()
117 {
118   return myChannel0;
119 }
120 
121 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
channel1()122 AudioChannel& Audio::channel1()
123 {
124   return myChannel1;
125 }
126 
127 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
save(Serializer & out) const128 bool Audio::save(Serializer& out) const
129 {
130   try
131   {
132     out.putByte(myCounter);
133 
134     // The queue starts out pristine after loading, so we don't need to save
135     // any other parts of our state
136 
137     if (!myChannel0.save(out)) return false;
138     if (!myChannel1.save(out)) return false;
139   #ifdef GUI_SUPPORT
140     out.putLong(uInt64(mySamples.size()));
141     out.putByteArray(mySamples.data(), mySamples.size());
142 
143     // TODO: check if this improves sound of playback for larger state gaps
144     //out.putInt(mySampleIndex);
145     //out.putShortArray((uInt16*)myCurrentFragment, myAudioQueue->fragmentSize());
146 
147     mySamples.clear();
148   #endif
149   }
150   catch(...)
151   {
152     cerr << "ERROR: TIA_Audio::save" << endl;
153     return false;
154   }
155 
156   return true;
157 }
158 
159 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
load(Serializer & in)160 bool Audio::load(Serializer& in)
161 {
162   try
163   {
164     myCounter = in.getByte();
165 
166     if (!myChannel0.load(in)) return false;
167     if (!myChannel1.load(in)) return false;
168   #ifdef GUI_SUPPORT
169     uInt64 sampleSize = in.getLong();
170     unique_ptr<uInt8[]> samples = make_unique<uInt8[]>(sampleSize);
171     in.getByteArray(samples.get(), sampleSize);
172 
173     //mySampleIndex = in.getInt();
174     //in.getShortArray((uInt16*)myCurrentFragment, myAudioQueue->fragmentSize());
175 
176     // Feed all loaded samples into the audio queue
177     for(size_t i = 0; i < sampleSize; i++)
178     {
179       uInt8 sample = samples[i];
180       uInt8 sample0 = sample & 0x0f;
181       uInt8 sample1 = sample >> 4;
182 
183       addSample(sample0, sample1);
184     }
185   #endif
186   }
187   catch(...)
188   {
189     cerr << "ERROR: TIA_Audio::load" << endl;
190     return false;
191   }
192 
193   return true;
194 }
195