Subversion Repositories svn.mios32

Rev

Rev 2572 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2571 hawkeye 1
// MBLoopa Core Logic
2
// (c) Hawkeye 2015
3
//
4
// This unit provides the looper core data structures and the menu navigation and multi-clip support
5
//
6
// When recording clips, these are directly stored to a MIDI file, we cannot loop record (so what)
7
// After recording a clip, it will be instantly re-scanned from SD to gather key informations,
8
// like tick length, noteroll information, and so on.
9
//
10
// We cannot store all sequencer notes in memory, but we can pre-fetch notes for every midi-file
11
// from SD-Card, so that we might be able to play eight tracks (unmuted) with no sync issues.
12
// Track looping can be implemented likewise - if our SEQ Handler detects, that we are close
13
// to the end of a clip, and that clip is set to loop, we can resupply the seq_midi_out buffers...
14
//
15
// In playback, all clips will start to play back, regardless of their mute state.
16
// Also, all clips will loop by default (it is called loopA, anyways)
17
// This emulates the behaviour of the MBSEQ.
18
//
19
// The Loopa itself has a beatloop minimum sync range (default 16 steps). All playback clip regions
20
// will have lengths of multiples of this, when modifying start and stop positions (for looping),
21
// it is made sure, that the output clip has a length of n*beatloop
22
// (will be filled up with silence at the end, if necessary)
23
//
24
// Recording occurs directly to SD and can be of unlimited length. After recording, a new MID file
25
// with an increased number (e.g. C_3_0004.MID -> clip 4, recording take 5) is stored and reloaded
26
// and length-adjusted to n*beatloop requirements.
27
//
28
// If the beatloop value is changed in the UI, the clip endpoints are automatically adjusted.
29
//
30
// =================================================================================================
31
 
32
 
33
#include <mios32.h>
34
#include <mid_parser.h>
35
#include <FreeRTOS.h>
36
#include <seq_midi_out.h>
37
#include <seq_bpm.h>
38
#include <midi_port.h>
39
#include <midi_router.h>
40
#include <string.h>
41
 
42
#include "tasks.h"
43
#include "file.h"
44
#include "loopa.h"
45
#include "seq.h"
46
#include "mid_file.h"
47
#include "hardware.h"
48
#include "screen.h"
49
#include "voxelspace.h"
50
 
51
 
52
// --  Local types ---
53
 
54
typedef struct
55
{
56
   u32  initial_file_pos;
57
   u32  initial_tick;
58
   u32  file_pos;
59
   u32  chunk_end;
60
   u32  tick;
61
   u8   running_status;
62
} midi_track_t;
63
 
64
 
65
// --- Consts ---
66
 
67
const int PREFETCH_TIME_MS = 100;
68
 
69
 
70
// --- Global vars ---
71
 
72
static s32 (*clipPlayEventCallback)(u8 clipNumber, mios32_midi_package_t midi_package, u32 tick) = 0;     // fetchClipEvents() callback
73
static s32 (*clipMetaEventCallback)(u8 clipNumber, u8 meta, u32 len, u8 *buffer, u32 tick) = 0; // fetchClipEvents() callback
74
static u8 meta_buffer[MID_PARSER_META_BUFFER_SIZE];
75
 
76
u16 sessionNumber_ = 0;         // currently active session number (directory e.g. /SESSIONS/0001)
77
u8 baseView_ = 0;               // if not in baseView, we are in single clipView
78
u8 displayMode2d_ = 0;          // if not in 2d display mode, we are rendering voxelspace
79
u8 selectedClipNumber_ = 0;     // currently active or last active clip number (1-8)
80
u16 beatLoopSteps_ = 16;        // number of steps for one beatloop (adjustable)
81
u8 isRecording_ = 0;            // set, if currently recording
82
u8 stopRecordingRequest_ = 0;   // if set, the sequencer will stop recording at an even "beatLoopStep" interval, to create loopable clips
83
s8 reloadClipNumberRequest_ = -1; // if != -1, reload the given clip number after sequencer stop (usually, a track has been recorded, then!)
84
 
85
u16 seqPlayEnabledPorts_;
86
u16 seqRecEnabledPorts_;
87
 
88
static u32 clipFilePos_[8];     // file position within midi file (for 8 clips)
89
static u32 clipFileLen_[8];     // file length of midi file (for 8 clips)
90
u8 clipFileAvailable_[8];       // true, if clip midi file is available (for 8 clips)
91
static file_t clipFileFi_[8];   // clip file reference handles, only valid if clipFileAvailable is true (for 8 clips)
92
u16 clipMidiFileFormat_[8];     // clip midi file format (for 8 clips)
93
u16 clipMidiFilePPQN_[8];       // pulses per quarter note (for 8 clips)
94
midi_track_t clipMidiTrack_[8]; // max one midi track per clip mid file supported (for 8 clips)
95
 
96
u32 clipEventNumber_[8];        // number of events in the respective clip
97
u32 clipTicks_[8];              // number of available ticks in the respective clip
98
u8 clipMute_[8];                // mute state of each clip
99
 
100
static u8 ffwdSilentMode_;      // for FFWD function
101
static u32 nextPrefetch_[8];    // next tick at which the prefetch should take place for each clip
102
static u32 prefetchOffset_[8];  // already prefetched ticks for each clip
103
static u8 seqClkLocked;         // lock BPM, so that it can't be changed from MIDI player
104
 
105
// =================================================================================================
106
 
107
 
108
/**
109
 * Help function: convert tick number to step number
110
 * @return step
111
 *
112
 */
113
u16 tickToStep(u32 tick)
114
{
115
   return tick / (SEQ_BPM_PPQN_Get() / 4);
116
}
117
// -------------------------------------------------------------------------------------------------
118
 
119
 
120
/**
121
 * Reads <len> bytes from the .mid file into <buffer>
122
 * @return number of read bytes
123
 *
124
 */
125
u32 clipFileRead(u8 clipNumber, void *buffer, u32 len)
126
{
127
   s32 status;
128
 
129
   if (!clipFileAvailable_[clipNumber])
130
      return FILE_ERR_NO_FILE;
131
 
132
   MUTEX_SDCARD_TAKE;
133
   if ((status=FILE_ReadReOpen(&clipFileFi_[clipNumber])) >= 0)
134
   {
135
      status = FILE_ReadBuffer(buffer, len);
136
      FILE_ReadClose(&clipFileFi_[clipNumber]);
137
   }
138
   MUTEX_SDCARD_GIVE;
139
 
140
   return (status >= 0) ? len : 0;
141
}
142
// -------------------------------------------------------------------------------------------------
143
 
144
 
145
/**
146
 * @return 1, if end of file reached
147
 *
148
 */
149
s32 clipFileEOF(u8 clipNumber)
150
{
151
   if (clipFilePos_[clipNumber] >= clipFileLen_[clipNumber] || !FILE_SDCardAvailable())
152
      return 1; // end of file reached or SD card disconnected
153
 
154
   return 0;
155
}
156
// -------------------------------------------------------------------------------------------------
157
 
158
 
159
/**
160
 * Sets file pointer to a specific position
161
 * @return -1, if end of file reached
162
 *
163
 */
