loader.c 17.9 KB
Newer Older
natalie's avatar
natalie committed
1
2
3
4
#include "loader.h"
#include "esp_log.h"
#include "driver.h"
#include "mallocs.h"
5
6
#include "ioexp.h"
#include "dacstream.h"
7
#include "userled.h"
8
#include "player.h"
natalie's avatar
natalie committed
9
#include "taskmgr.h"
natalie's avatar
natalie committed
10
#include "clk.h"
11
12
13
#include "ui.h"
#include "ui/modal.h"
#include "sdcard.h"
14
#include "queue.h"
natalie's avatar
natalie committed
15
16
17

static const char* TAG = "Loader";

natalie's avatar
natalie committed
18
static uint8_t Loader_CurLoop = 0;
19

natalie's avatar
natalie committed
20
21
22
23
24
25
26
EventGroupHandle_t Loader_Status;
StaticEventGroup_t Loader_StatusBuf;
EventGroupHandle_t Loader_BufStatus;
StaticEventGroup_t Loader_BufStatusBuf;
FILE *Loader_File;
FILE *Loader_PcmFile;
VgmInfoStruct_t *Loader_VgmInfo;
natalie's avatar
natalie committed
27
static uint8_t Loader_VgmDataBlockIndex = 0;
28
volatile VgmDataBlockStruct_t Loader_VgmDataBlocks[MAX_REALTIME_DATABLOCKS+1];
natalie's avatar
natalie committed
29
30
31
32
static bool Loader_RequestedDacStreamFindStart = false;
static uint8_t Loader_VgmBuf[FREAD_LOCAL_BUF];
static uint16_t Loader_VgmBufPos = FREAD_LOCAL_BUF;
static IRAM_ATTR uint32_t Loader_VgmFilePos = 0;
33
volatile bool Loader_IgnoreZeroSampleLoops = true;
natalie's avatar
natalie committed
34
volatile bool Loader_FastOpnaUpload = false;
natalie's avatar
natalie committed
35
static bool Loader_HitLoop = false;
36
static bool Loader_IsBad = false;
natalie's avatar
natalie committed
37
38

//local buffer thingie. big speedup
matt's avatar
matt committed
39
40
41
#define LOADER_BUF_FILL \
    fseek(Loader_File, Loader_VgmFilePos, SEEK_SET); \
    fread(&Loader_VgmBuf[0], 1, sizeof(Loader_VgmBuf), Loader_File); \
42
43
44
45
46
47
48
49
50
51
52
53
    Loader_VgmBufPos = 0; \
    if (ferror(Loader_File)) { \
        file_error(); \
        continue; \
    }
#define LOADER_BUF_FILL_NOLOOP \
    fseek(Loader_File, Loader_VgmFilePos, SEEK_SET); \
    fread(&Loader_VgmBuf[0], 1, sizeof(Loader_VgmBuf), Loader_File); \
    Loader_VgmBufPos = 0; \
    if (ferror(Loader_File)) { \
        file_error(); \
        Loader_VgmBufPos = 0; \
natalie's avatar
natalie committed
54
        return false; \
55
    }
matt's avatar
matt committed
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#define LOADER_BUF_CHECK \
    if (Loader_VgmBufPos >= sizeof(Loader_VgmBuf)) { \
        LOADER_BUF_FILL; \
    }
#define LOADER_BUF_SEEK_SET(offset) \
    Loader_VgmFilePos = offset; \
    LOADER_BUF_FILL;
#define LOADER_BUF_SEEK_REL(offset) \
    Loader_VgmFilePos += offset; \
    Loader_VgmBufPos += offset; \
    LOADER_BUF_CHECK;
#define LOADER_BUF_READ(var) \
    var = Loader_VgmBuf[Loader_VgmBufPos]; \
    Loader_VgmBufPos++; \
    Loader_VgmFilePos++; \
    LOADER_BUF_CHECK;
