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-2019 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
23namespace {
24 constexpr double R_MAX = 30.;
25 constexpr double R = 1.;
26
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
36// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
37Audio::Audio()
38 : myAudioQueue(nullptr),
39 myCurrentFragment(nullptr)
40{
41 for (uInt8 i = 0; i <= 0x1e; ++i) myMixingTableSum[i] = mixingTableEntry(i, 0x1e);
42 for (uInt8 i = 0; i <= 0x0f; ++i) myMixingTableIndividual[i] = mixingTableEntry(i, 0x0f);
43
44 reset();
45}
46
47// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
48void Audio::reset()
49{
50 myCounter = 0;
51 mySampleIndex = 0;
52
53 myChannel0.reset();
54 myChannel1.reset();
55}
56
57// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
58void Audio::setAudioQueue(shared_ptr<AudioQueue> queue)
59{
60 myAudioQueue = queue;
61
62 myCurrentFragment = myAudioQueue->enqueue();
63 mySampleIndex = 0;
64}
65
66// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
67void Audio::tick()
68{
69 switch (myCounter) {
70 case 9:
71 case 81:
72 myChannel0.phase0();
73 myChannel1.phase0();
74
75 break;
76
77 case 37:
78 case 149:
79 phase1();
80 break;
81 }
82
83 if (++myCounter == 228) myCounter = 0;
84}
85
86// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
87void Audio::phase1()
88{
89 uInt8 sample0 = myChannel0.phase1();
90 uInt8 sample1 = myChannel1.phase1();
91
92 if (!myAudioQueue) return;
93
94 if (myAudioQueue->isStereo()) {
95 myCurrentFragment[2*mySampleIndex] = myMixingTableIndividual[sample0];
96 myCurrentFragment[2*mySampleIndex + 1] = myMixingTableIndividual[sample1];
97 } else {
98 myCurrentFragment[mySampleIndex] = myMixingTableSum[sample0 + sample1];
99 }
100
101 if (++mySampleIndex == myAudioQueue->fragmentSize()) {
102 mySampleIndex = 0;
103 myCurrentFragment = myAudioQueue->enqueue(myCurrentFragment);
104 }
105}
106
107// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
108AudioChannel& Audio::channel0()
109{
110 return myChannel0;
111}
112
113// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
114AudioChannel& Audio::channel1()
115{
116 return myChannel1;
117}
118
119// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
120bool Audio::save(Serializer& out) const
121{
122 try
123 {
124 out.putInt(myCounter);
125
126 // The queue starts out pristine after loading, so we don't need to save
127 // any other parts of our state
128
129 if (!myChannel0.save(out)) return false;
130 if (!myChannel1.save(out)) return false;
131 }
132 catch(...)
133 {
134 cerr << "ERROR: TIA_Audio::save" << endl;
135 return false;
136 }
137
138 return true;
139}
140
141// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
142bool Audio::load(Serializer& in)
143{
144 try
145 {
146 myCounter = in.getInt();
147
148 if (!myChannel0.load(in)) return false;
149 if (!myChannel1.load(in)) return false;
150 }
151 catch(...)
152 {
153 cerr << "ERROR: TIA_Audio::load" << endl;
154 return false;
155 }
156
157 return true;
158}
159