Subversion Repositories svn.mios32

Rev

Rev 2516 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2359 Sauraen 1
/*
2
 * MIDIbox Quad Genesis: Synth engine
3
 *
4
 * ==========================================================================
5
 *
6
 *  Copyright (C) 2016 Sauraen (sauraen@gmail.com)
7
 *  Licensed for personal non-commercial use only.
8
 *  All other rights reserved.
9
 *
10
 * ==========================================================================
11
 */
12
 
13
#include <mios32.h>
14
#include "syeng.h"
15
 
16
#include <genesis.h>
17
#include <vgm.h>
2396 Sauraen 18
#include "demoprograms.h"
2409 Sauraen 19
#include "mode_vgm.h"
2516 Sauraen 20
#include <string.h>
2359 Sauraen 21
 
2366 Sauraen 22
#define UNUSED_RECENCY 0x01000000ul //about 362 seconds ago
2359 Sauraen 23
 
2382 Sauraen 24
static const u8 use_scores[4] = {0, 1, 8, 100};
25
 
26
#define VOICECLEARVGMCMDS 45
27
static const u8 voiceclearvgm[VOICECLEARVGMCMDS*4] = {
28
    //Key off
29
    0x52, 0x28, 0x00, 0x00,
30
    //Set release rate to full so EG states return to 0 while we're writing the other stuff
31
    0x52, 0x80, 0xFF, 0x00,
32
    0x52, 0x84, 0xFF, 0x00,
33
    0x52, 0x88, 0xFF, 0x00,
34
    0x52, 0x8C, 0xFF, 0x00,
35
    //Turn on SSG mode so EG returns to 0 faster
36
    0x52, 0x90, 0x08, 0x00,
37
    0x52, 0x94, 0x08, 0x00,
38
    0x52, 0x98, 0x08, 0x00,
39
    0x52, 0x9C, 0x08, 0x00,
40
    //Set key scale rate to maximum so EG returns to 0 faster
41
    0x52, 0x50, 0xC0, 0x00,
42
    0x52, 0x54, 0xC0, 0x00,
43
    0x52, 0x58, 0xC0, 0x00,
44
    0x52, 0x5C, 0xC0, 0x00,
45
    //Set all channel registers to 0, except output
46
    0x52, 0xA4, 0x00, 0x00,
47
    0x52, 0xB0, 0x00, 0x00,
48
    0x52, 0xB4, 0xC0, 0x00,
49
    //Set all operator registers to 0
50
    0x52, 0x30, 0x00, 0x00,
51
    0x52, 0x34, 0x00, 0x00,
52
    0x52, 0x38, 0x00, 0x00,
53
    0x52, 0x3C, 0x00, 0x00,
54
    0x52, 0x40, 0x00, 0x00,
55
    0x52, 0x44, 0x00, 0x00,
56
    0x52, 0x48, 0x00, 0x00,
57
    0x52, 0x4C, 0x00, 0x00,
58
    0x52, 0x60, 0x00, 0x00,
59
    0x52, 0x64, 0x00, 0x00,
60
    0x52, 0x68, 0x00, 0x00,
61
    0x52, 0x6C, 0x00, 0x00,
62
    0x52, 0x70, 0x00, 0x00,
63
    0x52, 0x74, 0x00, 0x00,
64
    0x52, 0x78, 0x00, 0x00,
65
    0x52, 0x7C, 0x00, 0x00,
66
    //Wait a short time (~6 ms)
67
    0x61, 0x00, 0x00, 0x01,
68
    //Turn off KSR, SSG-EG, and reset release rates to zero
69
    0x52, 0x50, 0x00, 0x00,
70
    0x52, 0x54, 0x00, 0x00,
71
    0x52, 0x58, 0x00, 0x00,
72
    0x52, 0x5C, 0x00, 0x00,
73
    0x52, 0x90, 0x00, 0x00,
74
    0x52, 0x94, 0x00, 0x00,
75
    0x52, 0x98, 0x00, 0x00,
76
    0x52, 0x9C, 0x00, 0x00,
77
    0x52, 0x80, 0x00, 0x00,
78
    0x52, 0x84, 0x00, 0x00,
79
    0x52, 0x88, 0x00, 0x00,
80
    0x52, 0x8C, 0x00, 0x00
81
};
82
 
83
 
84
typedef struct voiceclearlink_s {
85
    VgmHead* head;
86
    struct voiceclearlink_s* link;
87
} voiceclearlink;
88
 
2359 Sauraen 89
syngenesis_t syngenesis[GENESIS_COUNT];
90
synproginstance_t proginstances[MBQG_NUM_PROGINSTANCES];
91
synchannel_t channels[16*MBQG_NUM_PORTS];
2382 Sauraen 92
u8 voiceclearfull;
2391 Sauraen 93
static VgmSource* voiceclearsource;
94
static voiceclearlink* voiceclearlist;
2359 Sauraen 95
 
2488 Sauraen 96
 
97
 
2360 Sauraen 98
//TODO divide chips that use globals by the types of globals used, so multiple
99
//voices using the same effect e.g. "ugly" can play together
2359 Sauraen 100
 
2382 Sauraen 101
static void VoiceReset(u8 g, u8 v){
102
    if(voiceclearfull){
103
        if(v >= 1 && v <= 6){
2412 Sauraen 104
            //DBG("Setting up g %d v %d to be cleared via VGM", g, v);
2382 Sauraen 105
            syngenesis[g].channels[v].beingcleared = 1;
106
            voiceclearlink* link = vgmh2_malloc(sizeof(voiceclearlink));
2515 Sauraen 107
            VgmHead* head = VGM_Head_Create(voiceclearsource, 0x1000, 0x1000, 0);
2382 Sauraen 108
            link->head = head;
109
            head->channel[1].map_chip = g;
110
            head->channel[1].map_voice = v-1;
111
            //Insert at head of queue
112
            //MIOS32_IRQ_Disable();
113
            link->link = voiceclearlist;
114
            voiceclearlist = link;
115
            //MIOS32_IRQ_Enable();
116
            //Start playing
117
            VGM_Head_Restart(head, VGM_Player_GetVGMTime());
118
            head->playing = 1;
119
        }else{
120
            VGM_ResetChipVoiceAsync(g, v);
121
        }
122
    }else{
123
        VGM_PartialResetChipVoiceAsync(g, v);
124
    }
125
}
126
 
2372 Sauraen 127
static void ReleaseAllPI(synproginstance_t* pi){
2382 Sauraen 128
    u8 i, g, v;
2360 Sauraen 129
    VgmHead_Channel pimap;
130
    syngenesis_t* sg;
131
    for(i=0; i<12; ++i){
132
        pimap = pi->mapping[i];
133
        if(pimap.nodata) continue;
2382 Sauraen 134
        g = pimap.map_chip;
135
        sg = &syngenesis[g];
2360 Sauraen 136
        if(i == 0){
2484 Sauraen 137
            /*
138
            TODO: When to clear OPN2 globals data when deleting a PI which was
139
            using them? For LFO or other globals, other PIs / voices may be
140
            using them, so clearing them would be bad.
141
            */
142
        }else if(i >= 1 && i <= 6){
2360 Sauraen 143
            //FM voice
2382 Sauraen 144
            v = pimap.map_voice+1;
145
            sg->channels[v].ALL = 0;
146
            VoiceReset(g, v);
147
            //See if this chip has any voice using LFO
148
            for(v=1; v<7; ++v) if(sg->channels[v].lfo) break;
149
            if(v == 7){ //No LFO used
2472 Sauraen 150
                sg->lfomode = 0;
2360 Sauraen 151
            }
152
        }else if(i == 7){
153
            //DAC
154
            sg->channels[7].ALL = 0;
2382 Sauraen 155
            VoiceReset(g, 7);
2360 Sauraen 156
        }else if(i >= 8 && i <= 10){
157
            //SQ voice
2382 Sauraen 158
            v = pimap.map_voice+8;
159
            sg->channels[v].ALL = 0;
160
            VoiceReset(g, v);
2360 Sauraen 161
        }else{
162
            //Noise
163
            sg->channels[11].ALL = 0;
164
            sg->noisefreqsq3 = 0;
2382 Sauraen 165
            VoiceReset(g, 11);
2360 Sauraen 166
        }
167
    }
168
}
169
 
