Subversion Repositories svn.mios32

Rev

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

Rev Author Line No. Line
2576 hawkeye 1
// LoopA Core Logic
2571 hawkeye 2
// (c) Hawkeye 2015-2018
2184 hawkeye 3
//
2571 hawkeye 4
// This unit provides the loopA core data structures and the menu navigation and multi-clip support
2184 hawkeye 5
//
2185 hawkeye 6
// In playback, all clips will start to play back, regardless of their mute state.
2576 hawkeye 7
// Also, all clips will loop by default (it is called LoopA, anyways)
2185 hawkeye 8
// This emulates the behaviour of the MBSEQ.
9
//
10
// =================================================================================================
2184 hawkeye 11
 
2593 hawkeye 12
#include "commonIncludes.h"
2185 hawkeye 13
 
2184 hawkeye 14
#include "loopa.h"
15
#include "hardware.h"
2595 hawkeye 16
#include "ui.h"
2184 hawkeye 17
#include "screen.h"
2576 hawkeye 18
#include "app_lcd.h"
2593 hawkeye 19
#include "voxelspace.h"
2596 hawkeye 20
#include "setup.h"
2184 hawkeye 21
 
22
// --  Local types ---
23
 
24
// --- Global vars ---
25
 
2186 hawkeye 26
static s32 (*clipPlayEventCallback)(u8 clipNumber, mios32_midi_package_t midi_package, u32 tick) = 0;     // fetchClipEvents() callback
27
static s32 (*clipMetaEventCallback)(u8 clipNumber, u8 meta, u32 len, u8 *buffer, u32 tick) = 0; // fetchClipEvents() callback
2185 hawkeye 28
 
2571 hawkeye 29
u32 tick_ = 0;                        // global seq tick
30
u16 bpm_ = 120;                       // bpm
31
u16 sessionNumber_ = 0;               // currently active session number (directory e.g. /SESSIONS/0001)
32
u8 sessionExistsOnDisk_ = 0;          // is the currently selected session number already saved on disk/sd card
2593 hawkeye 33
enum LoopaPage page_ = PAGE_MUTE;     // currently active page/view
2571 hawkeye 34
enum Command command_ = COMMAND_NONE; // currently active command
2184 hawkeye 35
 
2571 hawkeye 36
u8 activeTrack_ = 0;                  // currently active or last active clip number (0..5)
37
u8 activeScene_ = 0;                  // currently active scene number (0-15)
38
u16 beatLoopSteps_ = 16;              // number of steps for one beatloop (adjustable)
39
u8 isRecording_ = 0;                  // set, if currently recording to the selected clip
40
u8 oledBeatFlashState_ = 0;           // 0: don't flash, 1: flash slightly (normal 1/4th note), 2: flash intensively (after four 1/4th notes or 16 steps)
41
 
2185 hawkeye 42
u16 seqRecEnabledPorts_;
2571 hawkeye 43
s16 notePtrsOn_[128];                 // during recording - pointers to notes that are currently "on" (and waiting for an "off", therefore length yet undetermined) (-1: note not depressed)
2185 hawkeye 44
 
2595 hawkeye 45
u8 ffwdSilentMode_;                   // for FFWD function
46
u8 seqClkLocked;                      // lock BPM, so that it can't be changed from MIDI player
47
char filename_[20];                   // global, for filename operations
2184 hawkeye 48
 
2571 hawkeye 49
// --- Track data (saved to session on disk) ---
50
u8 trackMute_[TRACKS];                // mute state of each clip
51
u8 trackMidiPort_[TRACKS];
52
u8 trackMidiChannel_[TRACKS];
2184 hawkeye 53
 
2571 hawkeye 54
// --- Clip data (saved to session on disk) ---
55
u16 clipSteps_[TRACKS][SCENES];       // number of steps for each clip
56
u32 clipQuantize_[TRACKS][SCENES];    // brings all clip notes close to the specified timing, e.g. quantize = 4 - close to 4th notes, 16th = quantize to step, ...
57
s8 clipTranspose_[TRACKS][SCENES];
58
s16 clipScroll_[TRACKS][SCENES];
59
u8 clipStretch_[TRACKS][SCENES];      // 1: compress to 1/16th, 2: compress to 1/8th ... 16: no stretch, 32: expand 2x, 64: expand 4x, 128: expand 8x
60
NoteData clipNotes_[TRACKS][SCENES][MAXNOTES];  // clip note data storage (chained list, note start time and length/velocity)
61
u16 clipNotesSize_[TRACKS][SCENES];   // active number of notes currently in use for that clip
2184 hawkeye 62
 
2571 hawkeye 63
// --- Secondary data (not on disk) ---
64
u8 trackMuteToggleRequested_[TRACKS]; // 1: perform a mute/unmute toggle of the clip at the next beatstep (synced mute/unmute)
65
u8 sceneChangeRequested_ = 0;         // If != activeScene_, this will be the scene we are changing to
66
u16 clipActiveNote_[TRACKS][SCENES];  // currently active edited note number, when in noteroll editor
67
 
2185 hawkeye 68
// =================================================================================================
69
 
70
 
2184 hawkeye 71
/**
72
 * Help function: convert tick number to step number
73
 * @return step
74
 *
75
 */
76
u16 tickToStep(u32 tick)
77
{
2185 hawkeye 78
   return tick / (SEQ_BPM_PPQN_Get() / 4);
2184 hawkeye 79
}
80
// -------------------------------------------------------------------------------------------------
81
 
