예제 #1
0
/////////////////////////////////////////////////////////////////////////////
// Called by MIDI Tx Notificaton hook if a MIDI event should be sent
// Allows to provide additional MIDI ports
// If 1 is returned, package will be filtered!
/////////////////////////////////////////////////////////////////////////////
s32 SEQ_MIDI_PORT_NotifyMIDITx(mios32_midi_port_t port, mios32_midi_package_t package)
{
  // MIDI Out monitor function
  u8 mon_filtered = 0;
  if( seq_midi_port_mon_filter.MIDI_CLOCK && package.evnt0 == 0xf8 )
    mon_filtered = 1;
  else if( seq_midi_port_mon_filter.ACTIVE_SENSE && package.evnt0 == 0xfe )
    mon_filtered = 1;

  if( !mon_filtered ) {
    int port_ix = -1;
    if( port != DEFAULT ) {
      port_ix = SEQ_MIDI_PORT_OutIxGet(port);

      if( !port_ix )
	port_ix = -1; // port not mapped
    }

    if( port_ix >= 0 ) {
      midi_out_package[port_ix] = package;
      midi_out_ctr[port_ix] = 20; // 2 seconds lifetime
      seq_midi_port_out_combined_ctr = 5; // 500 mS lifetime
    }
  }


  // DIN Sync Event (0xf9 sent over port 0xff)
  if( port == 0xff && package.evnt0 == 0xf9 ) {
    SEQ_CV_Clk_Trigger(package.evnt1); // second byte contains clock number (see also 0xf9 generation in seq_core)
    return 1; // filter package
  }

  if( (port & 0xf0) == OSC0 ) { // OSC1..4 port
    // avoid OSC feedback in seq_live.c (can cause infinite loops or stack overflows)
    if( filter_osc_packets || OSC_CLIENT_SendMIDIEvent(port & 0xf, package) >= 0 )
      return 1; // filter package
  } else if( port == 0x80 ) { // AOUT port
    if( SEQ_CV_SendPackage(port & 0xf, package) )
      return 1; // filter package
  } else if( port == 0xc0 ) { // Multi OUT port
    int i;
    u32 mask = 1;
    for(i=0; i<16; ++i, mask <<= 1) {
      if( seq_midi_port_multi_enable_flags & mask ) {
	// USB0/1/2/3, UART0/1/2/3, IIC0/1/2/3, OSC0/1/2/3
	mios32_midi_port_t port = 0x10 + ((i&0xc) << 2) + (i&3);
	if( port != seq_blm_port ) // ensure that no note will be sent to BLM port if enabled
	  MIOS32_MIDI_SendPackage(port, package);
      }
    }

    if( seq_midi_port_multi_enable_flags & (1 << 16) ) {
      MIOS32_MIDI_SendPackage(0x80, package); // AOUT
    }

    return 1; // filter forwarding
  }

  return 0; // don't filter package
}
예제 #2
0
/////////////////////////////////////////////////////////////////////////////
// Send a message to KissBox
/////////////////////////////////////////////////////////////////////////////
s32 TERMINAL_KissboxSendMsg(char *msg)
{
    mios32_midi_package_t p;
    p.ALL = 0;
    p.type = 0x1; // reserved in USB MIDI spec, used to send strings to KissBox

    int len;
    for(len=strlen(msg); len > 0; len-=3, msg+=3) {
        p.evnt0 = (len >= 1) ? msg[0] : 0;
        p.evnt1 = (len >= 2) ? msg[1] : 0;
        p.evnt2 = (len >= 3) ? msg[2] : 0;

#if 0
        MIOS32_MIDI_SendDebugMessage("[KISSBOX_MSG] 0x%08x\n", p.ALL);
#endif
        MIOS32_MIDI_SendPackage(SPIM0, p);
    }

    // special case: message length is dividable by 3: we need to send an extra package with 0 to terminate the message
    if( p.evnt2 ) {
        p.evnt0 = 0;
        p.evnt1 = 0;
        p.evnt2 = 0;

#if 0
        MIOS32_MIDI_SendDebugMessage("[KISSBOX_MSG] 0x%08x\n", p.ALL);
#endif
        MIOS32_MIDI_SendPackage(SPIM0, p);
    }

    return 0; // no error
}
예제 #3
0
파일: app.c 프로젝트: glocklueng/MIOS32
/////////////////////////////////////////////////////////////////////////////
// This hook is called when a MIDI package has been received
/////////////////////////////////////////////////////////////////////////////
void APP_MIDI_NotifyPackage(mios32_midi_port_t port, mios32_midi_package_t midi_package)
{
  // SysEx handled by APP_SYSEX_Parser()
  if( midi_package.type >= 4 && midi_package.type <= 7 )
    return;

  switch( port ) {
  case USB0:
    MIOS32_MIDI_SendPackage(UART0, midi_package);
    OSC_CLIENT_SendMIDIEvent(0, midi_package);
    led_trigger[0] = LED_PWM_PERIOD; // Board LED
    led_trigger[1] = LED_PWM_PERIOD; // J5A.0
    break;

  case USB1:
    MIOS32_MIDI_SendPackage(UART1, midi_package);
    OSC_CLIENT_SendMIDIEvent(1, midi_package);
    led_trigger[0] = LED_PWM_PERIOD; // Board LED
    led_trigger[2] = LED_PWM_PERIOD; // J5A.1
    break;

  case UART0:
    MIOS32_MIDI_SendPackage(USB0, midi_package);
    OSC_CLIENT_SendMIDIEvent(2, midi_package);
    led_trigger[0] = LED_PWM_PERIOD; // Board LED
    led_trigger[3] = LED_PWM_PERIOD; // J5A.2
    break;

  case UART1:
    MIOS32_MIDI_SendPackage(USB1, midi_package);
    OSC_CLIENT_SendMIDIEvent(3, midi_package);
    led_trigger[0] = LED_PWM_PERIOD; // Board LED
    led_trigger[4] = LED_PWM_PERIOD; // J5A.3
    break;
  }

  // forward to MIDI Monitor
  // SysEx messages have to be filtered for USB0 and UART0 to avoid data corruption
  // (the SysEx stream would interfere with monitor messages)
  u8 filter_sysex_message = (port == USB0) || (port == UART0);
  MIDIMON_Receive(port, midi_package, filter_sysex_message);
}
예제 #4
0
/////////////////////////////////////////////////////////////////////////////
// This function plays all "off" events
// Should be called on sequencer reset/restart/pause to avoid hanging notes
/////////////////////////////////////////////////////////////////////////////
static s32 SEQ_PlayOffEvents(void)
{
  // play "off events"
  SEQ_MIDI_OUT_FlushQueue();

  // send Note Off to all channels
  // TODO: howto handle different ports?
  // TODO: should we also send Note Off events? Or should we trace Note On events and send Off if required?
  int chn;
  mios32_midi_package_t midi_package;
  midi_package.type = CC;
  midi_package.event = CC;
  midi_package.evnt2 = 0;
  for(chn=0; chn<16; ++chn) {
    midi_package.chn = chn;
    midi_package.evnt1 = 123; // All Notes Off
    MIOS32_MIDI_SendPackage(DEFAULT, midi_package);
    midi_package.evnt1 = 121; // Controller Reset
    MIOS32_MIDI_SendPackage(DEFAULT, midi_package);
  }

  return 0; // no error
}
예제 #5
0
/////////////////////////////////////////////////////////////////////////////
// This function is called from LC_VPOT_SendENCEvent() in lc_vpot.c
// when GPC mode is activated and a V-Pot has been moved
/////////////////////////////////////////////////////////////////////////////
s32 LC_GPC_SendENCEvent(u8 encoder, s32 incrementer)
{
  u8 entry = gpc_offset + encoder;
  int prev_value = gpc_abs_value[entry];
  int new_value = prev_value;
  int min = 0;
  int max = 127;

  // add incrementer to absolute value
  if( incrementer >= 0 ) {
    if( (new_value += incrementer) >= max )
      new_value = max;
  } else {
    if( (new_value += incrementer < min ) )
      new_value = min;
  }

  if( new_value == prev_value )
    return 0; // no change

  // store new value
  LC_GPC_AbsValue_Received(entry, (u8)new_value);

  // send value
#if 0
  u8 evnt0 = MIOS_MPROC_EVENT_TABLE[2*entry+0];
  u8 evnt1 = MIOS_MPROC_EVENT_TABLE[2*entry+1];
#else
  // TODO: define new table
  u8 evnt0 = 0x90;
  u8 evnt1 = entry;
#endif
  u8 evnt2 = (u8)new_value;

  mios32_midi_package_t p;
  p.ALL = 0;
  p.type = (evnt0 >> 4) & 0xf;
  p.evnt0 = evnt0;
  p.evnt1 = evnt1;
  p.evnt2 = evnt2;
  MIOS32_MIDI_SendPackage(DEFAULT, p);

  return 1; // new value sent
}
예제 #6
0
/////////////////////////////////////////////////////////////////////////////
// Receives a MIDI package from APP_NotifyReceivedEvent (-> app.c)
/////////////////////////////////////////////////////////////////////////////
s32 SEQ_MIDI_IN_Receive(mios32_midi_port_t port, mios32_midi_package_t midi_package)
{
  s32 status = 0;

  // check if we should record this event
  u8 should_be_recorded =
    ui_page == SEQ_UI_PAGE_TRKREC &&
    ((!seq_midi_in_rec_port || port == seq_midi_in_rec_port) &&
     midi_package.chn == (seq_midi_in_rec_channel-1));

  // search for matching ports
  // status[0] set if at least one port matched
  // status[1] set if remote function active
  int bus;
  for(bus=0; bus<SEQ_MIDI_IN_NUM_BUSSES; ++bus) {
    // filter MIDI port (if 0: no filter, listen to all ports)
    if( (!seq_midi_in_port[bus] || port == seq_midi_in_port[bus]) &&
	midi_package.chn == (seq_midi_in_channel[bus]-1) ) {

      switch( midi_package.event ) {
      case NoteOff: 
	if( remote_active ) {
	  if( seq_hwcfg_midi_remote.key && midi_package.note == seq_hwcfg_midi_remote.key ) {
	    remote_active = 0;

	    // send "button depressed" state to all remote functions
	    int i;
	    for(i=0; i<128; ++i)
	      SEQ_UI_REMOTE_MIDI_Keyboard(i, 1); // depressed
	  } else {
	    SEQ_UI_REMOTE_MIDI_Keyboard(midi_package.note, 1); // depressed
	  }
	  status |= 2;
	} else {
	  if( !should_be_recorded &&
	      midi_package.note >= seq_midi_in_lower[bus] &&
	      (!seq_midi_in_upper[bus] || midi_package.note <= seq_midi_in_upper[bus]) ) {
	    if( seq_midi_in_options[bus].MODE_PLAY ) {
	      MUTEX_MIDIOUT_TAKE;
	      SEQ_CORE_PlayLive(SEQ_UI_VisibleTrackGet(), midi_package);
	      MUTEX_MIDIOUT_GIVE;
	    } else {
#if 0
	      // octave normalisation - too complicated for normal users...
	      mios32_midi_package_t p = midi_package;
	      if( seq_midi_in_lower[bus] ) { // normalize to first octave
		int normalized_note = 0x30 + p.note - ((int)seq_midi_in_lower[bus]/12)*12;
		while( normalized_note > 127 ) normalized_note -= 12;
		while( normalized_note < 0 ) normalized_note += 12;
		p.note = normalized_note;
	      }
	      SEQ_MIDI_IN_BusReceive(0xf0+bus, p, 0);
#else
	      SEQ_MIDI_IN_BusReceive(0xf0+bus, midi_package, 0);
#endif
	    }
	    status |= 1;
	  }
	}
	break;

      case NoteOn:
	if( remote_active )
	  SEQ_UI_REMOTE_MIDI_Keyboard(midi_package.note, midi_package.velocity ? 0 : 1); // depressed
	else {
	  if( seq_hwcfg_midi_remote.key && midi_package.note == seq_hwcfg_midi_remote.key ) {
	    remote_active = 1;
	    status |= 2;
	  } else {
	    if( !should_be_recorded &&
		midi_package.note >= seq_midi_in_lower[bus] &&
		(!seq_midi_in_upper[bus] || midi_package.note <= seq_midi_in_upper[bus]) ) {
	      if( seq_midi_in_options[bus].MODE_PLAY ) {
		MUTEX_MIDIOUT_TAKE;
		SEQ_CORE_PlayLive(SEQ_UI_VisibleTrackGet(), midi_package);
		MUTEX_MIDIOUT_GIVE;
	      } else {
#if 0
		// octave normalisation - too complicated for normal users...
		mios32_midi_package_t p = midi_package;
		if( seq_midi_in_lower[bus] ) { // normalize to first octave
		  int normalized_note = 0x30 + p.note - ((int)seq_midi_in_lower[bus]/12)*12;
		  while( normalized_note > 127 ) normalized_note -= 12;
		  while( normalized_note < 0 ) normalized_note += 12;
		  p.note = normalized_note;
		}
		SEQ_MIDI_IN_BusReceive(0xf0+bus, p, 0);
#else
		SEQ_MIDI_IN_BusReceive(0xf0+bus, midi_package, 0);
#endif
	      }
	      status |= 1;
	    }
	  }
	}
	break;

      case CC:
	if( !should_be_recorded ) {
	  if( seq_midi_in_options[bus].MODE_PLAY ) {
	    MUTEX_MIDIOUT_TAKE;
	    SEQ_CORE_PlayLive(SEQ_UI_VisibleTrackGet(), midi_package);
	    MUTEX_MIDIOUT_GIVE;
	  } else {
	    SEQ_MIDI_IN_BusReceive(0xf0+bus, midi_package, 0);
	  }
	}
	status |= 1;
	break;

      default:
	if( seq_midi_in_options[bus].MODE_PLAY ) {
	  MUTEX_MIDIOUT_TAKE;
	  SEQ_CORE_PlayLive(SEQ_UI_VisibleTrackGet(), midi_package);
	  MUTEX_MIDIOUT_GIVE;
	}
      }
    }
  }


  // record function
  if( !(status & 2) && should_be_recorded ) {
    SEQ_RECORD_Receive(midi_package, SEQ_UI_VisibleTrackGet());
  }

  // Section Changer
  if( !(status & 2) &&
      (seq_midi_in_sect_port && port == seq_midi_in_sect_port &&
       midi_package.chn == (seq_midi_in_sect_channel-1)) ) {
    u8 forward_event = 1;

    switch( midi_package.event ) {
      case NoteOff: 
	MUTEX_MIDIIN_TAKE;
	if( (status = SEQ_MIDI_IN_Receive_NoteSC(midi_package.note, 0x00)) >= 1 )
	  forward_event = 0;
	MUTEX_MIDIIN_GIVE;
	break;

      case NoteOn:
	MUTEX_MIDIIN_TAKE;
	if( (status = SEQ_MIDI_IN_Receive_NoteSC(midi_package.note, midi_package.velocity)) >= 1 )
	  forward_event = 0;
	MUTEX_MIDIIN_GIVE;
	break;
    }

    if( seq_midi_in_sect_fwd_port && forward_event ) { // octave hasn't been taken, optionally forward to forwarding port
      MUTEX_MIDIOUT_TAKE;
      MIOS32_MIDI_SendPackage(seq_midi_in_sect_fwd_port, midi_package);
      MUTEX_MIDIOUT_GIVE;
    }
  }

#if PATCH_CHANGER_ENABLED
  // Patch Changer (currently assigned to channel+1)
  if( !(status & 2) &&
      (seq_midi_in_sect_port && port == seq_midi_in_sect_port &&
       midi_package.chn == (seq_midi_in_sect_channel)) ) {
    u8 forward_event = 1;

    switch( midi_package.event ) {
      case NoteOff: 
	MUTEX_MIDIIN_TAKE;
	if( (status = SEQ_MIDI_IN_Receive_NotePC(midi_package.note, 0x00)) >= 1 )
	  forward_event = 0;
	MUTEX_MIDIIN_GIVE;
	break;

      case NoteOn:
	MUTEX_MIDIIN_TAKE;
	if( (status = SEQ_MIDI_IN_Receive_NotePC(midi_package.note, midi_package.velocity)) >= 1 )
	  forward_event = 0;
	MUTEX_MIDIIN_GIVE;
	break;
    }

    if( seq_midi_in_sect_fwd_port && forward_event ) { // octave hasn't been taken, optionally forward to forwarding port
      MUTEX_MIDIOUT_TAKE;
      MIOS32_MIDI_SendPackage(seq_midi_in_sect_fwd_port, midi_package);
      MUTEX_MIDIOUT_GIVE;
    }
  }
#endif

  return status;
}
예제 #7
0
/////////////////////////////////////////////////////////////////////////////
// This hook is called when a MIDI package has been received
/////////////////////////////////////////////////////////////////////////////
void APP_MIDI_NotifyPackage(mios32_midi_port_t port, mios32_midi_package_t midi_package)
{
  // Note On?
  if( midi_package.type == NoteOn && midi_package.velocity > 0 ) {
#if DEFAULT_MIDI_CHANNEL
    if( midi_package.chn != (DEFAULT_MIDI_CHANNEL-1) )
      return;
#endif

    // using the played note as index for "last played" array
    u8 note_ix = midi_package.note;

    // get scaled note
    if( selected_scale )
      SCALE_Note(&midi_package, selected_scale-1, selected_root);

    // same note already played from another key? then send note off and play it again
    int i;
    u8 *last_played_note_ptr = (u8 *)&last_played_note[0];
    for(i=0; i<128; ++i, ++last_played_note_ptr)
      if( *last_played_note_ptr == midi_package.note ) {
	mios32_midi_package_t p = midi_package;
	p.velocity = 0;

	// forward Note Off event to MIDI ports
	MIOS32_MIDI_SendPackage(USB0, p);
	MIOS32_MIDI_SendPackage(UART0, p);
	MIOS32_MIDI_SendPackage(UART1, p);

	*last_played_note_ptr = 0x80;
      }

    // store note
    last_played_note[note_ix] = midi_package.note;

    // forward Note On event to MIDI ports
    MIOS32_MIDI_SendPackage(USB0, midi_package);
    MIOS32_MIDI_SendPackage(UART0, midi_package);
    MIOS32_MIDI_SendPackage(UART1, midi_package);
    return;
  }

  // Note Off?
  if( midi_package.type == NoteOff ||
      (midi_package.type == NoteOn && midi_package.velocity == 0) ) {
#if DEFAULT_MIDI_CHANNEL
    if( midi_package.chn != (DEFAULT_MIDI_CHANNEL-1) )
      return;
#endif

    // using the played note as index for "last played" array
    u8 note_ix = midi_package.note;

    // map to last played note
    if( last_played_note[note_ix] < 0x80 ) {
      midi_package.note = last_played_note[note_ix];
      last_played_note[note_ix] = 0x80;

      // forward to MIDI ports
      MIOS32_MIDI_SendPackage(USB0, midi_package);
      MIOS32_MIDI_SendPackage(UART0, midi_package);
      MIOS32_MIDI_SendPackage(UART1, midi_package);
    }

    return;    
  }

  // CC?
  if( midi_package.type == CC ) {
#if DEFAULT_MIDI_CHANNEL
    if( midi_package.chn != (DEFAULT_MIDI_CHANNEL-1) )
      return;
#endif

    switch( midi_package.cc_number ) {
    case 16:
      if( midi_package.value <= SCALE_NumGet() ) {
	selected_scale = midi_package.value;
	display_update = 1;
      }
      break;
    case 17:
      if( midi_package.value < 12 ) {
	selected_root = midi_package.value;
	display_update = 1;
      }
      break;
    }

    return;
  }
}
예제 #8
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:
      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
}