164
s32 clipFileSeek(u8 clipNumber, u32 pos)
165
{
166
   s32 status;
167
 
168
   if (!clipFileAvailable_[clipNumber])
169
      return -1; // end of file reached
170
 
171
   clipFilePos_[clipNumber] = pos;
172
 
173
   MUTEX_SDCARD_TAKE;
174
 
175
   if (clipFilePos_[clipNumber] >= clipFileLen_[clipNumber])
176
      status = -1; // end of file reached
177
   else
178
   {
179
      if ((status=FILE_ReadReOpen(&clipFileFi_[clipNumber])) >= 0)
180
      {
181
         status = FILE_ReadSeek(pos);
182
         FILE_ReadClose(&clipFileFi_[clipNumber]);
183
      }
184
   }
185
 
186
   MUTEX_SDCARD_GIVE;
187
 
188
   return status;
189
}
190
// -------------------------------------------------------------------------------------------------
191
 
192
 
193
/**
194
 * Help function: reads a byte/hword/word from the .mid file
195
 *
196
 */
197
static u32 clipReadWord(u8 clipNumber, u8 len)
198
{
199
   int word = 0;
200
   int i;
201
 
202
   for (i=0; i<len; ++i)
203
   {
204
      // due to unknown endianess of the host processor, we have to read byte by byte!
205
      u8 byte;
206
      clipFileRead(clipNumber, &byte, 1);
207
      word = (word << 8) | byte;
208
   }
209
 
210
   return word;
211
}
212
// -------------------------------------------------------------------------------------------------
213
 
214
 
215
/**
216
 * Help function: reads a variable-length number from the .mid file
217
 * based on code example in MIDI file spec
218
 *
219
 */
220
static u32 clipReadVarLen(u8 clipNumber, u32* pos)
221
{
222
   u32 value;
223
   u8 c;
224
 
225
   *pos += clipFileRead(clipNumber, &c, 1);
226
   if ((value = c) & 0x80)
227
   {
228
      value &= 0x7f;
229
 
230
      do
231
      {
232
         *pos += clipFileRead(clipNumber, &c, 1);
233
         value = (value << 7) | (c & 0x7f);
234
      } while (c & 0x80);
235
   }
236
 
237
   return value;
238
}
239
// -------------------------------------------------------------------------------------------------
240
 
241
 
242
/**
243
 * Restarts a clip w/o reading the .mid file chunks again (saves time)
244
 * Resets the clip start position + prefetch positions.
245
 * Usually called when (re)starting a whole song.
246
 *
247
 */
248
s32 clipRestart(u8 clipNumber)
249
{
250
   if (!clipFileAvailable_[clipNumber])
251
      return FILE_ERR_NO_FILE;
252
 
253
   DEBUG_MSG("[clipRestart] clip %d", clipNumber);
254
 
255
   // restore saved filepos/tick values
256
   midi_track_t *mt = &clipMidiTrack_[clipNumber];
257
 
258
   mt->file_pos = mt->initial_file_pos; // XXX: Set prescanned loop-start point file position here
259
   mt->tick = mt->initial_tick;
260
   mt->running_status = 0x80;
261
 
262
   nextPrefetch_[clipNumber] = 0;
263
   prefetchOffset_[clipNumber] = 0;
264
 
265
   return 0; // no error
266
}
267
// -------------------------------------------------------------------------------------------------
268
 
269
 
270
 
271
/**
272
 * Rewinds a clip after a loop, not resetting the mt->tick position and prefetch offsets,
273
 * to stay in sync with the current song tick position.
274
 * Called, when looping a clip.
275
 *
276
 */
277
s32 clipRewind(u8 clipNumber)
278
{
279
   if (!clipFileAvailable_[clipNumber])
280
      return FILE_ERR_NO_FILE;
281
 
282
   DEBUG_MSG("[clipRewind] clip %d", clipNumber);
283
 
284
   // restore saved filepos value
285
   midi_track_t *mt = &clipMidiTrack_[clipNumber];
286
 
287
   mt->file_pos = mt->initial_file_pos; // XXX: Set prescanned loop-start point file position here
288
   mt->running_status = 0x80;
289
 
290
   return 0; // no error;
291
}
292
// -------------------------------------------------------------------------------------------------
293
 
294
 
295
 
296
/**
297
 * Toggle the mute state of a clip
298
 * (XXX: this must beat-synced, later on)
299
 * (XXX: also, hanging notes must be avoided)
300
 *
301
 */
302
void toggleMute(u8 clipNumber)
303
{
304
   u8 state = clipMute_[clipNumber];
305
 
306
   // If old state mute=1, after state change, the led must light up, so ledstate=old mute state
307
   switch (clipNumber)
308
   {
309
   case 0: MIOS32_DOUT_PinSet(led_clip1, state); break;
310
   case 1: MIOS32_DOUT_PinSet(led_clip2, state); break;
311
   case 2: MIOS32_DOUT_PinSet(led_clip3, state); break;
312
   case 3: MIOS32_DOUT_PinSet(led_clip4, state); break;
313
   case 4: MIOS32_DOUT_PinSet(led_clip5, state); break;
314
   case 5: MIOS32_DOUT_PinSet(led_clip6, state); break;
315
   case 6: MIOS32_DOUT_PinSet(led_clip7, state); break;
316
   case 7: MIOS32_DOUT_PinSet(led_clip8, state); break;
317
   }
318
 
319
   // invert the mute state
320
   clipMute_[clipNumber] = !state;
321
}
322
// -------------------------------------------------------------------------------------------------
323
 
324
 
325
/**
326
 * Initially open the current clip .mid file of a clipNumber (associated with current sessionNumber)
327
 * and parse for available header/track chunks.
328
 
329
 * Multiple recordings into that clip slot will produce higher -0000.MID suffixes, the highest
330
 * number is loaded.
331
 *
332
 * @return 0, if no errors occured
333
 *
334
 */