82
 
83
/**
2571 hawkeye 84
 * Help function: convert step number to tick number
85
 * @return step
2184 hawkeye 86
 *
87
 */
2571 hawkeye 88
u32 stepToTick(u16 step)
2184 hawkeye 89
{
2571 hawkeye 90
   return step * (SEQ_BPM_PPQN_Get() / 4);
2184 hawkeye 91
}
92
// -------------------------------------------------------------------------------------------------
93
 
94
 
95
/**
2571 hawkeye 96
 * Hard bound tick to the configured length (number of steps) of a clip
2184 hawkeye 97
 *
98
 */
2571 hawkeye 99
u32 boundTickToClipSteps(u32 tick, u8 clip)
2184 hawkeye 100
{
2571 hawkeye 101
   return tick % stepToTick(clipSteps_[clip][activeScene_]);
2184 hawkeye 102
}
103
// -------------------------------------------------------------------------------------------------
104
 
105
 
106
/**
2571 hawkeye 107
 * Quantize a "tick" time event to a tick quantization measure (e.g. 384), wrap around
108
 * ticks going over the clip length (clipLengthInTicks)
2184 hawkeye 109
 *
110
 */
2571 hawkeye 111
u32 quantize(u32 tick, u32 quantizeMeasure, u32 clipLengthInTicks)
2184 hawkeye 112
{
2571 hawkeye 113
   if (quantizeMeasure < 3)
114
      return tick; // no quantization
2184 hawkeye 115
 
2571 hawkeye 116
   u32 mod = tick % quantizeMeasure;
117
   u32 tickBase = tick - (tick % quantizeMeasure); // default: snap to previous measure
2184 hawkeye 118
 
2571 hawkeye 119
   // note: i did not like this improvement, as notes were cut off as they were moved newly ahead of the current play position and
120
   // thus were retriggered while still being held on the keyboard, but i fixed it, now not playing notes with length 0 (still being held) ;-)
121
   if (mod > (quantizeMeasure >> 1))
122
      tickBase = (tick - (tick % quantizeMeasure) + quantizeMeasure) % clipLengthInTicks; // snap to next measure as we are closer to this one
2184 hawkeye 123
 
2571 hawkeye 124
   return tickBase;
2184 hawkeye 125
}
126
// -------------------------------------------------------------------------------------------------
127
 
128
 
129
/**
2571 hawkeye 130
 * Transform (stretch and scroll) and then quantize a note in a clip
2184 hawkeye 131
 *
132
 */
2571 hawkeye 133
s32 quantizeTransform(u8 clip, u16 noteNumber)
2184 hawkeye 134
{
135
 
2571 hawkeye 136
   // Idea: scroll first, and modulo-map to trackstart/end boundaries
137
   //       scale afterwards
138
   //       apply track len afterwards
139
   //       drop notes with ticks < 0 and ticks > tracklen
2184 hawkeye 140
 
2571 hawkeye 141
   s32 tick = clipNotes_[clip][activeScene_][noteNumber].tick;
142
   s32 quantizeMeasure = clipQuantize_[clip][activeScene_];
143
   s32 clipLengthInTicks = getClipLengthInTicks(clip);
2184 hawkeye 144
 
2571 hawkeye 145
   // stretch
146
   tick *= clipStretch_[clip][activeScene_];
147
   tick = tick >> 4; // divide by 16 (stretch base)
2184 hawkeye 148
 
2571 hawkeye 149
   // only consider notes, that are within the clip length after stretching
150
   if (tick >= clipLengthInTicks)
151
      return -1;
2184 hawkeye 152
 
2571 hawkeye 153
   // scroll
154
   tick += clipScroll_[clip][activeScene_] * 24;
2184 hawkeye 155
 
2571 hawkeye 156
   while (tick < 0)
157
      tick += clipLengthInTicks;
2184 hawkeye 158
 
2571 hawkeye 159
   tick %= clipLengthInTicks;
160
 
161
   return quantize(tick, quantizeMeasure, clipLengthInTicks);
2184 hawkeye 162
}
163
// -------------------------------------------------------------------------------------------------
164
 
165
 
166
/**
2571 hawkeye 167
 * Get the clip length in ticks
2185 hawkeye 168
 */
2571 hawkeye 169
u32 getClipLengthInTicks(u8 clip)
2185 hawkeye 170
{
2571 hawkeye 171
   return stepToTick(clipSteps_[clip][activeScene_]);
2185 hawkeye 172
}
173
// -------------------------------------------------------------------------------------------------
174
 
175
 
176
/**
2571 hawkeye 177
 * Request (or cancel) a synced mute/unmute toggle
2186 hawkeye 178
 *
179
 */
2571 hawkeye 180
void toggleMute(u8 clipNumber)
2186 hawkeye 181
{
2571 hawkeye 182
   if (trackMute_[clipNumber])
183
      setActiveTrack(clipNumber); // if track was unmuted, set it as active track
2186 hawkeye 184
 
2571 hawkeye 185
   if (SEQ_BPM_IsRunning())
186
      trackMuteToggleRequested_[clipNumber] = !trackMuteToggleRequested_[clipNumber];
187
   else
188
      trackMute_[clipNumber] = !trackMute_[clipNumber];
2186 hawkeye 189
}
190
// -------------------------------------------------------------------------------------------------
191
 
192
 
