Beispiel #1
0
/////////////////////////////////////////////////////////////////////////////
// Set CCs via MIDI (different mapping, especially used by Loopback Feature)
// see also doc/mbseqv4_cc_implementation.txt
/////////////////////////////////////////////////////////////////////////////
s32 SEQ_CC_MIDI_Set(u8 track, u8 cc, u8 value)
{
  if( cc == 0x01 ) { // ModWheel -> Morph Value
    // update screen immediately if in morph page
    if( ui_page == SEQ_UI_PAGE_TRKMORPH )
      seq_ui_display_update_req = 1;
    // forward morph value
    return SEQ_MORPH_ValueSet(value);
  } else if( cc == 0x03 ) {
    seq_core_global_scale = value;
    return 1;
  } else if( cc >= 0x10 && cc <= 0x5f ) {
    u8 mapped_cc = cc+0x20;

    switch( mapped_cc ) {
      case SEQ_CC_LFO_AMPLITUDE:
	value *= 2; // 7bit -> 8bit
	break;
      case SEQ_CC_MIDI_PORT:
	if( value >= 0x70 )
	  value = 0xf0 | (value & 0x0f); // map to Bus
	else if( value >= 0x60 )
	  value = 0x80 | (value & 0x0f); // map to AOUT
	break;
    }

    return SEQ_CC_Set(track, mapped_cc, value); // 0x10..0x5f -> 0x30..0x7f
  }

  return -1; // CC not mapped
}
/////////////////////////////////////////////////////////////////////////////
// Local encoder callback function
// Should return:
//   1 if value has been changed
//   0 if value hasn't been changed
//  -1 if invalid or unsupported encoder
/////////////////////////////////////////////////////////////////////////////
static s32 Encoder_Handler(seq_ui_encoder_t encoder, s32 incrementer)
{
  u8 visible_track = SEQ_UI_VisibleTrackGet();

  switch( encoder ) {
    case SEQ_UI_ENCODER_GP1:
    case SEQ_UI_ENCODER_GP2:
      ui_selected_item = ITEM_GXTY;
      break;

    case SEQ_UI_ENCODER_GP3:
    case SEQ_UI_ENCODER_GP4:
      ui_selected_item = ITEM_REPEATS;
      break;

    case SEQ_UI_ENCODER_GP5:
    case SEQ_UI_ENCODER_GP6:
      ui_selected_item = ITEM_DELAY;
      break;

    case SEQ_UI_ENCODER_GP7:
    case SEQ_UI_ENCODER_GP8:
      ui_selected_item = ITEM_VELOCITY;
      break;

    case SEQ_UI_ENCODER_GP9:
    case SEQ_UI_ENCODER_GP10:
      ui_selected_item = ITEM_FB_VELOCITY;
      break;

    case SEQ_UI_ENCODER_GP11:
    case SEQ_UI_ENCODER_GP12:
      ui_selected_item = ITEM_FB_NOTE;
      break;

    case SEQ_UI_ENCODER_GP13:
    case SEQ_UI_ENCODER_GP14:
      ui_selected_item = ITEM_FB_GATELENGTH;
      break;

    case SEQ_UI_ENCODER_GP15:
    case SEQ_UI_ENCODER_GP16:
      ui_selected_item = ITEM_FB_TICKS;
      break;
  }

  // for GP encoders and Datawheel
  switch( ui_selected_item ) {
  case ITEM_GXTY:          return SEQ_UI_GxTyInc(incrementer);
  case ITEM_REPEATS:       return SEQ_UI_CC_Inc(SEQ_CC_ECHO_REPEATS, 0, 15, incrementer);
  case ITEM_DELAY: {
    // for compatibility with older patches pre Beta30
    u8 value = SEQ_CC_Get(visible_track, SEQ_CC_ECHO_DELAY);
    value = SEQ_CORE_Echo_MapInternalToUser(value);
    if( SEQ_UI_Var8_Inc(&value, 0, 21, incrementer) ) {
      value = SEQ_CORE_Echo_MapUserToInternal(value);
      SEQ_CC_Set(visible_track, SEQ_CC_ECHO_DELAY, value);
      return 1;
    }
    return 0;
  } break;

  case ITEM_VELOCITY:      return SEQ_UI_CC_Inc(SEQ_CC_ECHO_VELOCITY, 0, 40, incrementer);
  case ITEM_FB_VELOCITY:   return SEQ_UI_CC_Inc(SEQ_CC_ECHO_FB_VELOCITY, 0, 40, incrementer);
  case ITEM_FB_NOTE:       return SEQ_UI_CC_Inc(SEQ_CC_ECHO_FB_NOTE, 0, 49, incrementer);
  case ITEM_FB_GATELENGTH: return SEQ_UI_CC_Inc(SEQ_CC_ECHO_FB_GATELENGTH, 0, 40, incrementer);
  case ITEM_FB_TICKS:      return SEQ_UI_CC_Inc(SEQ_CC_ECHO_FB_TICKS, 0, 40, incrementer);
  }

  return -1; // invalid or unsupported encoder
}
Beispiel #3
0
/////////////////////////////////////////////////////////////////////////////
// Local encoder callback function
// Should return:
//   1 if value has been changed
//   0 if value hasn't been changed
//  -1 if invalid or unsupported encoder
/////////////////////////////////////////////////////////////////////////////
static s32 Encoder_Handler(seq_ui_encoder_t encoder, s32 incrementer)
{
  u8 visible_track = SEQ_UI_VisibleTrackGet();

  switch( encoder ) {
    case SEQ_UI_ENCODER_GP1:
      ui_selected_item = ITEM_GXTY;
      break;

    case SEQ_UI_ENCODER_GP2:
    case SEQ_UI_ENCODER_GP3:
    case SEQ_UI_ENCODER_GP4:
      ui_selected_item = ITEM_GROOVE_STYLE;
      break;

    case SEQ_UI_ENCODER_GP5:
    case SEQ_UI_ENCODER_GP6:
      ui_selected_item = ITEM_GROOVE_VALUE;
      break;

    case SEQ_UI_ENCODER_GP7:
    case SEQ_UI_ENCODER_GP8:
      ui_selected_item = ITEM_GROOVE_VALUE_GLB;
      break;

    case SEQ_UI_ENCODER_GP9:
      ui_selected_item = ITEM_GROOVE_STEP;
      break;
      
    case SEQ_UI_ENCODER_GP10:
      ui_selected_item = ITEM_GROOVE_DELAY;
      break;
      
    case SEQ_UI_ENCODER_GP11:
      ui_selected_item = ITEM_GROOVE_LENGTH;
      break;
      
    case SEQ_UI_ENCODER_GP12:
      ui_selected_item = ITEM_GROOVE_VELOCITY;
      break;

    case SEQ_UI_ENCODER_GP13:
      ui_selected_item = ITEM_GROOVE_NUM_STEPS;
      break;

    case SEQ_UI_ENCODER_GP14:
    case SEQ_UI_ENCODER_GP15:
    case SEQ_UI_ENCODER_GP16:
      return -1; // not mapped yet
  }

  // for GP encoders and Datawheel
  u8 grooves_total = SEQ_GROOVE_NUM_PRESETS+SEQ_GROOVE_NUM_TEMPLATES;
  u8 selected_groove = SEQ_CC_Get(visible_track, SEQ_CC_GROOVE_STYLE);
  s32 groove_template = selected_groove - SEQ_GROOVE_NUM_PRESETS; // negative if not a custom template
  seq_groove_entry_t *g;
  if( selected_groove >= SEQ_GROOVE_NUM_PRESETS )
    g = (seq_groove_entry_t *)&seq_groove_templates[selected_groove-SEQ_GROOVE_NUM_PRESETS];
  else
    g = (seq_groove_entry_t *)&seq_groove_presets[selected_groove];

  switch( ui_selected_item ) {
    case ITEM_GXTY:              return SEQ_UI_GxTyInc(incrementer);
    case ITEM_GROOVE_STYLE:      return SEQ_UI_CC_Inc(SEQ_CC_GROOVE_STYLE, 0, grooves_total-1, incrementer);
    case ITEM_GROOVE_VALUE:      return SEQ_UI_CC_Inc(SEQ_CC_GROOVE_VALUE, 0, 127, incrementer);
    case ITEM_GROOVE_STEP:       return SEQ_UI_Var8_Inc(&edit_step, 0, g->num_steps-1, incrementer);

    case ITEM_GROOVE_VALUE_GLB: {
      if( SEQ_UI_CC_Inc(SEQ_CC_GROOVE_VALUE, 0, 127, incrementer) > 0 ) {
	// change value for all tracks
	u8 value = SEQ_CC_Get(visible_track, SEQ_CC_GROOVE_VALUE);
	u8 track;
	for(track=0; track<SEQ_CORE_NUM_TRACKS; ++track) {
	  SEQ_CC_Set(track, SEQ_CC_GROOVE_VALUE, value);
	}
	return 1;
      }
      return 0;
    }

    case ITEM_GROOVE_DELAY: {
      if( groove_template < 0 || groove_template >= SEQ_GROOVE_NUM_TEMPLATES )
	return 0;
      u8 value = (u8)seq_groove_templates[groove_template].add_step_delay[edit_step] + 128;
      if( SEQ_UI_Var8_Inc(&value, 0, 255, incrementer) > 0 ) {
	seq_groove_templates[groove_template].add_step_delay[edit_step] = (s8)(value - 128);
	ui_store_file_required = 1;
	return 1;
      }
      return 0;
    }

    case ITEM_GROOVE_LENGTH: {
      if( groove_template < 0 || groove_template >= SEQ_GROOVE_NUM_TEMPLATES )
	return 0;
      u8 value = (u8)seq_groove_templates[groove_template].add_step_length[edit_step] + 128;
      if( SEQ_UI_Var8_Inc(&value, 0, 255, incrementer) > 0 ) {
	seq_groove_templates[groove_template].add_step_length[edit_step] = (s8)(value - 128);
	ui_store_file_required = 1;
	return 1;
      }
      return 0;
    }

    case ITEM_GROOVE_VELOCITY: {
      if( groove_template < 0 || groove_template >= SEQ_GROOVE_NUM_TEMPLATES )
	return 0;
      u8 value = (u8)seq_groove_templates[groove_template].add_step_velocity[edit_step] + 128;
      if( SEQ_UI_Var8_Inc(&value, 0, 255, incrementer) > 0 ) {
	seq_groove_templates[groove_template].add_step_velocity[edit_step] = (s8)(value - 128);
	ui_store_file_required = 1;
	return 1;
      }
      return 0;
    }

    case ITEM_GROOVE_NUM_STEPS: {
      if( groove_template < 0 || groove_template >= SEQ_GROOVE_NUM_TEMPLATES )
	return 0;
      u8 value = (u8)seq_groove_templates[groove_template].num_steps;
      if( SEQ_UI_Var8_Inc(&value, 1, 16, incrementer) > 0 ) {
	seq_groove_templates[groove_template].num_steps = value;
	ui_store_file_required = 1;
	return 1;
      }
      return 0;
    }

  }

  return -1; // invalid or unsupported encoder
}
Beispiel #4
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;
/////////////////////////////////////////////////////////////////////////////
// Local encoder callback function
// Should return:
//   1 if value has been changed
//   0 if value hasn't been changed
//  -1 if invalid or unsupported encoder
/////////////////////////////////////////////////////////////////////////////
static s32 Encoder_Handler(seq_ui_encoder_t encoder, s32 incrementer)
{
  u8 visible_track = SEQ_UI_VisibleTrackGet();

  switch( encoder ) {
    case SEQ_UI_ENCODER_GP1:
      ui_selected_item = ITEM_GXTY;
      break;

    case SEQ_UI_ENCODER_GP2:
      ui_selected_item = ITEM_WAVEFORM;
      break;

    case SEQ_UI_ENCODER_GP3:
      ui_selected_item = ITEM_AMPLITUDE;
      break;

    case SEQ_UI_ENCODER_GP4:
      ui_selected_item = ITEM_PHASE;
      break;

    case SEQ_UI_ENCODER_GP5:
      ui_selected_item = ITEM_STEPS;
      break;

    case SEQ_UI_ENCODER_GP6:
      ui_selected_item = ITEM_STEPS_RST;
      break;

    case SEQ_UI_ENCODER_GP7:
      ui_selected_item = ITEM_ENABLE_ONE_SHOT;
      break;

    case SEQ_UI_ENCODER_GP8:
      return -1; // not mapped

    case SEQ_UI_ENCODER_GP9:
      ui_selected_item = ITEM_ENABLE_NOTE;
      break;

    case SEQ_UI_ENCODER_GP10:
      ui_selected_item = ITEM_ENABLE_VELOCITY;
      break;

    case SEQ_UI_ENCODER_GP11:
      ui_selected_item = ITEM_ENABLE_LENGTH;
      break;

    case SEQ_UI_ENCODER_GP12:
      ui_selected_item = ITEM_ENABLE_CC;
      break;

    case SEQ_UI_ENCODER_GP13:
      return -1; // not mapped

    case SEQ_UI_ENCODER_GP14:
      // CC number selection now has to be confirmed with GP button
      if( ui_selected_item != ITEM_CC ) {
	edit_cc_number = SEQ_CC_Get(visible_track, SEQ_CC_LFO_CC);
	ui_selected_item = ITEM_CC;
	SEQ_UI_Msg(SEQ_UI_MSG_USER, 2000, "Please confirm CC", "with GP button!");
      } else if( incrementer == 0 ) {
	if( edit_cc_number != SEQ_CC_Get(visible_track, SEQ_CC_LFO_CC) ) {
	  SEQ_CC_Set(visible_track, SEQ_CC_LFO_CC, edit_cc_number);
	  SEQ_UI_Msg(SEQ_UI_MSG_USER, 2000, "CC number", "has been changed.");
	}

	// send event
	mios32_midi_package_t p;
	if( SEQ_LFO_FastCC_Event(visible_track, 0, &p, 1) >= 1 ) {
	  MUTEX_MIDIOUT_TAKE;
	  MIOS32_MIDI_SendPackage(SEQ_CC_Get(visible_track, SEQ_CC_MIDI_PORT), p);
	  MUTEX_MIDIOUT_GIVE;
	}
      }
      break;

    case SEQ_UI_ENCODER_GP15:
      ui_selected_item = ITEM_CC_OFFSET;
      break;

    case SEQ_UI_ENCODER_GP16:
      ui_selected_item = ITEM_CC_PPQN;
      break;
  }

  // for GP encoders and Datawheel
  switch( ui_selected_item ) {
    case ITEM_GXTY:          return SEQ_UI_GxTyInc(incrementer);
    case ITEM_WAVEFORM:      return SEQ_UI_CC_Inc(SEQ_CC_LFO_WAVEFORM, 0, 22, incrementer);
    case ITEM_AMPLITUDE:     return SEQ_UI_CC_Inc(SEQ_CC_LFO_AMPLITUDE, 0, 255, incrementer);
    case ITEM_PHASE:         return SEQ_UI_CC_Inc(SEQ_CC_LFO_PHASE, 0, 99, incrementer);
    case ITEM_STEPS:         return SEQ_UI_CC_Inc(SEQ_CC_LFO_STEPS, 0, 255, incrementer);
    case ITEM_STEPS_RST:     return SEQ_UI_CC_Inc(SEQ_CC_LFO_STEPS_RST, 0, 255, incrementer);
    case ITEM_ENABLE_ONE_SHOT:
    case ITEM_ENABLE_NOTE:
    case ITEM_ENABLE_VELOCITY:
    case ITEM_ENABLE_LENGTH:

    case ITEM_ENABLE_CC: {
      u8 flag = ui_selected_item - ITEM_ENABLE_ONE_SHOT;
      u8 mask = 1 << flag;
      u8 value = SEQ_CC_Get(visible_track, SEQ_CC_LFO_ENABLE_FLAGS);
      if( incrementer == 0 ) // toggle
	SEQ_UI_CC_SetFlags(SEQ_CC_LFO_ENABLE_FLAGS, mask, value ^ mask);
      else if( incrementer > 0 )
	SEQ_UI_CC_SetFlags(SEQ_CC_LFO_ENABLE_FLAGS, mask, mask);
      else
	SEQ_UI_CC_SetFlags(SEQ_CC_LFO_ENABLE_FLAGS, mask, 0);
    } break;

    case ITEM_CC: {
      // CC number selection now has to be confirmed with GP button
      s32 status = SEQ_UI_Var8_Inc(&edit_cc_number, 0, 127, incrementer);
      mios32_midi_port_t port = SEQ_CC_Get(visible_track, SEQ_CC_MIDI_PORT);
      u8 loopback = port == 0xf0;
      if( !edit_cc_number ) {
	SEQ_UI_Msg(SEQ_UI_MSG_USER_R, 1000, "LFO CC", "disabled");
      } else {
	SEQ_UI_Msg(SEQ_UI_MSG_USER_R, 1000, loopback ? "Loopback CC" : "Controller:", (char *)SEQ_CC_LABELS_Get(port, edit_cc_number));
      }
      return status;
    } break;

    case ITEM_CC_OFFSET:     return SEQ_UI_CC_Inc(SEQ_CC_LFO_CC_OFFSET, 0, 127, incrementer);
    case ITEM_CC_PPQN:       return SEQ_UI_CC_Inc(SEQ_CC_LFO_CC_PPQN, 0, 8, incrementer);
  }

  return -1; // invalid or unsupported encoder
}