0
|
1 /*
|
|
2 * miniJSS - Module playing routines
|
|
3 * Programmed and designed by Matti 'ccr' Hamalainen
|
|
4 * (C) Copyright 2006-2009 Tecnic Software productions (TNSP)
|
|
5 */
|
|
6 #include "jssplr.h"
|
|
7 #include <string.h>
|
|
8 #include <stdlib.h>
|
|
9
|
|
10 // FIXME!! FIX ME!
|
|
11 #include <math.h>
|
|
12
|
|
13 /* Miscellaneous tables
|
|
14 */
|
|
15 #define jmpNSineTable (256)
|
|
16 static int *jmpSineTable = NULL;
|
|
17
|
|
18
|
|
19 static const Sint16 jmpXMAmigaPeriodTab[13 * 8] = {
|
|
20 907, 900, 894, 887, 881, 875, 868, 862, 856, 850, 844, 838,
|
|
21 832, 826, 820, 814, 808, 802, 796, 791, 785, 779, 774, 768,
|
|
22 762, 757, 752, 746, 741, 736, 730, 725, 720, 715, 709, 704,
|
|
23 699, 694, 689, 684, 678, 675, 670, 665, 660, 655, 651, 646,
|
|
24 640, 636, 632, 628, 623, 619, 614, 610, 604, 601, 597, 592,
|
|
25 588, 584, 580, 575, 570, 567, 563, 559, 555, 551, 547, 543,
|
|
26 538, 535, 532, 528, 524, 520, 516, 513, 508, 505, 502, 498,
|
|
27 494, 491, 487, 484, 480, 477, 474, 470, 467, 463, 460, 457,
|
|
28
|
|
29 453, 450, 447, 443, 440, 437, 434, 431
|
|
30 };
|
|
31
|
|
32
|
|
33 #define jmpNMODEffectTable (36)
|
|
34 static const char jmpMODEffectTable[jmpNMODEffectTable] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
35
|
|
36
|
|
37 /* Helper functions
|
|
38 */
|
|
39 int jmpNoteToAmigaPeriod(int note, int finetune)
|
|
40 {
|
|
41 int tmp = (note + finetune + 8);
|
|
42 if (tmp < 0) tmp = 0; else
|
|
43 if (tmp > 103) tmp = 103;
|
|
44 return jmpXMAmigaPeriodTab[tmp];
|
|
45 }
|
|
46
|
|
47
|
|
48 static int jmpGetPeriodFromNote(JSSPlayer *mp, int note, int finetune)
|
|
49 {
|
|
50 int res;
|
|
51
|
|
52 if (JMPGETMODFLAGS(mp, jmdfAmigaPeriods))
|
|
53 {
|
|
54 int mfinetune = finetune / 16,
|
|
55 mnote = (note % 12) * 8,
|
|
56 moctave = note / 12,
|
|
57 period1, period2;
|
|
58
|
|
59 period1 = jmpNoteToAmigaPeriod(mnote, mfinetune);
|
|
60
|
|
61 if (finetune < 0)
|
|
62 {
|
|
63 mfinetune--;
|
|
64 finetune = -finetune;
|
|
65 } else
|
|
66 mfinetune++;
|
|
67
|
|
68 period2 = jmpNoteToAmigaPeriod(mnote, mfinetune);
|
|
69
|
|
70 mfinetune = finetune & 15;
|
|
71 period1 *= (16 - mfinetune);
|
|
72 period2 *= mfinetune;
|
|
73
|
|
74 res = ((period1 + period2) * 2) >> moctave;
|
|
75
|
|
76 //fprintf(stderr, "jmpGetAmigaPeriod(%d, %d) = %d\n", note, finetune, res);
|
|
77 }
|
|
78 else
|
|
79 {
|
|
80 //fprintf(stderr, "jmpGetLinearPeriod(%d, %d) = %d\n", note, finetune, res);
|
|
81 //res = ((120 - note) << 6) - (finetune / 2);
|
|
82 res = 7680 - (note * 64) - (finetune / 2);
|
|
83 if (res < 1) res = 1;
|
|
84 }
|
|
85
|
|
86 return res;
|
|
87 }
|
|
88
|
|
89
|
|
90 static void jmpCSetPitch(JSSPlayer *mp, int channel, int value)
|
|
91 {
|
|
92 assert(mp != NULL);
|
|
93 assert(mp->pDevice != NULL);
|
|
94
|
|
95 if (value > 0)
|
|
96 {
|
|
97 if (JMPGETMODFLAGS(mp, jmdfAmigaPeriods))
|
|
98 {
|
|
99 // Frequency = 8363*1712 / Period
|
|
100 //fprintf(stderr, "jmpCSetPitch::AMIGA(%d, %d)\n", channel, value);
|
|
101 jvmSetFreq(mp->pDevice, channel, 14317456.0f / (double) value);
|
|
102 }
|
|
103 else
|
|
104 {
|
|
105 // Frequency = Frequency = 8363*2^((6*12*16*4 - Period) / (12*16*4))
|
|
106 //fprintf(stderr, "jmpCSetPitch::Linear(%d, %d)\n", channel, value);
|
|
107 jvmSetFreq(mp->pDevice, channel,
|
|
108 8363.0f * pow(2.0f, (4608.0f - (double) value) / 768.0f));
|
|
109 }
|
|
110 }
|
|
111 }
|
|
112
|
|
113
|
|
114 static void jmpCSetInstrument(JSSPlayer * mp, int channel)
|
|
115 {
|
|
116 JSSInstrument *instr;
|
|
117 assert(mp != NULL);
|
|
118 assert(mp->pDevice != NULL);
|
|
119
|
|
120 instr = mp->iCInstrument[channel];
|
|
121
|
|
122 if (instr)
|
|
123 {
|
|
124 jvmSetSample(mp->pDevice, channel,
|
|
125 instr->data, instr->size,
|
|
126 instr->loopS, instr->loopE,
|
|
127 instr->flags);
|
|
128 }
|
|
129 }
|
|
130
|
|
131
|
|
132 static void jmpCSetVolume(JSSPlayer * mp, int channel, int volume)
|
|
133 {
|
|
134 assert(mp != NULL);
|
|
135 assert(mp->pDevice != NULL);
|
|
136 if (volume < 0) volume = 0; else
|
|
137 if (volume > 64) volume = 64;
|
|
138
|
|
139 //fprintf(stderr, "chn %d: vol=%d, fad=%d, env=%d\n", channel, volume, mp->iCFadeOutVol[channel], mp->iCVolEnv[channel]);
|
|
140
|
|
141 jvmSetVolume(mp->pDevice, channel,
|
|
142 (mp->iCFadeOutVol[channel] * mp->iCVolEnv[channel] * volume) / (16 * 65536));
|
|
143 }
|
|
144
|
|
145
|
|
146 static void jmpCSetPanning(JSSPlayer * mp, int channel, int panning)
|
|
147 {
|
|
148 assert(mp != NULL);
|
|
149 assert(mp->pDevice != NULL);
|
|
150
|
|
151 jvmSetPan(mp->pDevice, channel,
|
|
152 panning + (((mp->iCPanEnv[channel] - 32) * (128 - abs(panning - 128))) / 32));
|
|
153 }
|
|
154
|
|
155
|
|
156 static void jmpCSetPosition(JSSPlayer * mp, int channel, int value)
|
|
157 {
|
|
158 assert(mp != NULL);
|
|
159 assert(mp->pDevice != NULL);
|
|
160
|
|
161 jvmSetPos(mp->pDevice, channel, value);
|
|
162 }
|
|
163
|
|
164
|
|
165 static void jmpCPlay(JSSPlayer * mp, int channel)
|
|
166 {
|
|
167 assert(mp != NULL);
|
|
168 assert(mp->pDevice != NULL);
|
|
169
|
|
170 jvmPlay(mp->pDevice, channel);
|
|
171 }
|
|
172
|
|
173
|
|
174 static void jmpCStop(JSSPlayer * mp, int channel)
|
|
175 {
|
|
176 assert(mp != NULL);
|
|
177 assert(mp->pDevice != NULL);
|
|
178
|
|
179 jvmStop(mp->pDevice, channel);
|
|
180 }
|
|
181
|
|
182
|
|
183
|
|
184 static int jmpFindEnvPoint(JSSEnvelope * env, int pos)
|
|
185 {
|
|
186 int i;
|
|
187
|
|
188 for (i = 0; i < env->npoints - 1; i++)
|
|
189 {
|
|
190 if (env->points[i].frame <= pos &&
|
|
191 env->points[i + 1].frame >= pos)
|
|
192 return i;
|
|
193 }
|
|
194
|
|
195 return -1;
|
|
196 }
|
|
197
|
|
198
|
|
199 static void jmpExecEnvelope(JSSEnvelope * env, BOOL keyOff, int * frames, BOOL * doExec, int * result)
|
|
200 {
|
|
201 int currPoint, delta;
|
|
202 JSSEnvelopePoint *ipf1, *ipf2;
|
|
203
|
|
204 // OK, find the current point based on frame
|
|
205 currPoint = jmpFindEnvPoint(env, *frames);
|
|
206
|
|
207 // Check if the envelope has ended
|
|
208 if (currPoint < 0 && (env->flags & jenvfLooped) == 0)
|
|
209 {
|
|
210 *doExec = FALSE;
|
|
211 return;
|
|
212 }
|
|
213
|
|
214 // Do the envelope looping here, if needed
|
|
215 if ((env->flags & jenvfLooped) && *frames >= env->points[env->loopE].frame)
|
|
216 {
|
|
217 currPoint = env->loopS;
|
|
218 *frames = env->points[currPoint].frame;
|
|
219 }
|
|
220
|
|
221 // If the current point is OK, then process the envelope
|
|
222 if (currPoint >= 0)
|
|
223 {
|
|
224 // Linearly interpolate the value for given frame
|
|
225 ipf1 = &env->points[currPoint];
|
|
226 ipf2 = &env->points[currPoint + 1];
|
|
227
|
|
228 delta = (ipf2->frame - ipf1->frame);
|
|
229 if (delta > 0)
|
|
230 *result = ipf1->value + ((ipf2->value - ipf1->value) * (*frames - ipf1->frame)) / delta;
|
|
231 else
|
|
232 *result = ipf1->value;
|
|
233 }
|
|
234
|
|
235 // The frame counter IS processed even if the envelope is not!
|
|
236 if ((env->flags & jenvfSustain) && currPoint == env->sustain &&
|
|
237 env->points[currPoint].frame == env->points[env->sustain].frame) {
|
|
238 if (keyOff) (*frames)++;
|
|
239 } else
|
|
240 (*frames)++;
|
|
241 }
|
|
242
|
|
243
|
|
244 static void jmpProcessExtInstrument(JSSPlayer * mp, int channel)
|
|
245 {
|
|
246 JSSExtInstrument *inst = mp->iCExtInstrument[channel];
|
|
247
|
|
248 // Get the instrument for envelope data
|
|
249 if (!inst) return;
|
|
250
|
|
251 // Process the autovibrato
|
|
252 /*
|
|
253 FIXME fix me FIX me!!! todo.
|
|
254 */
|
|
255
|
|
256 // Process the volume envelope
|
|
257 if (inst->volumeEnv.flags & jenvfUsed)
|
|
258 {
|
|
259 // Process the instrument volume fadeout
|
|
260 if (mp->iCKeyOff[channel] && (mp->iCFadeOutVol[channel] > 0) && (inst->fadeOut > 0))
|
|
261 {
|
|
262 int tmp = (mp->iCFadeOutVol[channel] - inst->fadeOut);
|
|
263 if (tmp < 0) tmp = 0;
|
|
264 mp->iCFadeOutVol[channel] = tmp;
|
|
265
|
|
266 JMPSETNDFLAGS(cdfNewVolume);
|
|
267 }
|
|
268
|
|
269 if (mp->iCVolEnv_Exec[channel])
|
|
270 {
|
|
271 // Execute the volume envelope
|
|
272 jmpExecEnvelope(&(inst->volumeEnv), mp->iCKeyOff[channel],
|
|
273 &(mp->iCVolEnv_Frames[channel]), &(mp->iCVolEnv_Exec[channel]),
|
|
274 &(mp->iCVolEnv[channel]));
|
|
275
|
|
276 JMPSETNDFLAGS(cdfNewVolume);
|
|
277 }
|
|
278 }
|
|
279 else
|
|
280 {
|
|
281 // If the envelope is not used, set max volume
|
|
282 if (mp->iCVolEnv[channel] != mpMaxVol)
|
|
283 {
|
|
284 mp->iCVolEnv[channel] = mpMaxVol;
|
|
285 mp->iCFadeOutVol[channel] = mpMaxFadeoutVol;
|
|
286 JMPSETNDFLAGS(cdfNewVolume);
|
|
287 }
|
|
288 }
|
|
289
|
|
290 // Process the panning envelope
|
|
291 if (inst->panningEnv.flags & jenvfUsed)
|
|
292 {
|
|
293 if (mp->iCPanEnv_Exec[channel])
|
|
294 {
|
|
295 // Execute the panning envelope
|
|
296 jmpExecEnvelope(&(inst->panningEnv), mp->iCKeyOff[channel],
|
|
297 &(mp->iCPanEnv_Frames[channel]), &(mp->iCPanEnv_Exec[channel]),
|
|
298 &(mp->iCPanEnv[channel]));
|
|
299
|
|
300 JMPSETNDFLAGS(cdfNewPanPos);
|
|
301 }
|
|
302 }
|
|
303 else
|
|
304 {
|
|
305 // If the envelope is not used, set center panning
|
|
306 if (mp->iCPanEnv[channel] != mpPanCenter)
|
|
307 {
|
|
308 mp->iCPanEnv[channel] = mpPanCenter;
|
|
309 JMPSETNDFLAGS(cdfNewPanPos);
|
|
310 }
|
|
311 }
|
|
312 }
|
|
313
|
|
314
|
|
315 /*
|
|
316 * The player
|
|
317 */
|
|
318 JSSPlayer *jmpInit(JSSMixer *pDevice)
|
|
319 {
|
|
320 JSSPlayer *mp;
|
|
321
|
|
322 // Initialize global tables
|
|
323 if (jmpSineTable == NULL)
|
|
324 {
|
|
325 int i;
|
|
326 if ((jmpSineTable = dmMalloc(jmpNSineTable * sizeof(int))) == NULL)
|
|
327 JSSERROR(DMERR_MALLOC, NULL, "Could not allocate memory for sinus table.\n");
|
|
328
|
|
329 for (i = 0; i < 256; i++)
|
|
330 {
|
|
331 float f = ((float) i * M_PI * 2.0f) / 256.0f;
|
|
332 jmpSineTable[i] = (int) (sin(f) * 2048.0f);
|
|
333 }
|
|
334 }
|
|
335
|
|
336 // Allocate a player structure
|
|
337 mp = dmMalloc0(sizeof(JSSPlayer));
|
|
338 if (!mp)
|
|
339 JSSERROR(DMERR_MALLOC, NULL, "Could not allocate memory for player structure.\n");
|
|
340
|
|
341 // Set variables
|
|
342 mp->pDevice = pDevice;
|
|
343 #ifdef JSS_SUP_THREADS
|
|
344 mp->mutex = dmCreateMutex();
|
|
345 #endif
|
|
346
|
|
347 return mp;
|
|
348 }
|
|
349
|
|
350
|
|
351 int jmpClose(JSSPlayer * mp)
|
|
352 {
|
|
353 if (mp == NULL)
|
|
354 return DMERR_NULLPTR;
|
|
355
|
|
356 // Stop player
|
|
357 jmpStop(mp);
|
|
358
|
|
359 // Deallocate resources
|
|
360 #ifdef JSS_SUP_THREADS
|
|
361 dmDestroyMutex(mp->mutex);
|
|
362 #endif
|
|
363
|
|
364 // Clear structure
|
|
365 memset(mp, 0, sizeof(JSSPlayer));
|
|
366 dmFree(mp);
|
|
367
|
|
368 return DMERR_OK;
|
|
369 }
|
|
370
|
|
371
|
|
372 /* Reset the envelopes for given channel.
|
|
373 */
|
|
374 static void jmpResetEnvelopes(JSSPlayer * mp, int channel)
|
|
375 {
|
|
376 assert(mp != NULL);
|
|
377
|
|
378 mp->iCPanEnv_Frames[channel] = mp->iCVolEnv_Frames[channel] = 0;
|
|
379 mp->iCPanEnv_Exec[channel] = mp->iCVolEnv_Exec[channel] = TRUE;
|
|
380 }
|
|
381
|
|
382
|
|
383 /* Clear module player structure
|
|
384 */
|
|
385 void jmpClearPlayer(JSSPlayer * mp)
|
|
386 {
|
|
387 int i;
|
|
388 assert(mp != NULL);
|
|
389 JSS_LOCK(mp);
|
|
390
|
|
391 // Initialize general variables
|
|
392 mp->iPatternDelay = 0;
|
|
393 mp->newRowSet = FALSE;
|
|
394 mp->newOrderSet = FALSE;
|
|
395 mp->iTick = jsetNotSet;
|
|
396 mp->iRow = 0;
|
|
397 mp->iLastPatLoopRow = 0;
|
|
398
|
|
399 // Initialize channel data
|
|
400 memset(&(mp->iPatLoopRow), 0, sizeof(mp->iPatLoopRow));
|
|
401 memset(&(mp->iPatLoopCount), 0, sizeof(mp->iPatLoopCount));
|
|
402
|
|
403 memset(&mp->iLastPortaParam, 0, sizeof(mp->iLastPortaParam));
|
|
404 memset(&mp->iLastPortaToNoteParam, 0, sizeof(mp->iLastPortaToNoteParam));
|
|
405 memset(&mp->iLastPortaToNotePitch, 0, sizeof(mp->iLastPortaToNotePitch));
|
|
406
|
|
407 memset(&mp->iVibratoPos, 0, sizeof(mp->iVibratoPos));
|
|
408 memset(&mp->iVibratoSpeed, 0, sizeof(mp->iVibratoSpeed));
|
|
409 memset(&mp->iVibratoDepth, 0, sizeof(mp->iVibratoDepth));
|
|
410 memset(&mp->iVibratoWC, 0, sizeof(mp->iVibratoWC));
|
|
411
|
|
412 memset(&mp->iTremoloPos, 0, sizeof(mp->iTremoloPos));
|
|
413 memset(&mp->iTremoloSpeed, 0, sizeof(mp->iTremoloSpeed));
|
|
414 memset(&mp->iTremoloDepth, 0, sizeof(mp->iTremoloDepth));
|
|
415 memset(&mp->iTremoloWC, 0, sizeof(mp->iTremoloWC));
|
|
416
|
|
417 memset(&mp->iLastTremorParam, 0, sizeof(mp->iLastTremorParam));
|
|
418 memset(&mp->iTremorCount, 0, sizeof(mp->iTremorCount));
|
|
419 memset(&mp->iLastSampleOffset, 0, sizeof(mp->iLastSampleOffset));
|
|
420 memset(&mp->iLastRetrigParam, 0, sizeof(mp->iLastRetrigParam));
|
|
421 memset(&mp->iLastVolSlideParam, 0, sizeof(mp->iLastVolSlideParam));
|
|
422
|
|
423 memset(&mp->iRetrigNDFlags, 0, sizeof(mp->iRetrigNDFlags));
|
|
424 memset(&mp->iSaveNDFlags, 0, sizeof(mp->iSaveNDFlags));
|
|
425
|
|
426 memset(&mp->iCNewDataFlags, 0, sizeof(mp->iCNewDataFlags));
|
|
427 memset(&mp->iCPitch, 0, sizeof(mp->iCPitch));
|
|
428 memset(&mp->iCPosition, 0, sizeof(mp->iCPosition));
|
|
429 memset(&mp->iCVolume, 0, sizeof(mp->iCVolume));
|
|
430 memset(&mp->iCFadeOutVol, 0, sizeof(mp->iCFadeOutVol));
|
|
431 memset(&mp->iCVolEnv, 0, sizeof(mp->iCVolEnv));
|
|
432
|
|
433 memset(&mp->iCAutoVib_Frames, 0, sizeof(mp->iCAutoVib_Frames));
|
|
434 memset(&mp->iCPanEnv_Frames, 0, sizeof(mp->iCPanEnv_Frames));
|
|
435 memset(&mp->iCVolEnv_Frames, 0, sizeof(mp->iCVolEnv_Frames));
|
|
436
|
|
437 memset(&mp->iCLastFineVolumeslideUpParam, 0, sizeof(mp->iCLastFineVolumeslideUpParam));
|
|
438 memset(&mp->iCLastFineVolumeslideDownParam, 0, sizeof(mp->iCLastFineVolumeslideDownParam));
|
|
439 memset(&mp->iCLastExtraFinePortamentoUpParam, 0, sizeof(mp->iCLastExtraFinePortamentoUpParam));
|
|
440 memset(&mp->iCLastExtraFinePortamentoDownParam, 0, sizeof(mp->iCLastExtraFinePortamentoDownParam));
|
|
441 memset(&mp->iCLastFinePortamentoUpParam, 0, sizeof(mp->iCLastFinePortamentoUpParam));
|
|
442 memset(&mp->iCLastFinePortamentoDownParam, 0, sizeof(mp->iCLastFinePortamentoDownParam));
|
|
443
|
|
444 memset(&mp->iCPanEnv_Exec, 0, sizeof(mp->iCPanEnv_Exec));
|
|
445 memset(&mp->iCVolEnv_Exec, 0, sizeof(mp->iCVolEnv_Exec));
|
|
446 memset(&mp->iCKeyOff, 0, sizeof(mp->iCKeyOff));
|
|
447
|
|
448 for (i = 0; i < jsetNChannels; i++)
|
|
449 {
|
|
450 mp->iCNote[i] = jsetNotSet;
|
|
451 mp->iCInstrument[i] = NULL;
|
|
452 mp->iCInstrumentN[i] = jsetNotSet;
|
|
453 mp->iCExtInstrument[i] = NULL;
|
|
454 mp->iCExtInstrumentN[i] = jsetNotSet;
|
|
455 mp->iCPanning[i] = mpPanCenter;
|
|
456 mp->iCPanEnv[i] = mpPanCenter;
|
|
457 }
|
|
458
|
|
459 JSS_UNLOCK(mp);
|
|
460 }
|
|
461
|
|
462
|
|
463 /* Set module
|
|
464 */
|
|
465 void jmpSetModule(JSSPlayer * mp, JSSModule * pModule)
|
|
466 {
|
|
467 assert(mp != NULL);
|
|
468 JSS_LOCK(mp);
|
|
469
|
|
470 jmpStop(mp);
|
|
471 jmpClearPlayer(mp);
|
|
472
|
|
473 mp->pModule = pModule;
|
|
474
|
|
475 JSS_UNLOCK(mp);
|
|
476 }
|
|
477
|
|
478
|
|
479 /* Stop playing
|
|
480 */
|
|
481 void jmpStop(JSSPlayer * mp)
|
|
482 {
|
|
483 assert(mp != NULL);
|
|
484 JSS_LOCK(mp);
|
|
485
|
|
486 if (mp->isPlaying)
|
|
487 {
|
|
488 // Remove callback
|
|
489 jvmRemoveCallback(mp->pDevice);
|
|
490 mp->isPlaying = FALSE;
|
|
491 }
|
|
492
|
|
493 JSS_UNLOCK(mp);
|
|
494 }
|
|
495
|
|
496
|
|
497 /* Resume playing
|
|
498 */
|
|
499 void jmpResume(JSSPlayer * mp)
|
|
500 {
|
|
501 assert(mp != NULL);
|
|
502 JSS_LOCK(mp);
|
|
503
|
|
504 if (!mp->isPlaying)
|
|
505 {
|
|
506 int result = jvmSetCallback(mp->pDevice, jmpExec, (void *) mp);
|
|
507 if (result != DMERR_OK)
|
|
508 JSSERROR(result,, "Could not initialize callback for player.\n");
|
|
509
|
|
510 mp->isPlaying = TRUE;
|
|
511 }
|
|
512
|
|
513 JSS_UNLOCK(mp);
|
|
514 }
|
|
515
|
|
516
|
|
517 /* Sets new order using given value as reference.
|
|
518 * Jumps over skip-points and invalid values, loops
|
|
519 * to first order if enabled.
|
|
520 */
|
|
521 static void jmpSetNewOrder(JSSPlayer * mp, int order)
|
|
522 {
|
|
523 BOOL orderOK;
|
|
524 int pattern;
|
|
525
|
|
526 pattern = jsetOrderEnd;
|
|
527 mp->iOrder = jsetNotSet;
|
|
528 orderOK = FALSE;
|
|
529
|
|
530 while (!orderOK)
|
|
531 {
|
|
532 if (order < 0 || order >= mp->pModule->norders)
|
|
533 {
|
|
534 jmpStop(mp);
|
|
535 orderOK = TRUE;
|
|
536 }
|
|
537 else
|
|
538 {
|
|
539 pattern = mp->pModule->orderList[order];
|
|
540 if (pattern == jsetOrderSkip)
|
|
541 {
|
|
542 order++;
|
|
543 }
|
|
544 else
|
|
545 if (pattern >= mp->pModule->npatterns || pattern == jsetOrderEnd)
|
|
546 {
|
|
547 jmpStop(mp);
|
|
548 orderOK = TRUE;
|
|
549 }
|
|
550 else
|
|
551 {
|
|
552 // All OK
|
|
553 orderOK = TRUE;
|
|
554 mp->pPattern = mp->pModule->patterns[pattern];
|
|
555 mp->iPattern = pattern;
|
|
556 mp->iOrder = order;
|
|
557 }
|
|
558 }
|
|
559 }
|
|
560 }
|
|
561
|
|
562
|
|
563 /* Set new tempo-value of the player.
|
|
564 */
|
|
565 void jmpSetTempo(JSSPlayer * mp, int tempo)
|
|
566 {
|
|
567 assert(mp != NULL);
|
|
568 JSS_LOCK(mp);
|
|
569 assert(mp->pDevice != NULL);
|
|
570
|
|
571 mp->iTempo = tempo;
|
|
572 jvmSetCallbackFreq(mp->pDevice, (tempo * 2) / 5);
|
|
573 JSS_UNLOCK(mp);
|
|
574 }
|
|
575
|
|
576
|
|
577 void jmpClearChannels(JSSPlayer * mp)
|
|
578 {
|
|
579 int i;
|
|
580 assert(mp != NULL);
|
|
581 JSS_LOCK(mp);
|
|
582 assert(mp->pDevice != NULL);
|
|
583 assert(mp->pModule != NULL);
|
|
584
|
|
585 for (i = 0; i < mp->pModule->nchannels; i++)
|
|
586 {
|
|
587 jvmStop(mp->pDevice, i);
|
|
588 jvmClear(mp->pDevice, i);
|
|
589 }
|
|
590
|
|
591 JSS_UNLOCK(mp);
|
|
592 }
|
|
593
|
|
594
|
|
595 /* Starts playing module from a given ORDER.
|
|
596 */
|
|
597 int jmpPlayOrder(JSSPlayer * mp, int iStartOrder)
|
|
598 {
|
|
599 int result;
|
|
600 assert(mp != NULL);
|
|
601
|
|
602 JSS_LOCK(mp);
|
|
603 assert(mp->pModule != NULL);
|
|
604
|
|
605 // Stop if already playing
|
|
606 jmpStop(mp);
|
|
607
|
|
608 jmpClearChannels(mp);
|
|
609
|
|
610 // Check starting order
|
|
611 if (iStartOrder < 0 || iStartOrder >= mp->pModule->norders)
|
|
612 {
|
|
613 JSS_UNLOCK(mp);
|
|
614 JSSERROR(DMERR_INVALID_ARGS, DMERR_INVALID_ARGS, "Invalid playing startorder given.\n");
|
|
615 }
|
|
616
|
|
617 // Initialize playing
|
|
618 jmpClearPlayer(mp);
|
|
619
|
|
620 jmpSetNewOrder(mp, iStartOrder);
|
|
621
|
|
622 if (mp->iOrder == jsetNotSet)
|
|
623 {
|
|
624 JSS_UNLOCK(mp);
|
|
625 JSSERROR(DMERR_NOT_SUPPORTED, DMERR_NOT_SUPPORTED,
|
|
626 "Could not start playing from given order #%i\n", iStartOrder);
|
|
627 }
|
|
628
|
|
629 mp->iSpeed = mp->pModule->defSpeed;
|
|
630 jmpSetTempo(mp, mp->pModule->defTempo);
|
|
631
|
|
632 // Set callback
|
|
633 result = jvmSetCallback(mp->pDevice, jmpExec, (void *) mp);
|
|
634 if (result != DMERR_OK)
|
|
635 {
|
|
636 JSS_UNLOCK(mp);
|
|
637 JSSERROR(result, result, "Could not initialize callback for player.\n");
|
|
638 }
|
|
639
|
|
640 mp->isPlaying = TRUE;
|
|
641
|
|
642 JSS_UNLOCK(mp);
|
|
643 return 0;
|
|
644 }
|
|
645
|
|
646
|
|
647 /* Play given pattern
|
|
648 */
|
|
649 int jmpPlayPattern(JSSPlayer * mp, int pattern)
|
|
650 {
|
|
651 int result;
|
|
652 assert(mp != NULL);
|
|
653 JSS_LOCK(mp);
|
|
654 assert(mp->pModule != NULL);
|
|
655
|
|
656 // Stop if already playing
|
|
657 jmpStop(mp);
|
|
658 jmpClearChannels(mp);
|
|
659
|
|
660 // Initialize playing
|
|
661 jmpClearPlayer(mp);
|
|
662
|
|
663 mp->iPattern = pattern;
|
|
664 mp->iSpeed = mp->pModule->defSpeed;
|
|
665 jmpSetTempo(mp, mp->pModule->defTempo);
|
|
666
|
|
667 // Set callback
|
|
668 result = jvmSetCallback(mp->pDevice, jmpExec, (void *) mp);
|
|
669 if (result != DMERR_OK)
|
|
670 {
|
|
671 JSS_UNLOCK(mp);
|
|
672 JSSERROR(result, result, "Could not initialize callback for player.\n");
|
|
673 }
|
|
674
|
|
675 mp->isPlaying = TRUE;
|
|
676
|
|
677 JSS_UNLOCK(mp);
|
|
678 return 0;
|
|
679 }
|
|
680
|
|
681
|
|
682 /* Set volume for given module channel.
|
|
683 */
|
|
684 static void jmpSetVolume(JSSPlayer * mp, int channel, int value)
|
|
685 {
|
|
686 // Check values
|
|
687 if (value < mpMinVol)
|
|
688 value = mpMinVol;
|
|
689 else if (value > mpMaxVol)
|
|
690 value = mpMaxVol;
|
|
691
|
|
692 // Set the volume
|
|
693 mp->iCVolume[channel] = value;
|
|
694 JMPSETNDFLAGS(cdfNewVolume);
|
|
695 }
|
|
696
|
|
697 #define jmpChangeVolume(Q, Z, X) jmpSetVolume(Q, Z, mp->iCVolume[channel] + (X))
|
|
698
|
|
699
|
|
700 /* Change the pitch of given channel by ADelta.
|
|
701 */
|
|
702 static void jmpChangePitch(JSSPlayer * mp, int channel, int delta)
|
|
703 {
|
|
704 int value;
|
|
705
|
|
706 // Calculate new pitch and check it
|
|
707 value = (mp->iCPitch[channel] + delta);
|
|
708 if (value < 0)
|
|
709 value = 0;
|
|
710
|
|
711 // Set the new pitch
|
|
712 mp->iCPitch[channel] = value;
|
|
713 JMPSETNDFLAGS(cdfNewPitch);
|
|
714 }
|
|
715
|
|
716
|
|
717 /* Do a note portamento (pitch slide) effect for given module channel.
|
|
718 */
|
|
719 static void jmpDoPortamento(JSSPlayer * mp, int channel)
|
|
720 {
|
|
721 // Check for zero parameter
|
|
722 if (mp->iLastPortaToNoteParam[channel] == 0)
|
|
723 {
|
|
724 JMPSETNDFLAGS(cdfNewPitch);
|
|
725 return;
|
|
726 }
|
|
727
|
|
728 /* Slide the pitch of channel to the destination value
|
|
729 * with speed of iLastPortaToNoteParam[] * 4 and stop when it equals.
|
|
730 */
|
|
731 if (mp->iCPitch[channel] != mp->iLastPortaToNotePitch[channel])
|
|
732 {
|
|
733 if (mp->iCPitch[channel] < mp->iLastPortaToNotePitch[channel])
|
|
734 {
|
|
735 // Increase pitch UP
|
|
736 jmpChangePitch(mp, channel, mp->iLastPortaToNoteParam[channel] * 4);
|
|
737 if (mp->iCPitch[channel] > mp->iLastPortaToNotePitch[channel])
|
|
738 mp->iCPitch[channel] = mp->iLastPortaToNotePitch[channel];
|
|
739 }
|
|
740 else
|
|
741 {
|
|
742 // Decrease pitch DOWN
|
|
743 jmpChangePitch(mp, channel, -(mp->iLastPortaToNoteParam[channel] * 4));
|
|
744 if (mp->iCPitch[channel] < mp->iLastPortaToNotePitch[channel])
|
|
745 mp->iCPitch[channel] = mp->iLastPortaToNotePitch[channel];
|
|
746 }
|
|
747 }
|
|
748 }
|
|
749
|
|
750
|
|
751 /* Do a tremolo effect for given module channel.
|
|
752 */
|
|
753 static void jmpDoTremolo(JSSPlayer * mp, int channel)
|
|
754 {
|
|
755 int delta, pos, depth;
|
|
756
|
|
757 // Check settings
|
|
758 if (mp->iTremoloDepth[channel] == 0 || mp->iTremoloSpeed[channel] == 0)
|
|
759 return;
|
|
760
|
|
761 // Get position of tremolo waveform
|
|
762 pos = mp->iTremoloPos[channel] & 255;
|
|
763 depth = mp->iTremoloDepth[channel];
|
|
764
|
|
765 switch (mp->iTremoloWC[channel] & 3)
|
|
766 {
|
|
767 case 0: // Sine-wave
|
|
768 delta = (jmpSineTable[pos] * depth) / 2048;
|
|
769 break;
|
|
770
|
|
771 case 1: // Ramp down
|
|
772 delta = ((pos - 128) * depth) / 128;
|
|
773 break;
|
|
774
|
|
775 case 2: // Square
|
|
776 delta = (((pos & 128) - 64) * depth) / 64;
|
|
777 break;
|
|
778
|
|
779 default:
|
|
780 return;
|
|
781 }
|
|
782
|
|
783 // Set the new volume
|
|
784 jmpCSetVolume(mp, channel, mp->iCVolume[channel] + delta);
|
|
785
|
|
786 // Advance tremolo waveform position
|
|
787 mp->iTremoloPos[channel] += mp->iTremoloSpeed[channel];
|
|
788 if (mp->iTremoloPos[channel] > 255)
|
|
789 mp->iTremoloPos[channel] = 0;
|
|
790 }
|
|
791
|
|
792
|
|
793 /* Do a vibrato effect for given module channel.
|
|
794 */
|
|
795 static void jmpDoVibrato(JSSPlayer * mp, int channel)
|
|
796 {
|
|
797 int delta, pos, depth;
|
|
798
|
|
799 // Check settings
|
|
800 if (mp->iVibratoDepth[channel] == 0 || mp->iVibratoSpeed[channel] == 0)
|
|
801 return;
|
|
802
|
|
803 // Get position of vibrato waveform
|
|
804 pos = mp->iVibratoPos[channel] & 255;
|
|
805 depth = mp->iVibratoDepth[channel];
|
|
806
|
|
807 switch (mp->iVibratoWC[channel] & 3)
|
|
808 {
|
|
809 case 0: // Sine-wave
|
|
810 delta = (jmpSineTable[pos] * depth) / 2048;
|
|
811 break;
|
|
812
|
|
813 case 1: // Ramp down
|
|
814 delta = ((pos - 128) * depth) / 16;
|
|
815 break;
|
|
816
|
|
817 case 2: // Square
|
|
818 delta = (((pos & 128) - 64) * depth) / 8;
|
|
819 break;
|
|
820
|
|
821 default:
|
|
822 return;
|
|
823 }
|
|
824
|
|
825 // Set the new frequency
|
|
826 jmpCSetPitch(mp, channel, mp->iCPitch[channel] + delta);
|
|
827
|
|
828 // Advance vibrato waveform position
|
|
829 mp->iVibratoPos[channel] += mp->iVibratoSpeed[channel];
|
|
830 if (mp->iVibratoPos[channel] > 255)
|
|
831 mp->iVibratoPos[channel] = 0;
|
|
832 }
|
|
833
|
|
834
|
|
835 /* Do a volume slide effect for given module channel.
|
|
836 */
|
|
837 static void jmpDoVolumeSlide(JSSPlayer * mp, int channel, int param)
|
|
838 {
|
|
839 int paramX, paramY;
|
|
840
|
|
841 JMPMAKEPARAM(param, paramX, paramY)
|
|
842
|
|
843 if (paramY == 0)
|
|
844 jmpChangeVolume(mp, channel, paramX);
|
|
845 if (paramX == 0)
|
|
846 jmpChangeVolume(mp, channel, -paramY);
|
|
847 }
|
|
848
|
|
849
|
|
850 /* Execute a pattern loop effect/command for given module channel.
|
|
851 *
|
|
852 * This routine works for most of the supported formats, as they
|
|
853 * use the 'standard' implementation ascending from MOD. However,
|
|
854 * here is included also a slightly kludgy implementation of the
|
|
855 * FT2 patloop bug.
|
|
856 */
|
|
857 static void jmpDoPatternLoop(JSSPlayer * mp, int channel, int paramY)
|
|
858 {
|
|
859 // Check what we need to do
|
|
860 if (paramY > 0)
|
|
861 {
|
|
862 // SBx/E6x loops 'x' times
|
|
863 if (mp->iPatLoopCount[channel] == 1)
|
|
864 mp->iPatLoopCount[channel] = 0;
|
|
865 else
|
|
866 {
|
|
867 // Check if we need to set the count
|
|
868 if (mp->iPatLoopCount[channel] == 0)
|
|
869 mp->iPatLoopCount[channel] = (paramY + 1);
|
|
870
|
|
871 // Loop to specified row
|
|
872 mp->iPatLoopCount[channel]--;
|
|
873 mp->iNewRow = mp->iPatLoopRow[channel];
|
|
874 mp->newRowSet = TRUE;
|
|
875 }
|
|
876 }
|
|
877 else
|
|
878 {
|
|
879 // SB0/E60 sets the loop start point
|
|
880 mp->iPatLoopRow[channel] = mp->iRow;
|
|
881
|
|
882 // This is here because of the infamous FT2 patloop bug
|
|
883 mp->iLastPatLoopRow = mp->iRow;
|
|
884 }
|
|
885 }
|
|
886
|
|
887
|
|
888 /* Do arpeggio effect
|
|
889 */
|
|
890 static void jmpDoArpeggio(JSSPlayer * mp, int channel, int paramY, int paramX)
|
|
891 {
|
|
892 JSSInstrument *tempInst = mp->iCInstrument[channel];
|
|
893 if (tempInst)
|
|
894 {
|
|
895 int tmp = mp->iCNote[channel];
|
|
896 if (tmp == jsetNotSet || tmp == jsetNoteOff) return;
|
|
897 switch (mp->iTick & 3)
|
|
898 {
|
|
899 case 1:
|
|
900 tmp += paramX;
|
|
901 break;
|
|
902 case 2:
|
|
903 tmp += paramY;
|
|
904 break;
|
|
905 }
|
|
906 jmpCSetPitch(mp, channel, jmpGetPeriodFromNote(mp, tmp + tempInst->ERelNote, tempInst->EFineTune));
|
|
907 }
|
|
908 }
|
|
909
|
|
910
|
|
911 /*
|
|
912 * Process pattern effects
|
|
913 */
|
|
914 static void jmpProcessRowEffect(JSSPlayer * mp, int channel, JSSNote * currNote)
|
|
915 {
|
|
916 int param, paramX, paramY;
|
|
917 char effect;
|
|
918
|
|
919 param = currNote->param;
|
|
920 JMPMAKEPARAM(param, paramX, paramY);
|
|
921 JMPGETEFFECT(effect, currNote->effect);
|
|
922
|
|
923 switch (effect)
|
|
924 {
|
|
925 case '0': // 0xy = Arpeggio
|
|
926 jmpDoArpeggio(mp, channel, paramX, paramY);
|
|
927 break;
|
|
928
|
|
929 case 'W': // Used widely in demo-music as MIDAS Sound System sync-command
|
|
930 case 'Q': // SoundTracker/OpenCP: Qxx = Set LP filter resonance
|
|
931 case 'Z': // SoundTracker/OpenCP: Zxx = Set LP filter cutoff freq
|
|
932 break;
|
|
933
|
|
934 case '1':
|
|
935 case '2': // 1xy = Portamento Up, 2xy = Portamento Down : IMPL.VERIFIED
|
|
936 if (param)
|
|
937 mp->iLastPortaParam[channel] = param;
|
|
938 break;
|
|
939
|
|
940 case '3': // 3xy = Porta To Note
|
|
941 if (param)
|
|
942 mp->iLastPortaToNoteParam[channel] = param;
|
|
943
|
|
944 if (currNote->note != jsetNotSet && currNote->note != jsetNoteOff) {
|
|
945 mp->iLastPortaToNotePitch[channel] = mp->iCPitch[channel];
|
|
946 mp->iCPitch[channel] = mp->iCOldPitch[channel];
|
|
947 JMPUNSETNDFLAGS(cdfNewPitch | cdfNewInstr | cdfNewPanPos);
|
|
948 }
|
|
949 break;
|
|
950
|
|
951 case '4': // 4xy = Vibrato : IMPL.VERIFIED
|
|
952 if (paramX)
|
|
953 mp->iVibratoSpeed[channel] = paramX;
|
|
954
|
|
955 if (paramY)
|
|
956 mp->iVibratoDepth[channel] = paramY;
|
|
957
|
|
958 if ((mp->iVibratoWC[channel] & 4) == 0)
|
|
959 mp->iVibratoPos[channel] = 0;
|
|
960 break;
|
|
961
|
|
962 case '5': // 5xy = Portamento + Volume Slide
|
|
963 case '6': // 6xy = Vibrato + Volume slide
|
|
964 if (param)
|
|
965 mp->iLastVolSlideParam[channel] = param;
|
|
966 break;
|
|
967
|
|
968 case '7': // 7xy = Tremolo
|
|
969 if (paramX)
|
|
970 mp->iTremoloSpeed[channel] = paramX;
|
|
971
|
|
972 if (paramY)
|
|
973 mp->iTremoloDepth[channel] = paramY;
|
|
974
|
|
975 if ((mp->iTremoloWC[channel] & 4) == 0)
|
|
976 mp->iTremoloPos[channel] = 0;
|
|
977 break;
|
|
978
|
|
979 case '8': // 8xx = Set Panning
|
|
980 JMPDEBUG("Set Panning used, UNIMPLEMENTED");
|
|
981 break;
|
|
982
|
|
983 case '9': // 9xx = Set Sample Offset : IMPL.VERIFIED
|
|
984 if (mp->iCNewDataFlags[channel] & cdfNewPitch) {
|
|
985 mp->iCPosition[channel] = param * 0x100;
|
|
986 JMPSETNDFLAGS(cdfNewPos);
|
|
987 }
|
|
988 break;
|
|
989
|
|
990 case 'A': // Axy = Volume Slide : IMPL.VERIFIED
|
|
991 if (param)
|
|
992 mp->iLastVolSlideParam[channel] = param;
|
|
993 break;
|
|
994
|
|
995 case 'B': // Bxx = Pattern Jump : IMPL.VERIFIED
|
|
996 mp->iNewOrder = param;
|
|
997 mp->newOrderSet = TRUE;
|
|
998 mp->jumpFlag = TRUE;
|
|
999 mp->iLastPatLoopRow = 0;
|
|
1000 break;
|
|
1001
|
|
1002 case 'C': // Cxx = Set Volume : IMPL.VERIFIED
|
|
1003 jmpSetVolume(mp, channel, param);
|
|
1004 break;
|
|
1005
|
|
1006 case 'D': // Dxx = Pattern Break : IMPL.VERIFIED
|
|
1007 // Compute the new row
|
|
1008 mp->iNewRow = (paramX * 10) + paramY;
|
|
1009 if (mp->iNewRow >= mp->pPattern->nrows)
|
|
1010 mp->iNewRow = 0;
|
|
1011
|
|
1012 mp->newRowSet = TRUE;
|
|
1013
|
|
1014 // Now we do some tricky tests
|
|
1015 if (!mp->breakFlag && !mp->jumpFlag) {
|
|
1016 mp->iNewOrder = mp->iOrder + 1;
|
|
1017 mp->newOrderSet = TRUE;
|
|
1018 }
|
|
1019
|
|
1020 mp->breakFlag = TRUE;
|
|
1021 break;
|
|
1022
|
|
1023 case 'E': // Exy = Special Effects
|
|
1024 switch (paramX) {
|
|
1025 case 0x00: // E0x - Set filter (NOT SUPPORTED)
|
|
1026 JMPDEBUG("Set Filter used, UNSUPPORTED");
|
|
1027 break;
|
|
1028
|
|
1029 case 0x01: // E1x - Fine Portamento Up
|
|
1030 if (paramY)
|
|
1031 mp->iCLastFinePortamentoUpParam[channel] = paramY;
|
|
1032
|
|
1033 jmpChangePitch(mp, channel, -(mp->iCLastFinePortamentoUpParam[channel] * 4));
|
|
1034 break;
|
|
1035
|
|
1036 case 0x02: // E2x - Fine Portamento Down
|
|
1037 if (paramY)
|
|
1038 mp->iCLastFinePortamentoDownParam[channel] = paramY;
|
|
1039
|
|
1040 jmpChangePitch(mp, channel, (mp->iCLastFinePortamentoDownParam[channel] * 4));
|
|
1041 break;
|
|
1042
|
|
1043 case 0x03: // E3x - Glissando Control (NOT SUPPORTED)
|
|
1044 break;
|
|
1045
|
|
1046 case 0x04: // E4x - Set Vibrato waveform
|
|
1047 mp->iVibratoWC[channel] = paramY;
|
|
1048 break;
|
|
1049
|
|
1050 case 0x05: // E5x - Set Finetune
|
|
1051 JMPDEBUG("Set Finetune used, UNIMPLEMENTED");
|
|
1052 break;
|
|
1053
|
|
1054 case 0x06: // E6x - Set Pattern Loop
|
|
1055 jmpDoPatternLoop(mp, channel, paramY);
|
|
1056 break;
|
|
1057
|
|
1058 case 0x07: // E7x - Set Tremolo waveform
|
|
1059 mp->iTremoloWC[channel] = paramY;
|
|
1060 break;
|
|
1061
|
|
1062 case 0x08: // E8x - Set Pan Position
|
|
1063 mp->iCPanning[channel] = (paramY * 16);
|
|
1064 JMPSETNDFLAGS(cdfNewPanPos);
|
|
1065 break;
|
|
1066
|
|
1067 case 0x09: // E9x - Retrig note
|
|
1068 JMPDEBUG("Retrig Note used, UNIMPLEMENTED");
|
|
1069 break;
|
|
1070
|
|
1071 case 0x0a: // EAx - Fine Volumeslide Up
|
|
1072 if (paramY)
|
|
1073 mp->iCLastFineVolumeslideUpParam[channel] = paramY;
|
|
1074
|
|
1075 jmpChangeVolume(mp, channel, mp->iCLastFineVolumeslideUpParam[channel]);
|
|
1076 break;
|
|
1077
|
|
1078 case 0x0b: // EBx - Fine Volumeslide Down
|
|
1079 if (paramY)
|
|
1080 mp->iCLastFineVolumeslideDownParam[channel] = paramY;
|
|
1081 jmpChangeVolume(mp, channel, -(mp->iCLastFineVolumeslideDownParam[channel]));
|
|
1082 break;
|
|
1083
|
|
1084 case 0x0c: // ECx - Set Note Cut (NOT PROCESSED IN TICK0)
|
|
1085 break;
|
|
1086
|
|
1087 case 0x0d: // EDx - Set Note Delay : IMPL.VERIFIED
|
|
1088 if (paramY > 0)
|
|
1089 {
|
|
1090 // Save the ND-flags, then clear
|
|
1091 mp->iSaveNDFlags[channel] = mp->iCNewDataFlags[channel];
|
|
1092 mp->iCNewDataFlags[channel] = 0;
|
|
1093 // TODO .. does this only affect NOTE or also instrument?
|
|
1094 }
|
|
1095 break;
|
|
1096
|
|
1097 case 0x0e: // EEx - Set Pattern Delay : IMPL.VERIFIED
|
|
1098 mp->iPatternDelay = paramY;
|
|
1099 break;
|
|
1100
|
|
1101 case 0x0f: // EFx - Invert Loop (NOT SUPPORTED)
|
|
1102 JMPDEBUG("Invert Loop used, UNSUPPORTED");
|
|
1103 break;
|
|
1104
|
|
1105 default:
|
|
1106 JMPDEBUG("Unsupported special command used");
|
|
1107 }
|
|
1108 break;
|
|
1109
|
|
1110 case 'F': // Fxy = Set Speed / Tempo : IMPL.VERIFIED
|
|
1111 if (param > 0)
|
|
1112 {
|
|
1113 if (param < 0x20)
|
|
1114 mp->iSpeed = param;
|
|
1115 else
|
|
1116 jmpSetTempo(mp, param);
|
|
1117 }
|
|
1118 break;
|
|
1119
|
|
1120 case 'G': // Gxx = Global Volume
|
|
1121 mp->iGlobalVol = param;
|
|
1122 JMPSETNDFLAGS(cdfNewGlobalVol);
|
|
1123 break;
|
|
1124
|
|
1125
|
|
1126 case 'H': // Hxx = Global Volume Slide
|
|
1127 JMPDEBUG("Global Volume Slide used, UNIMPLEMENTED");
|
|
1128 break;
|
|
1129
|
|
1130 case 'K': // Kxx = Key-off (Same as key-off note)
|
|
1131 mp->iCKeyOff[channel] = TRUE;
|
|
1132 break;
|
|
1133
|
|
1134 case 'L': // Lxx = Set Envelope Position
|
|
1135 JMPDEBUG("Set Envelope Position used, NOT verified with FT2");
|
|
1136 mp->iCPanEnv_Frames[channel] = param;
|
|
1137 mp->iCVolEnv_Frames[channel] = param;
|
|
1138 mp->iCPanEnv_Exec[channel] = TRUE;
|
|
1139 mp->iCVolEnv_Exec[channel] = TRUE;
|
|
1140 break;
|
|
1141
|
|
1142 case 'R': // Rxy = Multi Retrig note
|
|
1143 JMPDEBUG("Multi Retrig Note used, UNIMPLEMENTED");
|
|
1144 break;
|
|
1145
|
|
1146 case 'T': // Txy = Tremor
|
|
1147 if (param)
|
|
1148 mp->iLastTremorParam[channel] = param;
|
|
1149 break;
|
|
1150
|
|
1151 case 'X': // Xxy = Extra Fine Portamento
|
|
1152 switch (paramX)
|
|
1153 {
|
|
1154 case 0x01: // X1y - Extra Fine Portamento Up
|
|
1155 if (paramY)
|
|
1156 mp->iCLastExtraFinePortamentoUpParam[param] = paramY;
|
|
1157
|
|
1158 jmpChangePitch(mp, channel, -(mp->iCLastExtraFinePortamentoUpParam[param]));
|
|
1159 break;
|
|
1160
|
|
1161 case 0x02: // X2y - Extra Fine Portamento Down
|
|
1162 if (paramY)
|
|
1163 mp->iCLastExtraFinePortamentoDownParam[param] = paramY;
|
|
1164
|
|
1165 jmpChangePitch(mp, channel, mp->iCLastExtraFinePortamentoUpParam[param]);
|
|
1166 break;
|
|
1167
|
|
1168 default:
|
|
1169 JMPDEBUG("Unsupported value in Extra Fine Portamento command!");
|
|
1170 break;
|
|
1171 }
|
|
1172 break;
|
|
1173
|
|
1174 default:
|
|
1175 JMPDEBUG("Unsupported effect");
|
|
1176 break;
|
|
1177 }
|
|
1178 }
|
|
1179
|
|
1180
|
|
1181 static void jmpProcessNewRow(JSSPlayer * mp, int channel)
|
|
1182 {
|
|
1183 JSSNote *currNote;
|
|
1184 JSSExtInstrument *extInst = NULL;
|
|
1185 JSSInstrument *inst = NULL;
|
|
1186 BOOL newNote = FALSE;
|
|
1187 int tmp, paramX, paramY;
|
|
1188
|
|
1189 JMPGETNOTE(currNote, mp->iRow, channel);
|
|
1190
|
|
1191 // Check for a new note/keyoff here
|
|
1192 if (currNote->note == jsetNoteOff)
|
|
1193 mp->iCKeyOff[channel] = TRUE;
|
|
1194 else
|
|
1195 if (currNote->note >= 0 && currNote->note <= 96)
|
|
1196 {
|
|
1197 // New note was set
|
|
1198 newNote = TRUE;
|
|
1199 mp->iCNote[channel] = currNote->note;
|
|
1200 }
|
|
1201
|
|
1202 // Check for new instrument
|
|
1203 if (currNote->instrument != jsetNotSet) {
|
|
1204 /* Envelopes and ext.instrument fadeout are initialized always if
|
|
1205 * new instrument is set, even if the instrument does not exist.
|
|
1206 */
|
|
1207 jmpResetEnvelopes(mp, channel);
|
|
1208 mp->iCKeyOff[channel] = FALSE;
|
|
1209 mp->iCFadeOutVol[channel] = mpMaxFadeoutVol;
|
|
1210
|
|
1211 // We save the instrument number here for later use
|
|
1212 if (currNote->instrument >= 0 && currNote->instrument < mp->pModule->nextInstruments)
|
|
1213 mp->iCExtInstrumentN[channel] = currNote->instrument;
|
|
1214 }
|
|
1215
|
|
1216 /* ONLY if newNote was SET NOW and ExtInstrument HAS BEEN set, we can
|
|
1217 * set new pitches, and other things...
|
|
1218 */
|
|
1219 if (newNote)
|
|
1220 {
|
|
1221 if (mp->iCExtInstrumentN[channel] != jsetNotSet)
|
|
1222 extInst = mp->pModule->extInstruments[mp->iCExtInstrumentN[channel]];
|
|
1223 else
|
|
1224 extInst = NULL;
|
|
1225
|
|
1226 if (extInst)
|
|
1227 {
|
|
1228 // Set instrument
|
|
1229 int note = mp->iCNote[channel];
|
|
1230 mp->iCExtInstrument[channel] = extInst;
|
|
1231
|
|
1232 // We set new Instrument ONLY if NEW NOTE has been set
|
|
1233 if (note != jsetNotSet)
|
|
1234 {
|
|
1235 // Get instrument number
|
|
1236 tmp = extInst->sNumForNotes[note];
|
|
1237
|
|
1238 if (tmp >= 0 && tmp < mp->pModule->ninstruments) {
|
|
1239 // Set the new instrument
|
|
1240 inst = mp->pModule->instruments[tmp];
|
|
1241 mp->iCInstrumentN[channel] = tmp;
|
|
1242 mp->iCInstrument[channel] = inst;
|
|
1243 mp->iCVolume[channel] = inst->volume;
|
|
1244 mp->iCPanning[channel] = inst->EPanning;
|
|
1245 mp->iCPosition[channel] = 0;
|
|
1246
|
|
1247 // Set NDFlags
|
|
1248 JMPSETNDFLAGS(cdfNewInstr | cdfNewPos | cdfNewPanPos | cdfNewVolume);
|
|
1249 }
|
|
1250 }
|
|
1251 }
|
|
1252 }
|
|
1253
|
|
1254 if (inst)
|
|
1255 {
|
|
1256 // Save old pitch for later use
|
|
1257 mp->iCOldPitch[channel] = mp->iCPitch[channel];
|
|
1258
|
|
1259 // Compute new pitch
|
|
1260 tmp = (mp->iCNote[channel] + inst->ERelNote);
|
|
1261 //fprintf(stderr, "HEH: %d + %d = %d\n", mp->iCNote[channel], inst->ERelNote, tmp);
|
|
1262 if (tmp < 0)
|
|
1263 tmp = 0;
|
|
1264 else if (tmp > 119)
|
|
1265 tmp = 119;
|
|
1266
|
|
1267 mp->iCPitch[channel] = jmpGetPeriodFromNote(mp, tmp, inst->EFineTune);
|
|
1268 JMPSETNDFLAGS(cdfNewPitch);
|
|
1269 }
|
|
1270
|
|
1271 // Process the volume column
|
|
1272 JMPMAKEPARAM(currNote->volume, paramX, paramY);
|
|
1273
|
|
1274 switch (paramX)
|
|
1275 {
|
|
1276 case 0x00:
|
|
1277 case 0x01:
|
|
1278 case 0x02:
|
|
1279 case 0x03:
|
|
1280 case 0x04:
|
|
1281 jmpSetVolume(mp, channel, currNote->volume);
|
|
1282 break;
|
|
1283
|
|
1284 case 0x07: // Dx = Fine Volumeslide Down : IMPL.VERIFIED
|
|
1285 jmpChangeVolume(mp, channel, -paramY);
|
|
1286 break;
|
|
1287
|
|
1288 case 0x08: // Ux = Fine Volumeslide Up : IMPL.VERIFIED
|
|
1289 jmpChangeVolume(mp, channel, paramY);
|
|
1290 break;
|
|
1291
|
|
1292 case 0x09: // Sx = Set vibrato speed : IMPL.VERIFIED
|
|
1293 mp->iVibratoSpeed[channel] = paramY;
|
|
1294 break;
|
|
1295
|
|
1296 case 0x0a: // Vx = Vibrato : IMPL.VERIFIED
|
|
1297 if (paramY)
|
|
1298 mp->iVibratoDepth[channel] = paramY;
|
|
1299 break;
|
|
1300
|
|
1301 case 0x0e: // Mx = Porta To Note : IMPL.VERIFIED
|
|
1302 if (paramY)
|
|
1303 mp->iLastPortaToNoteParam[channel] = paramY;
|
|
1304
|
|
1305 if (currNote->note != jsetNotSet && currNote->note != jsetNoteOff) {
|
|
1306 mp->iLastPortaToNotePitch[channel] = mp->iCPitch[channel];
|
|
1307 mp->iCPitch[channel] = mp->iCOldPitch[channel];
|
|
1308 JMPUNSETNDFLAGS(cdfNewPitch | cdfNewInstr | cdfNewPanPos);
|
|
1309 }
|
|
1310 break;
|
|
1311 }
|
|
1312
|
|
1313 // ...And finally process the Normal effects
|
|
1314 if (currNote->effect != jsetNotSet)
|
|
1315 jmpProcessRowEffect(mp, channel, currNote);
|
|
1316 }
|
|
1317
|
|
1318
|
|
1319 static void jmpProcessEffects(JSSPlayer * mp, int channel)
|
|
1320 {
|
|
1321 JSSNote *currNote;
|
|
1322 int param, paramX, paramY, tmp;
|
|
1323 char effect;
|
|
1324
|
|
1325 // Process the volume column effects
|
|
1326 JMPGETNOTE(currNote, mp->iRow, channel);
|
|
1327 JMPMAKEPARAM(currNote->volume, paramX, paramY);
|
|
1328
|
|
1329 switch (paramX)
|
|
1330 {
|
|
1331 case 0x05: // -x = Volumeslide Down : IMPL.VERIFIED
|
|
1332 jmpChangeVolume(mp, channel, -paramY);
|
|
1333 break;
|
|
1334
|
|
1335 case 0x06: // +x = Volumeslide Down : IMPL.VERIFIED
|
|
1336 jmpChangeVolume(mp, channel, paramY);
|
|
1337 break;
|
|
1338
|
|
1339 case 0x0a: // Vx = Vibrato : IMPL.VERIFIED
|
|
1340 jmpDoVibrato(mp, channel);
|
|
1341 break;
|
|
1342
|
|
1343 case 0x0e: // Mx = Porta To Note : IMPL.VERIFIED
|
|
1344 jmpDoPortamento(mp, channel);
|
|
1345 break;
|
|
1346 }
|
|
1347
|
|
1348 // ...And finally process the Normal effects
|
|
1349 if (currNote->effect == jsetNotSet)
|
|
1350 return;
|
|
1351
|
|
1352 param = currNote->param;
|
|
1353 JMPMAKEPARAM(param, paramX, paramY);
|
|
1354 JMPGETEFFECT(effect, currNote->effect);
|
|
1355
|
|
1356 switch (effect)
|
|
1357 {
|
|
1358 case '0': // 0xy = Arpeggio
|
|
1359 jmpDoArpeggio(mp, channel, paramX, paramY);
|
|
1360 break;
|
|
1361
|
|
1362 case '1': // 1xy = Portamento Up
|
|
1363 if (mp->iLastPortaParam[channel] > 0)
|
|
1364 jmpChangePitch(mp, channel, -(mp->iLastPortaParam[channel] * 4));
|
|
1365 break;
|
|
1366
|
|
1367 case '2': // 2xy = Portamento Down
|
|
1368 if (mp->iLastPortaParam[channel] > 0)
|
|
1369 jmpChangePitch(mp, channel, (mp->iLastPortaParam[channel] * 4));
|
|
1370 break;
|
|
1371
|
|
1372 case '3': // 3xy = Porta To Note
|
|
1373 jmpDoPortamento(mp, channel);
|
|
1374 break;
|
|
1375
|
|
1376 case '4': // 4xy = Vibrato
|
|
1377 jmpDoVibrato(mp, channel);
|
|
1378 break;
|
|
1379
|
|
1380 case '5': // 5xy = Portamento + Volume Slide
|
|
1381 jmpDoPortamento(mp, channel);
|
|
1382 jmpDoVolumeSlide(mp, channel, mp->iLastVolSlideParam[channel]);
|
|
1383 break;
|
|
1384
|
|
1385 case '6': // 6xy = Vibrato + Volume Slide
|
|
1386 jmpDoVibrato(mp, channel);
|
|
1387 jmpDoVolumeSlide(mp, channel, mp->iLastVolSlideParam[channel]);
|
|
1388 break;
|
|
1389
|
|
1390 case '7': // 7xy = Tremolo
|
|
1391 jmpDoTremolo(mp, channel);
|
|
1392 break;
|
|
1393
|
|
1394 case 'A': // Axy = Volume slide
|
|
1395 jmpDoVolumeSlide(mp, channel, mp->iLastVolSlideParam[channel]);
|
|
1396 break;
|
|
1397
|
|
1398 case 'E': // Exy = Special Effects
|
|
1399 switch (paramX)
|
|
1400 {
|
|
1401 case 0x0c: // ECx - Set Note Cut
|
|
1402 if (mp->iTick == paramY)
|
|
1403 jmpSetVolume(mp, channel, jsetMinVol);
|
|
1404 break;
|
|
1405
|
|
1406 case 0x0d: // EDx - Set Note Delay
|
|
1407 if (mp->iTick == paramY)
|
|
1408 mp->iCNewDataFlags[channel] = mp->iSaveNDFlags[channel];
|
|
1409 break;
|
|
1410 }
|
|
1411 break;
|
|
1412
|
|
1413 case 'T': // Txy = Tremor
|
|
1414 JMPMAKEPARAM(mp->iLastTremorParam[channel], paramX, paramY)
|
|
1415 paramX++;
|
|
1416 paramY++;
|
|
1417 tmp = (mp->iTremorCount[channel] % (paramX + paramY));
|
|
1418 if (tmp < paramX)
|
|
1419 jmpCSetVolume(mp, channel, mp->iCVolume[channel]);
|
|
1420 else
|
|
1421 jmpCSetVolume(mp, channel, jsetMinVol);
|
|
1422
|
|
1423 mp->iTremorCount[channel] = (tmp + 1);
|
|
1424 break;
|
|
1425 }
|
|
1426 }
|
|
1427
|
|
1428
|
|
1429 /* This is the main processing callback-loop of a module player.
|
|
1430 * It processes the ticks, calling the needed jmpProcessNewRow()
|
|
1431 * and jmpProcessEffects() methods for processing the module playing.
|
|
1432 */
|
|
1433 void jmpExec(void *pDEV, void *pMP)
|
|
1434 {
|
|
1435 JSSPlayer *mp;
|
|
1436 JSSMixer *dev;
|
|
1437 int channel, flags;
|
|
1438
|
|
1439 // Check some things via assert()
|
|
1440 assert(pMP != NULL);
|
|
1441 mp = (JSSPlayer *) pMP;
|
|
1442 JSS_LOCK(mp);
|
|
1443
|
|
1444 dev = (JSSMixer *) pDEV;
|
|
1445 assert(mp->pDevice == dev);
|
|
1446 assert(mp->pModule != NULL);
|
|
1447
|
|
1448 // Check if we are playing
|
|
1449 if (!mp->isPlaying)
|
|
1450 goto out;
|
|
1451
|
|
1452 // Clear channel new data flags
|
|
1453 mp->jumpFlag = FALSE;
|
|
1454 mp->breakFlag = FALSE;
|
|
1455 memset(mp->iCNewDataFlags, 0, sizeof(mp->iCNewDataFlags));
|
|
1456
|
|
1457 //fprintf(stderr, "1: iTick=%d, iOrder=%d, iPattern=%d, iRow=%d\n", mp->iTick, mp->iOrder, mp->iPattern, mp->iRow);
|
|
1458
|
|
1459 // Check for init-tick
|
|
1460 if (mp->iTick < 0)
|
|
1461 {
|
|
1462 // Initialize pattern
|
|
1463 if (mp->iOrder != jsetNotSet)
|
|
1464 jmpSetNewOrder(mp, mp->iOrder);
|
|
1465
|
|
1466 mp->iNewRow = 0;
|
|
1467 mp->newRowSet = TRUE;
|
|
1468 mp->iTick = mp->iSpeed;
|
|
1469 }
|
|
1470
|
|
1471 //fprintf(stderr, "2: iTick=%d, iOrder=%d, iPattern=%d, iRow=%d\n", mp->iTick, mp->iOrder, mp->iPattern, mp->iRow);
|
|
1472
|
|
1473 // Check if we are playing
|
|
1474 if (!mp->isPlaying)
|
|
1475 goto out;
|
|
1476
|
|
1477 assert(mp->pPattern);
|
|
1478
|
|
1479 // Update the tick
|
|
1480 mp->iTick++;
|
|
1481 if (mp->iTick >= mp->iSpeed)
|
|
1482 {
|
|
1483 // Re-init tick counter
|
|
1484 mp->iTick = 0;
|
|
1485
|
|
1486 // Check pattern delay
|
|
1487 if (mp->iPatternDelay > 0)
|
|
1488 mp->iPatternDelay--;
|
|
1489 else
|
|
1490 {
|
|
1491 // New pattern row
|
|
1492 if (mp->newRowSet)
|
|
1493 {
|
|
1494 mp->iRow = mp->iNewRow;
|
|
1495 mp->newRowSet = FALSE;
|
|
1496 } else
|
|
1497 mp->iRow++;
|
|
1498
|
|
1499 // Check for end of pattern
|
|
1500 if (mp->iRow >= mp->pPattern->nrows)
|
|
1501 {
|
|
1502 // Go to next order
|
|
1503 if (mp->iOrder != jsetNotSet)
|
|
1504 jmpSetNewOrder(mp, mp->iOrder + 1);
|
|
1505 else
|
|
1506 mp->isPlaying = FALSE;
|
|
1507
|
|
1508 // Check for FT2 quirks
|
|
1509 if (JMPGETMODFLAGS(mp, jmdfFT2Replay))
|
|
1510 mp->iRow = mp->iLastPatLoopRow;
|
|
1511 else
|
|
1512 mp->iRow = 0;
|
|
1513 }
|
|
1514
|
|
1515 if (!mp->isPlaying)
|
|
1516 goto out;
|
|
1517
|
|
1518 // Check current order
|
|
1519 if (mp->newOrderSet)
|
|
1520 {
|
|
1521 jmpSetNewOrder(mp, mp->iNewOrder);
|
|
1522 mp->newOrderSet = FALSE;
|
|
1523 }
|
|
1524
|
|
1525 //fprintf(stderr, "3: iTick=%d, iOrder=%d, iPattern=%d, iRow=%d\n", mp->iTick, mp->iOrder, mp->iPattern, mp->iRow);
|
|
1526
|
|
1527 if (!mp->isPlaying)
|
|
1528 goto out;
|
|
1529
|
|
1530 // TICK #0: Process new row
|
|
1531 for (channel = 0; channel < mp->pModule->nchannels; channel++)
|
|
1532 jmpProcessNewRow(mp, channel);
|
|
1533 } // iPatternDelay
|
|
1534 } // iTick
|
|
1535 else
|
|
1536 {
|
|
1537 // Implement FT2's pattern delay-effect: don't update effects while on patdelay
|
|
1538 if (!JMPGETMODFLAGS(mp, jmdfFT2Replay) ||
|
|
1539 (JMPGETMODFLAGS(mp, jmdfFT2Replay) && mp->iPatternDelay <= 0))
|
|
1540 {
|
|
1541 // TICK n: Process the effects
|
|
1542 for (channel = 0; channel < mp->pModule->nchannels; channel++)
|
|
1543 jmpProcessEffects(mp, channel);
|
|
1544 }
|
|
1545 }
|
|
1546
|
|
1547 // Check if playing has stopped
|
|
1548 if (!mp->isPlaying)
|
|
1549 goto out;
|
|
1550
|
|
1551 // Update player data to audio device/mixer
|
|
1552 for (channel = 0; channel < mp->pModule->nchannels; channel++)
|
|
1553 {
|
|
1554 // Process extended instruments
|
|
1555 jmpProcessExtInstrument(mp, channel);
|
|
1556
|
|
1557 // Check NDFlags and update channel data
|
|
1558 flags = mp->iCNewDataFlags[channel];
|
|
1559 if (flags)
|
|
1560 {
|
|
1561 // Check if we stop?
|
|
1562 if (flags & cdfStop)
|
|
1563 jmpCStop(mp, channel);
|
|
1564 else
|
|
1565 {
|
|
1566 // No, handle other flags
|
|
1567 if (flags & cdfNewInstr)
|
|
1568 {
|
|
1569 jmpCSetInstrument(mp, channel);
|
|
1570 jmpCPlay(mp, channel);
|
|
1571 }
|
|
1572
|
|
1573 if (flags & cdfNewPitch)
|
|
1574 jmpCSetPitch(mp, channel, mp->iCPitch[channel]);
|
|
1575
|
|
1576 if (flags & cdfNewPos)
|
|
1577 jmpCSetPosition(mp, channel, mp->iCPosition[channel]);
|
|
1578
|
|
1579 if (flags & cdfNewVolume)
|
|
1580 jmpCSetVolume(mp, channel, mp->iCVolume[channel]);
|
|
1581
|
|
1582 if (flags & cdfNewPanPos)
|
|
1583 jmpCSetPanning(mp, channel, mp->iCPanning[channel]);
|
|
1584
|
|
1585 if (flags & cdfNewGlobalVol)
|
|
1586 jvmSetGlobalVol(mp->pDevice, mp->iGlobalVol);
|
|
1587 }
|
|
1588 }
|
|
1589 }
|
|
1590
|
|
1591 out:
|
|
1592 JSS_UNLOCK(mp);
|
|
1593 }
|