193
/**
2571 hawkeye 194
 * Synchronized (to beatstep) mutes and unmutes
2184 hawkeye 195
 *
196
 */
2571 hawkeye 197
void performSyncedMutesAndUnmutes()
2184 hawkeye 198
{
2571 hawkeye 199
   u8 clip;
2185 hawkeye 200
 
2571 hawkeye 201
   for (clip = 0; clip < TRACKS; clip++)
2184 hawkeye 202
   {
2571 hawkeye 203
      if (trackMuteToggleRequested_[clip])
2184 hawkeye 204
      {
2571 hawkeye 205
         if (tickToStep(tick_) % beatLoopSteps_ == 0)
2184 hawkeye 206
         {
2571 hawkeye 207
            u8 state = trackMute_[clip];
208
            trackMute_[clip] = !state;
2184 hawkeye 209
 
2571 hawkeye 210
            trackMuteToggleRequested_[clip] = 0;
2184 hawkeye 211
         }
212
      }
2571 hawkeye 213
   }
214
};
215
// -------------------------------------------------------------------------------------------------
2184 hawkeye 216
 
2186 hawkeye 217
 
2571 hawkeye 218
/**
219
 * Synchronized (to beatstep) scene changes
220
 *
221
 */
222
void performSyncedSceneChanges()
223
{
224
   if (sceneChangeRequested_ != activeScene_)
225
   {
226
      u8 sceneChangeInTicks = beatLoopSteps_ - (tickToStep(tick_) % beatLoopSteps_);
2186 hawkeye 227
 
2571 hawkeye 228
      // flash indicate target scene (after synced scene switch)
229
      if (tick_ % 16 < 8)
230
      {
231
         MUTEX_DIGITALOUT_TAKE;
232
         MIOS32_DOUT_PinSet(led_scene1, sceneChangeRequested_ == 0);
233
         MIOS32_DOUT_PinSet(led_scene2, sceneChangeRequested_ == 1);
234
         MIOS32_DOUT_PinSet(led_scene3, sceneChangeRequested_ == 2);
235
         MIOS32_DOUT_PinSet(led_scene4, sceneChangeRequested_ == 3);
236
         MIOS32_DOUT_PinSet(led_scene5, sceneChangeRequested_ == 4);
237
         MIOS32_DOUT_PinSet(led_scene6, sceneChangeRequested_ == 5);
238
         MUTEX_DIGITALOUT_GIVE;
2184 hawkeye 239
      }
240
      else
241
      {
2571 hawkeye 242
         MUTEX_DIGITALOUT_TAKE;
243
         MIOS32_DOUT_PinSet(led_scene1, 0);
244
         MIOS32_DOUT_PinSet(led_scene2, 0);
245
         MIOS32_DOUT_PinSet(led_scene3, 0);
246
         MIOS32_DOUT_PinSet(led_scene4, 0);
247
         MIOS32_DOUT_PinSet(led_scene5, 0);
248
         MIOS32_DOUT_PinSet(led_scene6, 0);
249
         MUTEX_DIGITALOUT_GIVE;
2184 hawkeye 250
      }
251
 
2571 hawkeye 252
      if (tickToStep(tick_) % beatLoopSteps_ == 0)
2184 hawkeye 253
      {
2571 hawkeye 254
         setActiveScene(sceneChangeRequested_);
255
         sceneChangeRequested_ = activeScene_;
256
         sceneChangeInTicks = 0;
2184 hawkeye 257
      }
2571 hawkeye 258
 
259
      screenSetSceneChangeInTicks(sceneChangeInTicks);
2184 hawkeye 260
   }
261
}
262
// -------------------------------------------------------------------------------------------------
263
 
264
 
265
/**
2571 hawkeye 266
 * convert sessionNumber to global filename_
2595 hawkeye 267
 *
2184 hawkeye 268
 */
2595 hawkeye 269
void sessionNumberToFilename(u16 sessionNumber)
2184 hawkeye 270
{
2571 hawkeye 271
   sprintf(filename_, "/SESSIONS/%04d.LPA", sessionNumber);
2184 hawkeye 272
}
273
// -------------------------------------------------------------------------------------------------
274
 
275
 
276
/**
2571 hawkeye 277
 * Save session to defined session number file
2184 hawkeye 278
 *
279
 */