335
s32 clipFileOpen(u8 clipNumber)
336
{
337
   // invalidate current file
338
   clipFileAvailable_[clipNumber] = 0;
339
 
340
   static char filepath[48];
341
 
342
   u32 rec_num = 0;
343
   MUTEX_SDCARD_TAKE;
344
   while (1)
345
   {
346
      sprintf(filepath, "/SESSIONS/%04d/C_%d-%04d.MID", sessionNumber_, clipNumber, rec_num);
347
      if (FILE_FileExists(filepath) <= 0)
348
         break;
349
      ++rec_num;
350
   }
351
   MUTEX_SDCARD_GIVE;
352
 
353
   sprintf(filepath, "/SESSIONS/%04d/C_%d-%04d.MID", sessionNumber_, clipNumber, rec_num-1);
354
 
355
   MUTEX_SDCARD_TAKE;
356
   s32 status = FILE_ReadOpen(&clipFileFi_[clipNumber], filepath);
357
   FILE_ReadClose(&clipFileFi_[clipNumber]); // close again - file will be reopened by read handler
358
   MUTEX_SDCARD_GIVE;
359
 
360
   if (status != 0)
361
   {
362
      DEBUG_MSG("[clipFileOpen] failed to open file %s, status: %d\n", filepath, status);
363
      return status;
364
   }
365
 
366
   // File is available
367
 
368
   clipFilePos_[clipNumber] = 0;
369
   clipFileLen_[clipNumber] = clipFileFi_[clipNumber].fsize;
370
   clipFileAvailable_[clipNumber] = 1;
371
 
372
   DEBUG_MSG("[clipFileOpen] opened '%s' of length %u\n", filepath, clipFileLen_[clipNumber]);
373
 
374
   // Parse header...
375
   u8 chunk_type[4];
376
   u32 chunk_len;
377
 
378
   DEBUG_MSG("[clipFileOpen] reading file\n\r");
379
 
380
   // reset file position
381
   clipFileSeek(clipNumber, 0);
382
   u32 file_pos = 0;
383
   u16 num_tracks = 0;
384
 
385
   // read chunks
386
   while (!clipFileEOF(clipNumber))
387
   {
388
      file_pos += clipFileRead(clipNumber, chunk_type, 4);
389
 
390
      if (clipFileEOF(clipNumber))
391
         break; // unexpected: end of file reached
392
 
393
      chunk_len = clipReadWord(clipNumber, 4);
394
      file_pos += 4;
395
 
396
      DEBUG_MSG("[clipFileOpen] chunk len %u", chunk_len);
397
 
398
 
399
      if (clipFileEOF(clipNumber))
400
         break; // unexpected: end of file reached
401
 
402
      if (memcmp(chunk_type, "MThd", 4) == 0)
403
      {
404
         DEBUG_MSG("[clipFileOpen] Found Header with size: %u\n\r", chunk_len);
405
         if( chunk_len != 6 )
406
         {
407
            DEBUG_MSG("[clipFileOpen] invalid header size - skip!\n\r");
408
         }
409
         else
410
         {
411
            clipMidiFileFormat_[clipNumber] = (u16)clipReadWord(clipNumber, 2);
412
            u16 tempTracksNum = (u16)clipReadWord(clipNumber, 2);
413
            clipMidiFilePPQN_[clipNumber] = (u16)clipReadWord(clipNumber, 2);
414
            file_pos += 6;
415
            DEBUG_MSG("[clipFileOpen] MIDI file format: %u\n\r", clipMidiFileFormat_[clipNumber]);
416
            DEBUG_MSG("[clipFileOpen] Number of tracks: %u\n\r", tempTracksNum);
417
            DEBUG_MSG("[clipFileOpen] ppqn (n per quarter note): %u\n\r", clipMidiFilePPQN_[clipNumber]);
418
         }
419
      }
420
      else if (memcmp(chunk_type, "MTrk", 4) == 0)
421
      {
422
         if (num_tracks >= 1)
423
         {
424
            DEBUG_MSG("[clipFileOpen] Found Track with size: %u must be ignored due to MID_PARSER_MAX_TRACKS\n\r", chunk_len);
425
         }
426
         else
427
         {
428
            u32 num_bytes = 0;
429
            u32 delta = (u32)clipReadVarLen(clipNumber, &num_bytes);
430
            file_pos += num_bytes;
431
            chunk_len -= num_bytes;
432
 
433
            midi_track_t *mt = &clipMidiTrack_[clipNumber];
434
            mt->initial_file_pos = file_pos;
435
            mt->initial_tick = delta;
436
            mt->file_pos = file_pos;
437
            mt->chunk_end = file_pos + chunk_len - 1;
438
            mt->tick = delta;
439
            mt->running_status = 0x80;
440
            ++num_tracks;
441
 
442
            DEBUG_MSG("[clipFileOpen] Found Track %d with size: %u, initial_tick: %u, starting at tick: %u\n\r", num_tracks, chunk_len + num_bytes, mt->initial_tick, mt->tick);
443
 
444
            break;
445
         }
446
 
447
         // switch to next track
448
         file_pos += chunk_len;
449
         clipFileSeek(clipNumber, file_pos);
450
      }
451
      else
452
      {
453
         DEBUG_MSG("[clipFileOpen] Found unknown chunk '%c%c%c%c' of size %u\n\r",
454
                   chunk_type[0], chunk_type[1], chunk_type[2], chunk_type[3],
455
                   chunk_len);
456
 
457
         if (num_tracks >= 1 )
458
         {
459
            // Found at least one track, all is good
460
            break;
461
         }
462
         else
463
         {
464
            // Invalidate file, no tracks found
465
            clipFileAvailable_[clipNumber] = 0;
466
            return -1;
467
         }
468
      }
469
   }
470
 
471
   clipFileAvailable_[clipNumber] = 1;
472
 
473
   return 0; // no error
474
}
475
// -------------------------------------------------------------------------------------------------
476
 
477
 
478
/**
479
 * Callback method to count a loaded play event in memory.
480
 * Triggered by MID_PARSER_FetchEvents during loadClip
481
 *
482
 */
483
static s32 countPlayEvent(u8 clipNumber, mios32_midi_package_t midi_package, u32 tick)
484
{
485
   /*
486
   if (midi_package.event == NoteOff || midi_package.velocity == 0)
487
      DEBUG_MSG("[countPlayEvent] @tick %u note off", tick);
488
   else if (midi_package.event == NoteOn)
489
      DEBUG_MSG("[countPlayEvent] @tick %u note on", tick);
490
   */
491
 
492
   clipEventNumber_[clipNumber]++;
493
 
494
   if (tick > clipTicks_[clipNumber])
495
      clipTicks_[clipNumber] = tick;
496
 
497
   return 0;
498
}
499
// -------------------------------------------------------------------------------------------------
500
 
501
 
502
/**
503
 * Callback method to count a loaded play event in memory.
504
 * Triggered by MID_PARSER_FetchEvents during loadClip
505
 *
506
 */
507
static s32 countMetaEvent(u8 clipNumber, u8 meta, u32 len, u8 *buffer, u32 tick)
508
{
509
   clipEventNumber_[clipNumber]++;
510
 
511
   if (tick > clipTicks_[clipNumber])
512
      clipTicks_[clipNumber] = tick;
513
 
514
   return 0;
515
}
516
// -------------------------------------------------------------------------------------------------
517
 
518
 
519
/**
520
 * Prefetches MIDI events from the given clip number file for a given number of MIDI ticks
521
 *
522
 * tick_offsets are scaled to clip loop ranges
523
 *
524
 * returns < 0 on errors
525
 * returns > 0 if tracks are still playing
526
 * returns 0 if song is finished
527
 *
528
 */