2372 Sauraen 170
static void ClearPI(synproginstance_t* pi){
2360 Sauraen 171
    //Stop actually playing
172
    if(pi->head != NULL){
173
        VGM_Head_Delete(pi->head);
174
        pi->head = NULL;
175
    }
2409 Sauraen 176
    //If this was being used statically, invalidate it
177
    if(pi->isstatic){
178
        Mode_Vgm_InvalidatePI(pi);
179
    }
2360 Sauraen 180
    //Invalidate PI
181
    pi->valid = 0;
2409 Sauraen 182
    pi->isstatic = 0;
2360 Sauraen 183
    pi->playing = 0;
184
    pi->playinginit = 0;
2417 Sauraen 185
    pi->needsnewinit = 0;
2366 Sauraen 186
    pi->recency = VGM_Player_GetVGMTime() - UNUSED_RECENCY;
2360 Sauraen 187
    //Release all resources
188
    ReleaseAllPI(pi);
189
}
190
 
2380 Sauraen 191
void SyEng_ClearVoice(u8 g, u8 v){
2376 Sauraen 192
    u8 use = syngenesis[g].channels[v].use;
193
    if(use == 0) return;
194
    if(use == 3){
195
        //Tracker voice
2380 Sauraen 196
        return;
2376 Sauraen 197
    }else{
198
        ClearPI(&proginstances[syngenesis[g].channels[v].pi_using]);
199
    }
200
    syngenesis[g].channels[v].use = 0;
201
}
202
 
2491 Sauraen 203
static void SetPIMappedVoicesUse(synproginstance_t* pi, u8 use){
2360 Sauraen 204
    u8 i, v;
205
    VgmHead_Channel pimap;
206
    syngenesis_t* sg;
207
    for(i=0; i<12; ++i){
208
        pimap = pi->mapping[i];
209
        if(pimap.nodata) continue;
2363 Sauraen 210
        sg = &syngenesis[pimap.map_chip];
2360 Sauraen 211
        if(i == 0){
2491 Sauraen 212
            if(pimap.option){
213
                //Only using globals for LFO
214
                //Don't touch use, since multiple voices may be using it
215
            }else{
216
                //We were using OPN2 globals
217
                for(v=0; v<8; ++v){
218
                    sg->channels[v].use = use;
219
                }
220
                //Skip to PSG section
221
                i = 7;
222
                continue;
2360 Sauraen 223
            }
2491 Sauraen 224
        }else if(i >= 1 && i <= 6){
2360 Sauraen 225
            //FM voice
226
            v = pimap.map_voice;
2491 Sauraen 227
            sg->channels[v+1].use = use;
2360 Sauraen 228
        }else if(i == 7){
229
            //DAC
2491 Sauraen 230
            sg->channels[7].use = use;
2360 Sauraen 231
        }else if(i >= 8 && i <= 10){
232
            //SQ voice
233
            v = pimap.map_voice;
2491 Sauraen 234
            sg->channels[v+8].use = use;
2360 Sauraen 235
        }else{
236
            //Noise
2491 Sauraen 237
            sg->channels[11].use = use;
2360 Sauraen 238
        }
239
    }
240
}
241
 
2380 Sauraen 242
static s8 FindOPN2ClearLFO(){
2360 Sauraen 243
    //Find an OPN2 with as few voices as possible using the LFO
2380 Sauraen 244
    u8 use, g, v, full;
245
    s8 bestg = -1;
2376 Sauraen 246
    u16 score, bestscore;
2380 Sauraen 247
    bestscore = 99;
2360 Sauraen 248
    for(g=0; g<GENESIS_COUNT; ++g){
249
        score = 0;
2376 Sauraen 250
        full = 1;
2360 Sauraen 251
        for(v=1; v<6; ++v){
2376 Sauraen 252
            use = syngenesis[g].channels[v].use;
2491 Sauraen 253
            if(use <= 1) full = 0;
2382 Sauraen 254
            if(syngenesis[g].channels[v].lfo){
2360 Sauraen 255
                //Only for voices using the LFO
2376 Sauraen 256
                score += use_scores[use];
257
                full = 0;
2360 Sauraen 258
            }
259
        }
2376 Sauraen 260
        if(full){
261
            //All voices are taken, and none are using the LFO, so clearing
262
            //this one won't do us any good
263
            score = 0xFF;
264
        }
2360 Sauraen 265
        if(score < bestscore){
266
            bestscore = score;
267
            bestg = g;
268
        }
269
    }
2380 Sauraen 270
    if(bestg >= 0){
2472 Sauraen 271
        //Kick out voices using the LFO, or if none of them fit, all
2484 Sauraen 272
        if(bestscore >= 99){
273
            DBG("FindOPN2ClearLFO kicking all voices out of OPN2 %d", bestg);
274
        }else{
275
            DBG("FindOPN2ClearLFO kicking LFO-using voices out of OPN2 %d", bestg);
276
        }
2380 Sauraen 277
        for(v=1; v<6; ++v){
2472 Sauraen 278
            if(syngenesis[bestg].channels[v].lfo || bestscore >= 99){
2380 Sauraen 279
                SyEng_ClearVoice(bestg, v);
280
            }
2360 Sauraen 281
        }
2484 Sauraen 282
    }else{
283
        DBG("FindOPN2ClearLFO returning g %d", bestg);
2360 Sauraen 284
    }
285
    return bestg;
286
}
287
 
2372 Sauraen 288
static void AssignVoiceToGenesis(u8 piindex, synproginstance_t* pi, u8 g, u8 vsource, u8 vdest, u8 vlfo){
2484 Sauraen 289
    DBG("--AssignVoiceToGenesis PI %d voice %d to genesis %d voice %d, vlfo=%d", piindex, vsource, g, vdest, vlfo);
2363 Sauraen 290
    syngenesis_usage_t* sgusage = &syngenesis[g].channels[vdest];
2380 Sauraen 291
    SyEng_ClearVoice(g, vdest);
2360 Sauraen 292
    //Assign voices
2364 Sauraen 293
    u8 proper, map_voice;
294
    if(vdest >= 1 && vdest <= 6){
295
        proper = 1;
296
        map_voice = vdest - 1;
297
    }else if(vdest >= 8 && vdest <= 10){
298
        proper = 0;
299
        map_voice = vdest - 8;
300
    }else{
301
        proper = 0;
302
        map_voice = 0;
303
    }
2484 Sauraen 304
    sgusage->use = 2; //pi->isstatic ? 3 : 2;
2360 Sauraen 305
    sgusage->pi_using = piindex;
306
    //Map PI
2364 Sauraen 307
    pi->mapping[vsource] = (VgmHead_Channel){.nodata = 0, .mute = 0, .map_chip = g, .map_voice = map_voice, .option = vlfo};
2482 Sauraen 308
    if(vlfo && proper){
309
        sgusage->lfo = 1;
310
        //Send LFO commands from this head to the same chip as this channel is on
2491 Sauraen 311
        pi->mapping[0] = (VgmHead_Channel){.nodata = 0, .mute = 0, .map_chip = g, .map_voice = 0, .option = 1};
2482 Sauraen 312
        //TODO potential bug:
313
        /*
314
        If we have a VGM with two voices in LFO Fixed mode, and the first gets
315
        assigned to one OPN2 and the second to another, we can only send the
316
        actual LFO commands to one of those OPN2s, so one OPN2 will never actually
317
        get the command to turn on its LFO.
318
        */
2484 Sauraen 319
    }else{
320
        sgusage->lfo = 0;
2482 Sauraen 321
    }
2360 Sauraen 322
}
323
 