2571 hawkeye 280
void saveSession(u16 sessionNumber)
2184 hawkeye 281
{
2595 hawkeye 282
   sessionNumberToFilename(sessionNumber);
2184 hawkeye 283
 
2571 hawkeye 284
   MUTEX_SDCARD_TAKE;
2185 hawkeye 285
 
2571 hawkeye 286
   s32 status;
287
   if ((status = FILE_WriteOpen(filename_, 1)) < 0)
2185 hawkeye 288
   {
2571 hawkeye 289
      DEBUG_MSG("[FILE] Failed to open/create %s, status: %d\n", filename_, status);
2185 hawkeye 290
   }
2571 hawkeye 291
   else
292
   {
293
      FILE_WriteBuffer((u8*)"LPAV2200", 8);
2185 hawkeye 294
 
2571 hawkeye 295
      status |= FILE_WriteBuffer((u8*)trackMute_, sizeof(trackMute_));
296
      status |= FILE_WriteBuffer((u8*)trackMidiPort_, sizeof(trackMidiPort_));
297
      status |= FILE_WriteBuffer((u8*)trackMidiChannel_, sizeof(trackMidiChannel_));
298
 
299
      status |= FILE_WriteBuffer((u8*)clipSteps_, sizeof(clipSteps_));
300
      status |= FILE_WriteBuffer((u8*)clipQuantize_, sizeof(clipQuantize_));
301
      status |= FILE_WriteBuffer((u8*)clipTranspose_, sizeof(clipTranspose_));
302
      status |= FILE_WriteBuffer((u8*)clipScroll_, sizeof(clipScroll_));
303
      status |= FILE_WriteBuffer((u8*)clipStretch_, sizeof(clipStretch_));
304
 
305
      status |= FILE_WriteBuffer((u8*)clipNotes_, sizeof(clipNotes_));
306
      status |= FILE_WriteBuffer((u8*)clipNotesSize_, sizeof(clipNotesSize_));
307
      status |= FILE_WriteBuffer((u8*)notePtrsOn_, sizeof(notePtrsOn_));
2186 hawkeye 308
 
2571 hawkeye 309
      status |= FILE_WriteClose();
310
   }
311
 
312
   if (status == 0)
313
      screenFormattedFlashMessage("Saved");
314
   else
315
      screenFormattedFlashMessage("Save failed");
316
 
317
   MUTEX_SDCARD_GIVE;
2184 hawkeye 318
}
319
// -------------------------------------------------------------------------------------------------
320
 
321
 
2571 hawkeye 322
 
2184 hawkeye 323
/**
2571 hawkeye 324
 * Load session from defined session number file
2184 hawkeye 325
 *
326
 */
2571 hawkeye 327
void loadSession(u16 sessionNumber)
2184 hawkeye 328
{
2595 hawkeye 329
   sessionNumberToFilename(sessionNumber);
2184 hawkeye 330
 
2571 hawkeye 331
   MUTEX_SDCARD_TAKE;
2185 hawkeye 332
 
2571 hawkeye 333
   s32 status;
334
   file_t file;
335
   if ((status = FILE_ReadOpen(&file, filename_)) < 0)
336
   {
337
      DEBUG_MSG("[FILE] Failed to open %s, status: %d\n", filename_, status);
338
   }
339
   else
340
   {
341
      char version[8];
342
      FILE_ReadBuffer((u8*)version, 8);
343
 
344
      status |= FILE_ReadBuffer((u8*)trackMute_, sizeof(trackMute_));
345
      status |= FILE_ReadBuffer((u8*)trackMidiPort_, sizeof(trackMidiPort_));
346
      status |= FILE_ReadBuffer((u8*)trackMidiChannel_, sizeof(trackMidiChannel_));
347
 
348
      status |= FILE_ReadBuffer((u8*)clipSteps_, sizeof(clipSteps_));
349
      status |= FILE_ReadBuffer((u8*)clipQuantize_, sizeof(clipQuantize_));
350
      status |= FILE_ReadBuffer((u8*)clipTranspose_, sizeof(clipTranspose_));
351
      status |= FILE_ReadBuffer((u8*)clipScroll_, sizeof(clipScroll_));
352
      status |= FILE_ReadBuffer((u8*)clipStretch_, sizeof(clipStretch_));
353
 
354
      status |= FILE_ReadBuffer((u8*)clipNotes_, sizeof(clipNotes_));
355
      status |= FILE_ReadBuffer((u8*)clipNotesSize_, sizeof(clipNotesSize_));
356
      status |= FILE_ReadBuffer((u8*)notePtrsOn_, sizeof(notePtrsOn_));
357
 
358
      status |= FILE_ReadClose(&file);
359
   }
360
 
361
   if (status == 0)
362
      screenFormattedFlashMessage("Loaded");
363
   else
364
      screenFormattedFlashMessage("Load failed");
365
 
366
   MUTEX_SDCARD_GIVE;
2184 hawkeye 367
}
368
// -------------------------------------------------------------------------------------------------
369
 
370
 
371
/**
372
 * First callback from app - render Loopa Startup logo on screen
373
 *
374
 */
375
void loopaStartup()
376
{
377
   screenShowLoopaLogo(1);
378
}
379
// -------------------------------------------------------------------------------------------------
380
 
381
 
382
/**
2185 hawkeye 383
 * Get Clock Mode
384
 * adds a fourth mode which locks the BPM so that it can't be modified by the MIDI file
385
 *
386
 */
2571 hawkeye 387
/*u8 seqClockModeGet(void)
2185 hawkeye 388
{
389
   if (seqClkLocked)
390
      return 3;
391
 
392
   return SEQ_BPM_ModeGet();
393
}
394
// -------------------------------------------------------------------------------------------------
2571 hawkeye 395
*/
2185 hawkeye 396
 
397
/**
398
 * Set Clock Mode
399
 * adds a fourth mode which locks the BPM so that it can't be modified by the MIDI file
400
 *
401
 */
2571 hawkeye 402
/*
2185 hawkeye 403
s32 seqClockModeSet(u8 mode)
404
{
405
  if (mode > 3)
406
     return -1; // invalid mode
407
 
408
  if (mode == 3)
409
  {
410
     SEQ_BPM_ModeSet(SEQ_BPM_MODE_Master);
411
     seqClkLocked = 1;
412
  }
413
  else
414
  {
415
     SEQ_BPM_ModeSet(mode);
416
     seqClkLocked = 0;
417
  }
418
 
419
  return 0; // no error
420
}
421
// -------------------------------------------------------------------------------------------------
2571 hawkeye 422
*/
2185 hawkeye 423
 
