static void knobValueSet(u32 ix, u16 value) { if( knobValue[ix] != value ) { knobValue[ix] = value; MIOS32_MIDI_SendCC(USB0, knobChn[ix], knobCC[ix], knobValue[ix]); MIOS32_MIDI_SendCC(UART0, knobChn[ix], knobCC[ix], knobValue[ix]); } }
///////////////////////////////////////////////////////////////////////////// // This hook is called when an AINSER pot has been moved ///////////////////////////////////////////////////////////////////////////// static void APP_AINSER_NotifyChange(u32 module, u32 pin, u32 pin_value) { // toggle Status LED on each AIN value change MIOS32_BOARD_LED_Set(0x0001, ~MIOS32_BOARD_LED_Get()); // convert 12bit value to 7bit value u8 value_7bit = pin_value >> 5; // send MIDI event MIOS32_MIDI_SendCC(DEFAULT, Chn1, pin + 0x10, value_7bit); }
///////////////////////////////////////////////////////////////////////////// // This hook is called when an encoder has been moved // incrementer is positive when encoder has been turned clockwise, else // it is negative ///////////////////////////////////////////////////////////////////////////// void APP_ENC_NotifyChange(u32 encoder, s32 incrementer) { #if DEBUG_VERBOSE_LEVEL >= 2 DEBUG_MSG("APP_ENC_NotifyChange(%d, %d)\n", encoder, incrementer); #endif // remember last pin and direction last_enc = encoder; last_enc_dir = (incrementer > 0) ? 1 : 0; // send MIDI event MIOS32_MIDI_SendCC(DEFAULT, Chn1, encoder, incrementer & 0x7f); }
///////////////////////////////////////////////////////////////////////////// // This hook is called when an encoder has been moved // incrementer is positive when encoder has been turned clockwise, else // it is negative ///////////////////////////////////////////////////////////////////////////// void APP_ENC_NotifyChange(u32 encoder, s32 incrementer) { // toggle Status LED on each AIN value change MIOS32_BOARD_LED_Set(0x0001, ~MIOS32_BOARD_LED_Get()); // determine relative value: 64 +/- <incrementer> int value = 64 + incrementer; // ensure that value is in range of 0..127 if( value < 0 ) value = 0; else if( value > 127 ) value = 127; // send event MIOS32_MIDI_SendCC(DEFAULT, Chn1, 0x10 + encoder, value); }
///////////////////////////////////////////////////////////////////////////// // Sends a single mixer value ///////////////////////////////////////////////////////////////////////////// s32 SEQ_MIXER_Send(u8 chn, seq_mixer_par_t par) { mios32_midi_port_t midi_port = SEQ_MIXER_Get(chn, SEQ_MIXER_PAR_PORT); mios32_midi_chn_t midi_chn = SEQ_MIXER_Get(chn, SEQ_MIXER_PAR_CHANNEL); s32 value; if( (value=SEQ_MIXER_Get(chn, par)) < 0 ) return 0; // don't return error, as it could be misinterpreded as a MIDI interface issue switch( par ) { case SEQ_MIXER_PAR_PRG: return value == 0 ? 0 : MIOS32_MIDI_SendProgramChange(midi_port, midi_chn, value-1); case SEQ_MIXER_PAR_VOLUME: return value == 0 ? 0 : MIOS32_MIDI_SendCC(midi_port, midi_chn, 7, value-1); case SEQ_MIXER_PAR_PANORAMA: return value == 0 ? 0 : MIOS32_MIDI_SendCC(midi_port, midi_chn, 10, value-1); case SEQ_MIXER_PAR_REVERB: return value == 0 ? 0 : MIOS32_MIDI_SendCC(midi_port, midi_chn, 91, value-1); case SEQ_MIXER_PAR_CHORUS: return value == 0 ? 0 : MIOS32_MIDI_SendCC(midi_port, midi_chn, 93, value-1); case SEQ_MIXER_PAR_MODWHEEL: return value == 0 ? 0 : MIOS32_MIDI_SendCC(midi_port, midi_chn, 1, value-1); case SEQ_MIXER_PAR_CC1: return value == 0 ? 0 : MIOS32_MIDI_SendCC(midi_port, midi_chn, SEQ_MIXER_Get(chn, SEQ_MIXER_PAR_CC1_NUM), value-1); case SEQ_MIXER_PAR_CC2: return value == 0 ? 0 : MIOS32_MIDI_SendCC(midi_port, midi_chn, SEQ_MIXER_Get(chn, SEQ_MIXER_PAR_CC2_NUM), value-1); case SEQ_MIXER_PAR_CC3: return value == 0 ? 0 : MIOS32_MIDI_SendCC(midi_port, midi_chn, SEQ_MIXER_Get(chn, SEQ_MIXER_PAR_CC3_NUM), value-1); case SEQ_MIXER_PAR_CC4: return value == 0 ? 0 : MIOS32_MIDI_SendCC(midi_port, midi_chn, SEQ_MIXER_Get(chn, SEQ_MIXER_PAR_CC4_NUM), value-1); } // not supported // don't return error, as this could be misinterpreted as a MIDI interface issue return 0; }
///////////////////////////////////////////////////////////////////////////// // This function should be called from ENC_NotifyChange() in main.c // when the jogwheel has been moved ///////////////////////////////////////////////////////////////////////////// s32 LC_VPOT_SendJogWheelEvent(s32 incrementer) { if( lc_hwcfg_verbose_level >= 2 ) { MIOS32_MIDI_SendDebugMessage(" -> LC_VPOT_SendJogWheelEvent(%d)\n", incrementer); } if( lc_flags.GPC_SEL ) { // in GPC mode: select GPC offset LC_GPC_SendJogWheelEvent(incrementer); } else { // otherwise send relative event to host application u8 cc_number = 0x3c; u8 value = (incrementer < 0) ? ((-incrementer & 0x3f) | 0x40) : ((incrementer & 0x3f) | 0x00); MIOS32_MIDI_SendCC(DEFAULT, Chn1, cc_number, value); } return 0; // no error }
///////////////////////////////////////////////////////////////////////////// // This function should be called from ENC_NotifyChange() in main.c // when a V-Pot has been moved ///////////////////////////////////////////////////////////////////////////// s32 LC_VPOT_SendENCEvent(u8 encoder, s32 incrementer) { if( lc_hwcfg_verbose_level >= 2 ) { MIOS32_MIDI_SendDebugMessage(" -> LC_VPOT_SendEncEvent(%d, %d)\n", encoder, incrementer); } if( lc_flags.GPC_SEL ) { // in GPC mode: change GPC value LC_GPC_SendENCEvent(encoder, incrementer); } else { // otherwise send relative event to host application u8 cc_number = 0x10 | (encoder & 0x07); u8 value = (incrementer < 0) ? ((-incrementer & 0x3f) | 0x40) : ((incrementer & 0x3f) | 0x00); MIOS32_MIDI_SendCC(DEFAULT, Chn1, cc_number, value); } return 0; // no error }
///////////////////////////////////////////////////////////////////////////// // This hook is called when an encoder has been moved // incrementer is positive when encoder has been turned clockwise, else // it is negative ///////////////////////////////////////////////////////////////////////////// void APP_ENC_NotifyChange(u32 encoder, s32 incrementer) { // toggle Status LED on each AIN value change MIOS32_BOARD_LED_Set(1, ~MIOS32_BOARD_LED_Get()); // increment to virtual position and ensure that the value is in range 0..127 int value = enc_virtual_pos[encoder] + incrementer; if( value < 0 ) value = 0; else if( value > 127 ) value = 127; // only send if value has changed if( enc_virtual_pos[encoder] != value ) { // store new value enc_virtual_pos[encoder] = value; // send event MIOS32_MIDI_SendCC(DEFAULT, Chn1, 0x10 + encoder, value); } }
///////////////////////////////////////////////////////////////////////////// // Local button callback function // Should return: // 1 if value has been changed // 0 if value hasn't been changed // -1 if invalid or unsupported button ///////////////////////////////////////////////////////////////////////////// static s32 Button_Handler(seq_ui_button_t button, s32 depressed) { if( SEQ_FILE_FormattingRequired() ) return 0; // no button action as long as files not available u8 group; s32 status; switch( button ) { case SEQ_UI_BUTTON_Edit: { if ( depressed && (selected_page == PAGE_REMIX) ) { // we want to show vertical VU meters normal mode SEQ_LCD_InitSpecialChars(SEQ_LCD_CHARSET_VBars); selected_page = PAGE_MAIN; // // here we run the commands for remix states // u16 remix_map_tmp; // if we are in no preview_mode them call the demix procedment in case we got some request if ( (seq_pattern_remix_map != seq_pattern_demix_map) && ( remix_mode ) ) { remix_map_tmp = seq_pattern_remix_map; seq_pattern_remix_map = ~(seq_pattern_remix_map ^ seq_pattern_demix_map); if (ableton_api) { // TODO: implements a ableton remotescript to the clip control functionality u8 track; for(track=0; track<SEQ_CORE_NUM_TRACKS; ++track) { // if we got the track bit setup inside our remix_map, them do not send mixer data for that track channel, let it be mixed down if ( ((1 << track) | seq_pattern_remix_map) == seq_pattern_remix_map ) { // do nothing... } else { // delay our task for ableton get a breath before change the pattern line vTaskDelay(50); // send slot play envet to ableton: cc (111 + track) with value 127 to channel 16 MIOS32_MIDI_SendCC(ableton_port, 15, (111 + track), 127); } } } // if we are in remix mode lets start the process if (remix_map_tmp == 0) { //pattern_name = pattern_name_remix; preview_mode = 0; remix_mode = 0; // set the pattern_remix_timer reference pattern_remix_timer = MIOS32_SYS_TimeGet(); } // Change Pattern u8 group; for(group=0; group<SEQ_CORE_NUM_GROUPS; ++group) { // change pattern SEQ_PATTERN_Change(group, seq_pattern[group], 0); } // copy the pattern name for future reference sprintf(pattern_name, seq_pattern_name[0]); // getting back seq_pattern_remix_map = remix_map_tmp; seq_pattern_demix_map = seq_pattern_remix_map; } } else if ( !depressed ) { // we want to show horizontal VU meters in remix mode SEQ_LCD_InitSpecialChars(SEQ_LCD_CHARSET_HBars); selected_page = PAGE_REMIX; } return 1; } break; case SEQ_UI_BUTTON_Select: { if(!depressed) { selected_page = PAGE_OPTION; return 1; } if( (depressed) && (selected_page == PAGE_OPTION) ) { selected_page = PAGE_MAIN; } } break; default: break; } if( depressed ) return 0; // ignore when button depressed if its not SEQ_UI_BUTTON_Select or SEQ_UI_BUTTON_Edit switch( selected_page ) { /////////////////////////////////////////////////////////////////////////// // REMIX PAGE case PAGE_REMIX: { if( button <= SEQ_UI_BUTTON_GP16 ) { // re-using encoder routine return Encoder_Handler(button, 0); } } break; /////////////////////////////////////////////////////////////////////////// // OPTION PAGE case PAGE_OPTION: { switch( button ) { case SEQ_UI_BUTTON_GP1: // Save Pattern(all 4 groups at once) for(group=0; group<SEQ_CORE_NUM_GROUPS; ++group) { if( (status=SEQ_PATTERN_Save(group, seq_pattern[group])) < 0 ) { SEQ_UI_SDCardErrMsg(2000, status); return 0; } } // Save the pattern mixer SEQ_MIXER_Save(SEQ_MIXER_NumGet()); break; case SEQ_UI_BUTTON_GP2: // Edit Pattern Name //SEQ_LCD_Clear(); selected_page = PAGE_NAME_EDIT; break; case SEQ_UI_BUTTON_GP3: // Copy Pattern // We are going to use the multicopy procedure SEQ_UI_PATTERN_Copy(); // coping mixer SEQ_UI_MIXER_Copy(); break; case SEQ_UI_BUTTON_GP4: // Paste Pattern // We are going to use the multicopy procedure SEQ_UI_PATTERN_Paste(); // paste mixer SEQ_UI_MIXER_Paste(); break; case SEQ_UI_BUTTON_GP5: case SEQ_UI_BUTTON_GP6: return -1; // not mapped case SEQ_UI_BUTTON_GP7: case SEQ_UI_BUTTON_GP8: // Track Delay Page //SEQ_LCD_Clear(); //selected_page = PAGE_TRK_DELAY; //break; return -1; // not mapped case SEQ_UI_BUTTON_GP9: case SEQ_UI_BUTTON_GP10: return -1; // not mapped case SEQ_UI_BUTTON_GP11: case SEQ_UI_BUTTON_GP12: return -1; // not mapped case SEQ_UI_BUTTON_GP13: case SEQ_UI_BUTTON_GP14: // Auto Save switch auto_save ^= 1; break; break; case SEQ_UI_BUTTON_GP15: case SEQ_UI_BUTTON_GP16: // Ableton API switch ableton_api ^= 1; return -1; // not mapped } return 0; } break; /////////////////////////////////////////////////////////////////////////// // PATTERN NAME EDIT PAGE case PAGE_NAME_EDIT: { return Encoder_Handler((seq_ui_encoder_t)button, 0); } break; /////////////////////////////////////////////////////////////////////////// // TRACK DELAY PAGE case PAGE_TRK_DELAY: { } break; /////////////////////////////////////////////////////////////////////////// // MAIN PAGE case PAGE_MAIN: { // Pattern Change Group Requests if( button <= SEQ_UI_BUTTON_GP8 ) { u8 group; for(group=0; group<SEQ_CORE_NUM_GROUPS; ++group) { if( selected_pattern[group].group == button ) { if( SEQ_FILE_B_NumPatterns(selected_pattern[group].bank) > 64 ) { selected_pattern[group].lower ^= 1; } else { selected_pattern[group].lower = 0; // toggling not required } } else { selected_pattern[group].group = button; preview_bank = button; //preview_mode = 0; } } SEQ_PATTERN_PeekName(selected_pattern[0], preview_name); preview_mode = 1; return 1; // value always changed } // Pattern Change Request if( button >= SEQ_UI_BUTTON_GP9 && button <= SEQ_UI_BUTTON_GP16 ) { //u8 group; s32 status = 0; // setting save patterns for later autosave procedure //for(group=0; group<SEQ_CORE_NUM_GROUPS; ++group) // save_pattern[group] = seq_pattern[group]; // change preview pattern selected_pattern[0].num = button-8; selected_pattern[1].num = button-8; selected_pattern[2].num = button-8; selected_pattern[3].num = button-8; if ( !preview_mode ) { preview_num = button-8; SEQ_PATTERN_PeekName(selected_pattern[0], preview_name); preview_mode = 1; // for security reasons in live environment, we can not accept rapid 2x press of pattern button. // if the buttons are not good quality they can send a 2x rapid signal os pressing in just one physical pressing // the time we need is bassicly the human time to read the name of patterns requested // start time in count pc_time_control = seq_core_timestamp_ms + 200; // vTaksDelay(200); } else { // if ( remix_mode == 0 ) { //char str[30]; //sprintf(str, "%d = %d %d = %d", selected_pattern[0].num, preview_num, selected_pattern[0].group, preview_bank); //SEQ_UI_Msg(SEQ_UI_MSG_USER, 2000, str, "debug"); // Check for remix state if ( (selected_pattern[0].num == preview_num) && (selected_pattern[0].group == preview_bank) ) { //if ( pc_time_control >= seq_core_timestamp_ms ) // return 0; // do nothing, just to be shure we are not handle acidental double clicks, it happens! // if we got some remix bit setup, enter in remix mode if (seq_pattern_remix_map != 0) { // set the remix mode remix_mode = 1; } } } // Change Pattern if ( (selected_pattern[0].num == preview_num) && (selected_pattern[0].group == preview_bank) ) { if ( pc_time_control >= seq_core_timestamp_ms ) return 0; // do nothing, just to be shure we are not handle acidental double clicks if (ableton_api) { u8 ableton_pattern_number = 0; // // ABLETON API PATTERN CHANGE HANDLE // // send ableton event to change the line clips if (selected_pattern[0].lower) { ableton_pattern_number = ((selected_pattern[0].group - 1) * 8) + button; } else { ableton_pattern_number = (((selected_pattern[0].group - 1) + 8) * 8) + button; } // midi_cc to send = 110 // midi_chn = 16 // midi_port = ableton_port // here we need to send a clip line change request MIOS32_MIDI_SendCC(ableton_port, 15, 110, ableton_pattern_number); // delay our task for ableton get a breath before change the pattern line vTaskDelay(50); // clip triggering u8 track; for(track=0; track<SEQ_CORE_NUM_TRACKS; ++track) { // if we got the track bit setup inside our remix_map, them do not send mixer data for that track channel, let it be mixed down if ( ((1 << track) | seq_pattern_remix_map) == seq_pattern_remix_map ) { // do nothing... } else { // delay our task for ableton get a breath before change the pattern line vTaskDelay(50); // send clip slot play envet to ableton cc (111 + track) with value 127 to channel 16 MIOS32_MIDI_SendCC(ableton_port, 15, (111 + track), 127); } } // send a global clip change(all clips in the clip line) // send play envet to ableton cc 20 with value 127 to channel 16 //MIOS32_MIDI_SendCC(ableton_port, 15, 20, 127); } // // Autosave Pattern // if (auto_save) { // Autosave all 4 group of patterns for(group=0; group<SEQ_CORE_NUM_GROUPS; ++group) { // Save to SD Card if( (status=SEQ_PATTERN_Save(group, seq_pattern[group])) < 0 ) { SEQ_UI_SDCardErrMsg(2000, status); } } // Autosave Mixer Map SEQ_MIXER_Save(SEQ_MIXER_NumGet()); } // if we are not in remix mode if (seq_pattern_remix_map == 0) { // keep the name of old pattern(because we still got 1 or more tracks belongs to this pattern) //pattern_name = seq_pattern_name; // set timer for mixed pattern pattern_remix_timer = MIOS32_SYS_TimeGet(); } // // Pattern change request // for(group=0; group<SEQ_CORE_NUM_GROUPS; ++group) { // change pattern //selected_pattern[group].num = button-8; SEQ_PATTERN_Change(group, selected_pattern[group], 0); //if ( remix_mode ) { // keep the pattern name... //sprintf(seq_pattern_name[group], pattern_name); //} } if ( !remix_mode ) { // copy the pattern name for future reference //sprintf(pattern_name, seq_pattern_name[0]); SEQ_PATTERN_PeekName(selected_pattern[0], pattern_name); // TODO: sync this with the pattern change handled by SEQ_PATTERN_Handler() //pattern_timer.seconds = 0; preview_mode = 0; } // support for demix seq_pattern_demix_map = seq_pattern_remix_map; //preview_mode = 0; // Request a Pattern Preview } else { preview_num = button-8; SEQ_PATTERN_PeekName(selected_pattern[0], preview_name); // for security reasons in live environment, we can not accept 2x rapid press of pattern button. // if the buttons are not good quality they can send a 2x rapid signal os pressing in just one physical pressing // the time we need is bassicly the human time to read the name of patterns requested // start time in count pc_time_control = seq_core_timestamp_ms + 200; } } return 1; // value always changed } u8 visible_track = SEQ_UI_VisibleTrackGet(); switch( button ) { case SEQ_UI_BUTTON_Right: // switch to next track if( visible_track < (SEQ_CORE_NUM_TRACKS - 1) ) visible_track++; ui_selected_tracks = (1 << visible_track); ui_selected_group = visible_track / 4; return 1; // value always changed case SEQ_UI_BUTTON_Left: // switch to previous track if( visible_track >= 1 ) visible_track--; ui_selected_tracks = (1 << visible_track); ui_selected_group = visible_track / 4; return 1; // value always changed case SEQ_UI_BUTTON_Up: return Encoder_Handler(SEQ_UI_ENCODER_Datawheel, 1); case SEQ_UI_BUTTON_Down: return Encoder_Handler(SEQ_UI_ENCODER_Datawheel, -1); } return -1; // invalid or unsupported button } break; /////////////////////////////////////////////////////////////////////////// // Page do not exist default: break; } return 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) { if( SEQ_FILE_FormattingRequired() ) return 0; // no encoder action as long as files not available switch( selected_page ) { /////////////////////////////////////////////////////////////////////////// // REMIX PAGE case PAGE_REMIX: { // TODO: finish the incrementer helper for gp encoders if( encoder <= SEQ_UI_ENCODER_GP16 ) { if ( preview_mode ) { // the remix state if( ((1 << encoder) | seq_pattern_remix_map) == seq_pattern_remix_map ) { // if we already got the requested bit setup // unset him inside remix map seq_pattern_remix_map &= ~(1 << encoder); } else { // else setup it! // set him inside remix map seq_pattern_remix_map |= (1 << encoder); } } else { // the demix state // in this state we can not mix, just turn mixed states track into demixed state if( ((1 << encoder) | seq_pattern_remix_map) == seq_pattern_remix_map ) { // if we already got the requested bit setup inside remix map // unset him inside remix map seq_pattern_remix_map &= ~(1 << encoder); } else if ( ((1 << encoder) | seq_pattern_demix_map) == seq_pattern_demix_map ) { // if we already got the requested bit setup demix map // set him inside remix map seq_pattern_remix_map |= (1 << encoder); } } if ( !preview_mode && ableton_api) { // send slot play envet to ableton cc (111 + track) with value 127 to channel 16 MIOS32_MIDI_SendCC(ableton_port, 15, (111 + encoder), 127); } } } break; /////////////////////////////////////////////////////////////////////////// // OPTION PAGE case PAGE_OPTION: { } break; /////////////////////////////////////////////////////////////////////////// // PATTERN NAME EDIT PAGE case PAGE_NAME_EDIT: { u8 group; s32 status; // if we got a OK button pressed if (encoder==SEQ_UI_ENCODER_GP16) { // Get back to main page selected_page = PAGE_MAIN; return 1; } if( (status=SEQ_UI_KeyPad_Handler(encoder, incrementer, (char *)&seq_pattern_name[0], 20)) < 0 ) { // error return 0; } else { // copy the name of group 1 pattern to all other pattern groups for(group=1; group<SEQ_CORE_NUM_GROUPS; ++group) { //SEQ_LABEL_CopyPresetCategory(seq_pattern[0].num, (char *)&seq_pattern_name[group][0]); sprintf(seq_pattern_name[group], seq_pattern_name[0]); } // copy the pattern name sprintf(pattern_name, seq_pattern_name[0]); } return 1; } break; /////////////////////////////////////////////////////////////////////////// // TRACK DELAY PAGE case PAGE_TRK_DELAY: { } break; /////////////////////////////////////////////////////////////////////////// // MAIN PAGE case PAGE_MAIN: { switch( encoder ) { case SEQ_UI_ENCODER_Datawheel: incrementer = incrementer; u16 value = (u16)(seq_core_bpm_preset_tempo[seq_core_bpm_preset_num]*10); if( SEQ_UI_Var16_Inc(&value, 25, 3000, incrementer) ) { // at 384ppqn, the minimum BPM rate is ca. 2.5 // set new BPM seq_core_bpm_preset_tempo[seq_core_bpm_preset_num] = (float)value/10.0; SEQ_CORE_BPM_Update(seq_core_bpm_preset_tempo[seq_core_bpm_preset_num], seq_core_bpm_preset_ramp[seq_core_bpm_preset_num]); return 1; // value has been changed } else { return 0; // value hasn't been changed } default: return -1; // invalid or unsupported encoder } } break; /////////////////////////////////////////////////////////////////////////// // Page do not exist default: break; } return 0; }