2484 Sauraen 324
static void FindBestVoice(s8* bestg, s8* bestv, u32 now, s8 forceg, u8 vstart, u8 vend, u8 sumoverv){
325
    u16 score, totalscore, bestscore = 0x7FFF;
326
    u32 recency, totalrecency, maxrecency = 0;
327
    u8 g, v, use;
328
    u8 gstart = (forceg < 0) ? 0 : forceg;
329
    u8 gend = (forceg < 0) ? GENESIS_COUNT : forceg+1;
330
    syngenesis_usage_t* sgu; synproginstance_t* pi;
331
    *bestg = -1;
332
    *bestv = -1;
333
    for(g=gstart; g<gend; ++g){
334
        totalscore = 0;
335
        totalrecency = 0;
336
        for(v=vstart; v<=vend; ++v){
337
            sgu = &syngenesis[g].channels[v];
338
            use = sgu->use;
339
            score = use_scores[use] << 1;
340
            if(vstart == 1 && vend == 6 && (v == 3 || v == 6)) ++score; //If choosing from all 6, penalize 3+6
341
            if(use >= 1 && sgu->pi_using < MBQG_NUM_PROGINSTANCES){
342
                pi = &proginstances[sgu->pi_using];
343
                recency = pi->recency;
344
                if(pi->isstatic){
345
                    score = 100;
346
                }
347
            }else{
348
                recency = UNUSED_RECENCY;
349
            }
350
            if(sumoverv){
351
                totalscore += score;
352
                totalrecency += recency;
353
            }else{
354
                if(score < bestscore || (score == bestscore && recency > maxrecency)){
355
                    bestscore = score;
356
                    *bestv = v;
357
                    *bestg = g;
358
                    maxrecency = recency;
359
                }
360
            }
361
        }
362
        if(sumoverv){
363
            if(totalscore < bestscore || (totalscore == bestscore && totalrecency > maxrecency)){
364
                bestscore = totalscore;
365
                *bestv = vstart;
366
                *bestg = g;
367
                maxrecency = totalrecency;
368
            }
369
        }
370
    }
2491 Sauraen 371
    DBG("FindBestVoice asked forceg %d voices %d-%d sumoverv %d, result G%d V%d", forceg, vstart, vend, sumoverv, *bestg, *bestv);
2484 Sauraen 372
}
373
 
2418 Sauraen 374
static s32 AllocatePI(u8 piindex, VgmUsageBits pusage){
2360 Sauraen 375
    synproginstance_t* pi = &proginstances[piindex];
376
    syngenesis_t* sg;
2472 Sauraen 377
    u8 i, g, v, use, lfog;
2380 Sauraen 378
    s8 bestg, bestv;
2484 Sauraen 379
    u16 score;
380
    u32 now = VGM_Player_GetVGMTime();
2360 Sauraen 381
    //Initialize channels to have no data
382
    for(i=0; i<12; ++i){
383
        pi->mapping[i] = (VgmHead_Channel){.nodata = 1, .mute = 0, .map_chip = 0, .map_voice = 0, .option = 0};
384
    }
385
    ////////////////////////////////////////////////////////////////////////////
386
    //////////////////////////// OPN2 ALLOCATION ///////////////////////////////
387
    ////////////////////////////////////////////////////////////////////////////
388
    if(pusage.opn2_globals){
389
        //We need an entire OPN2; find the best one to replace
2484 Sauraen 390
        FindBestVoice(&bestg, &bestv, now, -1, 0, 7, 1);
2380 Sauraen 391
        if(bestg < 0){
392
            ReleaseAllPI(pi);
393
            return -1;
394
        }
2360 Sauraen 395
        //Replace this one
396
        sg = &syngenesis[bestg];
397
        for(v=0; v<8; ++v){
2364 Sauraen 398
            AssignVoiceToGenesis(piindex, pi, bestg, v, v, 1);
2360 Sauraen 399
        }
400
        //Set up additional bits in chip allocation record
2472 Sauraen 401
        sg->lfomode = 2;
2360 Sauraen 402
    }else{
2472 Sauraen 403
        if(pusage.lfomode >= 2){ //Any LFO used and not fixed
2380 Sauraen 404
            bestg = FindOPN2ClearLFO();
405
            if(bestg < 0){
406
                ReleaseAllPI(pi);
407
                return -2;
408
            }
409
            lfog = bestg;
2472 Sauraen 410
            syngenesis[lfog].lfomode = 2;
2363 Sauraen 411
        }else{
412
            lfog = 0;
2360 Sauraen 413
        }
414
        //Assign Ch6 if DAC used
415
        if(pusage.dac){
416
            if(pusage.fm6_lfo){
2472 Sauraen 417
                if(pusage.lfomode >= 2){
2360 Sauraen 418
                    //LFO non-fixed; has to get assigned to lfog:6
2364 Sauraen 419
                    AssignVoiceToGenesis(piindex, pi, lfog, 6, 6, 1);
420
                    AssignVoiceToGenesis(piindex, pi, lfog, 7, 7, 0);
2360 Sauraen 421
                }else{
422
                    //LFO fixed; can we find an OPN2 with DAC open with the same LFO fixed?
423
                    for(g=0; g<GENESIS_COUNT; ++g){
424
                        sg = &syngenesis[lfog];
2472 Sauraen 425
                        if(sg->lfomode == 1 && sg->lfofixedspeed == pusage.lfofixedspeed && sg->channels[6].use == 0){
2360 Sauraen 426
                            break;
427
                        }
428
                    }
429
                    if(g == GENESIS_COUNT){
430
                        //Clear an OPN2 of LFO
2380 Sauraen 431
                        bestg = FindOPN2ClearLFO();
432
                        if(bestg < 0){
433
                            ReleaseAllPI(pi);
434
                            return -3;
435
                        }
436
                        g = bestg;
2360 Sauraen 437
                        //Set it up to be fixed to us
2472 Sauraen 438
                        syngenesis[g].lfomode = 1;
2360 Sauraen 439
                        syngenesis[g].lfofixedspeed = pusage.lfofixedspeed;
440
                        //Assign DAC to it, possibly overriding what was there
441
                    }
2364 Sauraen 442
                    AssignVoiceToGenesis(piindex, pi, g, 6, 6, 1);
443
                    AssignVoiceToGenesis(piindex, pi, g, 7, 7, 1);
2360 Sauraen 444
                }
445
            }else{
446
                //DAC without LFO
447
                //Find the best to replace
2484 Sauraen 448
                FindBestVoice(&bestg, &bestv, now, -1, 6, 7, 1);
2380 Sauraen 449
                if(bestg < 0){
450
                    ReleaseAllPI(pi);
451
                    return -4;
452
                }
2360 Sauraen 453
                //Use bestg
2364 Sauraen 454
                AssignVoiceToGenesis(piindex, pi, bestg, 6, 6, 0);
455
                AssignVoiceToGenesis(piindex, pi, bestg, 7, 7, 0);
2360 Sauraen 456
            }
457
            pusage.fm6 = 0;
458
        }
459
        //Assign FM3 if FM3 Special mode
460
        if(pusage.fm3_special){
461
            if(pusage.fm3_lfo){
2472 Sauraen 462
                if(pusage.lfomode >= 2){
2360 Sauraen 463
                    //LFO non-fixed; has to get assigned to lfog:3
2364 Sauraen 464
                    AssignVoiceToGenesis(piindex, pi, lfog, 3, 3, 1);
2360 Sauraen 465
                }else{
466
                    //LFO fixed; can we find an OPN2 with FM3 open with the same LFO fixed?
467
                    for(g=0; g<GENESIS_COUNT; ++g){
468
                        sg = &syngenesis[lfog];
2472 Sauraen 469
                        if(sg->lfomode == 1 && sg->lfofixedspeed == pusage.lfofixedspeed && sg->channels[3].use == 0){
2360 Sauraen 470
                            break;
471
                        }
472
                    }
473
                    if(g == GENESIS_COUNT){
474
                        //Clear an OPN2 of LFO
2380 Sauraen 475
                        bestg = FindOPN2ClearLFO();
476
                        if(bestg < 0){
477
                            ReleaseAllPI(pi);
478
                            return -5;
479
                        }
480
                        g = bestg;
2360 Sauraen 481
                        //Set it up to be fixed to us
2472 Sauraen 482
                        syngenesis[g].lfomode = 1;
2360 Sauraen 483
                        syngenesis[g].lfofixedspeed = pusage.lfofixedspeed;
484
                        //Assign FM3 to it, possibly overriding what was there
485
                    }
2364 Sauraen 486
                    AssignVoiceToGenesis(piindex, pi, g, 3, 3, 1);
2360 Sauraen 487
                }
488
            }else{
489
                //FM3 without LFO
490
                //Find the best to replace
2484 Sauraen 491
                FindBestVoice(&bestg, &bestv, now, -1, 3, 3, 0);
2380 Sauraen 492
                if(bestg < 0){
493
                    ReleaseAllPI(pi);
494
                    return -6;
495
                }
2360 Sauraen 496
                //Use bestg
2364 Sauraen 497
                AssignVoiceToGenesis(piindex, pi, bestg, 3, 3, 0);
2360 Sauraen 498
            }
499
            pusage.fm3 = 0;
500
        }
501
        //Assign normal voices
2363 Sauraen 502
        for(i=1; i<=6; ++i){
503
            if(pusage.all & (1 << (i-1))){ //Voice in use
504
                if(pusage.all & (1 << (i+5))){ //LFO in use
2472 Sauraen 505
                    if(pusage.lfomode >= 2){
2360 Sauraen 506
                        //Have to use lfog, find best voice
2484 Sauraen 507
                        FindBestVoice(&bestg, &bestv, now, lfog, 1, 6, 0);
2380 Sauraen 508
                        if(bestv < 0){
509
                            ReleaseAllPI(pi);
510
                            return -7;
511
                        }
2363 Sauraen 512
                        //Use this voice
2364 Sauraen 513
                        AssignVoiceToGenesis(piindex, pi, lfog, i, bestv, 1);
2360 Sauraen 514
                    }else{
2363 Sauraen 515
                        //LFO fixed: First is there a chip with LFO Fixed correct and a free voice?
516
                        for(g=0; g<GENESIS_COUNT; ++g){
517
                            sg = &syngenesis[g];
2472 Sauraen 518
                            if(!(sg->lfomode == 1) || sg->lfofixedspeed != pusage.lfofixedspeed) continue;
2363 Sauraen 519
                            //If we have a chip with the right LFO Fixed, check the voices
2366 Sauraen 520
                            score = 0xFF;
2363 Sauraen 521
                            for(v=1; v<=6; ++v){
522
                                use = syngenesis[g].channels[v].use;
2366 Sauraen 523
                                if(use <= 1){
524
                                    score = 0;
2363 Sauraen 525
                                    break;
526
                                }
527
                            }
528
                            if(score == 0) break;
529
                        }
530
                        if(g == GENESIS_COUNT){
531
                            //Find the OPN2 with the least LFO use
2380 Sauraen 532
                            bestg = FindOPN2ClearLFO();
533
                            if(bestg < 0){
534
                                ReleaseAllPI(pi);
535
                                return -8;
536
                            }
537
                            g = bestg;
2418 Sauraen 538
                            //Set it up to be fixed to us
2472 Sauraen 539
                            syngenesis[g].lfomode = 1;
2418 Sauraen 540
                            syngenesis[g].lfofixedspeed = pusage.lfofixedspeed;
2363 Sauraen 541
                        }
542
                        //Find best voice
2484 Sauraen 543
                        FindBestVoice(&bestg, &bestv, now, g, 1, 6, 0);
2380 Sauraen 544
                        if(bestv < 0){
545
                            ReleaseAllPI(pi);
546
                            return -9;
547
                        }
2363 Sauraen 548
                        //Use this voice
2364 Sauraen 549
                        AssignVoiceToGenesis(piindex, pi, g, i, bestv, 1);
2360 Sauraen 550
                    }
551
                }else{
552
                    //No LFO, find best voice anywhere
2484 Sauraen 553
                    FindBestVoice(&bestg, &bestv, now, -1, 1, 6, 0);
2380 Sauraen 554
                    if(bestg < 0){ // || bestv < 0
555
                        ReleaseAllPI(pi);
556
                        return -10;
557
                    }
2363 Sauraen 558
                    //Use this voice
2364 Sauraen 559
                    AssignVoiceToGenesis(piindex, pi, bestg, i, bestv, 0);
2360 Sauraen 560
                }
561
            }
562
        }
563
    }
2364 Sauraen 564
    if(pusage.noisefreqsq3){
565
        //Need SQ3 and NS together
2484 Sauraen 566
        FindBestVoice(&bestg, &bestv, now, -1, 10, 11, 1);
2380 Sauraen 567
        if(bestg < 0){
568
            ReleaseAllPI(pi);
569
            return -11;
570
        }
2364 Sauraen 571
        //Use this chip
572
        AssignVoiceToGenesis(piindex, pi, bestg, 10, 10, 0);
573
        AssignVoiceToGenesis(piindex, pi, bestg, 11, 11, 0);
574
        //Mark these two as taken care of
575
        pusage.sq3 = 0;
576
        pusage.noise = 0;
577
    }
578
    if(pusage.noise){
579
        //Need NS
2484 Sauraen 580
        FindBestVoice(&bestg, &bestv, now, -1, 11, 11, 0);
2380 Sauraen 581
        if(bestg < 0){
582
            ReleaseAllPI(pi);
583
            return -12;
584
        }
2364 Sauraen 585
        //Use this chip
586
        AssignVoiceToGenesis(piindex, pi, bestg, 11, 11, 0);
587
    }
588
    //Squares
589
    for(i=8; i<=10; ++i){
590
        if(pusage.all & (0x01000000 << (i-8))){ //Voice in use
591
            //Find best voice anywhere
2484 Sauraen 592
            FindBestVoice(&bestg, &bestv, now, -1, 8, 10, 0);
2380 Sauraen 593
            if(bestg < 0){
594
                ReleaseAllPI(pi);
595
                return -13;
596
            }
2364 Sauraen 597
            //Use this voice
598
            AssignVoiceToGenesis(piindex, pi, bestg, i, bestv, 0);
599
        }
600
    }
2482 Sauraen 601
    VgmHead_Channel m;
2484 Sauraen 602
    DBG("AllocatePI results:");
2482 Sauraen 603
    for(v=0; v<0xB; ++v){
604
        m = pi->mapping[v];
2484 Sauraen 605
        if(!m.nodata){
606
            DBG("--Voice %X --> chip=%d voice=%d, option=%d, mute=%d",
607
                    v, m.map_chip, m.map_voice, m.option, m.mute);
608
        }
2482 Sauraen 609
    }
2380 Sauraen 610
    return 1;
2360 Sauraen 611
}
612
 