424
/**
425
 * This main sequencer handler is called periodically to poll the clock/current tick
426
 * from BPM generator
427
 *
428
 */
429
s32 seqHandler(void)
430
{
431
   // handle BPM requests
432
 
433
   // note: don't remove any request check - clocks won't be propagated
434
   // so long any Stop/Cont/Start/SongPos event hasn't been flagged to the sequencer
435
   if (SEQ_BPM_ChkReqStop())
436
   {
437
      seqPlayOffEvents();
438
      MIDI_ROUTER_SendMIDIClockEvent(0xfc, 0);
439
   }
440
 
2571 hawkeye 441
   /* Hawkeye new - no continuation if (SEQ_BPM_ChkReqCont())
2185 hawkeye 442
   {
443
      MIDI_ROUTER_SendMIDIClockEvent(0xfb, 0);
444
   }
2571 hawkeye 445
   */
2185 hawkeye 446
 
447
   if (SEQ_BPM_ChkReqStart())
448
   {
449
      MIDI_ROUTER_SendMIDIClockEvent(0xfa, 0);
450
      seqReset(1);
451
      seqSongPos(0);
452
   }
453
 
454
   u16 new_song_pos;
455
   if (SEQ_BPM_ChkReqSongPos(&new_song_pos))
456
   {
457
      seqSongPos(new_song_pos);
458
   }
459
 
460
   u32 bpm_tick;
461
   if (SEQ_BPM_ChkReqClk(&bpm_tick) > 0)
462
   {
463
      seqUpdateBeatLEDs(bpm_tick);
2186 hawkeye 464
 
465
      // set initial BPM according to MIDI spec
466
      if (bpm_tick == 0 && !seqClkLocked)
467
         SEQ_BPM_Set(120.0);
468
 
469
      if (bpm_tick == 0) // send start (again) to synchronize with new MIDI songs
470
         MIDI_ROUTER_SendMIDIClockEvent(0xfa, 0);
471
 
472
      seqTick(bpm_tick);
2185 hawkeye 473
   }
474
 
475
   return 0; // no error
476
}
477
// -------------------------------------------------------------------------------------------------
478
 
479
 
480
/**
481
 * This function plays all "off" events
482
 * Should be called on sequencer reset/restart/pause to avoid hanging notes
483
 *
484
 */
2595 hawkeye 485
s32 seqPlayOffEvents(void)
2185 hawkeye 486
{
487
  // play "off events"
488
  SEQ_MIDI_OUT_FlushQueue();
489
 
490
  // send Note Off to all channels
491
  // TODO: howto handle different ports?
492
  // TODO: should we also send Note Off events? Or should we trace Note On events and send Off if required?
493
  int chn;
494
  mios32_midi_package_t midi_package;
495
  midi_package.type = CC;
496
  midi_package.event = CC;
497
  midi_package.evnt2 = 0;
498
 
499
  for (chn=0; chn<16; ++chn)
500
  {
501
     midi_package.chn = chn;
502
     midi_package.evnt1 = 123; // All Notes Off
503
     hookMIDISendPackage(UART0, midi_package);
504
     midi_package.evnt1 = 121; // Controller Reset
505
     hookMIDISendPackage(UART0, midi_package);
506
  }
507
 
508
  return 0; // no error
509
}
510
// -------------------------------------------------------------------------------------------------
511
 
512
 
513
/**
514
 * Reset song position of sequencer
515
 *
516
 */
517
s32 seqReset(u8 play_off_events)
518
{
519
   // install seqPlayEvent callback for clipFetchEvents()
520
   clipPlayEventCallback = seqPlayEvent;
521
 
522
   // since timebase has been changed, ensure that Off-Events are played
523
   // (otherwise they will be played much later...)
524
   if (play_off_events)
525
      seqPlayOffEvents();
526
 
527
   // release  FFWD mode
528
   ffwdSilentMode_ = 0;
529
 
2571 hawkeye 530
   // set pulses per quarter note (internal resolution, 96 is standard)
531
   SEQ_BPM_PPQN_Set(96); // 384
2185 hawkeye 532
 
533
   // reset BPM tick
534
   SEQ_BPM_TickSet(0);
535
 
536
   return 0; // no error
537
}
538
// -------------------------------------------------------------------------------------------------
539
 
540
 
541
/**
542
 * Sets new song position (new_song_pos resolution: 16th notes)
543
 *
544
 */
2595 hawkeye 545
s32 seqSongPos(u16 new_song_pos)
2185 hawkeye 546
{
547
   u32 new_tick = new_song_pos * (SEQ_BPM_PPQN_Get() / 4);
548
 
549
   portENTER_CRITICAL();
550
 
551
   // set new tick value
552
   SEQ_BPM_TickSet(new_tick);
553
 
2571 hawkeye 554
   // DEBUG_MSG("[SEQ] Setting new song position %u (-> %u ticks)\n", new_song_pos, new_tick);
2185 hawkeye 555
 
556
   // since timebase has been changed, ensure that Off-Events are played
557
   // (otherwise they will be played much later...)
558
   seqPlayOffEvents();
559
 
560
   portEXIT_CRITICAL();
561
 
562
   return 0; // no error
563
}
564
// -------------------------------------------------------------------------------------------------
565
 
566
 
567
/**
2571 hawkeye 568
 * Update BEAT LEDs / Clip positions
2185 hawkeye 569
 *
570
 */
