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 "instrument.h"
9
10 #include <QMutex>
11
12 #include <stdexcept>
13 #include <iostream>
14 #include <QJsonArray>
15 #include <QJsonObject>
16 #include "mainwindow.h"
17 #include "instrumentstab.h"
18
19
20 namespace Track {
21
22 /* TODO: More sensible empty detection, like a bool flag that gets
23 * changed whenever the instrument data gets updated by the GUI...
24 */
isEmpty()25 bool Instrument::isEmpty() {
26 bool empty = true;
27 if (name != "---"
28 || envelopeLength > 2
29 || frequencies.size() > 2
30 || volumes[0] != 0 || volumes[1] != 0
31 || frequencies[0] != 0 || frequencies[1] != 0
32 || sustainStart != 0 || releaseStart != 1
33 || baseDistortion != TiaSound::Distortion::PURE_COMBINED
34 ) {
35 empty = false;
36 }
37
38 return empty;
39 }
40
41 /*************************************************************************/
42
toJson(QJsonObject & json)43 void Instrument::toJson(QJsonObject &json) {
44 json["version"] = MainWindow::version;
45 json["name"] = name;
46 json["waveform"] = static_cast<int>(baseDistortion);
47 json["envelopeLength"] = envelopeLength;
48 json["sustainStart"] = sustainStart;
49 json["releaseStart"] = releaseStart;
50
51 QJsonArray freqArray;
52 for (int iFreq = 0; iFreq < envelopeLength; ++iFreq) {
53 freqArray.append(QJsonValue(frequencies[iFreq]));
54 }
55 json["frequencies"] = freqArray;
56
57 QJsonArray volArray;
58 for (int iVol = 0; iVol < envelopeLength; ++iVol) {
59 volArray.append(QJsonValue(volumes[iVol]));
60 }
61 json["volumes"] = volArray;
62 }
63
64 /*************************************************************************/
65
import(const QJsonObject & json)66 bool Instrument::import(const QJsonObject &json) {
67 int version = json["version"].toInt();
68 if (version > MainWindow::version) {
69 MainWindow::displayMessage("An instrument is from a later version of TIATracker!");
70 return false;
71 }
72
73 QString newName = json["name"].toString();
74 int newWaveform = json["waveform"].toInt();
75 int newEnvelopeLength = json["envelopeLength"].toInt();
76 int newSustainStart = json["sustainStart"].toInt();
77 int newReleaseStart = json["releaseStart"].toInt();
78 QJsonArray freqArray = json["frequencies"].toArray();
79 QJsonArray volArray = json["volumes"].toArray();
80
81 // Check for data validity
82 if (newName.length() > InstrumentsTab::maxInstrumentNameLength) {
83 MainWindow::displayMessage("An instrument has an invalid name: " + newName);
84 return false;
85 }
86 if (newWaveform < 0 || newWaveform > 16) {
87 MainWindow::displayMessage("An instrument has an invalid waveform: " + newName);
88 return false;
89 }
90 if (newEnvelopeLength < 2 || newEnvelopeLength > 99
91 || newSustainStart < 0 || newSustainStart >= newEnvelopeLength - 1
92 || newReleaseStart <= newSustainStart || newReleaseStart > newEnvelopeLength - 1) {
93 MainWindow::displayMessage("An instrument has an invalid envelope structure: " + newName);
94 return false;
95 }
96 if (newEnvelopeLength != freqArray.size() || newEnvelopeLength != volArray.size()) {
97 MainWindow::displayMessage("An instrument has an invalid frequency envelope: " + newName);
98 return false;
99 }
100
101 // Copy data. Adjust volumes or frequencies if necessary.
102 frequencies.clear();
103 volumes.clear();
104 for (int frame = 0; frame < newEnvelopeLength; ++frame) {
105 int newVolume = volArray[frame].toInt();
106 if (newVolume < 0) {
107 newVolume = 0;
108 }
109 if (newVolume > 15) {
110 newVolume = 15;
111 }
112 volumes.append(newVolume);
113 int newFrequency = freqArray[frame].toInt();
114 if (newFrequency < -8) {
115 newFrequency = -8;
116 }
117 if (newFrequency > 7) {
118 newFrequency = 7;
119 }
120 frequencies.append(newFrequency);
121 }
122 name = newName;
123 baseDistortion = TiaSound::distortions[newWaveform];
124 envelopeLength = newEnvelopeLength;
125 sustainStart = newSustainStart;
126 releaseStart = newReleaseStart;
127
128 return true;
129 }
130
131 /*************************************************************************/
132
setEnvelopeLength(int newSize)133 void Instrument::setEnvelopeLength(int newSize) {
134 if (newSize > volumes.size()) {
135 // grow
136 int lastVolume = volumes[volumes.size() - 1];
137 int lastFrequency = frequencies[volumes.size() - 1];
138 while (volumes.size() != newSize) {
139 volumes.append(lastVolume);
140 frequencies.append(lastFrequency);
141 }
142 }
143 envelopeLength = newSize;
144 validateSustainReleaseValues();
145 }
146
147 /*************************************************************************/
148
getAudCValue(int frequency)149 int Instrument::getAudCValue(int frequency) {
150 int result;
151
152 if (baseDistortion != TiaSound::Distortion::PURE_COMBINED) {
153 result = (static_cast<int>(baseDistortion));
154 } else {
155 if (frequency < 32) {
156 result = (static_cast<int>(TiaSound::Distortion::PURE_HIGH));
157 } else {
158 result = (static_cast<int>(TiaSound::Distortion::PURE_LOW));
159 }
160 }
161
162 return result;
163 }
164
165 /*************************************************************************/
166
validateSustainReleaseValues()167 void Instrument::validateSustainReleaseValues() {
168 if (releaseStart >= envelopeLength) {
169 releaseStart = envelopeLength - 1;
170 }
171 if (sustainStart >= releaseStart) {
172 sustainStart = releaseStart - 1;
173 }
174 }
175
176 /*************************************************************************/
177
setSustainAndRelease(int newSustainStart,int newReleaseStart)178 void Instrument::setSustainAndRelease(int newSustainStart, int newReleaseStart) {
179 // TODO: change intro assert
180 if (newReleaseStart <= newSustainStart
181 || newSustainStart >= envelopeLength - 1
182 || newReleaseStart >= envelopeLength) {
183 throw std::invalid_argument("Invalid new sustain/release index values!");
184 }
185 sustainStart = newSustainStart;
186 releaseStart = newReleaseStart;
187 }
188
189 /*************************************************************************/
190
deleteInstrument()191 void Instrument::deleteInstrument()
192 {
193 name = "---";
194 while (volumes.size() > 2) {
195 volumes.removeLast();
196 frequencies.removeLast();
197 }
198 volumes[0] = 0;
199 volumes[1] = 0;
200 frequencies[0] = 0;
201 frequencies[1] = 0;
202 envelopeLength = 2;
203 sustainStart = 0;
204 releaseStart = 1;
205 baseDistortion = TiaSound::Distortion::PURE_COMBINED;
206 }
207
208 /*************************************************************************/
209
getMinVolume()210 int Instrument::getMinVolume() {
211 int min = volumes[0];
212 for (int i = 1; i < volumes.size(); ++i) {
213 min = qMin(min, volumes[i]);
214 }
215 return min;
216 }
217
218 /*************************************************************************/
219
getMaxVolume()220 int Instrument::getMaxVolume() {
221 int max = volumes[0];
222 for (int i = 1; i < volumes.size(); ++i) {
223 max = qMax(max, volumes[i]);
224 }
225 return max;
226 }
227
228 /*************************************************************************/
229
correctSustainReleaseForInsert(int frame)230 void Instrument::correctSustainReleaseForInsert(int frame) {
231 if (frame < sustainStart) {
232 // in AD
233 sustainStart++;
234 releaseStart++;
235 } else if (frame < releaseStart) {
236 // in Sustain
237 releaseStart++;
238 }
239 }
240
insertFrameBefore(int frame)241 void Instrument::insertFrameBefore(int frame) {
242 if (envelopeLength == maxEnvelopeLength) {
243 return;
244 }
245 envelopeLength++;
246
247 correctSustainReleaseForInsert(frame);
248
249 // Interpolate
250 int volBefore = 0;
251 int freqBefore = 0;
252 if (frame != 0) {
253 volBefore = volumes[frame - 1];
254 freqBefore = frequencies[frame - 1];
255 }
256 int volAfter = volumes[frame];
257 int newVol = int((volAfter + volBefore)/2);
258 int freqAfter = frequencies[frame];
259 int freqNew = int((freqAfter + freqBefore)/2);
260
261 volumes.insert(frame, newVol);
262 frequencies.insert(frame, freqNew);
263 }
264
265 /*************************************************************************/
266
insertFrameAfter(int frame)267 void Instrument::insertFrameAfter(int frame) {
268 if (envelopeLength == maxEnvelopeLength) {
269 return;
270 }
271 envelopeLength++;
272
273 correctSustainReleaseForInsert(frame);
274
275 // Interpolate
276 int volBefore = volumes[frame];
277 int freqBefore = frequencies[frame];
278 int volAfter = 0;
279 int freqAfter = 0;
280 // -1 b/c we increased length already
281 if (frame + 1 != envelopeLength - 1) {
282 volAfter = volumes[frame + 1];
283 freqAfter = frequencies[frame + 1];
284 }
285 int newVol = int((volAfter + volBefore)/2);
286 int newFreq = int((freqAfter + freqBefore)/2);
287
288 volumes.insert(frame + 1, newVol);
289 frequencies.insert(frame + 1, newFreq);
290 }
291
292 /*************************************************************************/
293
deleteFrame(int frame)294 void Instrument::deleteFrame(int frame) {
295 if (envelopeLength == 2) {
296 return;
297 }
298 envelopeLength--;
299
300 // Correct sustain and release
301 if (frame < sustainStart) {
302 // in AD
303 sustainStart--;
304 releaseStart--;
305 } else if (frame < releaseStart) {
306 // in Sustain
307 if (releaseStart - sustainStart > 1) {
308 releaseStart--;
309 } else {
310 // Trying to delete single sustain frame.
311 if (sustainStart > 0) {
312 // Move sustain one back
313 sustainStart--;
314 releaseStart--;
315 } else {
316 // keep sustain at this position
317 }
318 }
319 } else {
320 // in Release
321 if (releaseStart - sustainStart == 1) {
322 // Move Release back one frame
323 sustainStart--;
324 releaseStart--;
325 }
326 }
327
328 // Delete frames
329 volumes.removeAt(frame);
330 frequencies.removeAt(frame);
331 }
332
333 /*************************************************************************/
334
getSustainStart() const335 int Instrument::getSustainStart() const {
336 return sustainStart;
337 }
338
getReleaseStart() const339 int Instrument::getReleaseStart() const {
340 return releaseStart;
341 }
342
343 /*************************************************************************/
344
calcEffectiveSize()345 int Instrument::calcEffectiveSize() {
346 int realSize = envelopeLength;
347 while (realSize > (releaseStart + 1) && frequencies[realSize - 1] == 0 && volumes[realSize - 1] == 0) {
348 realSize--;
349 }
350 return realSize + 1; // +1 for "end of instrument" 0 byte
351 }
352
353 /*************************************************************************/
354
getEnvelopeLength()355 int Instrument::getEnvelopeLength() {
356 return envelopeLength;
357 }
358
359 }
360