#define LOADER_BUF_READ4(var) \
    if (sizeof(Loader_VgmBuf)-Loader_VgmBufPos < 4) { \
        LOADER_BUF_FILL; \
    } \
    _Pragma("GCC diagnostic push") \
    _Pragma("GCC diagnostic ignored \"-Wstrict-aliasing\"") \
    var = *(uint32_t*)&Loader_VgmBuf[Loader_VgmBufPos]; \
    _Pragma("GCC diagnostic pop") \
    Loader_VgmBufPos += 4; \
    Loader_VgmFilePos += 4; \
    LOADER_BUF_CHECK;
natalie's avatar
natalie committed
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105

bool Loader_Setup() {
    ESP_LOGI(TAG, "Setting up");
    
    ESP_LOGI(TAG, "Creating status event group");
    Loader_Status = xEventGroupCreateStatic(&Loader_StatusBuf);
    if (Loader_Status == NULL) {
        ESP_LOGE(TAG, "Failed !!");
        return false;
    }
    xEventGroupSetBits(Loader_Status, LOADER_STOPPED);
    
    ESP_LOGI(TAG, "Creating buffer status event group");
    Loader_BufStatus = xEventGroupCreateStatic(&Loader_BufStatusBuf);
    if (Loader_BufStatus == NULL) {
        ESP_LOGE(TAG, "Failed !!");
        return false;
    }


    return true;
}

natalie's avatar
natalie committed
106
107
108
static IRAM_ATTR uint32_t Loader_PcmPos = 0;
static IRAM_ATTR uint32_t Loader_PcmOff = 0;
static uint32_t Loader_GetPcmOffset(uint32_t PcmPos) {
natalie's avatar
natalie committed
109
110
111
112
113
114
115
116
117
118
119
120
    uint32_t consumed = 0;
    for (uint8_t i=0;i<Loader_VgmDataBlockIndex;i++) {
        if (Loader_VgmDataBlocks[i].Type == 0) { //0 = ym2612 pcm
            if (PcmPos >= consumed && PcmPos < consumed + Loader_VgmDataBlocks[i].Size) {
                return Loader_VgmDataBlocks[i].Offset + (PcmPos - consumed);
            }
            consumed += Loader_VgmDataBlocks[i].Size;
        }
    }
    return 0xffffffff;
}

121
122
123
124
static uint8_t running = false;
static void file_error() {
    modal_show_simple(TAG, "SD Card Error", "There was an error reading the VGM from the SD card.\nPlease check that the card is inserted and try again.", LV_SYMBOL_OK " OK");
    running = false;
125
    xEventGroupClearBits(Loader_Status, LOADER_RUNNING); //prevent task priority issues from stopping loader
126
    xTaskNotify(Taskmgr_Handles[TASK_PLAYER], PLAYER_NOTIFY_STOP_RUNNING, eSetValueWithoutOverwrite);
127
128
    QueueLength = 0;
    QueuePosition = 0;
129
130
131
132
133
    Ui_Screen = UISCREEN_MAINMENU;
    Sdcard_Online = false;
    ESP_LOGE(TAG, "IO error");
}