613
 
2372 Sauraen 614
static void CopyPIMappingToHead(synproginstance_t* pi, VgmHead* head){
2391 Sauraen 615
    u8 i, m;
2359 Sauraen 616
    for(i=0; i<12; ++i){
2391 Sauraen 617
        m = head->channel[i].mute;
618
        head->channel[i].ALL = pi->mapping[i].ALL;
619
        head->channel[i].mute = m;
2359 Sauraen 620
    }
621
}
622
 
2396 Sauraen 623
static u8 IsAnyVoiceBeingCleared(synproginstance_t* pi){
624
    u8 c;
625
    if(pi->isstatic) return 0;
626
    for(c=1; c<7; ++c){
627
        VgmHead_Channel ch = pi->mapping[c];
628
        if(ch.nodata) continue;
629
        if(syngenesis[ch.map_chip].channels[ch.map_voice+1].beingcleared) return 1;
630
    }
631
    return 0;
632
}
633
 
634
static u8 FindBestPIToReplace(u8 chn, u8 note){
635
    //Look for an existing proginstance that's the best fit to replace
636
    //In order from best to worst:
637
    //0: Same channel, same note (playing or not playing doesn't matter)
638
    //1: Same channel, not playing
639
    //2: Invalid
640
    //3: Other channel, not playing
641
    //4: Same channel, playing
642
    //5: Other channel, playing
2484 Sauraen 643
    //6: Static
2396 Sauraen 644
    u8 bestrating = 0xFF;
645
    u8 bestrated = 0xFF;
646
    u32 recency, maxrecency = 0, now = VGM_Player_GetVGMTime();
647
    u8 rating, i;
648
    synproginstance_t* pi;
649
    for(i=0; i<MBQG_NUM_PROGINSTANCES; ++i){
650
        pi = &proginstances[i];
651
        if(!pi->valid){
652
            rating = 2;
2484 Sauraen 653
        }else if(pi->isstatic){
654
            rating = 6;
2396 Sauraen 655
        }else{
656
            if(pi->sourcechannel == chn){
657
                if(pi->note == note){
658
                    rating = 0;
659
                }else if(pi->playing){
660
                    rating = 4;
661
                }else{
662
                    rating = 1;
663
                }
664
            }else{
665
                if(pi->playing){
666
                    rating = 5;
667
                }else{
668
                    rating = 3;
669
                }
670
            }
671
        }
672
        recency = now - pi->recency;
673
        if(rating < bestrating || (rating == bestrating && recency > maxrecency)){
674
            bestrating = rating;
675
            bestrated = i;
676
            maxrecency = recency;
677
        }
678
    }
679
    return bestrated;
680
}
681
 
2488 Sauraen 682
VgmSource** SelSource(synprogram_t* prog, u8 num){
683
    switch(num){
684
        case 0: return &prog->initsource;
685
        case 1: return &prog->noteonsource;
686
        case 2: return &prog->noteoffsource;
687
        default:
688
            DBG("SelSource error!");
689
            return &prog->noteonsource;
690
    }
691
}
692
 
