1 /* TIATracker, (c) 2016 Andre "Kylearan" Wichmann.
2 * Website: https://bitbucket.org/kylearan/tiatracker
3 * Email: andre.wichmann@gmx.de
4 * See the file "license.txt" for information on usage and redistribution
5 * of this file.
6 */
7
8 #include "percussion.h"
9 #include <QJsonArray>
10 #include <QJsonObject>
11 #include "mainwindow.h"
12 #include "percussiontab.h"
13
14
15 namespace Track {
16
17 /* TODO: More sensible empty detection, like a bool flag that gets
18 * changed whenever the percussion data gets updated by the GUI...
19 */
isEmpty()20 bool Percussion::isEmpty() {
21 bool empty = true;
22 if (name != "---"
23 || envelopeLength > 1
24 || frequencies.size() > 1
25 || waveforms.size() > 1
26 || volumes[0] != 0
27 || frequencies[0] != 0
28 || waveforms[0] != TiaSound::Distortion::WHITE_NOISE
29 || overlay
30 ) {
31 empty = false;
32 }
33
34 return empty;
35 }
36
37 /*************************************************************************/
38
getEnvelopeLength()39 int Percussion::getEnvelopeLength() {
40 return envelopeLength;
41 }
42
43 /*************************************************************************/
44
setEnvelopeLength(int newSize)45 void Percussion::setEnvelopeLength(int newSize) {
46 if (newSize > volumes.size()) {
47 // grow
48 int lastVolume = volumes[volumes.size() - 1];
49 int lastFrequency = frequencies[volumes.size() - 1];
50 TiaSound::Distortion lastWaveform = waveforms[volumes.size() - 1];
51 while (volumes.size() != newSize) {
52 volumes.append(lastVolume);
53 frequencies.append(lastFrequency);
54 waveforms.append(lastWaveform);
55 }
56 }
57 envelopeLength = newSize;
58 }
59
60 /*************************************************************************/
61
toJson(QJsonObject & json)62 void Percussion::toJson(QJsonObject &json) {
63 json["version"] = MainWindow::version;
64 json["name"] = name;
65 json["envelopeLength"] = envelopeLength;
66 json["overlay"] = overlay;
67
68 QJsonArray freqArray;
69 for (int iFreq = 0; iFreq < envelopeLength; ++iFreq) {
70 freqArray.append(QJsonValue(frequencies[iFreq]));
71 }
72 json["frequencies"] = freqArray;
73
74 QJsonArray volArray;
75 for (int iVol = 0; iVol < envelopeLength; ++iVol) {
76 volArray.append(QJsonValue(volumes[iVol]));
77 }
78 json["volumes"] = volArray;
79
80 QJsonArray waveformArray;
81 for (int iWave = 0; iWave < envelopeLength; ++iWave) {
82 int waveformValue = TiaSound::getDistortionInt(waveforms[iWave]);
83 waveformArray.append(QJsonValue(waveformValue));
84 }
85 json["waveforms"] = waveformArray;
86
87 }
88
89 /*************************************************************************/
90
import(const QJsonObject & json)91 bool Percussion::import(const QJsonObject &json) {
92 int version = json["version"].toInt();
93 if (version > MainWindow::version) {
94 MainWindow::displayMessage("A percussion is from a later version of TIATracker!");
95 return false;
96 }
97
98 QString newName = json["name"].toString();
99 int newEnvelopeLength = json["envelopeLength"].toInt();
100 bool newOverlay = json["overlay"].toBool();
101 QJsonArray freqArray = json["frequencies"].toArray();
102 QJsonArray volArray = json["volumes"].toArray();
103 QJsonArray waveformArray = json["waveforms"].toArray();
104
105 // Check for data validity
106 if (newName.length() > PercussionTab::maxPercussionNameLength) {
107 MainWindow::displayMessage("A percussion has an invalid name: " + newName);
108 return false;
109 }
110 if (newEnvelopeLength < 1 || newEnvelopeLength > 99) {
111 MainWindow::displayMessage("A percussion has an invalid envelope length: " + newName);
112 return false;
113 }
114 if (newEnvelopeLength != freqArray.size() || newEnvelopeLength != volArray.size()
115 || newEnvelopeLength != waveformArray.size()) {
116 MainWindow::displayMessage("A percussion has an invalid envelope: " + newName);
117 return false;
118 }
119
120 // Copy data. Adjust volumes, frequencies or waveforms if necessary.
121 frequencies.clear();
122 volumes.clear();
123 waveforms.clear();
124 for (int frame = 0; frame < newEnvelopeLength; ++frame) {
125 int newVolume = volArray[frame].toInt();
126 if (newVolume < 0) {
127 newVolume = 0;
128 }
129 if (newVolume > 15) {
130 newVolume = 15;
131 }
132 volumes.append(newVolume);
133 int newFrequency = freqArray[frame].toInt();
134 if (newFrequency < 0) {
135 newFrequency = 0;
136 }
137 if (newFrequency > 31) {
138 newFrequency = 31;
139 }
140 frequencies.append(newFrequency);
141 int newWaveform = waveformArray[frame].toInt();
142 if (newWaveform < 0 || newWaveform > 15) {
143 newWaveform = 0;
144 }
145 TiaSound::Distortion newDist = TiaSound::distortions[newWaveform];
146 waveforms.append(newDist);
147
148 }
149 name = newName;
150 envelopeLength = newEnvelopeLength;
151 overlay = newOverlay;
152
153 return true;
154 }
155
156 /*************************************************************************/
157
deletePercussion()158 void Percussion::deletePercussion() {
159 name = "---";
160 while (volumes.size() > 1) {
161 volumes.removeLast();
162 frequencies.removeLast();
163 waveforms.removeLast();
164 }
165 volumes[0] = 0;
166 frequencies[0] = 0;
167 waveforms[0] = TiaSound::Distortion::WHITE_NOISE;
168 envelopeLength = 1;
169 overlay = false;
170 }
171
172 /*************************************************************************/
173
getMinVolume()174 int Percussion::getMinVolume() {
175 int min = volumes[0];
176 for (int i = 1; i < volumes.size(); ++i) {
177 min = qMin(min, volumes[i]);
178 }
179 return min;
180 }
181
182 /*************************************************************************/
183
getMaxVolume()184 int Percussion::getMaxVolume() {
185 int max = volumes[0];
186 for (int i = 1; i < volumes.size(); ++i) {
187 max = qMax(max, volumes[i]);
188 }
189 return max;
190
191 }
192
193 /*************************************************************************/
194
insertFrameBefore(int frame)195 void Percussion::insertFrameBefore(int frame) {
196 if (envelopeLength == maxEnvelopeLength) {
197 return;
198 }
199 envelopeLength++;
200
201 // Interpolate
202 int volBefore = 0;
203 int freqBefore = frequencies[frame];
204 if (frame != 0) {
205 volBefore = volumes[frame - 1];
206 freqBefore = frequencies[frame - 1];
207 }
208 int volAfter = volumes[frame];
209 int newVol = int((volAfter + volBefore)/2);
210 int freqAfter = frequencies[frame];
211 int freqNew = int((freqAfter + freqBefore)/2);
212 TiaSound::Distortion distNew = waveforms[frame];
213
214 volumes.insert(frame, newVol);
215 frequencies.insert(frame, freqNew);
216 waveforms.insert(frame, distNew);
217 }
218
219 /*************************************************************************/
220
insertFrameAfter(int frame)221 void Percussion::insertFrameAfter(int frame) {
222 if (envelopeLength == maxEnvelopeLength) {
223 return;
224 }
225 envelopeLength++;
226
227 // Interpolate
228 int volBefore = volumes[frame];
229 int freqBefore = frequencies[frame];
230 int volAfter = 0;
231 int freqAfter = frequencies[frame];
232 // -1 b/c we increased length already
233 if (frame + 1 != envelopeLength - 1) {
234 volAfter = volumes[frame + 1];
235 freqAfter = frequencies[frame + 1];
236 }
237 int newVol = int((volAfter + volBefore)/2);
238 int newFreq = int((freqAfter + freqBefore)/2);
239 TiaSound::Distortion distNew = waveforms[frame];
240
241 volumes.insert(frame + 1, newVol);
242 frequencies.insert(frame + 1, newFreq);
243 waveforms.insert(frame, distNew);
244 }
245
246 /*************************************************************************/
247
deleteFrame(int frame)248 void Percussion::deleteFrame(int frame) {
249 if (envelopeLength == 1) {
250 return;
251 }
252 envelopeLength--;
253
254 // Delete frames
255 volumes.removeAt(frame);
256 frequencies.removeAt(frame);
257 waveforms.removeAt(frame);
258 }
259
260 /*************************************************************************/
261
calcEffectiveSize()262 int Percussion::calcEffectiveSize() {
263 int realSize = envelopeLength;
264 while (realSize > 0 && waveforms[realSize - 1] == TiaSound::Distortion::SILENT && volumes[realSize - 1] == 0) {
265 realSize--;
266 }
267 return realSize + 1; // +1 for End byte
268 }
269
270 }
271