natalie's avatar
natalie committed
134
135
136
137
static IRAM_ATTR uint32_t Loader_Pending = 0;
static bool Loader_EndReached = false;
static uint8_t Loader_PcmBuf[FREAD_LOCAL_BUF];
static uint16_t Loader_PcmBufUsed = FREAD_LOCAL_BUF;
natalie's avatar
natalie committed
138
static IRAM_ATTR uint32_t adjustedprio = false;
natalie's avatar
natalie committed
139
140
141
void Loader_Main() {
    ESP_LOGI(TAG, "Task start");
    while (1) {
142
        EventBits_t bits = xEventGroupWaitBits(Loader_Status, LOADER_START_REQUEST | LOADER_RUNNING | LOADER_STOP_REQUEST, false, false, pdMS_TO_TICKS(75));
natalie's avatar
natalie committed
143
144
        if (bits & LOADER_START_REQUEST) {
            ESP_LOGI(TAG, "Loader starting");
145
            xEventGroupClearBits(Loader_Status, LOADER_STOPPED);
natalie's avatar
natalie committed
146
147
            xEventGroupSetBits(Loader_Status, LOADER_RUNNING);
            xEventGroupClearBits(Loader_Status, LOADER_START_REQUEST);
148
149
150
151
            if (Loader_IsBad) {
                uint8_t badval = 0xff;
                MegaStream_Send(&Driver_CommandStream, &badval, 1);
            }
natalie's avatar
natalie committed
152
153
154
            running = true;
        } else if (bits & LOADER_STOP_REQUEST) {
            ESP_LOGI(TAG, "Loader stopping");
155
156
            running = false;
            xEventGroupClearBits(Loader_Status, LOADER_RUNNING);
natalie's avatar
natalie committed
157
158
159
160
            xEventGroupSetBits(Loader_Status, LOADER_STOPPED);
            xEventGroupClearBits(Loader_Status, LOADER_STOP_REQUEST);
        }
        if (running) {
161
            uint16_t spaces = MegaStream_Free(&Driver_CommandStream);
162
            EventBits_t bbits = xEventGroupGetBits(Loader_BufStatus);
natalie's avatar
natalie committed
163
164
165
166
167
168
            if (spaces == 0 && !(bbits & LOADER_BUF_FULL)) {
                xEventGroupSetBits(Loader_BufStatus, LOADER_BUF_FULL);
                xEventGroupClearBits(Loader_BufStatus, 0xff ^ LOADER_BUF_FULL);
            } else if (spaces == DRIVER_QUEUE_SIZE) {
                xEventGroupSetBits(Loader_BufStatus, LOADER_BUF_EMPTY);
                xEventGroupClearBits(Loader_BufStatus, 0xff ^ LOADER_BUF_EMPTY);
169
            } else if (spaces < (DRIVER_QUEUE_SIZE-(DRIVER_QUEUE_SIZE/4)) || Loader_EndReached || Loader_HitLoop) {
natalie's avatar
natalie committed
170
171
                xEventGroupSetBits(Loader_BufStatus, LOADER_BUF_OK);
                xEventGroupClearBits(Loader_BufStatus, 0xff ^ LOADER_BUF_OK);
natalie's avatar
natalie committed
172
            } else {
natalie's avatar
natalie committed
173
174
175
                xEventGroupSetBits(Loader_BufStatus, LOADER_BUF_LOW);
                xEventGroupClearBits(Loader_BufStatus, 0xff ^ LOADER_BUF_LOW);
            }
176
            if (Loader_HitLoop) Loader_HitLoop = false;
natalie's avatar
natalie committed
177
178
179
180
181
            if (adjustedprio) {
                vTaskPrioritySet(Taskmgr_Handles[TASK_LOADER], LOADER_TASK_PRIO_NORM);
                ESP_LOGW(TAG, "Returning to normal priority");
                adjustedprio = false;
            }
natalie's avatar
natalie committed
182
            if (!Loader_EndReached && (spaces > DRIVER_QUEUE_SIZE/16)) {
natalie's avatar
natalie committed
183
                UserLedMgr_DiskState[DISKSTATE_VGM] = true;
184
                UserLedMgr_Notify();
185
186
                while (running && MegaStream_Free(&Driver_CommandStream) >= 11) { //11 is the biggest fixed-size vgm command, so make sure there's at least that much space
                    if (MegaStream_Free(&Driver_CommandStream) > (DRIVER_QUEUE_SIZE/3)) {
natalie's avatar
natalie committed
187
188
189
190
191
192
                        if (!adjustedprio) {
                            ESP_LOGW(TAG, "Switching to high priority");
                            vTaskPrioritySet(Taskmgr_Handles[TASK_LOADER], LOADER_TASK_PRIO_HIGH);
                            adjustedprio = true;
                        }
                    }
natalie's avatar
natalie committed
193
194
                    uint8_t d = 0x00;
                    LOADER_BUF_READ(d);
195
196
197
198
199
200
201
202
203
204
205
206
                    if (d == 0xe0) { //pcm seek
                        uint32_t NewPos = 0;
                        LOADER_BUF_READ4(NewPos);
                        uint32_t NewOff = Loader_GetPcmOffset(NewPos);
                        if (NewOff != (Loader_PcmOff+1)) {
                            ESP_LOGD(TAG, "Pcm seeking to NewOff %d BufUsed %d PcmOff %d", NewOff, Loader_PcmBufUsed, Loader_PcmOff);
                            if (NewOff <= Loader_PcmOff && Loader_PcmOff - NewOff <= Loader_PcmBufUsed) { //don't seek the file if we can just rewind the buffer. todo: possibly handle optimize handling skipping forward within the buf's range?
                                ESP_LOGD(TAG, "PCM buffer rewind");
                                Loader_PcmBufUsed -= Loader_PcmOff - NewOff;
                                Loader_PcmOff = NewOff;
                            } else { //it's either ahead of our current position, or too far behind that it's not in the buffer
                                ESP_LOGD(TAG, "PCM actual read");
natalie's avatar
natalie committed
207
208
                                fseek(Loader_PcmFile, NewOff, SEEK_SET);
                                Loader_PcmOff = NewOff;
natalie's avatar
natalie committed
209
                                Loader_PcmBufUsed = FREAD_LOCAL_BUF;
natalie's avatar
natalie committed
210
                            }
211
212
213
214
215
                        }
                        Loader_PcmPos = NewPos;
                    } else if (d == 0x67) { //datablock
                        if (Loader_VgmDataBlockIndex == MAX_REALTIME_DATABLOCKS) {
                            ESP_LOGE(TAG, "loader datablocks over !!");
natalie's avatar
Fix #68    
natalie committed
216
217
218
219
220
221
                            Loader_EndReached = true;
                            running = false;
                            xEventGroupClearBits(Loader_Status, LOADER_RUNNING);
                            xEventGroupSetBits(Loader_Status, LOADER_STOPPED);
                            xEventGroupSetBits(Loader_BufStatus, LOADER_BUF_OK);
                            xEventGroupClearBits(Loader_BufStatus, 0xff ^ LOADER_BUF_OK);
natalie's avatar
natalie committed
222
                        } else {
223
224
225
                            //here are some hacks to wrap around VgmParseDataBlock not using our local buffer thing
                            fseek(Loader_File, Loader_VgmFilePos, SEEK_SET); //destroys buf
                            if (Loader_CurLoop == 0) { //only load datablocks on first loop through
matt's avatar
matt committed
226
                                VgmParseDataBlock(Loader_File, (VgmDataBlockStruct_t *)&Loader_VgmDataBlocks[Loader_VgmDataBlockIndex++]);
natalie's avatar
natalie committed
227
                            } else {
228
                                //can't simply skip over the datablock because they're variable-length. use the last entry as a garbage can
matt's avatar
matt committed
229
                                VgmParseDataBlock(Loader_File, (VgmDataBlockStruct_t *)&Loader_VgmDataBlocks[MAX_REALTIME_DATABLOCKS]);
230
231
232
233
                            }
                            LOADER_BUF_SEEK_SET(ftell(Loader_File)); //fix buf

                            //handle opna pcm datablocks, since they need to be uploaded
234
                            if (Loader_VgmDataBlocks[Loader_VgmDataBlockIndex-1].Type == 0x81 && Loader_VgmDataBlocks[Loader_VgmDataBlockIndex-1].Size > 8) {
235
236
237
238
239
240
241
242
243
244
                                ESP_LOGI(TAG, "Requesting OPNA PCM upload");
                                Clk_TempSet(0,Loader_FastOpnaUpload?12000000:8000000); //avoid timing issues / speed reduction if chip underclocked
                                Driver_Opna_PcmUploadId = Loader_VgmDataBlockIndex-1;
                                Driver_Opna_PcmUpload = true;
                                while (Driver_Opna_PcmUpload) {
                                    vTaskDelay(pdMS_TO_TICKS(10));
                                    //todo: timeout?
                                }
                                Clk_Restore(0);
                                ESP_LOGI(TAG, "Done");
natalie's avatar
natalie committed
245
246
                            }
                        }
247
248
249
250
251
252
253
254
255
256
                    } else if (d == 0x68) {

                    } else if ((d&0xf0) == 0x80) { //pcm and wait
                        if (Loader_PcmOff == 0) { //if this is the first sample being played, need to do an initial seek
                            Loader_PcmOff = Loader_GetPcmOffset(Loader_PcmPos);
                            fseek(Loader_PcmFile, Loader_PcmOff, SEEK_SET);
                            Loader_PcmBufUsed = FREAD_LOCAL_BUF;
                        }
                        if (Loader_PcmBufUsed == FREAD_LOCAL_BUF) {
                            fread(&Loader_PcmBuf[0], 1, FREAD_LOCAL_BUF, Loader_PcmFile); //todo: fix read past eof
257
258
259
260
                            if (ferror(Loader_PcmFile)) {
                                file_error();
                                continue;
                            }
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
                            Loader_PcmBufUsed = 0;
                        }
                        MegaStream_Send(&Driver_PcmStream, &Loader_PcmBuf[Loader_PcmBufUsed++], 1); //theoretically pcmqueue should never ever be full while there are still spaces in commandqueue
                        Loader_PcmPos++;
                        #ifdef PARANOID_THAT_THERE_MIGHT_BE_VGMS_THAT_PLAY_PCM_ACROSS_BLOCK_BOUNDARIES
                        uint32_t NewOff = Loader_GetPcmOffset(Loader_PcmPos);
                        if (NewOff != (Loader_PcmOff+1)) {
                            ESP_LOGI(TAG, "pcm seeking to %d after sample load", NewOff);
                            fseek(Loader_PcmFile, NewOff, SEEK_SET);
                            Loader_PcmOff = NewOff;
                            Loader_PcmBufUsed = FREAD_LOCAL_BUF;
                        }
                        #else
                        Loader_PcmOff++;
                        #endif
                        MegaStream_Send(&Driver_CommandStream, &d, 1);
                    } else if (d == 0x66) { //end of music, optionally loop
                        ESP_LOGI(TAG, "reached end of music");
                        if (Loader_VgmInfo->LoopOffset == 0 || (Loader_IgnoreZeroSampleLoops && Loader_VgmInfo->LoopSamples == 0)) { //there is no loop point at all
                            ESP_LOGI(TAG, "no loop point");
                            MegaStream_Send(&Driver_CommandStream, &d, 1); //let driver figure out it's the end
                            Loader_EndReached = true;
                            break;
                        }
                        ESP_LOGI(TAG, "looping");
                        if (Loader_VgmInfo->LoopSamples == 0) ESP_LOGW(TAG, "looping despite LoopSamples == 0 !!");
287
                        if (Loader_CurLoop == 0) Loader_HitLoop = true;
288
289
290
                        Loader_CurLoop++; //still need to keep track of this so dacstream loads aren't duplicated
                        MegaStream_Send(&Driver_CommandStream, &d, 1);
                        LOADER_BUF_SEEK_SET(Loader_VgmInfo->LoopOffset);
291
                        break;
292
293
                    } else if (d >= 0x90 && d <= 0x95) { //dacstream command
                        if (!Loader_RequestedDacStreamFindStart) {
matt's avatar
matt committed
294
                            DacStream_BeginFinding((VgmDataBlockStruct_t *)&Loader_VgmDataBlocks, Loader_VgmDataBlockIndex, Loader_VgmFilePos-1);
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
                            Loader_RequestedDacStreamFindStart = true;
                        }
                        MegaStream_Send(&Driver_CommandStream, &d, 1); //command
                        uint8_t cmdlen = VgmCommandLength(d);
                        for (uint8_t i=1;i<cmdlen;i++) { //command data
                            LOADER_BUF_READ(d);
                            MegaStream_Send(&Driver_CommandStream, &d, 1);
                        }
                    } else { //just a regular command
                        MegaStream_Send(&Driver_CommandStream, &d, 1); //command
                        uint8_t cmdlen = VgmCommandLength(d)-1; //it's really the command's attached data
                        if (cmdlen == 0) continue; //don't bother with any of the below if there is no attached data

                        //fill up the fread buf if necessary, so we can copy the whole command in one call
                        if (Loader_VgmBufPos + cmdlen >= FREAD_LOCAL_BUF) {
                            LOADER_BUF_FILL;
                        }

                        MegaStream_Send(&Driver_CommandStream, &Loader_VgmBuf[Loader_VgmBufPos], cmdlen);

                        //fix up the buffer tracking vars that the LOADER_BUF_READ macro normally handles
                        Loader_VgmBufPos += cmdlen;
                        Loader_VgmFilePos += cmdlen;
                        LOADER_BUF_CHECK;
natalie's avatar
natalie committed
319
320
                    }
                }
natalie's avatar
natalie committed
321
                UserLedMgr_DiskState[DISKSTATE_VGM] = false;
322
                UserLedMgr_Notify();
natalie's avatar
natalie committed
323
                vTaskDelay(pdMS_TO_TICKS(75)); //just filled, big delay now
natalie's avatar
natalie committed
324
            } else {
natalie's avatar
natalie committed
325
                vTaskDelay(pdMS_TO_TICKS(25)); //keep an eye on queue spaces
natalie's avatar
natalie committed
326
327
328
329
330
            }
        }
    }
}