529
s32 clipFetchEvents(u8 clipNumber, u32 tick_offset, u32 num_ticks)
530
{
531
   u8 stillRunning = 0;
532
 
533
   if (clipFileAvailable_[clipNumber] == 0)
534
      return 1; // fake for compatibility reasons
535
 
536
   midi_track_t *mt = &clipMidiTrack_[clipNumber];
537
 
538
   DEBUG_MSG("[fetch events] clip %d fpos %d endpos %d | tick_offs: %d", clipNumber, mt->file_pos, mt->chunk_end, tick_offset);
539
 
540
   // Special case - if we are at the end of our midi file, loop back to start
541
   // XXX: later on - skip to preindexed loop start point, for now, just rewind
542
   if (mt->file_pos >= mt->chunk_end)
543
      clipRewind(clipNumber);
544
 
545
   while (mt->file_pos < mt->chunk_end)
546
   {
547
      stillRunning = 1;
548
 
549
      // exit, if next tick is not within given timeframe
550
      if (mt->tick >= tick_offset + num_ticks)
551
         break;
552
 
553
      // re-set to current file pos
554
      clipFileSeek(clipNumber, mt->file_pos);
555
 
556
      // get event
557
      u8 event;
558
      mt->file_pos += clipFileRead(clipNumber, &event, 1);
559
 
560
      if (event == 0xf0)
561
      {
562
         // SysEx event
563
         u32 length = (u32)clipReadVarLen(clipNumber, &mt->file_pos);
564
         #if DEBUG_VERBOSE_LEVEL >= 3
565
         DEBUG_MSG("[MID_PARSER:%d:%u] SysEx event with %u bytes\n\r", track, mt->tick, length);
566
         #endif
567
 
568
         // TODO: use optimized packages for SysEx!
569
         mios32_midi_package_t midi_package;
570
         midi_package.type = 0xf; // single bytes will be transmitted
571
 
572
         // initial 0xf0
573
         midi_package.evnt0 = 0xf0;
574
         if (clipPlayEventCallback != NULL)
575
            clipPlayEventCallback(clipNumber, midi_package, mt->tick);
576
 
577
         // remaining bytes
578
         int i;
579
         for (i=0; i<length; ++i)
580
         {
581
            u8 evnt0;
582
            mt->file_pos += clipFileRead(clipNumber, &evnt0, 1);
583
            midi_package.evnt0 = evnt0;
584
            if (clipPlayEventCallback != NULL)
585
               clipPlayEventCallback(clipNumber, midi_package, mt->tick);
586
         }
587
      }
588
      else if (event == 0xf7)
589
      {
590
         // "Escaped" event (allows to send any MIDI data)
591
         u32 length = (u32)clipReadVarLen(clipNumber, &mt->file_pos);
592
         #if DEBUG_VERBOSE_LEVEL >= 3
593
         DEBUG_MSG("[MID_PARSER:%d:%u] Escaped event with %u bytes\n\r", track, mt->tick, length);
594
         #endif
595
 
596
         mios32_midi_package_t midi_package;
597
         midi_package.type = 0xf; // single bytes will be transmitted
598
         int i;
599
         for (i=0; i<length; ++i)
600
         {
601
            u8 evnt0;
602
            mt->file_pos += clipFileRead(clipNumber, &evnt0, 1);
603
            midi_package.evnt0 = evnt0;
604
            if (clipPlayEventCallback != NULL)
605
               clipPlayEventCallback(clipNumber, midi_package, mt->tick);
606
         }
607
      }
608
      else if (event == 0xff)
609
      {
610
         // Meta Event
611
         u8 meta;
612
         mt->file_pos += clipFileRead(clipNumber, &meta, 1); // Meta type 1 byte, e.g. 0x2f "EndOfTrack"
613
         u32 length = (u32)clipReadVarLen(clipNumber, &mt->file_pos);
614
 
615
         if (clipMetaEventCallback != NULL)
616
         {
617
            u32 buflen = length;
618
            if (buflen > (MID_PARSER_META_BUFFER_SIZE-1))
619
            {
620
               buflen = MID_PARSER_META_BUFFER_SIZE - 1;
621
               DEBUG_MSG("[MID_PARSER:%d:%u] Meta Event 0x%02x with %u bytes - cut at %u bytes!\n\r", clipNumber, mt->tick, meta, length, buflen);
622
            }
623
            else
624
            {
625
               DEBUG_MSG("[MID_PARSER:%d:%u] Meta Event 0x%02x with %u bytes\n\r", clipNumber, mt->tick, meta, buflen);
626
            }
627
 
628
            if (buflen)
629
            {
630
               // copy bytes into buffer
631
               mt->file_pos += clipFileRead(clipNumber, meta_buffer, buflen);
632
 
633
               if (length > buflen)
634
               {
635
                  // no free memory: dummy reads
636
                  int i;
637
                  u8 dummy;
638
                  for(i=buflen; i<length; ++i)
639
                     mt->file_pos += clipFileRead(clipNumber, &dummy, 1);
640
               }
641
            }
642
 
643
            meta_buffer[buflen] = 0; // terminate with 0 for the case that a string has been transfered
644
 
645
            // -> forward to callback function
646
            clipMetaEventCallback(clipNumber, meta, buflen, meta_buffer, mt->tick);
647
         }
648
      }
649
      else
650
      {
651
         // common MIDI event
652
         mios32_midi_package_t midi_package;
653
 
654
         if (event & 0x80)
655
         {
656
            mt->running_status = event;
657
            midi_package.evnt0 = event;
658
            u8 evnt1;
659
            mt->file_pos += clipFileRead(clipNumber, &evnt1, 1);
660
            midi_package.evnt1 = evnt1;
661
         }
662
         else
663
         {
664
            midi_package.evnt0 = mt->running_status;
665
            midi_package.evnt1 = event;
666
         }
667
         midi_package.type = midi_package.event;
668
 
669
         switch (midi_package.event)
670
         {
671
         case NoteOff:
672
         case NoteOn:
673
         case PolyPressure:
674
         case CC:
675
         case PitchBend:
676
            {
677
               u8 evnt2;
678
               mt->file_pos += clipFileRead(clipNumber, &evnt2, 1);
679
               midi_package.evnt2 = evnt2;
680
 
681
               if (clipPlayEventCallback != NULL)
682
                  clipPlayEventCallback(clipNumber, midi_package, mt->tick);
683
 
684
               #if DEBUG_VERBOSE_LEVEL >= 3
685
               DEBUG_MSG("[MID_PARSER:%d:%u] %02x%02x%02x\n\r", clipNumber, mt->tick, midi_package.evnt0, midi_package.evnt1, midi_package.evnt2);
686
               #endif
687
            }
688
            break;
689
         case ProgramChange:
690
         case Aftertouch:
691
            if (clipPlayEventCallback != NULL)
692
               clipPlayEventCallback(clipNumber, midi_package, mt->tick);
693
 
694
            #if DEBUG_VERBOSE_LEVEL >= 3
695
            DEBUG_MSG("[MID_PARSER:%d:%u] %02x%02x\n\r", track, mt->tick, midi_package.evnt0, midi_package.evnt1);
696
            #endif
697
            break;
698
 
699
         default:
700
            #if DEBUG_VERBOSE_LEVEL >= 1
701
            DEBUG_MSG("[MID_PARSER:%d:%u] ooops? got 0xf0 status in MIDI event stream!\n\r", track, mt->tick);
702
            #endif
703
            break;
704
         }
705
      }
706
 
707
      // get delta ticks to next event, if end of track hasn't been reached yet
708
      if (mt->file_pos < mt->chunk_end)
709
      {
710
         u32 delta = (u32)clipReadVarLen(clipNumber, &mt->file_pos);
711
         mt->tick += delta;
712
      }
713
   }
714
 
715
   return stillRunning;
716
}
717
// -------------------------------------------------------------------------------------------------
718
 
719
 
720
/**
721
 * Scan a clip and read the number of events and midi ticks contained
722
 *
723
 */
724
void clipScan(u8 clipNumber)
725
{
726
   clipEventNumber_[clipNumber] = 0;
727
   clipTicks_[clipNumber] = 0;
728
 
729
   // Install "count-only" callback
730
   clipPlayEventCallback = countPlayEvent;
731
   clipMetaEventCallback = countMetaEvent;
732
 
733
   clipFetchEvents(clipNumber, 0, 0xFFFFFFF);
734
}
735
// -------------------------------------------------------------------------------------------------
736
 
737
 
738
/**
739
 * (Re)load a session clip. It will have changed after recording.
740
 * The clip is also automatically unmuted, if it was muted before and has enough data.
741
 * If no clip was found, the local clipData will be empty
742
 *
743
 */
744
void loadClip(u8 clipNumber)
745
{
746
   clipFileOpen(clipNumber);
747
   clipScan(clipNumber);
748
 
749
   DEBUG_MSG("loadClip(): counted %d events and %d ticks (= %d steps) for clip %d.", clipEventNumber_[clipNumber], clipTicks_[clipNumber], tickToStep(clipTicks_[clipNumber]), clipNumber);
750
 
751
   if (clipEventNumber_[clipNumber] < 3)
752
   {
753
      DEBUG_MSG("loadClip(): disabling clip: not enough events (%d events)", clipEventNumber_[clipNumber]);
754
      clipTicks_[clipNumber] = 0;
755
      clipEventNumber_[clipNumber] = 0;
756
      clipFileAvailable_[clipNumber] = 0;
757
   }
758
 
759
   screenSetClipLength(clipNumber, tickToStep(clipTicks_[clipNumber]));
760
   screenSetClipPosition(clipNumber, 0);
761
 
762
   // unmute available clip
763
   clipMute_[clipNumber] = clipFileAvailable_[clipNumber];
764
   toggleMute(clipNumber);
765
}
766
// -------------------------------------------------------------------------------------------------
767
 
