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
|
30
|
984 if (mp->iCNewDataFlags[channel] & cdfNewPitch)
|
|
985 {
|
0
|
986 mp->iCPosition[channel] = param * 0x100;
|
|
987 JMPSETNDFLAGS(cdfNewPos);
|
|
988 }
|
|
989 break;
|
|
990
|
|
991 case 'A': // Axy = Volume Slide : IMPL.VERIFIED
|
|
992 if (param)
|
|
993 mp->iLastVolSlideParam[channel] = param;
|
|
994 break;
|
|
995
|
|
996 case 'B': // Bxx = Pattern Jump : IMPL.VERIFIED
|
|
997 mp->iNewOrder = param;
|
|
998 mp->newOrderSet = TRUE;
|
|
999 mp->jumpFlag = TRUE;
|
|
1000 mp->iLastPatLoopRow = 0;
|
|
1001 break;
|
|
1002
|
|
1003 case 'C': // Cxx = Set Volume : IMPL.VERIFIED
|
|
1004 jmpSetVolume(mp, channel, param);
|
|
1005 break;
|
|
1006
|
|
1007 case 'D': // Dxx = Pattern Break : IMPL.VERIFIED
|
|
1008 // Compute the new row
|
|
1009 mp->iNewRow = (paramX * 10) + paramY;
|
|
1010 if (mp->iNewRow >= mp->pPattern->nrows)
|
|
1011 mp->iNewRow = 0;
|
|
1012
|
|
1013 mp->newRowSet = TRUE;
|
|
1014
|
|
1015 // Now we do some tricky tests
|
|
1016 if (!mp->breakFlag && !mp->jumpFlag) {
|
|
1017 mp->iNewOrder = mp->iOrder + 1;
|
|
1018 mp->newOrderSet = TRUE;
|
|
1019 }
|
|
1020
|
|
1021 mp->breakFlag = TRUE;
|
|
1022 break;
|
|
1023
|
|
1024 case 'E': // Exy = Special Effects
|
|
1025 switch (paramX) {
|
|
1026 case 0x00: // E0x - Set filter (NOT SUPPORTED)
|
|
1027 JMPDEBUG("Set Filter used, UNSUPPORTED");
|
|
1028 break;
|
|
1029
|
|
1030 case 0x01: // E1x - Fine Portamento Up
|
|
1031 if (paramY)
|
|
1032 mp->iCLastFinePortamentoUpParam[channel] = paramY;
|
|
1033
|
|
1034 jmpChangePitch(mp, channel, -(mp->iCLastFinePortamentoUpParam[channel] * 4));
|
|
1035 break;
|
|
1036
|
|
1037 case 0x02: // E2x - Fine Portamento Down
|
|
1038 if (paramY)
|
|
1039 mp->iCLastFinePortamentoDownParam[channel] = paramY;
|
|
1040
|
|
1041 jmpChangePitch(mp, channel, (mp->iCLastFinePortamentoDownParam[channel] * 4));
|
|
1042 break;
|
|
1043
|
|
1044 case 0x03: // E3x - Glissando Control (NOT SUPPORTED)
|
|
1045 break;
|
|
1046
|
|
1047 case 0x04: // E4x - Set Vibrato waveform
|
|
1048 mp->iVibratoWC[channel] = paramY;
|
|
1049 break;
|
|
1050
|
|
1051 case 0x05: // E5x - Set Finetune
|
|
1052 JMPDEBUG("Set Finetune used, UNIMPLEMENTED");
|
|
1053 break;
|
|
1054
|
|
1055 case 0x06: // E6x - Set Pattern Loop
|
|
1056 jmpDoPatternLoop(mp, channel, paramY);
|
|
1057 break;
|
|
1058
|
|
1059 case 0x07: // E7x - Set Tremolo waveform
|
|
1060 mp->iTremoloWC[channel] = paramY;
|
|
1061 break;
|
|
1062
|
|
1063 case 0x08: // E8x - Set Pan Position
|
|
1064 mp->iCPanning[channel] = (paramY * 16);
|
|
1065 JMPSETNDFLAGS(cdfNewPanPos);
|
|
1066 break;
|
|
1067
|
|
1068 case 0x09: // E9x - Retrig note
|
|
1069 JMPDEBUG("Retrig Note used, UNIMPLEMENTED");
|
|
1070 break;
|
|
1071
|
|
1072 case 0x0a: // EAx - Fine Volumeslide Up
|
|
1073 if (paramY)
|
|
1074 mp->iCLastFineVolumeslideUpParam[channel] = paramY;
|
|
1075
|
|
1076 jmpChangeVolume(mp, channel, mp->iCLastFineVolumeslideUpParam[channel]);
|
|
1077 break;
|
|
1078
|
|
1079 case 0x0b: // EBx - Fine Volumeslide Down
|
|
1080 if (paramY)
|
|
1081 mp->iCLastFineVolumeslideDownParam[channel] = paramY;
|
|
1082 jmpChangeVolume(mp, channel, -(mp->iCLastFineVolumeslideDownParam[channel]));
|
|
1083 break;
|
|
1084
|
|
1085 case 0x0c: // ECx - Set Note Cut (NOT PROCESSED IN TICK0)
|
|
1086 break;
|
|
1087
|
|
1088 case 0x0d: // EDx - Set Note Delay : IMPL.VERIFIED
|
|
1089 if (paramY > 0)
|
|
1090 {
|
|
1091 // Save the ND-flags, then clear
|
|
1092 mp->iSaveNDFlags[channel] = mp->iCNewDataFlags[channel];
|
|
1093 mp->iCNewDataFlags[channel] = 0;
|
|
1094 // TODO .. does this only affect NOTE or also instrument?
|
|
1095 }
|
|
1096 break;
|
|
1097
|
|
1098 case 0x0e: // EEx - Set Pattern Delay : IMPL.VERIFIED
|
|
1099 mp->iPatternDelay = paramY;
|
|
1100 break;
|
|
1101
|
|
1102 case 0x0f: // EFx - Invert Loop (NOT SUPPORTED)
|
|
1103 JMPDEBUG("Invert Loop used, UNSUPPORTED");
|
|
1104 break;
|
|
1105
|
|
1106 default:
|
|
1107 JMPDEBUG("Unsupported special command used");
|
|
1108 }
|
|
1109 break;
|
|
1110
|
|
1111 case 'F': // Fxy = Set Speed / Tempo : IMPL.VERIFIED
|
|
1112 if (param > 0)
|
|
1113 {
|
|
1114 if (param < 0x20)
|
|
1115 mp->iSpeed = param;
|
|
1116 else
|
|
1117 jmpSetTempo(mp, param);
|
|
1118 }
|
|
1119 break;
|
|
1120
|
|
1121 case 'G': // Gxx = Global Volume
|
|
1122 mp->iGlobalVol = param;
|
|
1123 JMPSETNDFLAGS(cdfNewGlobalVol);
|
|
1124 break;
|
|
1125
|
|
1126
|
|
1127 case 'H': // Hxx = Global Volume Slide
|
|
1128 JMPDEBUG("Global Volume Slide used, UNIMPLEMENTED");
|
|
1129 break;
|
|
1130
|
|
1131 case 'K': // Kxx = Key-off (Same as key-off note)
|
|
1132 mp->iCKeyOff[channel] = TRUE;
|
|
1133 break;
|
|
1134
|
|
1135 case 'L': // Lxx = Set Envelope Position
|
|
1136 JMPDEBUG("Set Envelope Position used, NOT verified with FT2");
|
|
1137 mp->iCPanEnv_Frames[channel] = param;
|
|
1138 mp->iCVolEnv_Frames[channel] = param;
|
|
1139 mp->iCPanEnv_Exec[channel] = TRUE;
|
|
1140 mp->iCVolEnv_Exec[channel] = TRUE;
|
|
1141 break;
|
|
1142
|
|
1143 case 'R': // Rxy = Multi Retrig note
|
|
1144 JMPDEBUG("Multi Retrig Note used, UNIMPLEMENTED");
|
|
1145 break;
|
|
1146
|
|
1147 case 'T': // Txy = Tremor
|
|
1148 if (param)
|
|
1149 mp->iLastTremorParam[channel] = param;
|
|
1150 break;
|
|
1151
|
|
1152 case 'X': // Xxy = Extra Fine Portamento
|
|
1153 switch (paramX)
|
|
1154 {
|
|
1155 case 0x01: // X1y - Extra Fine Portamento Up
|
|
1156 if (paramY)
|
|
1157 mp->iCLastExtraFinePortamentoUpParam[param] = paramY;
|
|
1158
|
|
1159 jmpChangePitch(mp, channel, -(mp->iCLastExtraFinePortamentoUpParam[param]));
|
|
1160 break;
|
|
1161
|
|
1162 case 0x02: // X2y - Extra Fine Portamento Down
|
|
1163 if (paramY)
|
|
1164 mp->iCLastExtraFinePortamentoDownParam[param] = paramY;
|
|
1165
|
|
1166 jmpChangePitch(mp, channel, mp->iCLastExtraFinePortamentoUpParam[param]);
|
|
1167 break;
|
|
1168
|
|
1169 default:
|
|
1170 JMPDEBUG("Unsupported value in Extra Fine Portamento command!");
|
|
1171 break;
|
|
1172 }
|
|
1173 break;
|
|
1174
|
|
1175 default:
|
|
1176 JMPDEBUG("Unsupported effect");
|
|
1177 break;
|
|
1178 }
|
|
1179 }
|
|
1180
|
|
1181
|
|
1182 static void jmpProcessNewRow(JSSPlayer * mp, int channel)
|
|
1183 {
|
|
1184 JSSNote *currNote;
|
|
1185 JSSExtInstrument *extInst = NULL;
|
|
1186 JSSInstrument *inst = NULL;
|
|
1187 BOOL newNote = FALSE;
|
|
1188 int tmp, paramX, paramY;
|
|
1189
|
|
1190 JMPGETNOTE(currNote, mp->iRow, channel);
|
|
1191
|
|
1192 // Check for a new note/keyoff here
|
|
1193 if (currNote->note == jsetNoteOff)
|
|
1194 mp->iCKeyOff[channel] = TRUE;
|
|
1195 else
|
|
1196 if (currNote->note >= 0 && currNote->note <= 96)
|
|
1197 {
|
|
1198 // New note was set
|
|
1199 newNote = TRUE;
|
|
1200 mp->iCNote[channel] = currNote->note;
|
|
1201 }
|
|
1202
|
|
1203 // Check for new instrument
|
|
1204 if (currNote->instrument != jsetNotSet) {
|
|
1205 /* Envelopes and ext.instrument fadeout are initialized always if
|
|
1206 * new instrument is set, even if the instrument does not exist.
|
|
1207 */
|
|
1208 jmpResetEnvelopes(mp, channel);
|
|
1209 mp->iCKeyOff[channel] = FALSE;
|
|
1210 mp->iCFadeOutVol[channel] = mpMaxFadeoutVol;
|
|
1211
|
|
1212 // We save the instrument number here for later use
|
|
1213 if (currNote->instrument >= 0 && currNote->instrument < mp->pModule->nextInstruments)
|
|
1214 mp->iCExtInstrumentN[channel] = currNote->instrument;
|
|
1215 }
|
|
1216
|
|
1217 /* ONLY if newNote was SET NOW and ExtInstrument HAS BEEN set, we can
|
|
1218 * set new pitches, and other things...
|
|
1219 */
|
|
1220 if (newNote)
|
|
1221 {
|
|
1222 if (mp->iCExtInstrumentN[channel] != jsetNotSet)
|
|
1223 extInst = mp->pModule->extInstruments[mp->iCExtInstrumentN[channel]];
|
|
1224 else
|
|
1225 extInst = NULL;
|
|
1226
|
|
1227 if (extInst)
|
|
1228 {
|
|
1229 // Set instrument
|
|
1230 int note = mp->iCNote[channel];
|
|
1231 mp->iCExtInstrument[channel] = extInst;
|
|
1232
|
|
1233 // We set new Instrument ONLY if NEW NOTE has been set
|
|
1234 if (note != jsetNotSet)
|
|
1235 {
|
|
1236 // Get instrument number
|
|
1237 tmp = extInst->sNumForNotes[note];
|
|
1238
|
|
1239 if (tmp >= 0 && tmp < mp->pModule->ninstruments) {
|
|
1240 // Set the new instrument
|
|
1241 inst = mp->pModule->instruments[tmp];
|
|
1242 mp->iCInstrumentN[channel] = tmp;
|
|
1243 mp->iCInstrument[channel] = inst;
|
|
1244 mp->iCVolume[channel] = inst->volume;
|
|
1245 mp->iCPanning[channel] = inst->EPanning;
|
|
1246 mp->iCPosition[channel] = 0;
|
|
1247
|
|
1248 // Set NDFlags
|
|
1249 JMPSETNDFLAGS(cdfNewInstr | cdfNewPos | cdfNewPanPos | cdfNewVolume);
|
|
1250 }
|
|
1251 }
|
|
1252 }
|
|
1253 }
|
|
1254
|
|
1255 if (inst)
|
|
1256 {
|
|
1257 // Save old pitch for later use
|
|
1258 mp->iCOldPitch[channel] = mp->iCPitch[channel];
|
|
1259
|
|
1260 // Compute new pitch
|
|
1261 tmp = (mp->iCNote[channel] + inst->ERelNote);
|
|
1262 //fprintf(stderr, "HEH: %d + %d = %d\n", mp->iCNote[channel], inst->ERelNote, tmp);
|
|
1263 if (tmp < 0)
|
|
1264 tmp = 0;
|
|
1265 else if (tmp > 119)
|
|
1266 tmp = 119;
|
|
1267
|
|
1268 mp->iCPitch[channel] = jmpGetPeriodFromNote(mp, tmp, inst->EFineTune);
|
|
1269 JMPSETNDFLAGS(cdfNewPitch);
|
|
1270 }
|
|
1271
|
|
1272 // Process the volume column
|
|
1273 JMPMAKEPARAM(currNote->volume, paramX, paramY);
|
|
1274
|
|
1275 switch (paramX)
|
|
1276 {
|
|
1277 case 0x00:
|
|
1278 case 0x01:
|
|
1279 case 0x02:
|
|
1280 case 0x03:
|
|
1281 case 0x04:
|
|
1282 jmpSetVolume(mp, channel, currNote->volume);
|
|
1283 break;
|
|
1284
|
|
1285 case 0x07: // Dx = Fine Volumeslide Down : IMPL.VERIFIED
|
|
1286 jmpChangeVolume(mp, channel, -paramY);
|
|
1287 break;
|
|
1288
|
|
1289 case 0x08: // Ux = Fine Volumeslide Up : IMPL.VERIFIED
|
|
1290 jmpChangeVolume(mp, channel, paramY);
|
|
1291 break;
|
|
1292
|
|
1293 case 0x09: // Sx = Set vibrato speed : IMPL.VERIFIED
|
|
1294 mp->iVibratoSpeed[channel] = paramY;
|
|
1295 break;
|
|
1296
|
|
1297 case 0x0a: // Vx = Vibrato : IMPL.VERIFIED
|
|
1298 if (paramY)
|
|
1299 mp->iVibratoDepth[channel] = paramY;
|
|
1300 break;
|
|
1301
|
|
1302 case 0x0e: // Mx = Porta To Note : IMPL.VERIFIED
|
|
1303 if (paramY)
|
|
1304 mp->iLastPortaToNoteParam[channel] = paramY;
|
|
1305
|
|
1306 if (currNote->note != jsetNotSet && currNote->note != jsetNoteOff) {
|
|
1307 mp->iLastPortaToNotePitch[channel] = mp->iCPitch[channel];
|
|
1308 mp->iCPitch[channel] = mp->iCOldPitch[channel];
|
|
1309 JMPUNSETNDFLAGS(cdfNewPitch | cdfNewInstr | cdfNewPanPos);
|
|
1310 }
|
|
1311 break;
|
|
1312 }
|
|
1313
|
|
1314 // ...And finally process the Normal effects
|
|
1315 if (currNote->effect != jsetNotSet)
|
|
1316 jmpProcessRowEffect(mp, channel, currNote);
|
|
1317 }
|
|
1318
|
|
1319
|
|
1320 static void jmpProcessEffects(JSSPlayer * mp, int channel)
|
|
1321 {
|
|
1322 JSSNote *currNote;
|
|
1323 int param, paramX, paramY, tmp;
|
|
1324 char effect;
|
|
1325
|
|
1326 // Process the volume column effects
|
|
1327 JMPGETNOTE(currNote, mp->iRow, channel);
|
|
1328 JMPMAKEPARAM(currNote->volume, paramX, paramY);
|
|
1329
|
|
1330 switch (paramX)
|
|
1331 {
|
|
1332 case 0x05: // -x = Volumeslide Down : IMPL.VERIFIED
|
|
1333 jmpChangeVolume(mp, channel, -paramY);
|
|
1334 break;
|
|
1335
|
|
1336 case 0x06: // +x = Volumeslide Down : IMPL.VERIFIED
|
|
1337 jmpChangeVolume(mp, channel, paramY);
|
|
1338 break;
|
|
1339
|
|
1340 case 0x0a: // Vx = Vibrato : IMPL.VERIFIED
|
|
1341 jmpDoVibrato(mp, channel);
|
|
1342 break;
|
|
1343
|
|
1344 case 0x0e: // Mx = Porta To Note : IMPL.VERIFIED
|
|
1345 jmpDoPortamento(mp, channel);
|
|
1346 break;
|
|
1347 }
|
|
1348
|
|
1349 // ...And finally process the Normal effects
|
|
1350 if (currNote->effect == jsetNotSet)
|
|
1351 return;
|
|
1352
|
|
1353 param = currNote->param;
|
|
1354 JMPMAKEPARAM(param, paramX, paramY);
|
|
1355 JMPGETEFFECT(effect, currNote->effect);
|
|
1356
|
|
1357 switch (effect)
|
|
1358 {
|
|
1359 case '0': // 0xy = Arpeggio
|
|
1360 jmpDoArpeggio(mp, channel, paramX, paramY);
|
|
1361 break;
|
|
1362
|
|
1363 case '1': // 1xy = Portamento Up
|
|
1364 if (mp->iLastPortaParam[channel] > 0)
|
|
1365 jmpChangePitch(mp, channel, -(mp->iLastPortaParam[channel] * 4));
|
|
1366 break;
|
|
1367
|
|
1368 case '2': // 2xy = Portamento Down
|
|
1369 if (mp->iLastPortaParam[channel] > 0)
|
|
1370 jmpChangePitch(mp, channel, (mp->iLastPortaParam[channel] * 4));
|
|
1371 break;
|
|
1372
|
|
1373 case '3': // 3xy = Porta To Note
|
|
1374 jmpDoPortamento(mp, channel);
|
|
1375 break;
|
|
1376
|
|
1377 case '4': // 4xy = Vibrato
|
|
1378 jmpDoVibrato(mp, channel);
|
|
1379 break;
|
|
1380
|
|
1381 case '5': // 5xy = Portamento + Volume Slide
|
|
1382 jmpDoPortamento(mp, channel);
|
|
1383 jmpDoVolumeSlide(mp, channel, mp->iLastVolSlideParam[channel]);
|
|
1384 break;
|
|
1385
|
|
1386 case '6': // 6xy = Vibrato + Volume Slide
|
|
1387 jmpDoVibrato(mp, channel);
|
|
1388 jmpDoVolumeSlide(mp, channel, mp->iLastVolSlideParam[channel]);
|
|
1389 break;
|
|
1390
|
|
1391 case '7': // 7xy = Tremolo
|
|
1392 jmpDoTremolo(mp, channel);
|
|
1393 break;
|
|
1394
|
|
1395 case 'A': // Axy = Volume slide
|
|
1396 jmpDoVolumeSlide(mp, channel, mp->iLastVolSlideParam[channel]);
|
|
1397 break;
|
|
1398
|
|
1399 case 'E': // Exy = Special Effects
|
|
1400 switch (paramX)
|
|
1401 {
|
|
1402 case 0x0c: // ECx - Set Note Cut
|
|
1403 if (mp->iTick == paramY)
|
|
1404 jmpSetVolume(mp, channel, jsetMinVol);
|
|
1405 break;
|
|
1406
|
|
1407 case 0x0d: // EDx - Set Note Delay
|
|
1408 if (mp->iTick == paramY)
|
|
1409 mp->iCNewDataFlags[channel] = mp->iSaveNDFlags[channel];
|
|
1410 break;
|
|
1411 }
|
|
1412 break;
|
|
1413
|
|
1414 case 'T': // Txy = Tremor
|
|
1415 JMPMAKEPARAM(mp->iLastTremorParam[channel], paramX, paramY)
|
|
1416 paramX++;
|
|
1417 paramY++;
|
|
1418 tmp = (mp->iTremorCount[channel] % (paramX + paramY));
|
|
1419 if (tmp < paramX)
|
|
1420 jmpCSetVolume(mp, channel, mp->iCVolume[channel]);
|
|
1421 else
|
|
1422 jmpCSetVolume(mp, channel, jsetMinVol);
|
|
1423
|
|
1424 mp->iTremorCount[channel] = (tmp + 1);
|
|
1425 break;
|
|
1426 }
|
|
1427 }
|
|
1428
|
|
1429
|
|
1430 /* This is the main processing callback-loop of a module player.
|
|
1431 * It processes the ticks, calling the needed jmpProcessNewRow()
|
|
1432 * and jmpProcessEffects() methods for processing the module playing.
|
|
1433 */
|
|
1434 void jmpExec(void *pDEV, void *pMP)
|
|
1435 {
|
|
1436 JSSPlayer *mp;
|
|
1437 JSSMixer *dev;
|
|
1438 int channel, flags;
|
|
1439
|
|
1440 // Check some things via assert()
|
|
1441 assert(pMP != NULL);
|
|
1442 mp = (JSSPlayer *) pMP;
|
|
1443 JSS_LOCK(mp);
|
|
1444
|
|
1445 dev = (JSSMixer *) pDEV;
|
|
1446 assert(mp->pDevice == dev);
|
|
1447 assert(mp->pModule != NULL);
|
|
1448
|
|
1449 // Check if we are playing
|
|
1450 if (!mp->isPlaying)
|
|
1451 goto out;
|
|
1452
|
|
1453 // Clear channel new data flags
|
|
1454 mp->jumpFlag = FALSE;
|
|
1455 mp->breakFlag = FALSE;
|
|
1456 memset(mp->iCNewDataFlags, 0, sizeof(mp->iCNewDataFlags));
|
|
1457
|
|
1458 //fprintf(stderr, "1: iTick=%d, iOrder=%d, iPattern=%d, iRow=%d\n", mp->iTick, mp->iOrder, mp->iPattern, mp->iRow);
|
|
1459
|
|
1460 // Check for init-tick
|
|
1461 if (mp->iTick < 0)
|
|
1462 {
|
|
1463 // Initialize pattern
|
|
1464 if (mp->iOrder != jsetNotSet)
|
|
1465 jmpSetNewOrder(mp, mp->iOrder);
|
|
1466
|
|
1467 mp->iNewRow = 0;
|
|
1468 mp->newRowSet = TRUE;
|
|
1469 mp->iTick = mp->iSpeed;
|
|
1470 }
|
|
1471
|
|
1472 //fprintf(stderr, "2: iTick=%d, iOrder=%d, iPattern=%d, iRow=%d\n", mp->iTick, mp->iOrder, mp->iPattern, mp->iRow);
|
|
1473
|
|
1474 // Check if we are playing
|
|
1475 if (!mp->isPlaying)
|
|
1476 goto out;
|
|
1477
|
|
1478 assert(mp->pPattern);
|
|
1479
|
|
1480 // Update the tick
|
|
1481 mp->iTick++;
|
|
1482 if (mp->iTick >= mp->iSpeed)
|
|
1483 {
|
|
1484 // Re-init tick counter
|
|
1485 mp->iTick = 0;
|
|
1486
|
|
1487 // Check pattern delay
|
|
1488 if (mp->iPatternDelay > 0)
|
|
1489 mp->iPatternDelay--;
|
|
1490 else
|
|
1491 {
|
|
1492 // New pattern row
|
|
1493 if (mp->newRowSet)
|
|
1494 {
|
|
1495 mp->iRow = mp->iNewRow;
|
|
1496 mp->newRowSet = FALSE;
|
|
1497 } else
|
|
1498 mp->iRow++;
|
|
1499
|
|
1500 // Check for end of pattern
|
|
1501 if (mp->iRow >= mp->pPattern->nrows)
|
|
1502 {
|
|
1503 // Go to next order
|
|
1504 if (mp->iOrder != jsetNotSet)
|
|
1505 jmpSetNewOrder(mp, mp->iOrder + 1);
|
|
1506 else
|
|
1507 mp->isPlaying = FALSE;
|
|
1508
|
|
1509 // Check for FT2 quirks
|
|
1510 if (JMPGETMODFLAGS(mp, jmdfFT2Replay))
|
|
1511 mp->iRow = mp->iLastPatLoopRow;
|
|
1512 else
|
|
1513 mp->iRow = 0;
|
|
1514 }
|
|
1515
|
|
1516 if (!mp->isPlaying)
|
|
1517 goto out;
|
|
1518
|
|
1519 // Check current order
|
|
1520 if (mp->newOrderSet)
|
|
1521 {
|
|
1522 jmpSetNewOrder(mp, mp->iNewOrder);
|
|
1523 mp->newOrderSet = FALSE;
|
|
1524 }
|
|
1525
|
|
1526 //fprintf(stderr, "3: iTick=%d, iOrder=%d, iPattern=%d, iRow=%d\n", mp->iTick, mp->iOrder, mp->iPattern, mp->iRow);
|
|
1527
|
|
1528 if (!mp->isPlaying)
|
|
1529 goto out;
|
|
1530
|
|
1531 // TICK #0: Process new row
|
|
1532 for (channel = 0; channel < mp->pModule->nchannels; channel++)
|
|
1533 jmpProcessNewRow(mp, channel);
|
|
1534 } // iPatternDelay
|
|
1535 } // iTick
|
|
1536 else
|
|
1537 {
|
|
1538 // Implement FT2's pattern delay-effect: don't update effects while on patdelay
|
|
1539 if (!JMPGETMODFLAGS(mp, jmdfFT2Replay) ||
|
|
1540 (JMPGETMODFLAGS(mp, jmdfFT2Replay) && mp->iPatternDelay <= 0))
|
|
1541 {
|
|
1542 // TICK n: Process the effects
|
|
1543 for (channel = 0; channel < mp->pModule->nchannels; channel++)
|
|
1544 jmpProcessEffects(mp, channel);
|
|
1545 }
|
|
1546 }
|
|
1547
|
|
1548 // Check if playing has stopped
|
|
1549 if (!mp->isPlaying)
|
|
1550 goto out;
|
|
1551
|
|
1552 // Update player data to audio device/mixer
|
|
1553 for (channel = 0; channel < mp->pModule->nchannels; channel++)
|
|
1554 {
|
|
1555 // Process extended instruments
|
|
1556 jmpProcessExtInstrument(mp, channel);
|
|
1557
|
|
1558 // Check NDFlags and update channel data
|
|
1559 flags = mp->iCNewDataFlags[channel];
|
|
1560 if (flags)
|
|
1561 {
|
|
1562 // Check if we stop?
|
|
1563 if (flags & cdfStop)
|
|
1564 jmpCStop(mp, channel);
|
|
1565 else
|
|
1566 {
|
|
1567 // No, handle other flags
|
|
1568 if (flags & cdfNewInstr)
|
|
1569 {
|
|
1570 jmpCSetInstrument(mp, channel);
|
|
1571 jmpCPlay(mp, channel);
|
|
1572 }
|
|
1573
|
|
1574 if (flags & cdfNewPitch)
|
|
1575 jmpCSetPitch(mp, channel, mp->iCPitch[channel]);
|
|
1576
|
|
1577 if (flags & cdfNewPos)
|
|
1578 jmpCSetPosition(mp, channel, mp->iCPosition[channel]);
|
|
1579
|
|
1580 if (flags & cdfNewVolume)
|
|
1581 jmpCSetVolume(mp, channel, mp->iCVolume[channel]);
|
|
1582
|
|
1583 if (flags & cdfNewPanPos)
|
|
1584 jmpCSetPanning(mp, channel, mp->iCPanning[channel]);
|
|
1585
|
|
1586 if (flags & cdfNewGlobalVol)
|
|
1587 jvmSetGlobalVol(mp->pDevice, mp->iGlobalVol);
|
|
1588 }
|
|
1589 }
|
|
1590 }
|
|
1591
|
|
1592 out:
|
|
1593 JSS_UNLOCK(mp);
|
|
1594 }
|