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