768
 
769
/**
770
 * Load new session data
771
 * also load all stored clips into memory
772
 *
773
 */
774
void loadSession(u16 newSessionNumber)
775
{
776
   sessionNumber_ = newSessionNumber;
777
 
778
   u8 clip;
779
   for (clip = 0; clip < 8; clip++)
780
      loadClip(clip);
781
 
782
   selectedClipNumber_ = 0;
783
   screenSetClipSelected(selectedClipNumber_);
784
   screenFormattedFlashMessage("Loaded Session %d", newSessionNumber);
785
}
786
// -------------------------------------------------------------------------------------------------
787
 
788
 
789
/**
790
 * First callback from app - render Loopa Startup logo on screen
791
 *
792
 */
793
void loopaStartup()
794
{
795
   screenShowLoopaLogo(1);
796
 
797
   // Set up fixed MIDI router
798
   DEBUG_MSG("Setting up MIDI router");
799
 
800
   // Router: IN1 all -> OUT2 all 
801
   midi_router_node_entry_t *n = &midi_router_node[0];
802
   n->src_port = USB0 + ((4&0xc) << 2) + (4&3);;
803
   n->src_chn = 17;
804
   n->dst_port = USB0 + ((5&0xc) << 2) + (5&3);
805
   n->dst_chn = 17;
806
 
807
   // Router: IN2 all -> OUT1 all
808
   n = &midi_router_node[1];
809
   n->src_port = USB0 + ((5&0xc) << 2) + (5&3);;
810
   n->src_chn = 17;
811
   n->dst_port = USB0 + ((4&0xc) << 2) + (4&3);
812
   n->dst_chn = 17;
813
 
814
   // Router: IN1 all -> OUT3 all 
815
   n = &midi_router_node[2];
816
   n->src_port = USB0 + ((4&0xc) << 2) + (4&3);;
817
   n->src_chn = 17;
818
   n->dst_port = USB0 + ((6&0xc) << 2) + (6&3);
819
   n->dst_chn = 17;
820
 
821
   // Router: IN2 all -> OUT1 all
822
   n = &midi_router_node[3];
823
   n->src_port = USB0 + ((6&0xc) << 2) + (6&3);;
824
   n->src_chn = 17;
825
   n->dst_port = USB0 + ((4&0xc) << 2) + (4&3);
826
   n->dst_chn = 17;
827
 
828
 
829
}
830
// -------------------------------------------------------------------------------------------------
831
 
832
 
833
////////////////////////////////////////////////////////////////////////////////////////////////////
834
// LOOPA Sequencer Section
835
////////////////////////////////////////////////////////////////////////////////////////////////////
836
 
837
static s32  seqPlayOffEvents(void);
838
static s32  seqReset(u8 play_off_events);
839
static s32  seqSongPos(u16 new_song_pos);
840
static void seqUpdateBeatLEDs(u32 bpm_tick);
841
static s32  seqTick(u32 bpm_tick);
842
static s32  hookMIDISendPackage(mios32_midi_port_t port, mios32_midi_package_t package);
843
static s32  seqPlayEvent(u8 clipNumber, mios32_midi_package_t midi_package, u32 tick);
844
static s32  seqIgnoreMetaEvent(u8 clipNumber, u8 meta, u32 len, u8 *buffer, u32 tick);
845
 
846
 
847
 
848
/**
849
 * Initialize Loopa SEQ
850
 *
851
 */
852
s32 seqInit()
853
{
854
   // play mode
855
   seqClkLocked = 0;
856
 
857
   // play over USB0 and UART0/1
858
   seqPlayEnabledPorts_ = 0x01 | (0x03 << 4);
859
 
860
   // record over USB0 and UART0/1
861
   seqRecEnabledPorts_ = 0x01 | (0x03 << 4);
862
 
863
   // reset sequencer
864
   seqReset(0);
865
 
866
   // init BPM generator
867
   SEQ_BPM_Init(0);
868
   SEQ_BPM_Set(120.0);
869
 
870
   // scheduler should send packages to private hook
871
   SEQ_MIDI_OUT_Callback_MIDI_SendPackage_Set(hookMIDISendPackage);
872
 
873
   // install seqPlayEvent callback for clipFetchEvents()
874
   clipPlayEventCallback = seqPlayEvent;
875
   clipMetaEventCallback = seqIgnoreMetaEvent;
876
 
877
   return 0; // no error
878
}
879
// -------------------------------------------------------------------------------------------------
880
 
881
 
882
/**
883
 * Get Clock Mode
884
 * adds a fourth mode which locks the BPM so that it can't be modified by the MIDI file
885
 *
886
 */
887
u8 seqClockModeGet(void)
888
{
889
   if (seqClkLocked)
890
      return 3;
891
 
892
   return SEQ_BPM_ModeGet();
893
}
894
// -------------------------------------------------------------------------------------------------
895
 
896
 
897
/**
898
 * Set Clock Mode
899
 * adds a fourth mode which locks the BPM so that it can't be modified by the MIDI file
900
 *
901
 */
902
s32 seqClockModeSet(u8 mode)
903
{
904
  if (mode > 3)
905
     return -1; // invalid mode
906
 
907
  if (mode == 3)
908
  {
909
     SEQ_BPM_ModeSet(SEQ_BPM_MODE_Master);
910
     seqClkLocked = 1;
911
  }
912
  else
913
  {
914
     SEQ_BPM_ModeSet(mode);
915
     seqClkLocked = 0;
916
  }
917
 
918
  return 0; // no error
919
}
920
// -------------------------------------------------------------------------------------------------
921
 
922
 
923
/**
924
 * This main sequencer handler is called periodically to poll the clock/current tick
925
 * from BPM generator
926
 *
927
 */
928
s32 seqHandler(void)
929
{
930
   // handle BPM requests
931
 
932
   // note: don't remove any request check - clocks won't be propagated
933
   // so long any Stop/Cont/Start/SongPos event hasn't been flagged to the sequencer
934
   if (SEQ_BPM_ChkReqStop())
935
   {
936
      seqPlayOffEvents();
937
      MID_FILE_SetRecordMode(0);
938
      MIDI_ROUTER_SendMIDIClockEvent(0xfc, 0);
939
   }
940
 
941
   if (SEQ_BPM_ChkReqCont())
942
   {
943
      MIDI_ROUTER_SendMIDIClockEvent(0xfb, 0);
944
   }
945
 
946
   if (SEQ_BPM_ChkReqStart())
947
   {
948
      MIDI_ROUTER_SendMIDIClockEvent(0xfa, 0);
949
      seqReset(1);
950
      seqSongPos(0);
951
   }
952
 
953
   u16 new_song_pos;
954
   if (SEQ_BPM_ChkReqSongPos(&new_song_pos))
955
   {
956
      seqSongPos(new_song_pos);
957
   }
958
 
959
 
960
   u32 bpm_tick;
961
   if (SEQ_BPM_ChkReqClk(&bpm_tick) > 0)
962
   {
963
      seqUpdateBeatLEDs(bpm_tick);
964
 
965
      // set initial BPM according to MIDI spec
966
      if (bpm_tick == 0 && !seqClkLocked)
967
         SEQ_BPM_Set(120.0);
968
 
969
      if (bpm_tick == 0) // send start (again) to synchronize with new MIDI songs
970
         MIDI_ROUTER_SendMIDIClockEvent(0xfa, 0);
971
 
972
      seqTick(bpm_tick);
973
 
974
      // Perform synchronized recording stop (tracklength a multiple of beatsteps)
975
      if (stopRecordingRequest_)
976
      {
977
         if (tickToStep(bpm_tick) % beatLoopSteps_ == 0)
978
         {
979
            SEQ_BPM_Stop();          // stop sequencer
980
            MIOS32_DOUT_PinSet(led_armrecord, 0);
981
 
982
            screenFormattedFlashMessage("Stopped Recording");
983
            MID_FILE_SetRecordMode(0);
984
            stopRecordingRequest_ = 0;
985
 
986
            loadClip(reloadClipNumberRequest_);
987
            reloadClipNumberRequest_ = -1;
988
         }
989
         else
990
         {
991
            u8 remainSteps = 16 - (tickToStep(bpm_tick) % beatLoopSteps_);
992
            screenFormattedFlashMessage("Stop Recording in %d", remainSteps);
993
         }
994
      }
995
   }
996
 
997
   return 0; // no error
998
}
999
// -------------------------------------------------------------------------------------------------
1000
 