2359 Sauraen 693
void SyEng_Init(){
2363 Sauraen 694
    u8 i, j;
2359 Sauraen 695
    //Initialize syngenesis
696
    for(i=0; i<GENESIS_COUNT; ++i){
2363 Sauraen 697
        syngenesis[i].optionbits = 0;
698
        for(j=0; j<12; ++j){
699
            syngenesis[i].channels[j].ALL = 0;
700
        }
2359 Sauraen 701
    }
702
    //Initialize proginstances
703
    for(i=0; i<MBQG_NUM_PROGINSTANCES; ++i){
704
        proginstances[i].valid = 0;
705
        proginstances[i].head = NULL;
706
    }
707
    //Initialize channels
708
    for(i=0; i<16*MBQG_NUM_PORTS; ++i){
709
        channels[i].trackermode = 0;
710
        channels[i].program = NULL;
711
    }
2382 Sauraen 712
    //Initialize OPN2 voice clearing
713
    voiceclearsource = VGM_SourceRAM_Create();
714
    ((VgmSourceRAM*)voiceclearsource->data)->numcmds = VOICECLEARVGMCMDS;
715
    ((VgmSourceRAM*)voiceclearsource->data)->cmds = (VgmChipWriteCmd*)voiceclearvgm;
716
    voiceclearlist = NULL;
717
    //Other
718
    voiceclearfull = 1;
2396 Sauraen 719
    DemoPrograms_Init();
2359 Sauraen 720
}
721
 
2382 Sauraen 722
 
2359 Sauraen 723
void SyEng_Tick(){
724
    u8 i;
2382 Sauraen 725
    //Clear voiceclear VGMs
726
    //MIOS32_IRQ_Disable();
727
    voiceclearlink* link = voiceclearlist;
728
    voiceclearlink** backlink = &voiceclearlist;
729
    voiceclearlink* oldlink;
730
    while(link != NULL){
731
        if(link->head->isdone){
732
            //Mark the voice as clear
733
            VgmHead_Channel ch = link->head->channel[1];
2412 Sauraen 734
            //DBG("Clearing VGM for g %d v %d is done", ch.map_chip, ch.map_voice+1);
2382 Sauraen 735
            syngenesis[ch.map_chip].channels[ch.map_voice+1].beingcleared = 0;
736
            //Delete the head
737
            VGM_Head_Delete(link->head);
738
            //Delete the link
739
            *backlink = link->link;
740
            oldlink = link;
741
            link = link->link;
742
            vgmh2_free(oldlink);
743
        }else{
744
            //Traverse the linked list
745
            backlink = &link->link;
746
            link = link->link;
747
        }
748
    }
749
    //MIOS32_IRQ_Enable();
750
    //Clear program heads, change init to noteon
2359 Sauraen 751
    synproginstance_t* pi;
752
    synprogram_t* prog;
753
    for(i=0; i<MBQG_NUM_PROGINSTANCES; ++i){
754
        pi = &proginstances[i];
755
        if(!pi->valid) continue;
2396 Sauraen 756
        if(pi->isstatic) continue;
2382 Sauraen 757
        if(pi->waitingforclear){
758
            //Is it cleared yet?
759
            if(IsAnyVoiceBeingCleared(pi)) continue;
760
            //Done, start playing init VGM
761
            pi->waitingforclear = 0;
762
            prog = channels[pi->sourcechannel].program;
763
            if(prog == NULL){
764
                DBG("ERROR program disappeared while playing, could not start playing init after clearing!");
765
                continue;
766
            }
2391 Sauraen 767
            if(prog->initsource != NULL){
2412 Sauraen 768
                //DBG("PI %d ch %d note %d done clearing, starting init VGM", i, pi->sourcechannel, pi->note);
2515 Sauraen 769
                SyEng_PlayVGMOnPI(pi, prog->initsource, prog, 1);
2391 Sauraen 770
            }else{
771
                pi->playinginit = 0;
772
                if(prog->noteonsource != NULL){
2412 Sauraen 773
                    //DBG("PI %d ch %d note %d done clearing, no init VGM, starting noteon VGM", i, pi->sourcechannel, pi->note);
2515 Sauraen 774
                    SyEng_PlayVGMOnPI(pi, prog->noteonsource, prog, 1);
2391 Sauraen 775
                }else{
2412 Sauraen 776
                    //DBG("PI %d ch %d note %d done clearing, but has no init or noteon VGM, doing nothing", i, pi->sourcechannel, pi->note);
2391 Sauraen 777
                }
778
            }
2382 Sauraen 779
            continue;
780
        }
2359 Sauraen 781
        if(pi->head == NULL) continue;
782
        if(pi->head->isdone){
783
            VGM_Head_Delete(pi->head);
784
            pi->head = NULL;
785
            if(!pi->playinginit) continue;
2391 Sauraen 786
            pi->playinginit = 0;
2417 Sauraen 787
            pi->needsnewinit = 0;
2359 Sauraen 788
            prog = channels[pi->sourcechannel].program;
789
            if(prog == NULL){
790
                DBG("ERROR program disappeared while playing, could not switch from init to noteon!");
791
                continue;
792
            }
793
            //Switch from init to noteon VGM
2391 Sauraen 794
            if(prog->noteonsource != NULL){
2412 Sauraen 795
                //DBG("PI %d ch %d note %d switching from init to noteon VGM", i, pi->sourcechannel, pi->note);
2515 Sauraen 796
                SyEng_PlayVGMOnPI(pi, prog->noteonsource, prog, 1);
2391 Sauraen 797
            }else{
2412 Sauraen 798
                //DBG("PI %d ch %d note %d done playing init, but has no noteon VGM, doing nothing", i, pi->sourcechannel, pi->note);
2391 Sauraen 799
            }
2359 Sauraen 800
        }
801
    }
802
}
803
 
2418 Sauraen 804
u8 SyEng_GetStaticPI(VgmUsageBits usage){
2396 Sauraen 805
    u8 piindex = FindBestPIToReplace(0xFF, 0xFF);
806
    synproginstance_t* pi = &proginstances[piindex];
2409 Sauraen 807
    //If this PI was previously in use: release its resources, stop playing, reset voices
808
    if(pi->valid){
2412 Sauraen 809
        //DBG("--Clearing existing PI resources");
2396 Sauraen 810
        ClearPI(pi);
811
    }
2484 Sauraen 812
    pi->isstatic = 1;
2396 Sauraen 813
    //Find best allocation
814
    s32 ret = AllocatePI(piindex, usage);
815
    if(ret < 0){
816
        DBG("--Could not allocate resources for PI (voices full)! code = %d", ret);
817
        return 0xFF;
818
    }
819
    //Set up the PI
820
    pi->valid = 1;
821
    pi->sourcechannel = 0xFF;
822
    pi->note = 60;
823
    return piindex;
824
}
825
 
826
void SyEng_ReleaseStaticPI(u8 piindex){
827
    synproginstance_t* pi = &proginstances[piindex];
828
    if(!pi->isstatic) return;
829
    if(pi->head != NULL){
830
        //Stop playing whatever it was playing
831
        VGM_Head_Delete(pi->head);
832
        pi->head = NULL;
833
    }
834
    pi->isstatic = 0;
835
    ClearPI(pi);
836
}
837
 
2515 Sauraen 838
void SyEng_PlayVGMOnPI(synproginstance_t* pi, VgmSource* source, synprogram_t* prog, u8 startplaying){
839
    u8 rootnote = 60;
840
    u32 tloffs = 0;
841
    if(prog != NULL){
842
        rootnote = prog->rootnote;
843
        u8 i, t;
844
        for(i=0; i<4; ++i){
845
            t = (u32)(prog->tlbaseoff[i]) * (u32)(127 - pi->vel) / 127;
846
            tloffs = tloffs | ((u32)t << (i << 3));
847
        }
848
    }
2491 Sauraen 849
    SetPIMappedVoicesUse(pi, pi->isstatic ? 3 : 2);
2515 Sauraen 850
    pi->head = VGM_Head_Create(source, VGM_getFreqMultiplier((s8)pi->note - (s8)rootnote), 0x1000, tloffs);
2396 Sauraen 851
    CopyPIMappingToHead(pi, pi->head);
852
    u32 vgmtime = VGM_Player_GetVGMTime();
853
    VGM_Head_Restart(pi->head, vgmtime);
2491 Sauraen 854
    //DBG("PlayVGMOnPi after restart, iswait %d iswrite %d isdone %d firstoftwo %d, cmd %08X", pi->head->iswait, pi->head->iswrite, pi->head->isdone, pi->head->firstoftwo, pi->head->writecmd.all);
2409 Sauraen 855
    pi->head->playing = startplaying;
2396 Sauraen 856
    pi->recency = vgmtime;
857
}
858
 