2595 hawkeye 571
void seqUpdateBeatLEDs(u32 bpm_tick)
2185 hawkeye 572
{
573
   static u8 lastLEDstate = 255;
574
 
575
   u16 ticksPerStep = SEQ_BPM_PPQN_Get() / 4;
576
   u8 beatled = (bpm_tick / ticksPerStep) % 4;
577
 
578
   if (beatled != lastLEDstate)
579
   {
580
      lastLEDstate = beatled;
581
 
2594 hawkeye 582
      if (!screenIsInMenu() && !screenIsInShift())
2185 hawkeye 583
      {
2594 hawkeye 584
         MUTEX_DIGITALOUT_TAKE;
585
         switch (beatled) {
586
            case 0:
587
               oledBeatFlashState_ = (bpm_tick / (ticksPerStep * 4) % 4 == 0) ? 2
588
                                                                              : 1; // flash background (strong/normal)
589
               MIOS32_DOUT_PinSet(led_beat0, 1);
590
               MIOS32_DOUT_PinSet(led_beat1, 0);
591
               MIOS32_DOUT_PinSet(led_beat2, 0);
592
               MIOS32_DOUT_PinSet(led_beat3, 0);
593
               break;
594
            case 1:
595
               MIOS32_DOUT_PinSet(led_beat0, 0);
596
               MIOS32_DOUT_PinSet(led_beat1, 1);
597
               MIOS32_DOUT_PinSet(led_beat2, 0);
598
               MIOS32_DOUT_PinSet(led_beat3, 0);
599
               break;
600
            case 2:
601
               MIOS32_DOUT_PinSet(led_beat0, 0);
602
               MIOS32_DOUT_PinSet(led_beat1, 0);
603
               MIOS32_DOUT_PinSet(led_beat2, 1);
604
               MIOS32_DOUT_PinSet(led_beat3, 0);
605
               break;
606
            case 3:
607
               MIOS32_DOUT_PinSet(led_beat0, 0);
608
               MIOS32_DOUT_PinSet(led_beat1, 0);
609
               MIOS32_DOUT_PinSet(led_beat2, 0);
610
               MIOS32_DOUT_PinSet(led_beat3, 1);
611
               break;
612
         }
613
         MUTEX_DIGITALOUT_GIVE;
2185 hawkeye 614
      }
615
 
616
      // New step, Update clip positions
617
      u8 i;
2571 hawkeye 618
      for (i = 0; i < TRACKS; i++)
2187 hawkeye 619
      {
2571 hawkeye 620
         screenSetClipPosition(i, ((u32) (bpm_tick / ticksPerStep) % clipSteps_[i][activeScene_]));
2187 hawkeye 621
      }
2185 hawkeye 622
 
623
      // Set global song step (non-wrapping), e.g. for recording clips
624
      screenSetSongStep(bpm_tick / ticksPerStep);
625
   }
626
}
627
// -------------------------------------------------------------------------------------------------
628
 
629
 
630
/**
631
 * Perform a single bpm tick
632
 *
633
 */
2595 hawkeye 634
s32 seqTick(u32 bpm_tick)
2185 hawkeye 635
{
2571 hawkeye 636
   tick_ = bpm_tick;
637
 
2185 hawkeye 638
   // send MIDI clock depending on ppqn
2571 hawkeye 639
   if ((bpm_tick % (SEQ_BPM_PPQN_Get()/24)) == 0)
2185 hawkeye 640
   {
2571 hawkeye 641
      // TODO: Don't send MIDI clock, when receiving external clock!
642
 
2185 hawkeye 643
      // DEBUG_MSG("Tick %d, SEQ BPM PPQN/24 %d", bpm_tick, SEQ_BPM_PPQN_Get()/24);
644
      MIDI_ROUTER_SendMIDIClockEvent(0xf8, bpm_tick);
645
   }
646
 
2571 hawkeye 647
   // perform synced clip mutes/unmutes (only at full steps)
648
   if ((bpm_tick % (SEQ_BPM_PPQN_Get()/4)) == 0)
649
   {
650
      performSyncedMutesAndUnmutes();
651
      performSyncedSceneChanges();
652
   }
2185 hawkeye 653
 
2571 hawkeye 654
   u8 clip;
655
   for (clip = 0; clip < TRACKS; clip++)
2185 hawkeye 656
   {
2571 hawkeye 657
      if (!trackMute_[clip])
2185 hawkeye 658
      {
2571 hawkeye 659
         u32 clipNoteTime = boundTickToClipSteps(bpm_tick, clip);
660
         u16 i;
2185 hawkeye 661
 
2571 hawkeye 662
         for (i=0; i < clipNotesSize_[clip][activeScene_]; i++)
2185 hawkeye 663
         {
2571 hawkeye 664
            if (clipNotes_[clip][activeScene_][i].length > 0) // not still being held/recorded!
2185 hawkeye 665
            {
2571 hawkeye 666
               if (/*quantize(clipNotes_[clip][activeScene_][i].tick,
667
                            clipQuantize_[clip][activeScene_],
668
                            getClipLengthInTicks(clip)) == clipNoteTime */
669
                     quantizeTransform(clip, i) == clipNoteTime)
670
               {
671
                  s16 note = clipNotes_[clip][activeScene_][i].note + clipTranspose_[clip][activeScene_];
672
                  note = note < 0 ? 0 : note;
673
                  note = note > 127 ? 127 : note;
2185 hawkeye 674
 
2571 hawkeye 675
                  mios32_midi_package_t package;
676
                  package.type = NoteOn;
677
                  package.event = NoteOn;
678
                  package.chn = trackMidiChannel_[clip];
679
                  package.note = note;
680
                  package.velocity = clipNotes_[clip][activeScene_][i].velocity;
681
                  hookMIDISendPackage(clip, package); // play NOW
2185 hawkeye 682
 
2571 hawkeye 683
                  package.type = NoteOff;
684
                  package.event = NoteOff;
685
                  package.note = note;
686
                  package.velocity = 0;
687
                  seqPlayEvent(clip, package, bpm_tick + clipNotes_[clip][activeScene_][i].length); // always play off event (schedule later)
688
               }
689
            }
2185 hawkeye 690
         }
691
      }
692
   }
693
 
694
   return 0; // no error
695
}
696
// -------------------------------------------------------------------------------------------------
697
 