1001
 
1002
/**
1003
 * This function plays all "off" events
1004
 * Should be called on sequencer reset/restart/pause to avoid hanging notes
1005
 *
1006
 */
1007
static s32 seqPlayOffEvents(void)
1008
{
1009
  // play "off events"
1010
  SEQ_MIDI_OUT_FlushQueue();
1011
 
1012
  // send Note Off to all channels
1013
  // TODO: howto handle different ports?
1014
  // TODO: should we also send Note Off events? Or should we trace Note On events and send Off if required?
1015
  int chn;
1016
  mios32_midi_package_t midi_package;
1017
  midi_package.type = CC;
1018
  midi_package.event = CC;
1019
  midi_package.evnt2 = 0;
1020
 
1021
  for (chn=0; chn<16; ++chn)
1022
  {
1023
     midi_package.chn = chn;
1024
     midi_package.evnt1 = 123; // All Notes Off
1025
     hookMIDISendPackage(UART0, midi_package);
1026
     midi_package.evnt1 = 121; // Controller Reset
1027
     hookMIDISendPackage(UART0, midi_package);
1028
  }
1029
 
1030
  return 0; // no error
1031
}
1032
// -------------------------------------------------------------------------------------------------
1033
 
1034
 
1035
/**
1036
 * Reset song position of sequencer
1037
 *
1038
 */
1039
s32 seqReset(u8 play_off_events)
1040
{
1041
   // install seqPlayEvent callback for clipFetchEvents()
1042
   clipPlayEventCallback = seqPlayEvent;
1043
 
1044
   // since timebase has been changed, ensure that Off-Events are played
1045
   // (otherwise they will be played much later...)
1046
   if (play_off_events)
1047
      seqPlayOffEvents();
1048
 
1049
   // release  FFWD mode
1050
   ffwdSilentMode_ = 0;
1051
 
1052
   u8 i;
1053
   for (i = 0; i < 8; i++)
1054
      clipRestart(i);
1055
 
1056
   // set initial BPM (according to MIDI file spec)
1057
   SEQ_BPM_PPQN_Set(384); // not specified
1058
   //SEQ_BPM_Set(120.0);
1059
   // will be done at tick 0 to avoid overwrite in record mode!
1060
 
1061
 
1062
   // reset BPM tick
1063
   SEQ_BPM_TickSet(0);
1064
 
1065
   return 0; // no error
1066
}
1067
// -------------------------------------------------------------------------------------------------
1068
 
1069
 
1070
/**
1071
 * Sets new song position (new_song_pos resolution: 16th notes)
1072
 *
1073
 */
1074
static s32 seqSongPos(u16 new_song_pos)
1075
{
1076
   if (MID_FILE_RecordingEnabled())
1077
      return 0; // nothing to do
1078
 
1079
   u32 new_tick = new_song_pos * (SEQ_BPM_PPQN_Get() / 4);
1080
 
1081
   portENTER_CRITICAL();
1082
 
1083
   // set new tick value
1084
   SEQ_BPM_TickSet(new_tick);
1085
 
1086
   DEBUG_MSG("[SEQ] Setting new song position %u (-> %u ticks)\n", new_song_pos, new_tick);
1087
 
1088
   // since timebase has been changed, ensure that Off-Events are played
1089
   // (otherwise they will be played much later...)
1090
   seqPlayOffEvents();
1091
 
1092
   // restart song
1093
   //MID_PARSER_RestartSong();
1094
 
1095
   // Rewind clips (they have been scanned for length before)
1096
   u8 i;
1097
   for (i = 0; i < 8; i++)
1098
      clipRestart(i);
1099
 
1100
   /* XXX TODO
1101
   if( new_song_pos > 1 )
1102
   {
1103
      // (silently) fast forward to requested position
1104
      ffwdSilentMode = 1;
1105
      MID_PARSER_FetchEvents(0, new_tick-1);
1106
      ffwd_silent_mode = 0;
1107
   }
1108
 
1109
   // when do we expect the next prefetch:
1110
   nextPrefetch_ = new_tick;
1111
   prefetchOffset_ = new_tick;
1112
   */
1113
 
1114
   portEXIT_CRITICAL();
1115
 
1116
   return 0; // no error
1117
}
1118
// -------------------------------------------------------------------------------------------------
1119
 
1120
 
1121
/**
1122
 * Update BEAT LEDs / Voxel space beat line / Clip positions
1123
 *
1124
 */
1125
static void seqUpdateBeatLEDs(u32 bpm_tick)
1126
{
1127
   static u8 lastLEDstate = 255;
1128
 
1129
   u16 ticksPerStep = SEQ_BPM_PPQN_Get() / 4;
1130
 
1131
   u8 beatled = (bpm_tick / ticksPerStep) % 4;
1132
 
1133
   if (beatled != lastLEDstate)
1134
   {
1135
      lastLEDstate = beatled;
1136
 
1137
      switch (beatled)
1138
      {
1139
      case 0:
1140
         MIOS32_DOUT_PinSet(led_beat0, 1);
1141
         MIOS32_DOUT_PinSet(led_beat1, 0);
1142
         MIOS32_DOUT_PinSet(led_beat2, 0);
1143
         MIOS32_DOUT_PinSet(led_beat3, 0);
1144
         voxelTickLine();
1145
         break;
1146
      case 1:
1147
         MIOS32_DOUT_PinSet(led_beat0, 0);
1148
         MIOS32_DOUT_PinSet(led_beat1, 1);
1149
         MIOS32_DOUT_PinSet(led_beat2, 0);
1150
         MIOS32_DOUT_PinSet(led_beat3, 0);
1151
         break;
1152
      case 2:
1153
         MIOS32_DOUT_PinSet(led_beat0, 0);
1154
         MIOS32_DOUT_PinSet(led_beat1, 0);
1155
         MIOS32_DOUT_PinSet(led_beat2, 1);
1156
         MIOS32_DOUT_PinSet(led_beat3, 0);
1157
         break;
1158
      case 3:
1159
         MIOS32_DOUT_PinSet(led_beat0, 0);
1160
         MIOS32_DOUT_PinSet(led_beat1, 0);
1161
         MIOS32_DOUT_PinSet(led_beat2, 0);
1162
         MIOS32_DOUT_PinSet(led_beat3, 1);
1163
         break;
1164
      }
1165
 
1166
      // New step, Update clip positions
1167
      u8 i;
1168
      for (i = 0; i < 8; i++)
1169
      {
1170
         DEBUG_MSG("[SetClipPos] clip: %u bpm_tick: %u clipTicks_[clip]: %u ticksPerStep: %u", i, bpm_tick, clipTicks_[i], ticksPerStep);
1171
         screenSetClipPosition(i, ((u32) (bpm_tick % clipTicks_[i]) / ticksPerStep));
1172
      }
1173
 
1174
      // Set global song step (non-wrapping), e.g. for recording clips
1175
      screenSetSongStep(bpm_tick / ticksPerStep);
1176
   }
1177
}
1178
// -------------------------------------------------------------------------------------------------
1179
 