331
bool Loader_Start(FILE *File, FILE *PcmFile, VgmInfoStruct_t *info, bool IsBad) {
natalie's avatar
natalie committed
332
333
334
335
336
    if (xEventGroupGetBits(Loader_Status) & LOADER_RUNNING) {
        //running, can't start
        return false;
    }

natalie's avatar
natalie committed
337
338
    Loader_File = File;
    Loader_PcmFile = PcmFile;
natalie's avatar
natalie committed
339
340
341
342
    Loader_VgmInfo = info;
    Loader_PcmPos = 0;
    Loader_PcmOff = 0;
    Loader_Pending = 0;
343
    Loader_EndReached = false;
344
    Loader_RequestedDacStreamFindStart = false;
natalie's avatar
natalie committed
345
    Loader_VgmDataBlockIndex = 0;
346
    Loader_CurLoop = 0;
347
    Loader_IsBad = IsBad;
348
    Loader_PcmBufUsed = FREAD_LOCAL_BUF;
natalie's avatar
natalie committed
349
350

    Loader_VgmFilePos = Loader_VgmInfo->DataOffset;
351
    LOADER_BUF_FILL_NOLOOP;
natalie's avatar
natalie committed
352
353
354
355
356
357
358

    xEventGroupSetBits(Loader_Status, LOADER_START_REQUEST);

    return true;
}