698
 
699
/**
700
 * Schedule a MIDI event to be played at a given tick
701
 *
702
 */
2595 hawkeye 703
s32 seqPlayEvent(u8 clipNumber, mios32_midi_package_t midi_package, u32 tick)
2185 hawkeye 704
{
705
   // DEBUG_MSG("[seqPlayEvent] silent:%d type:%d", ffwdSilentMode_, midi_package.type);
706
 
707
   // ignore all events in silent mode (for SEQ_SongPos function)
708
   // we could implement a more intelligent parser, which stores the sent CC/program change, etc...
709
   // and sends the last received values before restarting the song...
710
   if (ffwdSilentMode_)
711
      return 0;
712
 
713
   // In order to support an unlimited SysEx stream length, we pass them as single bytes directly w/o the sequencer!
714
   if (midi_package.type == 0xf)
715
   {
716
      hookMIDISendPackage(clipNumber, midi_package);
717
      return 0;
718
   }
719
 
720
   seq_midi_out_event_type_t event_type = SEQ_MIDI_OUT_OnEvent;
721
   if (midi_package.event == NoteOff || (midi_package.event == NoteOn && midi_package.velocity == 0))
722
      event_type = SEQ_MIDI_OUT_OffEvent;
723
 
2571 hawkeye 724
   // output events on "clipNumber" port (which are then redirected just in time by the SEQ back to hookMIDISendPackage)
2185 hawkeye 725
   u32 status = 0;
726
   status |= SEQ_MIDI_OUT_Send(clipNumber, midi_package, event_type, tick, 0);
727
 
728
   return status;
729
}
730
// -------------------------------------------------------------------------------------------------
731
 
732
 
733
/**
2186 hawkeye 734
 * Ignore track meta events
735
 *
736
 */
2595 hawkeye 737
s32 seqIgnoreMetaEvent(u8 clipNumber, u8 meta, u32 len, u8 *buffer, u32 tick)
2186 hawkeye 738
{
739
   return 0;
740
}
741
// -------------------------------------------------------------------------------------------------
742
 
743
 
744
/**
2185 hawkeye 745
 * Realtime output hook: called exactly, when the MIDI scheduler has a package to send
746
 *
747
 */
2595 hawkeye 748
s32 hookMIDISendPackage(mios32_midi_port_t clipNumber, mios32_midi_package_t package)
2185 hawkeye 749
{
2571 hawkeye 750
   // realtime events are already scheduled by MIDI_ROUTER_SendMIDIClockEvent()
751
   if (package.evnt0 >= 0xf8)
2185 hawkeye 752
   {
2571 hawkeye 753
      MIOS32_MIDI_SendPackage(UART0, package);
754
   }
755
   else
756
   {
757
      mios32_midi_port_t port = trackMidiPort_[clipNumber];
758
      MIOS32_MIDI_SendPackage(port, package);
2185 hawkeye 759
 
2571 hawkeye 760
      // screenFormattedFlashMessage("play %d on %x", package.note, port);
2185 hawkeye 761
 
2571 hawkeye 762
      // DEBUG: SEND TO USB0, too (can be removed)
763
      port = USB0;
764
      MIOS32_MIDI_SendPackage(port, package);
2185 hawkeye 765
   }
766
 
767
   return 0; // no error
768
}
769
// -------------------------------------------------------------------------------------------------
770
 
771
 
772
/**
2571 hawkeye 773
 * Handle a stop request
2186 hawkeye 774
 *
775
 */
776
void handleStop()
777
{
2571 hawkeye 778
   screenFormattedFlashMessage("Stopped");
2186 hawkeye 779
 
2571 hawkeye 780
   MUTEX_DIGITALOUT_TAKE;
2594 hawkeye 781
   MIOS32_DOUT_PinSet(HW_LED_GREEN_RUNSTOP, 0);
782
   MIOS32_DOUT_PinSet(HW_LED_RED_ARM, 0);
2571 hawkeye 783
   MUTEX_DIGITALOUT_GIVE;
2186 hawkeye 784
 
2571 hawkeye 785
   SEQ_BPM_Stop(); // stop sequencer
786
 
787
   // Clear "stuck" notes, that may still be depressed while stop has been hit
788
   u8 i;
789
   for (i=0; i<128; i++)
790
      notePtrsOn_[i] = -1;
791
 
2186 hawkeye 792
   isRecording_ = 0;
793
}
794
// -------------------------------------------------------------------------------------------------
795
 
796
 
2185 hawkeye 797
 