1180
 
1181
/**
1182
 * Perform a single bpm tick
1183
 *
1184
 */
1185
static s32 seqTick(u32 bpm_tick)
1186
{
1187
   // send MIDI clock depending on ppqn
1188
   if( (bpm_tick % (SEQ_BPM_PPQN_Get()/24)) == 0)
1189
   {
1190
      // DEBUG_MSG("Tick %d, SEQ BPM PPQN/24 %d", bpm_tick, SEQ_BPM_PPQN_Get()/24);
1191
      MIDI_ROUTER_SendMIDIClockEvent(0xf8, bpm_tick);
1192
   }
1193
 
1194
 
1195
   u8 clipNumber;
1196
   for (clipNumber = 0; clipNumber < 8; clipNumber++)
1197
   {
1198
      if (clipFileAvailable_[clipNumber])
1199
      {
1200
         // DEBUG_MSG("clip: %d bpm_tick: %d nextPrefetch: %d", clipNumber, bpm_tick, nextPrefetch_[clipNumber]);
1201
 
1202
         if (bpm_tick >= nextPrefetch_[clipNumber])
1203
         {
1204
            // get number of prefetch ticks depending on current BPM
1205
            u32 prefetch_ticks = SEQ_BPM_TicksFor_mS(PREFETCH_TIME_MS);
1206
 
1207
            if (bpm_tick >= prefetchOffset_[clipNumber])
1208
            {
1209
               DEBUG_MSG("[PREFETCH] BUFFER UNDERRUN");
1210
               // buffer underrun - fetch more!
1211
               prefetch_ticks += (bpm_tick - prefetchOffset_[clipNumber]);
1212
               nextPrefetch_[clipNumber] = bpm_tick; // ASAP
1213
            }
1214
            else if ((prefetchOffset_[clipNumber] - bpm_tick) < prefetch_ticks)
1215
            {
1216
               DEBUG_MSG("[PREFETCH] nearly a buffer underrun");
1217
               // close to a buffer underrun - fetch more!
1218
               prefetch_ticks *= 2;
1219
               nextPrefetch_[clipNumber] = bpm_tick; // ASAP
1220
            }
1221
            else
1222
            {
1223
               nextPrefetch_[clipNumber] += prefetch_ticks;
1224
            }
1225
 
1226
            DEBUG_MSG("[SEQ] Prefetch for clip %d started at tick %u (prefetching %u..%u)\n", clipNumber, bpm_tick, prefetchOffset_[clipNumber], prefetchOffset_[clipNumber]+prefetch_ticks-1);
1227
 
1228
            clipFetchEvents(clipNumber, prefetchOffset_[clipNumber], prefetch_ticks);
1229
            prefetchOffset_[clipNumber] += prefetch_ticks;
1230
 
1231
            DEBUG_MSG("[SEQ] Prefetch for clip %d finished at tick %u\n", clipNumber, SEQ_BPM_TickGet());
1232
         }
1233
      }
1234
   }
1235
 
1236
   return 0; // no error
1237
}
1238
// -------------------------------------------------------------------------------------------------
1239
 
1240
 
1241
/**
1242
 * Schedule a MIDI event to be played at a given tick
1243
 *
1244
 */
1245
static s32 seqPlayEvent(u8 clipNumber, mios32_midi_package_t midi_package, u32 tick)
1246
{
1247
   // DEBUG_MSG("[seqPlayEvent] silent:%d type:%d", ffwdSilentMode_, midi_package.type);
1248
 
1249
   // ignore all events in silent mode (for SEQ_SongPos function)
1250
   // we could implement a more intelligent parser, which stores the sent CC/program change, etc...
1251
   // and sends the last received values before restarting the song...
1252
   if (ffwdSilentMode_)
1253
      return 0;
1254
 
1255
   // In order to support an unlimited SysEx stream length, we pass them as single bytes directly w/o the sequencer!
1256
   if (midi_package.type == 0xf)
1257
   {
1258
      hookMIDISendPackage(clipNumber, midi_package);
1259
      return 0;
1260
   }
1261
 
1262
   seq_midi_out_event_type_t event_type = SEQ_MIDI_OUT_OnEvent;
1263
   if (midi_package.event == NoteOff || (midi_package.event == NoteOn && midi_package.velocity == 0))
1264
      event_type = SEQ_MIDI_OUT_OffEvent;
1265
 
1266
   // output events on "clipNumber" port
1267
   u32 status = 0;
1268
   status |= SEQ_MIDI_OUT_Send(clipNumber, midi_package, event_type, tick, 0);
1269
 
1270
   return status;
1271
}
1272
// -------------------------------------------------------------------------------------------------
1273
 
1274
 
1275
/**
1276
 * Ignore track meta events
1277
 *
1278
 */
1279
static s32 seqIgnoreMetaEvent(u8 clipNumber, u8 meta, u32 len, u8 *buffer, u32 tick)
1280
{
1281
   return 0;
1282
}
1283
// -------------------------------------------------------------------------------------------------
1284
 
1285
 
1286
/**
1287
 * Realtime output hook: called exactly, when the MIDI scheduler has a package to send
1288
 *
1289
 */
1290
static s32 hookMIDISendPackage(mios32_midi_port_t clipNumber, mios32_midi_package_t package)
1291
{
1292
   // XXX: Map clipNumber to configured output port/channel for this clip
1293
 
1294
   // DEBUG_MSG("hook clipNumber %d mute of this channel %d", clipNumber, clipMute_[clipNumber]);
1295
 
1296
   if (clipNumber > 7 || (!clipMute_[clipNumber]))
1297
   {
1298
      // realtime events are already scheduled by MIDI_ROUTER_SendMIDIClockEvent()
1299
      if (package.evnt0 >= 0xf8)
1300
      {
1301
         MIOS32_MIDI_SendPackage(UART0, package);
1302
      }
1303
      else
1304
      {
1305
         // forward to enabled MIDI ports
1306
         int i;
1307
         u16 mask = 1;
1308
 
1309
         for (i=0; i<16; ++i, mask <<= 1)
1310
         {
1311
            if (seqPlayEnabledPorts_ & mask)
1312
            {
1313
               // USB0/1/2/3, UART0/1/2/3, IIC0/1/2/3, OSC0/1/2/3
1314
               mios32_midi_port_t port = USB0 + ((i&0xc) << 2) + (i&3);
1315
               MIOS32_MIDI_SendPackage(port, package);
1316
            }
1317
         }
1318
      }
1319
 
1320
      // Voxelspace note rendering
1321
      if (package.event == NoteOn && package.velocity > 0)
1322
         voxelNoteOn(package.note, package.velocity);
1323
 
1324
      if (package.event == NoteOff || (package.event == NoteOn && package.velocity == 0))
1325
         voxelNoteOff(package.note);
1326
   }
1327
 
1328
   return 0; // no error
1329
}
1330
// -------------------------------------------------------------------------------------------------
1331
 