859
void SyEng_SilencePI(synproginstance_t* pi){
860
    u8 i;
861
    VgmHead_Channel* c;
862
    for(i=0; i<12; ++i){
863
        c = &pi->mapping[i];
864
        if(c->nodata) continue;
865
        //Key off
866
        if(i >= 1 && i <= 6){
867
            VGM_Tracker_Enqueue((VgmChipWriteCmd){.cmd = (c->map_chip << 4)|2, .addr = 0x28, .data = (c->map_voice >= 3 ? c->map_voice+1 : c->map_voice), .data2 = 0}, 0);
868
        }else if(i >= 8 && i <= 10){
869
            VGM_Tracker_Enqueue((VgmChipWriteCmd){.cmd = (c->map_chip << 4), .addr = 0, .data = 0x9F|(c->map_voice << 5), .data2 = 0}, 0);
870
        }else if(i == 11){
871
            VGM_Tracker_Enqueue((VgmChipWriteCmd){.cmd = (c->map_chip << 4), .addr = 0, .data = 0xFF, .data2 = 0}, 0);
872
        }
873
    }
874
}
875
 
2515 Sauraen 876
static void StartProgramNote(synprogram_t* prog, u8 chn, u8 note, u8 vel){
2359 Sauraen 877
    if(prog == NULL){
2396 Sauraen 878
        DBG("Note on %d ch %d, no program on this channel", note, chn);
2359 Sauraen 879
        return;
880
    }
2396 Sauraen 881
    u8 piindex = FindBestPIToReplace(chn, note);
882
    synproginstance_t* pi = &proginstances[piindex];
883
    pi->note = note;
2515 Sauraen 884
    pi->vel = vel;
2359 Sauraen 885
    //Do we already have the right mapping allocated?
2396 Sauraen 886
    if(pi->valid && !pi->isstatic && pi->sourcechannel == chn){
2359 Sauraen 887
        if(pi->head != NULL){
888
            //Stop playing whatever it was playing
889
            VGM_Head_Delete(pi->head);
890
            pi->head = NULL;
891
        }
2417 Sauraen 892
        if(pi->needsnewinit && prog->initsource != NULL){
893
            //Right mapping, but have to re-initialize
2515 Sauraen 894
            SyEng_PlayVGMOnPI(pi, prog->initsource, prog, 1);
2417 Sauraen 895
            pi->playinginit = 1;
2391 Sauraen 896
        }else{
2417 Sauraen 897
            //Skip the init VGM, start the note on VGM
898
            if(prog->noteonsource != NULL){
2515 Sauraen 899
                SyEng_PlayVGMOnPI(pi, prog->noteonsource, prog, 1);
2417 Sauraen 900
            }else{
901
                //DBG("PI ch %d note %d doesn't need init, but has no noteon VGM, doing nothing", pi->sourcechannel, pi->note);
902
            }
2391 Sauraen 903
        }
2359 Sauraen 904
        pi->playing = 1;
905
        return;
906
    }
2409 Sauraen 907
    //If this PI was previously in use: release its resources, stop playing, reset voices
908
    if(pi->valid){
2412 Sauraen 909
        //DBG("--Clearing existing PI resources");
2363 Sauraen 910
        ClearPI(pi);
2359 Sauraen 911
    }
912
    //Find best allocation
2396 Sauraen 913
    s32 ret = AllocatePI(piindex, prog->usage);
2380 Sauraen 914
    if(ret < 0){
915
        DBG("--Could not allocate resources for PI (voices full)! code = %d", ret);
916
        return;
917
    }
2382 Sauraen 918
    //Set up the PI for the new program
2363 Sauraen 919
    pi->valid = 1;
2396 Sauraen 920
    pi->isstatic = 0;
2391 Sauraen 921
    pi->playinginit = 1;
2363 Sauraen 922
    pi->playing = 1;
2396 Sauraen 923
    pi->sourcechannel = chn;
2382 Sauraen 924
    //See if we're waiting for voices to be cleared
925
    if(IsAnyVoiceBeingCleared(pi)){
926
        pi->waitingforclear = 1;
927
        return; //Init will be played in SyEng_Tick
928
    }
2391 Sauraen 929
    //Otherwise, start the init VGM
930
    if(prog->initsource != NULL){
2515 Sauraen 931
        SyEng_PlayVGMOnPI(pi, prog->initsource, prog, 1);
2391 Sauraen 932
    }else{
2417 Sauraen 933
        //Unless we don't have one, in which case skip to noteon
2391 Sauraen 934
        pi->playinginit = 0;
935
        if(prog->noteonsource != NULL){
2412 Sauraen 936
            //DBG("PI ch %d note %d skipping missing init VGM, starting noteon", pi->sourcechannel, pi->note);
2515 Sauraen 937
            SyEng_PlayVGMOnPI(pi, prog->noteonsource, prog, 1);
2391 Sauraen 938
        }else{
2412 Sauraen 939
            //DBG("PI ch %d note %d doesn't have init or noteon VGMs, doing nothing", pi->sourcechannel, pi->note);
2391 Sauraen 940
        }
941
    }
2359 Sauraen 942
}
2396 Sauraen 943
static void StopProgramNote(synprogram_t* prog, u8 chn, u8 note){
2359 Sauraen 944
    synproginstance_t* pi;
945
    u8 i;
946
    for(i=0; i<MBQG_NUM_PROGINSTANCES; ++i){
947
        pi = &proginstances[i];
948
        if(!pi->valid) continue;
2417 Sauraen 949
        if(pi->sourcechannel != chn) continue;
2396 Sauraen 950
        if(pi->isstatic) continue;
951
        if(!pi->playing) continue;
952
        if(pi->note != note) continue;
2359 Sauraen 953
        break;
954
    }
955
    if(i == MBQG_NUM_PROGINSTANCES){
2396 Sauraen 956
        DBG("Note off %d ch %d, but no PI playing this note", note, chn);
2359 Sauraen 957
        return; //no corresponding note on
958
    }
2417 Sauraen 959
    //Check if we have a valid program
2359 Sauraen 960
    if(prog == NULL){
961
        DBG("--ERROR program disappeared while playing, could not switch to noteoff!");
962
        return;
963
    }
2391 Sauraen 964
    if(prog->noteoffsource != NULL){
965
        //Stop playing note-on VGM only if there's a noteoff to play
966
        if(pi->head != NULL){
967
            VGM_Head_Delete(pi->head);
968
        }
969
        //Start playing note-off VGM
2515 Sauraen 970
        SyEng_PlayVGMOnPI(pi, prog->noteoffsource, prog, 1);
2391 Sauraen 971
    }else{
2412 Sauraen 972
        //DBG("PI ch %d note %d doesn't have noteoff VGM, doing nothing", pi->sourcechannel, pi->note);
2391 Sauraen 973
    }
2359 Sauraen 974
    //Mark pi as not playing, release resources
2491 Sauraen 975
    SetPIMappedVoicesUse(pi, 1);
2359 Sauraen 976
    pi->playing = 0;
977
}
2396 Sauraen 978
 
979
void SyEng_Note_On(mios32_midi_package_t pkg){
2515 Sauraen 980
    StartProgramNote(channels[pkg.chn].program, pkg.chn, pkg.note, pkg.velocity);
2396 Sauraen 981
}
982
void SyEng_Note_Off(mios32_midi_package_t pkg){
983
    StopProgramNote(channels[pkg.chn].program, pkg.chn, pkg.note);
984
}
985
 