798
/**
2571 hawkeye 799
 * Initialize Loopa SEQ
800
 *
801
 */
802
s32 seqInit()
803
{
804
   // play mode
805
   seqClkLocked = 0;
2185 hawkeye 806
 
2571 hawkeye 807
   // record over USB0 and UART0/1
808
   seqRecEnabledPorts_ = 0x01 | (0x03 << 4);
2185 hawkeye 809
 
2571 hawkeye 810
   // reset sequencer
811
   seqReset(0);
812
 
813
   // init BPM generator
814
   SEQ_BPM_Init(0);
815
   SEQ_BPM_ModeSet(SEQ_BPM_MODE_Auto);
816
   SEQ_BPM_Set(120.0);
817
   bpm_ = 120;
818
 
819
   // scheduler should send packages to private hook
820
   SEQ_MIDI_OUT_Callback_MIDI_SendPackage_Set(hookMIDISendPackage);
821
 
822
   // install seqPlayEvent callback for clipFetchEvents()
823
   clipPlayEventCallback = seqPlayEvent;
824
   clipMetaEventCallback = seqIgnoreMetaEvent;
825
 
826
   u8 i, j;
827
   for (i = 0; i < TRACKS; i++)
828
   {
829
      trackMute_[i] = 0;
830
      trackMidiPort_[i] = 0x20; // UART0 aka OUT1
831
      trackMidiChannel_[i] = 0; // Channel 1
832
      trackMuteToggleRequested_[i] = 0;
833
 
834
      for (j = 0; j < SCENES; j++)
835
      {
836
         clipQuantize_[i][j] = 1;
837
         clipTranspose_[i][j] = 0;
838
         clipScroll_[i][j] = 0;
839
         clipStretch_[i][j] = 16;
840
         clipSteps_[i][j] = 64;
841
         clipNotesSize_[i][j] = 0;
842
         clipActiveNote_[i][j] = 0;
843
      }
844
   }
845
 
846
   for (i=0; i<128; i++)
847
      notePtrsOn_[i] = -1;
848
 
849
   // turn on LEDs for active track, scene and page
850
   setActiveTrack(activeTrack_);
851
   setActiveScene(activeScene_);
852
   setActivePage(page_);
853
 
854
   return 0; // no error
855
}
856
// -------------------------------------------------------------------------------------------------
857
 
858
 
2185 hawkeye 859
/**
2571 hawkeye 860
 * SD Card available, initialize LOOPA, load session, prepare screen and menus
2184 hawkeye 861
 *
862
 */
863
void loopaSDCardAvailable()
864
{
2596 hawkeye 865
   readSetup();
866
 
867
   loadSession(1); // Todo: load last session (stored in setup)
2185 hawkeye 868
   seqInit();
2571 hawkeye 869
   trackMute_[0] = 0;
870
   seqArmButton();
2184 hawkeye 871
   screenShowLoopaLogo(0); // Disable logo, normal operations started
2571 hawkeye 872
}
873
// -------------------------------------------------------------------------------------------------
2185 hawkeye 874
 
2571 hawkeye 875
 
876
/**
877
 * Record midi event
878
 *
879
 */
880
void loopaRecord(mios32_midi_port_t port, mios32_midi_package_t midi_package)
881
{
882
   u16 clipNoteNumber = clipNotesSize_[activeTrack_][activeScene_];
883
 
884
   if (isRecording_ && SEQ_BPM_IsRunning())
885
   {
886
      u32 clipNoteTime = boundTickToClipSteps(tick_, activeTrack_);
887
 
888
      if (clipNoteNumber < MAXNOTES && midi_package.type == NoteOn && midi_package.velocity > 0)
889
      {
890
         clipNotes_[activeTrack_][activeScene_][clipNoteNumber].tick = clipNoteTime;
891
         clipNotes_[activeTrack_][activeScene_][clipNoteNumber].length = 0; // not yet determined
892
         clipNotes_[activeTrack_][activeScene_][clipNoteNumber].note = midi_package.note;
893
         clipNotes_[activeTrack_][activeScene_][clipNoteNumber].velocity = midi_package.velocity;
894
 
895
         if (notePtrsOn_[midi_package.note] >= 0)
896
            screenFormattedFlashMessage("dbg: note alrdy on");
897
 
898
         notePtrsOn_[midi_package.note] = clipNoteNumber;
899
 
900
         // screenFormattedFlashMessage("Note %d on - ptr %d", midi_package.note, clipNoteNumber);
901
         clipNotesSize_[activeTrack_][activeScene_]++;
902
      }
903
      else if (midi_package.type == NoteOff || (midi_package.type == NoteOn && midi_package.velocity == 0))
904
      {
905
         s16 notePtr = notePtrsOn_[midi_package.note];
906
 
907
         if (notePtr >= 0)
908
         {
909
            s32 len = clipNoteTime - clipNotes_[activeTrack_][activeScene_][notePtr].tick;
910
 
911
            if (len == 0)
912
               len = 1;
913
 
914
            if (len < 0)
915
               len += getClipLengthInTicks(activeTrack_);
916
 
917
            // screenFormattedFlashMessage("o %d - p %d - l %d", midi_package.note, notePtr, len);
918
            clipNotes_[activeTrack_][activeScene_][notePtr].length = len;
919
         }
920
         notePtrsOn_[midi_package.note] = -1;
921
      }
922
   }
923
}
2595 hawkeye 924
// -------------------------------------------------------------------------------------------------