1332
 
1333
/**
1334
 * Handle a stop request (with beatstep synced recording stop)
1335
 *
1336
 */
1337
void handleStop()
1338
{
1339
   if (!isRecording_)
1340
   {
1341
      screenFormattedFlashMessage("Stopped Playing");
1342
      SEQ_BPM_Stop();          // stop sequencer
1343
 
1344
      MIOS32_DOUT_PinSet(led_startstop, 0);
1345
   }
1346
   else
1347
      stopRecordingRequest_ = 1;
1348
 
1349
   isRecording_ = 0;
1350
}
1351
// -------------------------------------------------------------------------------------------------
1352
 
1353
 
1354
/**
1355
 * Control the play/stop button function
1356
 *
1357
 */
1358
s32 seqPlayStopButton(void)
1359
{
1360
  if (SEQ_BPM_IsRunning())
1361
  {
1362
     handleStop();
1363
  }
1364
  else
1365
  {
1366
     SEQ_BPM_CheckAutoMaster();
1367
 
1368
     // reset sequencer
1369
     seqReset(1);
1370
 
1371
     // start sequencer
1372
     SEQ_BPM_Start();
1373
 
1374
     MIOS32_DOUT_PinSet(led_startstop, 1);
1375
     MIOS32_DOUT_PinSet(led_armrecord, 0);
1376
 
1377
     screenFormattedFlashMessage("Play");
1378
  }
1379
 
1380
  return 0; // no error
1381
}
1382
// -------------------------------------------------------------------------------------------------
1383
 
1384
 
1385
/**
1386
 * To control the rec/stop button function
1387
 *
1388
 */
1389
s32 seqRecStopButton(void)
1390
{
1391
  if( SEQ_BPM_IsRunning() )
1392
  {
1393
     handleStop();
1394
  }
1395
  else
1396
  {
1397
     // if in auto mode and BPM generator is clocked in slave mode:
1398
     // change to master mode
1399
     SEQ_BPM_CheckAutoMaster();
1400
 
1401
     // enter record mode
1402
     if (MID_FILE_SetRecordMode(1) >= 0)
1403
     {
1404
        reloadClipNumberRequest_ = selectedClipNumber_;
1405
        isRecording_ = 1;
1406
 
1407
        // reset sequencer
1408
        seqReset(1);
1409
 
1410
        // start sequencer
1411
        SEQ_BPM_Start();
1412
 
1413
        MIOS32_DOUT_PinSet(led_startstop, 1);
1414
        MIOS32_DOUT_PinSet(led_armrecord, 1);
1415
 
1416
        screenFormattedFlashMessage("Recording Clip %d", selectedClipNumber_ + 1);
1417
     }
1418
  }
1419
 
1420
  return 0; // no error
1421
}
1422
// -------------------------------------------------------------------------------------------------
1423
 
1424
 
1425
/**
1426
 * Fast forward (XXX: not yet implemented)
1427
 *
1428
 */
1429
s32 seqFFwdButton(void)
1430
{
1431
   u32 tick = SEQ_BPM_TickGet();
1432
   u32 ticks_per_step = SEQ_BPM_PPQN_Get() / 4;
1433
   u32 ticks_per_measure = ticks_per_step * 16;
1434
 
1435
   int measure = tick / ticks_per_measure;
1436
   int song_pos = 16 * (measure + 1);
1437
   if (song_pos > 65535)
1438
      song_pos = 65535;
1439
 
1440
   return seqSongPos(song_pos);
1441
}
1442
// -------------------------------------------------------------------------------------------------
1443
 
1444
 
1445
/**
1446
 * Rewind (XXX: not yet implemented)
1447
 *
1448
 */
1449
s32 seqFRewButton(void)
1450
{
1451
   u32 tick = SEQ_BPM_TickGet();
1452
   u32 ticks_per_step = SEQ_BPM_PPQN_Get() / 4;
1453
   u32 ticks_per_measure = ticks_per_step * 16;
1454
 
1455
   int measure = tick / ticks_per_measure;
1456
   int song_pos = 16 * (measure - 1);
1457
   if (song_pos < 0)
1458
      song_pos = 0;
1459
 
1460
   return seqSongPos(song_pos);
1461
}
1462
// -------------------------------------------------------------------------------------------------
1463
 
1464
 
1465
 
1466
 
1467
/**
1468
 * SD Card Available, initialize LOOPA, load session, prepare screen and menus
1469
 *
1470
 */
1471
void loopaSDCardAvailable()
1472
{
1473
   loadSession(1); // -> XXX: load latest session with a free clip slot instead
1474
   seqInit();
1475
   screenShowLoopaLogo(0); // Disable logo, normal operations started
1476
 
1477
}
1478
// -------------------------------------------------------------------------------------------------
1479
 
1480
 
1481
/**
1482
 * A buttonpress has occured
1483
 *
1484
 */
1485
void loopaButtonPressed(s32 pin)
1486
{
1487
   if (pin == sw_startstop)
1488
   {
1489
      voxelClearNotes();
1490
      MIOS32_MIDI_SendDebugMessage("Button: start/stop\n");
1491
      seqPlayStopButton();
1492
   }
1493
   else if (pin == sw_armrecord)
1494
   {
1495
      voxelClearNotes();
1496
      voxelClearField();
1497
      MIOS32_MIDI_SendDebugMessage("Button: arm for record\n");
1498
      seqRecStopButton();
1499
 
1500
   }
1501
   else if (pin == sw_looprange)
1502
   {
1503
      MIOS32_DOUT_PinSet(led_looprange, 1);
1504
      MIOS32_MIDI_SendDebugMessage("Button: loop range\n");
1505
   }
1506
   else if (pin == sw_editclip)
1507
   {
1508
      MIOS32_DOUT_PinSet(led_editclip, 1);
1509
      MIOS32_MIDI_SendDebugMessage("Button: edit clip\n");
1510
   }
1511
   else if (pin == sw_clip1)
1512
   {
1513
      toggleMute(0);
1514
   }
1515
   else if (pin == sw_clip2)
1516
   {
1517
      toggleMute(1);
1518
   }
1519
   else if (pin == sw_clip3)
1520
   {
1521
      toggleMute(2);
1522
   }
1523
   else if (pin == sw_clip4)
1524
   {
1525
      toggleMute(3);
1526
   }
1527
   else if (pin == sw_clip5)
1528
   {
1529
      toggleMute(4);
1530
   }
1531
   else if (pin == sw_clip6)
1532
   {
1533
      toggleMute(5);
1534
   }
1535
   else if (pin == sw_clip7)
1536
   {
1537
      toggleMute(6);
1538
   }
1539
   else if (pin == sw_clip8)
1540
   {
1541
      toggleMute(7);
1542
   }
1543
}
1544
// -------------------------------------------------------------------------------------------------
1545
 
1546
 
1547
/**
1548
 * An encoder has been turned
1549
 *
1550
 */
1551
void loopaEncoderTurned(s32 encoder, s32 incrementer)
1552
{
1553
   if (encoder == enc_clipswitch)
1554
   {
1555
      selectedClipNumber_ += incrementer;
1556
      selectedClipNumber_ %= 8;
1557
      screenSetClipSelected(selectedClipNumber_);
1558
 
1559
      screenFormattedFlashMessage("Clip %d - %d Events", selectedClipNumber_ + 1, clipEventNumber_[selectedClipNumber_]);
1560
   }
1561
}
1562
// -------------------------------------------------------------------------------------------------