2417 Sauraen 986
void SyEng_HardFlushProgram(synprogram_t* prog){
2412 Sauraen 987
    u8 i, ch;
2417 Sauraen 988
    synproginstance_t* pi;
2412 Sauraen 989
    for(i=0; i<MBQG_NUM_PROGINSTANCES; ++i){
2417 Sauraen 990
        pi = &proginstances[i];
991
        if(!pi->valid) continue;
992
        if(pi->isstatic) continue;
993
        ch = pi->sourcechannel;
2412 Sauraen 994
        if(ch >= 16*MBQG_NUM_PORTS) continue;
995
        if(channels[ch].trackermode) continue;
996
        if(channels[ch].program == prog){
2417 Sauraen 997
            ClearPI(pi);
2412 Sauraen 998
        }
999
    }
1000
}
2417 Sauraen 1001
void SyEng_SoftFlushProgram(synprogram_t* prog){
1002
    u8 i, ch;
1003
    synproginstance_t* pi;
1004
    for(i=0; i<MBQG_NUM_PROGINSTANCES; ++i){
1005
        pi = &proginstances[i];
1006
        if(!pi->valid) continue;
1007
        if(pi->isstatic) continue;
1008
        ch = pi->sourcechannel;
1009
        if(ch >= 16*MBQG_NUM_PORTS) continue;
1010
        if(channels[ch].trackermode) continue;
1011
        if(channels[ch].program == prog){
1012
            pi->needsnewinit = 1;
1013
        }
1014
    }
1015
}
2472 Sauraen 1016
void recalcprogramusage_internal(VgmUsageBits* dest, VgmSource* vgs){
1017
    if(vgs == NULL) return;
1018
    VgmUsageBits* src = &vgs->usage;
1019
    if(dest->lfomode == 1 && src->lfomode == 1){
1020
        if(dest->lfofixedspeed != src->lfofixedspeed){
1021
            dest->lfomode = 3;
1022
        }
1023
    }
1024
    dest->all |= src->all;
1025
}
2482 Sauraen 1026
void SyEng_RecalcSourceAndProgramUsage(synprogram_t* prog, VgmSource* srcchanged){
1027
    if(srcchanged != NULL){
1028
        VGM_Source_UpdateUsage(srcchanged);
1029
    }
1030
    VgmUsageBits usage = (VgmUsageBits){.all = 0};
1031
    recalcprogramusage_internal(&usage, prog->initsource);
1032
    recalcprogramusage_internal(&usage, prog->noteonsource);
1033
    recalcprogramusage_internal(&usage, prog->noteoffsource);
1034
    if(usage.all != prog->usage.all){
2484 Sauraen 1035
        DBG("Program usage changed to");
2482 Sauraen 1036
        VGM_Cmd_DebugPrintUsage(usage);
1037
        prog->usage.all = usage.all;
1038
        SyEng_HardFlushProgram(prog);
1039
        if(selvgm == srcchanged && srcchanged != NULL){
1040
            Mode_Vgm_SelectVgm(selvgm); //Force mode_vgm to get a new previewpi
1041
        }
1042
    }
2472 Sauraen 1043
}
2412 Sauraen 1044
 
2486 Sauraen 1045
void SyEng_DeleteSource(VgmSource* src){
1046
    if(src == NULL) return;
1047
    Mode_Vgm_InvalidateVgm(src);
1048
    u8 i;
1049
    synproginstance_t* pi;
1050
    for(i=0; i<MBQG_NUM_PROGINSTANCES; ++i){
1051
        pi = &proginstances[i];
2490 Sauraen 1052
        if(pi->head == NULL) continue;
1053
        if(pi->head->source != src) continue;
1054
        ClearPI(pi);
2486 Sauraen 1055
    }
1056
    VGM_Source_Delete(src);
1057
}
1058
void SyEng_DeleteProgram(u8 chan){
1059
    synprogram_t* prog = channels[chan].program;
1060
    if(prog == NULL) return;
2490 Sauraen 1061
    SyEng_HardFlushProgram(prog);
2486 Sauraen 1062
    SyEng_DeleteSource(prog->initsource);
1063
    SyEng_DeleteSource(prog->noteonsource);
1064
    SyEng_DeleteSource(prog->noteoffsource);
1065
    vgmh2_free(prog);
1066
    channels[chan].program = NULL;
1067
}
1068
 
2516 Sauraen 1069
/*
1070
MIDIbox Quad Genesis Program File Format
1071
- 16 bytes u8: ignored ("MBQG Program" and four 0s)
1072
- 1 byte u8: Program type: 0 for instrument, 1 for percussion
1073
- 1 byte u8: prog->rootnote
1074
- 4 bytes u8: prog->tlbaseoff[0 through 3]
1075
- 13 bytes u8: prog->name, 12 characters plus 0
1076
- For init, key on, and key off VGM files in that order:
1077
--- 1 byte u8: n, number of characters in pathname, 0 if there is no VGM in this slot
1078
--- n bytes u8: full path to VGM file
1079
On loading, referenced VGM files will be loaded as RAM if memory permits, and
1080
otherwise streamed.
1081
For a program file stored to /SOME/PATH/PROGNAME.xyz, RAM-type VGM files will be
1082
stored in /SOME/PATH/PROGNAME/INIT.VGM, and same for KON.VGM and KOFF.VGM.
1083
Streamed VGM files are stored with the full path to the streamed file, and the
1084
actual streamed file is not touched by the save operation.
1085
*/
1086
 
1087
static const char* const vgmtypelabels[] = {
1088
    "INIT",
1089
    "KON",
1090
    "KOFF"
1091
};
1092
 
1093
s32 SyEng_LoadProgram(char* filepath, synprogram_t* prog){
1094
    if(filepath == NULL || prog == NULL) return -1;
1095
    //Read header
1096
    MUTEX_SDCARD_TAKE;
1097
    file_t file;
1098
    s32 ret = FILE_ReadOpen(&file, filepath);
2518 Sauraen 1099
    if(ret < 0){
1100
        MUTEX_SDCARD_GIVE;
1101
        return ret;
1102
    }
2516 Sauraen 1103
    FILE_ReadSeek(16);
1104
    u8 b;
1105
    FILE_ReadByte(&b);
1106
    if(b != 0){
1107
        DBG("Loading percussion program not implemented yet!");
2518 Sauraen 1108
        MUTEX_SDCARD_GIVE;
2516 Sauraen 1109
        return -50;
1110
    }
1111
    FILE_ReadByte(&prog->rootnote);
1112
    FILE_ReadWord(&prog->tlbaseoffs);
1113
    FILE_ReadBuffer((u8*)&prog->name, 13);
1114
    prog->name[12] = 0;
1115
    prog->usage.all = 0;
1116
    //Read VGM paths
1117
    char* tempbuf = vgmh2_malloc(256);
1118
    u8 v, l;
1119
    VgmSource** ss;
1120
    for(v=0; v<3; ++v){
1121
        ss = SelSource(prog, v);
1122
        *ss = NULL;
1123
        FILE_ReadByte(&l);
2518 Sauraen 1124
        if(l == 0) continue; //Null
2516 Sauraen 1125
        FILE_ReadBuffer((u8*)tempbuf, l);
1126
        tempbuf[l] = 0;
1127
        DBG("Program %s: loading VGM file %s", prog->name, tempbuf);
1128
        //Load file
1129
        FILE_ReadClose(&file);
1130
        MUTEX_SDCARD_GIVE;
1131
        ret = VGM_File_Load(tempbuf, ss, NULL);
1132
        if(ret < 0){
1133
            DBG("Error %d loading VGM file", ret);
1134
        }
2518 Sauraen 1135
        MUTEX_SDCARD_TAKE;
1136
        FILE_ReadReOpen(&file);
2516 Sauraen 1137
    }
2518 Sauraen 1138
    FILE_ReadClose(&file);
1139
    MUTEX_SDCARD_GIVE;
2516 Sauraen 1140
    vgmh2_free(tempbuf);
1141
    //Update program usage
1142
    SyEng_RecalcSourceAndProgramUsage(prog, NULL);
2518 Sauraen 1143
    return ret;
2516 Sauraen 1144
}
1145
 
1146
inline u8 TurnProgPathIntoVGMPath(char* tempbuf, char* filenamestart, u8 v){
1147
    u8 sl = strlen(vgmtypelabels[v]);
1148
    memcpy(filenamestart, vgmtypelabels[v], sl);
1149
    char* filenameend = filenamestart + sl;
1150
    memcpy(filenameend, ".VGM", 4);
1151
    filenameend += 4;
1152
    *filenameend = 0;
1153
    return filenameend - tempbuf;
1154
}
1155
 
