Exemplo n.º 1
0
s32 SEQ_TERMINAL_PrintTracks(void *_output_function)
{
  void (*out)(char *format, ...) = _output_function;
  char str_buffer[128];

  MUTEX_MIDIOUT_TAKE;
  out("Track Overview:\n");
  out("===============\n");

  out("| Track | Mode  | Layer P/T/I | Steps P/T | Length | Port  | Chn. | Muted |\n");
  out("+-------+-------+-------------+-----------+--------+-------+------+-------+\n");

  u8 track;
  for(track=0; track<SEQ_CORE_NUM_TRACKS; ++track) {
    seq_event_mode_t event_mode = SEQ_CC_Get(track, SEQ_CC_MIDI_EVENT_MODE);
    u16 num_instruments = SEQ_TRG_NumInstrumentsGet(track);
    u16 num_par_layers = SEQ_PAR_NumLayersGet(track);
    u16 num_par_steps = SEQ_PAR_NumStepsGet(track);
    u16 num_trg_layers = SEQ_TRG_NumLayersGet(track);
    u16 num_trg_steps = SEQ_TRG_NumStepsGet(track);
    u16 length = (u16)SEQ_CC_Get(track, SEQ_CC_LENGTH) + 1;
    mios32_midi_port_t midi_port = SEQ_CC_Get(track, SEQ_CC_MIDI_PORT);
    u8 midi_chn = SEQ_CC_Get(track, SEQ_CC_MIDI_CHANNEL) + 1;

    sprintf(str_buffer, "| G%dT%d  | %s |",
	    (track/4)+1, (track%4)+1,
	    SEQ_LAYER_GetEvntModeName(event_mode));

    sprintf((char *)(str_buffer + strlen(str_buffer)), "   %2d/%2d/%2d  |  %3d/%3d  |   %3d  | %s%c |  %2d  |",
	    num_par_layers, num_trg_layers, num_instruments, 
	    num_par_steps, num_trg_steps,
	    length,
	    SEQ_MIDI_PORT_OutNameGet(SEQ_MIDI_PORT_OutIxGet(midi_port)),
	    SEQ_MIDI_PORT_OutCheckAvailable(midi_port) ? ' ' : '*',
	    midi_chn);

    if( seq_core_trk_muted & (1 << track) )
      sprintf((char *)(str_buffer + strlen(str_buffer)), "  yes  |\n");
    else if( seq_core_trk[track].layer_muted )
      sprintf((char *)(str_buffer + strlen(str_buffer)), " layer |\n");
    else
      sprintf((char *)(str_buffer + strlen(str_buffer)), "  no   |\n");

    out(str_buffer);
  }

  out("+-------+-------+-------------+-----------+--------+-------+------+-------+\n");

  out("done.\n");
  MUTEX_MIDIOUT_GIVE;

  return 0; // no error
}
Exemplo n.º 2
0
/////////////////////////////////////////////////////////////////////////////
// Called from SEQ_MIDI_IN_Receive() if MIDI event has been received on
// matching IN port and channel
/////////////////////////////////////////////////////////////////////////////
s32 SEQ_RECORD_Receive(mios32_midi_package_t midi_package, u8 track)
{
  // step recording mode?
  // Note: if sequencer is not running, "Live Recording" will be handled like "Step Recording"
  u8 step_record_mode = seq_record_options.STEP_RECORD || !SEQ_BPM_IsRunning();

#if MBSEQV4L
  // extra for MBSEQ V4L: seq_record_state.ARMED_TRACKS and auto-assignment
  if( !seq_record_state.ARMED_TRACKS )
    return 0; // no track armed

  track = 0;
  if( seq_record_state.ARMED_TRACKS & 0xff00)
    track = 8;

  // search for free track/layer
  if( (midi_package.event == NoteOn) || (midi_package.event == NoteOff) ) {
    // fine, we will record Note in selected track
  } else if( midi_package.event == PitchBend ) {
    track += 3; // G1T4 resp. G3T4
  } else if( midi_package.event == CC ) {
    const u8 track_layer_cc_table[19][2] = {
      { 4, 0 },
      { 5, 0 },
      { 6, 0 },
      { 7, 0 },
      { 7, 1 }, { 7, 2 }, { 7, 3 },
      { 6, 1 }, { 6, 2 }, { 6, 3 },
      { 5, 1 }, { 5, 2 }, { 5, 3 },
      { 4, 1 }, { 4, 2 }, { 4, 3 },
      { 3, 1 }, { 3, 2 }, { 3, 3 },
    };

    // search for same (or free) CC entry
    // new track/layer search algorithm since V4L.082
    u8 seq_track_offset = track; // depends on sequence
    int par_layer = 0;
    int i;
    u8 free_layer_found = 0;
    for(i=0; i<19 && !free_layer_found; ++i) {
      track = seq_track_offset + track_layer_cc_table[i][0];
      par_layer = track_layer_cc_table[i][1];
      seq_cc_trk_t *tcc = &seq_cc_trk[track];
      u8 *layer_type_ptr = (u8 *)&tcc->lay_const[0*16 + par_layer];
      u8 *layer_cc_ptr = (u8 *)&tcc->lay_const[1*16 + par_layer];

      if( *layer_type_ptr == SEQ_PAR_Type_CC &&
	  (*layer_cc_ptr >= 0x80 || *layer_cc_ptr == midi_package.cc_number) &&
	  (seq_record_state.ARMED_TRACKS & (1 << track)) ) {

	if( *layer_cc_ptr >= 0x80 ) {
	  *layer_cc_ptr = midi_package.cc_number; // assing CC number to free track

	  // initialize whole layer with invalid value 0xc0 (indicates: not recorded)
	  int num_p_steps = SEQ_PAR_NumStepsGet(track);
	  int instrument = 0;
	  int step;
	  for(step=0; step<num_p_steps; ++step)
	    SEQ_PAR_Set(track, step, par_layer, instrument, 0xc0);
#if DEBUG_VERBOSE_LEVEL >= 2
	  DEBUG_MSG("[SEQ_RECORD_Receive] free CC layer found for CC#%d in track #%d.%c\n", midi_package.cc_number, track+1, 'A'+par_layer);
#endif
	}

	free_layer_found = 1;
      }
    }

    if( !free_layer_found ) {
#if DEBUG_VERBOSE_LEVEL >= 2
      DEBUG_MSG("[SEQ_RECORD_Receive] no free CC layer found for CC#%d\n", midi_package.cc_number);
#endif
      return 0; // no free layer
    }
  } else {
    return 0; // event not relevant
  }

  // exit if track not armed
  if( !(seq_record_state.ARMED_TRACKS & (1 << track)) )
    return 0;
#else
  // MBSEQV4 (without L)
  if( midi_package.event == CC && track == SEQ_UI_VisibleTrackGet() ) {
    // search for same (or free) CC entry
    seq_cc_trk_t *tcc = &seq_cc_trk[track];
    u8 free_layer_found = 0;
    {
      u8 num_p_layers = SEQ_PAR_NumLayersGet(track);
      u8 *layer_type_ptr = (u8 *)&tcc->lay_const[0*16];
      u8 *layer_cc_ptr = (u8 *)&tcc->lay_const[1*16];
      int par_layer;
      for(par_layer=0; par_layer<num_p_layers && !free_layer_found; ++par_layer, ++layer_type_ptr, ++layer_cc_ptr) {
	if( *layer_type_ptr == SEQ_PAR_Type_CC &&
	    (*layer_cc_ptr >= 0x80 || *layer_cc_ptr == midi_package.cc_number) ) {

	  if( *layer_cc_ptr >= 0x80 ) {
	    *layer_cc_ptr = midi_package.cc_number; // assing CC number to free track

	    // initialize whole layer with invalid value 0xc0 (indicates: not recorded)
	    int num_p_steps = SEQ_PAR_NumStepsGet(track);
	    int instrument = 0;
	    int step;
	    for(step=0; step<num_p_steps; ++step)
	      SEQ_PAR_Set(track, step, par_layer, instrument, 0xc0);
#if DEBUG_VERBOSE_LEVEL >= 2
	    DEBUG_MSG("[SEQ_RECORD_Receive] free CC layer found for CC#%d in track #%d.%c\n", midi_package.cc_number, track+1, 'A'+par_layer);
#endif
	  }

	  free_layer_found = 1;
	  break;
	}
      }
    }

    if( !free_layer_found ) {
#if DEBUG_VERBOSE_LEVEL >= 2
      DEBUG_MSG("[SEQ_RECORD_Receive] no free CC layer found for CC#%d\n", midi_package.cc_number);
#endif
      return 0; // no free layer
    }
  }
#endif

#if DEBUG_VERBOSE_LEVEL >= 2
  DEBUG_MSG("[SEQ_RECORD_Receive] %02x %02x %02x -> track #%d\n", 
	    midi_package.evnt0, midi_package.evnt1, midi_package.evnt2, 
	    track+1);
#endif

  // exit if track number too high
  if( track >= SEQ_CORE_NUM_TRACKS )
    return -1; // unsupported track

  seq_core_trk_t *t = &seq_core_trk[track];
  seq_cc_trk_t *tcc = &seq_cc_trk[track];

  // branch depending on event
  u8 rec_event = 0;
  u8 send_note_off = 0;
  switch( midi_package.event ) {
    case NoteOff:
    case NoteOn: {
      midi_package.note &= 0x7f; // to avoid array overwrites
      u32 note_mask = 1 << (midi_package.note & 0x1f);

      // if Note Off and new note number matches with recorded note number
      if( midi_package.event == NoteOff || midi_package.velocity == 0 ) {
	if( seq_record_played_notes[midi_package.note>>5] & note_mask ) {
	  MIOS32_IRQ_Disable();
	  // note not active anymore
	  seq_record_played_notes[midi_package.note>>5] &= ~note_mask;

	  // determine duration in mS (for step recording function)
	  u16 duration_ms = MIOS32_TIMESTAMP_Get() - seq_record_note_timestamp_ms[midi_package.note];
	  // map to BPM
	  int duration = (int)((float)duration_ms / ((1000.0*60.0) / SEQ_BPM_EffectiveGet() / (float)SEQ_BPM_PPQN_Get()));
#if DEBUG_VERBOSE_LEVEL >= 3
	  DEBUG_MSG("[SEQ_RECORD_Receive] duration of note 0x%02x was %d mS (%d ticks)\n",
		    midi_package.note, duration_ms, duration);
#endif	  

	  // insert length into current step
	  u8 instrument = 0;
	  int len;
	  if( step_record_mode ) {
	    len = 71; // 75%
	    if( tcc->event_mode != SEQ_EVENT_MODE_Drum )
	      len = (duration <= 96) ? duration : 96; // for duration >= 96 the length will be stretched after record
	  } else {
	    len = SEQ_BPM_TickGet() - t->rec_timestamp;

	    if( len < 1 )
	      len = 1;
	    else if( len > 95 )
	      len = 95;
	  }

	  int len_step = step_record_mode ? ui_selected_step : t->step;
	  u8 num_p_layers = SEQ_PAR_NumLayersGet(track);

	  while( 1 ) {
	    if( tcc->event_mode == SEQ_EVENT_MODE_Combined ) {
	      // extra for MBSEQ V4L:
	      // search for note in track 1/8, insert length into track 3/10
	      int par_layer;
	      for(par_layer=0; par_layer<num_p_layers; ++par_layer) {
		if( SEQ_PAR_Get(track, len_step, par_layer, instrument) == midi_package.note ) {
		  SEQ_PAR_Set(track+2, len_step, par_layer, instrument, len);
		  break;
		}
	      }
	    } else {
	      if( tcc->link_par_layer_length >= 0 )
		SEQ_PAR_Set(track, len_step, tcc->link_par_layer_length, instrument, len);
	    }

	    if( !step_record_mode )
	      break;
	    if( tcc->event_mode == SEQ_EVENT_MODE_Drum )
	      break;
	    if( duration <= 0 )
	      break;
	    duration -= 96;

	    // insert length into all following steps until a gate is set
	    if( ++len_step > tcc->length ) // TODO: handle this correctly if track is played backwards
	      len_step = tcc->loop;

	    if( SEQ_TRG_GateGet(track, len_step, instrument) )
	      break;

	    len = (duration > 0) ? 96 : -duration;

	    // copy notes
	    u8 *layer_type_ptr = (u8 *)&tcc->lay_const[0*16];
	    int par_layer;
	    for(par_layer=0; par_layer<num_p_layers; ++par_layer, ++layer_type_ptr) {
	      if( *layer_type_ptr == SEQ_PAR_Type_Note || *layer_type_ptr == SEQ_PAR_Type_Chord ) {
		u8 note = SEQ_PAR_Get(track, ui_selected_step, par_layer, instrument);
		SEQ_PAR_Set(track, len_step, par_layer, instrument, note);
	      }
	    }
	  }

	  MIOS32_IRQ_Enable();
	}

	if( step_record_mode && seq_record_options.FWD_MIDI ) {
	  // send Note Off events of current track if no key is played anymore
	  u8 any_note_played = seq_record_played_notes[0] || seq_record_played_notes[1] || seq_record_played_notes[2] || seq_record_played_notes[3];
	  if( !any_note_played )
	    send_note_off = 1;     
	}
      } else {
	MIOS32_IRQ_Disable();

	if( step_record_mode && tcc->event_mode != SEQ_EVENT_MODE_Drum ) {
	  // check if another note is already played
	  u8 any_note_played = seq_record_played_notes[0] || seq_record_played_notes[1] || seq_record_played_notes[2] || seq_record_played_notes[3];
	  // if not: clear poly counter and all notes (so that new chord can be entered if all keys were released)
	  if( !any_note_played ) {
	    t->rec_poly_ctr = 0;

	    u8 num_p_layers = SEQ_PAR_NumLayersGet(track);
	    u8 *layer_type_ptr = (u8 *)&tcc->lay_const[0*16];
	    int par_layer;
	    u8 instrument = 0;
	    for(par_layer=0; par_layer<num_p_layers; ++par_layer, ++layer_type_ptr) {
	      if( *layer_type_ptr == SEQ_PAR_Type_Note || *layer_type_ptr == SEQ_PAR_Type_Chord )
		SEQ_PAR_Set(track, ui_selected_step, par_layer, instrument, 0x00);
	    }
	  }
	}

	// note is active
	seq_record_played_notes[midi_package.note>>5] |= note_mask;
	// start measuring length
	t->rec_timestamp = SEQ_BPM_TickGet();
	// for step record function: independent from BPM
	seq_record_note_timestamp_ms[midi_package.note & 0x7f] = MIOS32_TIMESTAMP_Get(); // note: 16bit only

	MIOS32_IRQ_Enable();

	// record event
	rec_event = 1;
      }
    } break;

    case CC:
    case PitchBend: {
      rec_event = 1;
    } break;

    default: {
#if DEBUG_VERBOSE_LEVEL >= 2
      DEBUG_MSG("[SEQ_RECORD_Receive] event %x not supported.\n", midi_package.event);
#endif
      return -2; // unsupported event
    }
  }
Exemplo n.º 3
0
/////////////////////////////////////////////////////////////////////////////
// Called when a GP button has been toggled
/////////////////////////////////////////////////////////////////////////////
s32 SEQ_UI_PAGES_GP_Button_Handler(u8 button, u8 depressed)
{
  if( ui_controller_mode )
    return SEQ_UI_PAGES_GP_Button_Handler_Controller(button, depressed);

  // no page reacts on depressed buttons
  if( depressed )
    return 0;

  // clear button pressed: clear track
  if( seq_ui_button_state.CLEAR ) {
    u8 seq = (button >= 8) ? 1 : 0;
    u8 track_offset = seq ? 8 : 0;
    ui_selected_tracks = seq ? 0xff00 : 0x00ff;
    seq_record_state.ARMED_TRACKS = seq ? 0xff00 : 0x00ff;

    u8 seq_button = button % 8;
    switch( seq_button ) {
    case 0: { // clear note triggers (values are kept but not played)
      u8 track = track_offset;
      int num_t_steps = SEQ_TRG_NumStepsGet(track);
      int step8;
      u8 instrument = 0;
      u8 layer = 0;
      for(step8=0; step8<(num_t_steps/8); ++step8)
	SEQ_TRG_Set8(track, step8, layer, instrument, 0x00);
    } break;

    case 1: { // reset velocity values to 64
      u8 track = track_offset + 1;
      int num_p_layers = SEQ_PAR_NumLayersGet(track);
      int par_layer;
      for(par_layer=0; par_layer<num_p_layers; ++par_layer) {
	int num_p_instruments = SEQ_PAR_NumInstrumentsGet(track);
	int num_p_steps  = SEQ_PAR_NumStepsGet(track);;
	u8 init_value = 64;

	int step;
	int instrument;
	for(instrument=0; instrument<num_p_instruments; ++instrument)
	  for(step=0; step<num_p_steps; ++step)
	    SEQ_PAR_Set(track, step, par_layer, instrument, init_value);
      }
    } break;

    case 2: { // reset note length values to half step
      u8 track = track_offset + 2;
      int num_p_layers = SEQ_PAR_NumLayersGet(track);
      int par_layer;
      for(par_layer=0; par_layer<num_p_layers; ++par_layer) {
	int num_p_instruments = SEQ_PAR_NumInstrumentsGet(track);
	int num_p_steps  = SEQ_PAR_NumStepsGet(track);;
	u8 init_value = 51; // half length

	int step;
	int instrument;
	for(instrument=0; instrument<num_p_instruments; ++instrument)
	  for(step=0; step<num_p_steps; ++step)
	    SEQ_PAR_Set(track, step, par_layer, instrument, init_value);
      }
    } break;

    default: {
      u8 track = track_offset + seq_button;

      if( seq_button == 3 ) { // special treatmend for pitchbend
	int par_layer = 0;
	{
	  int num_p_instruments = SEQ_PAR_NumInstrumentsGet(track);
	  int num_p_steps  = SEQ_PAR_NumStepsGet(track);;
	  u8 init_value = 64;

	  int step;
	  int instrument;
	  for(instrument=0; instrument<num_p_instruments; ++instrument)
	    for(step=0; step<num_p_steps; ++step)
	      SEQ_PAR_Set(track, step, par_layer, instrument, init_value);
	}
	// send pitchbend 0 for proper reset of MIDI device
	SEQ_LAYER_DirectSendEvent(track, par_layer);
      }

      // clear CCs
      {
	int num_p_layers = SEQ_PAR_NumLayersGet(track);
	int par_layer;
	for(par_layer=(track == 3) ? 1 : 0; par_layer<num_p_layers; ++par_layer) { // don't touch pitchbender
	  int num_p_instruments = SEQ_PAR_NumInstrumentsGet(track);
	  int num_p_steps  = SEQ_PAR_NumStepsGet(track);;
	  u8 init_value = 64;

	  int step;
	  int instrument;
	  for(instrument=0; instrument<num_p_instruments; ++instrument)
	    for(step=0; step<num_p_steps; ++step)
	      SEQ_PAR_Set(track, step, par_layer, instrument, init_value);

	  // disable CC assignment
	  SEQ_CC_Set(track, SEQ_CC_LAY_CONST_B1+par_layer, 0x80);
	}
      }
    }
    }

    return 0;
  }

  switch( ui_page ) {

  ///////////////////////////////////////////////////////////////////////////
  case SEQ_UI_PAGE_LOAD:
  case SEQ_UI_PAGE_SAVE: {
    // not atomic so that files can be stored in background
    //portENTER_CRITICAL();

    u8 group;
    for(group=0; group<SEQ_CORE_NUM_GROUPS; ++group) {
      seq_pattern_t *pattern = &ui_selected_pattern[group];

      pattern->bank = group; // always same as group
      if( button < 8 ) {
	pattern->group = button;
	ui_selected_pattern_changing = 1;
      } else {
	pattern->num = button-8;
	ui_selected_pattern_changing = 0;

	if( ui_page == SEQ_UI_PAGE_SAVE ) {
	  //DEBUG_MSG("BEGIN Save %d:%c%d\n", pattern->bank+1, 'A'+pattern->group, pattern->num+1);
	  s32 status = 0;
	  if( (status=SEQ_PATTERN_Save(group, *pattern)) < 0 )
	    SEQ_UI_SDCardErrMsg(2000, status);
	  else
	    load_save_notifier_ctr = 300; // notify about save operation for 300 mS
	  //DEBUG_MSG("END   Save %d:%c%d\n", pattern->bank+1, 'A'+pattern->group, pattern->num+1);
	} else {
	  //DEBUG_MSG("BEGIN Load %d:%c%d\n", pattern->bank+1, 'A'+pattern->group, pattern->num+1);
	  SEQ_PATTERN_Change(group, *pattern, 0);
	  load_save_notifier_ctr = 300; // notify about load operation for 300 mS
	  //DEBUG_MSG("END   Load %d:%c%d\n", pattern->bank+1, 'A'+pattern->group, pattern->num+1);
	}
      }
    }

    //portEXIT_CRITICAL();
    return 0;
  } break;

  ///////////////////////////////////////////////////////////////////////////
  case SEQ_UI_PAGE_TRIGGER: {
    // should be atomic
    portENTER_CRITICAL();

    ui_selected_step = button;

    u8 track; // only change triggers if track 0 and 8
    for(track=0; track<SEQ_CORE_NUM_TRACKS; track+=8)
      if( ui_selected_tracks & (1 << track) ) {
	u8 *trg_ptr = (u8 *)&seq_trg_layer_value[track][2*ui_selected_step_view + (button>>3)];
	*trg_ptr ^= (1 << (button&7));
      }

    portEXIT_CRITICAL();
    return 0;
  } break;

  ///////////////////////////////////////////////////////////////////////////
  case SEQ_UI_PAGE_LENGTH: {
    // should be atomic
    portENTER_CRITICAL();

    u8 track;
    for(track=0; track<SEQ_CORE_NUM_TRACKS; ++track) {
      if( ui_selected_tracks & (1 << track) ) {
	if( seq_ui_button_state.LENGTH_PRESSED ) {
	  if( (track >= 0 && track <= 2) || (track >= 8 && track <= 10) )
	    SEQ_CC_Set(track, SEQ_CC_LOOP, 16*ui_selected_step_view + button);
	  else
	    SEQ_CC_Set(track, SEQ_CC_LOOP, 64*ui_selected_step_view + 4*button + 3); // 4x resolution
	} else {
	  if( (track >= 0 && track <= 2) || (track >= 8 && track <= 10) )
	    SEQ_CC_Set(track, SEQ_CC_LENGTH, 16*ui_selected_step_view + button);
	  else
	    SEQ_CC_Set(track, SEQ_CC_LENGTH, 64*ui_selected_step_view + 4*button + 3); // 4x resolution
	}
      }
    }

    portEXIT_CRITICAL();
    return 0;
  } break;

  ///////////////////////////////////////////////////////////////////////////
  case SEQ_UI_PAGE_PROGRESSION: {
    // should be atomic
    portENTER_CRITICAL();

    ui_selected_progression_preset = button;
    seq_ui_pages_progression_presets_t *preset = (seq_ui_pages_progression_presets_t *)&seq_ui_pages_progression_presets[ui_selected_progression_preset];
    u8 track;
    for(track=0; track<SEQ_CORE_NUM_TRACKS; track+=8) {
      if( ui_selected_tracks & (1 << track) ) {
	SEQ_CC_Set(track, SEQ_CC_STEPS_FORWARD, (preset->steps_forward > 0) ? (preset->steps_forward-1) : 0);
	SEQ_CC_Set(track, SEQ_CC_STEPS_JMPBCK, preset->steps_jump_back);
	SEQ_CC_Set(track, SEQ_CC_STEPS_REPLAY, preset->steps_replay);
	SEQ_CC_Set(track, SEQ_CC_STEPS_REPEAT, preset->steps_repeat);
	SEQ_CC_Set(track, SEQ_CC_STEPS_SKIP, preset->steps_skip);
	SEQ_CC_Set(track, SEQ_CC_STEPS_RS_INTERVAL, preset->steps_rs_interval);
      }
    }

    // GP1 also requests synch to measure
    // it's a good idea to do this for all tracks so that both sequences are in synch again
    if( button == 0 )
      SEQ_CORE_ManualSynchToMeasure(0xffff);

    portEXIT_CRITICAL();
    return 0;
  } break;

  ///////////////////////////////////////////////////////////////////////////
  case SEQ_UI_PAGE_GROOVE: {
    // should be atomic
    portENTER_CRITICAL();

    // first button turns off groove function
    // remaining buttons select custom groove #1..15
    u8 groove_style = 0;
    if( button > 0 ) // note: starting at second custom groove, the first groove is always "off"
      groove_style = button+8-1;
    SEQ_UI_CC_Set(SEQ_CC_GROOVE_STYLE, groove_style);
    portEXIT_CRITICAL();
    return 0;
  } break;

  ///////////////////////////////////////////////////////////////////////////
  case SEQ_UI_PAGE_ECHO: {
    // should be atomic
    portENTER_CRITICAL();

    ui_selected_echo_preset = button;
    seq_ui_pages_echo_presets_t *preset = (seq_ui_pages_echo_presets_t *)&seq_ui_pages_echo_presets[ui_selected_echo_preset];
    u8 track;
    for(track=0; track<SEQ_CORE_NUM_TRACKS; track+=8) {
      if( ui_selected_tracks & (1 << track) ) {
	SEQ_CC_Set(track, SEQ_CC_ECHO_REPEATS, preset->repeats);
	SEQ_CC_Set(track, SEQ_CC_ECHO_DELAY, preset->delay);
	SEQ_CC_Set(track, SEQ_CC_ECHO_VELOCITY, preset->velocity);
	SEQ_CC_Set(track, SEQ_CC_ECHO_FB_VELOCITY, preset->fb_velocity);
	SEQ_CC_Set(track, SEQ_CC_ECHO_FB_NOTE, preset->fb_note);
	SEQ_CC_Set(track, SEQ_CC_ECHO_FB_GATELENGTH, preset->fb_gatelength);
	SEQ_CC_Set(track, SEQ_CC_ECHO_FB_TICKS, preset->fb_ticks);
      }
    }

    portEXIT_CRITICAL();
  } break;

  ///////////////////////////////////////////////////////////////////////////
  case SEQ_UI_PAGE_HUMANIZER: {
    // should be atomic
    portENTER_CRITICAL();

    ui_selected_humanizer_preset = button;
    seq_ui_pages_humanizer_presets_t *preset = (seq_ui_pages_humanizer_presets_t *)&seq_ui_pages_humanizer_presets[ui_selected_humanizer_preset];
    u8 track;
    for(track=0; track<SEQ_CORE_NUM_TRACKS; track+=8) {
      if( ui_selected_tracks & (1 << track) ) {
	SEQ_CC_Set(track, SEQ_CC_HUMANIZE_MODE, preset->mode);
	SEQ_CC_Set(track, SEQ_CC_HUMANIZE_VALUE, preset->value);
      }
    }

    portEXIT_CRITICAL();
    return 0;
  } break;

  ///////////////////////////////////////////////////////////////////////////
  case SEQ_UI_PAGE_LFO: {
    // should be atomic
    portENTER_CRITICAL();

    ui_selected_lfo_preset = button;
    seq_ui_pages_lfo_presets_t *preset = (seq_ui_pages_lfo_presets_t *)&seq_ui_pages_lfo_presets[ui_selected_lfo_preset];
    u8 track;
    for(track=0; track<SEQ_CORE_NUM_TRACKS; track+=8) {
      if( ui_selected_tracks & (1 << track) ) {
	SEQ_CC_Set(track, SEQ_CC_LFO_WAVEFORM, preset->waveform);
	SEQ_CC_Set(track, SEQ_CC_LFO_AMPLITUDE, preset->amplitude);
	SEQ_CC_Set(track, SEQ_CC_LFO_PHASE, preset->phase);
	SEQ_CC_Set(track, SEQ_CC_LFO_STEPS, preset->steps);
	SEQ_CC_Set(track, SEQ_CC_LFO_STEPS_RST, preset->steps_rst);
	SEQ_CC_Set(track, SEQ_CC_LFO_ENABLE_FLAGS, preset->enable_flags);
	SEQ_CC_Set(track, SEQ_CC_LFO_CC, preset->cc);
	SEQ_CC_Set(track, SEQ_CC_LFO_CC_OFFSET, preset->cc_offset);
	SEQ_CC_Set(track, SEQ_CC_LFO_CC_PPQN, preset->cc_ppqn);
      }
    }

    portEXIT_CRITICAL();
    return 0;
  } break;

  ///////////////////////////////////////////////////////////////////////////
  case SEQ_UI_PAGE_SCALE: {
    // should be atomic
    portENTER_CRITICAL();

    // if SCALE button not pressed: select root key
    if( !seq_ui_button_state.SCALE_PRESSED ) {
      if( button < 12 ) {
	seq_core_global_scale_root_selection = button + 1;
      } else {
	seq_core_global_scale_root_selection = 0; // via keyboard
      }
    } else {
      // if pressed: select scale

      ui_selected_scale = button;

      if( ui_selected_scale == 0 ) {
	// disable force-to-scale for both note tracks (makes sense, since the scale itself is global as well)
	u8 track;
	for(track=0; track<SEQ_CORE_NUM_TRACKS; track+=8)
	  seq_cc_trk[track].mode.FORCE_SCALE = 0;
      } else {
	// select scale
	seq_core_global_scale = seq_ui_pages_scale_presets[ui_selected_scale];

	// enable force-to-scale for both note tracks (makes sense, since the scale itself is global as well)
	u8 track;
	for(track=0; track<SEQ_CORE_NUM_TRACKS; track+=8)
	  seq_cc_trk[track].mode.FORCE_SCALE = 1;
      }
    }

    portEXIT_CRITICAL();
    return 0;
  } break;

  ///////////////////////////////////////////////////////////////////////////
  case SEQ_UI_PAGE_MUTE: {
    // should be atomic
    portENTER_CRITICAL();

    seq_core_trk_muted ^= (1 << button);

    portEXIT_CRITICAL();
    return 0;
  } break;

  ///////////////////////////////////////////////////////////////////////////
  case SEQ_UI_PAGE_MIDICHN: {
    // should be atomic
    portENTER_CRITICAL();

    u8 track;
    for(track=0; track<SEQ_CORE_NUM_TRACKS; ++track)
      if( ui_selected_tracks & (1 << track) )
	SEQ_CC_Set(track, SEQ_CC_MIDI_CHANNEL, button);

    portEXIT_CRITICAL();
    return 0;
  } break;

  ///////////////////////////////////////////////////////////////////////////
  case SEQ_UI_PAGE_REC_ARM: {
    // should be atomic
    portENTER_CRITICAL();

    // allow to select an unplayed track
    if( button >= 8 && (seq_record_state.ARMED_TRACKS & 0x00ff) )
      seq_record_state.ARMED_TRACKS = 0xff00;
    else if( button < 8 && (seq_record_state.ARMED_TRACKS & 0xff00) )
      seq_record_state.ARMED_TRACKS = 0x00ff;
    else {
      seq_record_state.ARMED_TRACKS ^= (1 << button);
    }

    portEXIT_CRITICAL();
    return 0;
  } break;

  ///////////////////////////////////////////////////////////////////////////
  case SEQ_UI_PAGE_REC_STEP: {
  case SEQ_UI_PAGE_REC_LIVE:
    // should be atomic
    portENTER_CRITICAL();

    seq_record_step = 16*ui_selected_step_view + button;

    // we will always clear the current step for more comfortable handling
    // (no need to select the Trigger page for doing this)
    u8 track; // only change triggers if track 0 and 8
    for(track=0; track<SEQ_CORE_NUM_TRACKS; track+=8)
      if( seq_record_state.ARMED_TRACKS & (1 << track) ) {
	u8 *trg_ptr = (u8 *)&seq_trg_layer_value[track][2*ui_selected_step_view + (button>>3)];
	*trg_ptr &= ~(1 << (button&7));

	SEQ_RECORD_Reset(track);
      }

    portEXIT_CRITICAL();
    return 0;
  } break;