bool Loader_Stop() {
359
    if (xEventGroupGetBits(Loader_Status) & LOADER_STOPPED) {
360
        ESP_LOGW(TAG, "Loader_Stop() called but loader is already stopped !!");
361
    }
natalie's avatar
natalie committed
362
    xEventGroupSetBits(Loader_Status, LOADER_STOP_REQUEST);
363
    EventBits_t bits = xEventGroupWaitBits(Loader_Status, LOADER_STOPPED, false, false, pdMS_TO_TICKS(3000));
natalie's avatar
natalie committed
364
365
366
    if (bits & LOADER_STOPPED) {
        //cleanup stuff
        Loader_VgmDataBlockIndex = 0;
natalie's avatar
fix #2    
natalie committed
367
368
        xEventGroupSetBits(Loader_BufStatus, LOADER_BUF_EMPTY);
        xEventGroupClearBits(Loader_BufStatus, 0xff & ~LOADER_BUF_EMPTY);
369
370
371
        //resetting streams here is actually unsafe, but we're protected by player ensuring that driver is stopped before getting this far
        MegaStream_Reset(&Driver_CommandStream);
        MegaStream_Reset(&Driver_PcmStream);
natalie's avatar
natalie committed
372
373
374
375
376
        return true;
    } else {
        ESP_LOGE(TAG, "Loader stop request timeout !!");
        return false;
    }
matt's avatar
matt committed
377
}