1156
s32 SyEng_SaveProgram(synprogram_t* prog, char* filepath){
1157
    if(prog == NULL || filepath == NULL || strlen(filepath) == 0) return -1;
1158
    s32 ret = 0;
1159
    //Get and extend path
2518 Sauraen 1160
    //--Assume filepath contains /SOME/PATH/PROGNAME.PGM
2516 Sauraen 1161
    char* tempbuf = vgmh2_malloc(256);
1162
    u8 sl = strlen(filepath);
1163
    memcpy(tempbuf, filepath, sl);
1164
    char* filenamestart = tempbuf + sl;
1165
    *filenamestart = 0;
1166
    while(*filenamestart != '.') --filenamestart;
2518 Sauraen 1167
    *filenamestart = 0;
1168
    //--tempbuf now contains /SOME/PATH/PROGNAME
1169
    MUTEX_SDCARD_TAKE;
1170
    if(!FILE_DirExists(tempbuf)){
1171
        FILE_MakeDir(tempbuf);
1172
    }
2516 Sauraen 1173
    *filenamestart = '/';
1174
    ++filenamestart;
2518 Sauraen 1175
    //--tempbuf now contains /SOME/PATH/PROGNAME/PGM and filenamestart points to PGM
2516 Sauraen 1176
    //Start program file
1177
    if(FILE_FileExists(filepath)){
1178
        FILE_Remove(filepath);
1179
    }
1180
    FILE_WriteOpen(filepath, 1);
1181
    //Program file main
1182
    FILE_WriteBuffer((u8*)"MBQG Program", 12);
1183
    FILE_WriteWord(0);
1184
    FILE_WriteByte(0);
1185
    FILE_WriteByte(prog->rootnote);
1186
    FILE_WriteWord(prog->tlbaseoffs);
1187
    FILE_WriteBuffer((u8*)prog->name, 13);
1188
    //Program file VGM paths
1189
    u8 v;
1190
    VgmSource** ss;
1191
    for(v=0; v<3; ++v){
1192
        ss = SelSource(prog, v);
1193
        if(*ss == NULL){
1194
            FILE_WriteByte(0);
1195
            continue;
1196
        }
1197
        if((*ss)->type == VGM_SOURCE_TYPE_RAM){
1198
            sl = TurnProgPathIntoVGMPath(tempbuf, filenamestart, v);
1199
            FILE_WriteByte(sl);
1200
            FILE_WriteBuffer((u8*)tempbuf, sl);
1201
        }else if((*ss)->type == VGM_SOURCE_TYPE_STREAM){
1202
            VgmSourceStream* vss = (VgmSourceStream*)(*ss)->data;
1203
            if(vss->filepath == NULL){
1204
                DBG("Program saving error, unstarted stream!");
1205
                ret = -2;
1206
                FILE_WriteByte(0);
1207
            }else{
1208
                sl = strlen(vss->filepath);
1209
                FILE_WriteByte(sl);
1210
                FILE_WriteBuffer((u8*)vss->filepath, sl);
1211
            }
1212
        }else{
1213
            DBG("Program saving error, unknown source type %d!", (*ss)->type);
1214
            ret = -3;
1215
            FILE_WriteByte(0);
1216
        }
1217
    }
1218
    FILE_WriteClose();
1219
    MUTEX_SDCARD_GIVE;
1220
    //Save RAM VGMs
1221
    for(v=0; v<3; ++v){
1222
        ss = SelSource(prog, v);
1223
        if(*ss == NULL) continue;
1224
        if((*ss)->type != VGM_SOURCE_TYPE_RAM) continue;
1225
        TurnProgPathIntoVGMPath(tempbuf, filenamestart, v);
1226
        DBG("Saving %s VGM to %s", vgmtypelabels[v], tempbuf);
1227
        ret = VGM_File_SaveRAM(*ss, tempbuf);
1228
        if(ret < 0){
1229
            DBG("Error %d saving VGM");
1230
        }
1231
    }
1232
    vgmh2_free(tempbuf);
1233
    return ret;
1234
}
1235
 
2484 Sauraen 1236
void SyEng_PrintEngineDebugInfo(){
2494 Sauraen 1237
    u8 /*i,*/ g, v;
2491 Sauraen 1238
    DBG("=====================================================");
1239
    DBG("============ SyEng_PrintEngineDebugInfo =============");
1240
    DBG("=====================================================");
1241
    /*
2484 Sauraen 1242
    DBG("==== CHANNELS ====");
1243
    synprogram_t* prog;
2488 Sauraen 1244
    VgmSource* src;
2484 Sauraen 1245
    for(i=0; i<16*MBQG_NUM_PORTS; ++i){
1246
        if(channels[i].trackermode){
1247
            DBG("Chn %d Tracker to g %d v %d", i, channels[i].trackervoice >> 4, channels[i].trackervoice & 0xF);
1248
        }else if(channels[i].program != NULL){
1249
            prog = channels[i].program;
2488 Sauraen 1250
            DBG("Chn %d program %s", i, prog->name);
2484 Sauraen 1251
            VGM_Cmd_DebugPrintUsage(prog->usage);
2488 Sauraen 1252
            for(v=0; v<3; ++v){
1253
                src = *SelSource(prog, v);
1254
                if(src == NULL){
1255
                    DBG("--%d: NULL");
1256
                }else{
1257
                    DBG("--%d: type %d, opn2clock %d, psgclock %d, psgfreq0to1 %d,", v, src->type, src->opn2clock, src->psgclock, src->psgfreq0to1);
1258
                    DBG("----loopaddr %d, loopsamples %d, markstart %d, markend %d", src->loopaddr, src->loopsamples, src->markstart, src->markend);
1259
                    if(src->type == 1){
1260
                        VgmSourceRAM* vsr = (VgmSourceRAM*)src->data;
1261
                        DBG("----VgmSourceRAM: numcmds %d", vsr->numcmds);
1262
                    }else if(src->type == 2){
1263
                        VgmSourceStream* vss = (VgmSourceStream*)src->data;
1264
                        DBG("----VgmSourceStream: datalen %d, vgmdatastartaddr %d, blocklen %d", vss->datalen, vss->vgmdatastartaddr, vss->blocklen);
1265
                    }
1266
                }
1267
            }
2484 Sauraen 1268
        }
1269
    }
2491 Sauraen 1270
    */
1271
    /*
2484 Sauraen 1272
    DBG("==== PIs ====");
1273
    synproginstance_t* pi;
1274
    VgmHead_Channel ch;
1275
    char* buf = vgmh2_malloc(64);
1276
    char* buf2 = vgmh2_malloc(64);
1277
    for(i=0; i<MBQG_NUM_PROGINSTANCES; ++i){
1278
        pi = &proginstances[i];
1279
        if(pi->valid){
1280
            DBG("PI %d static %d playing %d sourcechannel %d note %d recency %d head %d",
1281
                    i, pi->isstatic, pi->playing, pi->sourcechannel, pi->note, pi->recency,
1282
                    pi->head != NULL);
1283
            DBG("Mapping: 0 1 2 3 4 5 6 7 8 9 A B");
1284
            sprintf(buf, "Chip:                           ");
1285
            sprintf(buf2, "Voice:                          ");
1286
            for(v=0; v<0xC; ++v){
1287
                ch = pi->mapping[v];
1288
                if(!ch.nodata){
1289
                    buf[9+(2*v)] = '0' + ch.map_chip;
1290
                    buf2[9+(2*v)] = (ch.map_voice > 9) ? ('A' + ch.map_voice - 10) : ('0' + ch.map_voice);
1291
                }
1292
            }
1293
            DBG(buf);
1294
            DBG(buf2);
1295
        }
1296
    }
1297
    vgmh2_free(buf);
1298
    vgmh2_free(buf2);
2491 Sauraen 1299
    */
2484 Sauraen 1300
    DBG("==== SYNGENESISES ====");
1301
    syngenesis_t* sg;
1302
    syngenesis_usage_t* sgu;
1303
    for(g=0; g<GENESIS_COUNT; ++g){
1304
        sg = &syngenesis[g];
1305
        DBG("G %d: lfomode %d lfofixedspeed %d noisefreqsq3 %d",
1306
                g, sg->lfomode, sg->lfofixedspeed, sg->noisefreqsq3);
1307
        for(v=0; v<0xC; ++v){
1308
            sgu = &sg->channels[v];
1309
            if(sgu->use > 0){
1310
                DBG("--V %d: use %d, pi_using %d, lfo %d",
1311
                        v, sgu->use, sgu->pi_using, sgu->lfo);
1312
            }
1313
        }
1314
    }
1315
}