///////////////////////////////////////////////////////////////////////////// // 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 received? if( midi_package.type == NoteOn && midi_package.chn == Chn1 && midi_package.velocity > 0 ) { // determine base key number (0=C, 1=C#, 2=D, ...) u8 base_key = midi_package.note % 12; // branch depending on note switch( base_key ) { case 0: // "C" starts the sequencer MIOS32_MIDI_SendDebugMessage("Start\n"); // if in auto mode and BPM generator is clocked in slave mode: // change to master mode SEQ_BPM_CheckAutoMaster(); // start sequencer SEQ_BPM_Start(); break; case 2: // "D" stops the sequencer. If pressed twice, the sequencer will be reset MIOS32_MIDI_SendDebugMessage("Stop\n"); if( SEQ_BPM_IsRunning() ) SEQ_BPM_Stop(); // stop sequencer else SEQ_Reset(1); // reset sequencer break; case 4: // "E" pauses the sequencer // if in auto mode and BPM generator is clocked in slave mode: // change to master mode SEQ_BPM_CheckAutoMaster(); // toggle pause mode seq_pause ^= 1; MIOS32_MIDI_SendDebugMessage("Pause %s\n", seq_pause ? "on" : "off"); // execute stop/continue depending on new mode if( seq_pause ) SEQ_BPM_Stop(); // stop sequencer else SEQ_BPM_Cont(); // continue sequencer break; case 5: // "F" switches to next file MIOS32_MIDI_SendDebugMessage("Next File\n"); SEQ_PlayFileReq(1); break; } } }
///////////////////////////////////////////////////////////////////////////// // Should be called whenever a Note event has been received. // We expect, that velocity is 0 on a Note Off event ///////////////////////////////////////////////////////////////////////////// s32 SEQ_NotifyNoteOn(u8 note, u8 velocity) { u8 clear_stack = 0; if( velocity ) // push note into note stack NOTESTACK_Push(¬estack, note, velocity); else { // remove note from note stack // function returns 2 if no note played anymore (all keys depressed) if( NOTESTACK_Pop(¬estack, note) == 2 ) clear_stack = 1; } // At least one note played? if( !clear_stack && notestack.len > 0 ) { // start sequencer if it isn't already running if( !SEQ_BPM_IsRunning() ) SEQ_BPM_Start(); } else { // clear stack NOTESTACK_Clear(¬estack); // no key is pressed anymore: stop sequencer SEQ_BPM_Stop(); } #if 1 // optional debug messages NOTESTACK_SendDebugMessage(¬estack); #endif return 0; // no error }
///////////////////////////////////////////////////////////////////////////// // Allows to request to play the next file from a lower priority task ///////////////////////////////////////////////////////////////////////////// s32 SEQ_PlayFileReq(u32 next) { // stop generator SEQ_BPM_Stop(); // request next file next_file_req = next ? 2 : 1; return 0; // no error }
///////////////////////////////////////////////////////////////////////////// //! To control the stop button function ///////////////////////////////////////////////////////////////////////////// s32 MBNG_SEQ_StopButton(void) { if( SEQ_BPM_IsRunning() ) { SEQ_BPM_Stop(); // stop sequencer } else { // reset sequencer MBNG_SEQ_Reset(); // disable pause mode MBNG_SEQ_SetPauseMode(0); } return 0; // no error }
///////////////////////////////////////////////////////////////////////////// // Plays the first .mid file if next == 0, the next file if next != 0 ///////////////////////////////////////////////////////////////////////////// static s32 SEQ_PlayFile(u32 next) { // play off events before loading new file SEQ_PlayOffEvents(); char next_file[13]; if( MID_FILE_FindNext(next ? MID_FILE_UI_NameGet() : NULL, next_file) == 1 || MID_FILE_FindNext(NULL, next_file) == 1 ) { // if next file not found, try first file #if DEBUG_VERBOSE_LEVEL >= 1 DEBUG_MSG("[SEQ] next file found '%s'\n", next_file); #endif SEQ_BPM_Stop(); // stop BPM generator if( MID_FILE_open(next_file) ) { // try to open next file #if DEBUG_VERBOSE_LEVEL >= 1 DEBUG_MSG("[SEQ] file %s cannot be opened (wrong directory?)\n", next_file); #endif return -1; // file cannot be opened } if( MID_PARSER_Read() < 0 ) { // read file, stop on failure #if DEBUG_VERBOSE_LEVEL >= 1 DEBUG_MSG("[SEQ] file %s is invalid!\n", next_file); #endif return -2; // file is invalid } SEQ_BPM_Start(); // start BPM generator } else { SEQ_BPM_Stop(); // stop BPM generator #if DEBUG_VERBOSE_LEVEL >= 1 DEBUG_MSG("[SEQ] no file found\n"); #endif return -1; // file not found } return 0; // no error }
///////////////////////////////////////////////////////////////////////////// //! To control the stop button function ///////////////////////////////////////////////////////////////////////////// s32 MBNG_SEQ_PauseButton(void) { // if in auto mode and BPM generator is not clocked in slave mode: // change to master mode SEQ_BPM_CheckAutoMaster(); // toggle pause seq_pause ^= 1; // execute stop/continue depending on new mode MIOS32_IRQ_Disable(); if( seq_pause ) { SEQ_BPM_Stop(); } else { if( !SEQ_BPM_IsRunning() ) SEQ_BPM_Cont(); } MIOS32_IRQ_Enable(); return 0; // no error }
void MClock_User_Stop(void) { SEQ_BPM_Stop(); }
///////////////////////////////////////////////////////////////////////////// // fetches the pos entries of a song // returns -1 if recursion counter reached max position ///////////////////////////////////////////////////////////////////////////// s32 SEQ_SONG_FetchPos(u8 force_immediate_change, u8 dont_dump_mixer_map) { int recursion_ctr = 0; u8 again; do { again = 0; // stop song once recursion counter reached 64 loops if( ++recursion_ctr >= 64 ) { SEQ_BPM_Stop(); return -1; // recursion detected } // reset song position if we reached the end (loop over 128 steps) if( song_pos >= SEQ_SONG_NUM_STEPS ) song_pos = 0; // get step entry seq_song_step_t *s = (seq_song_step_t *)&seq_song_steps[song_pos]; // branch depending on action switch( s->action ) { case SEQ_SONG_ACTION_End: #if 0 if( song_active ) // not in phrase mode SEQ_BPM_Stop(); #else song_finished = 1; // deactivate song incrementer #endif break; case SEQ_SONG_ACTION_JmpPos: song_pos = s->action_value % SEQ_SONG_NUM_STEPS; again = 1; break; case SEQ_SONG_ACTION_JmpSong: { if( song_active ) { // not in phrase mode u32 new_song_num = s->action_value % SEQ_SONG_NUM; SEQ_SONG_Save(song_num); SEQ_SONG_Load(new_song_num); song_pos = 0; again = 1; } } break; case SEQ_SONG_ACTION_SelMixerMap: SEQ_MIXER_Load(s->action_value); SEQ_MIDI_IN_ExtCtrlSend(SEQ_MIDI_IN_EXT_CTRL_MIXER_MAP, s->action_value, 0); if( !dont_dump_mixer_map ) SEQ_MIXER_SendAll(); ++song_pos; again = 1; break; case SEQ_SONG_ACTION_Tempo: { float bpm = (float)s->action_value; if( bpm < 25.0 ) bpm = 25.0; float ramp = (float)s->pattern_g1; SEQ_CORE_BPM_Update(bpm, ramp); ++song_pos; again = 1; } break; case SEQ_SONG_ACTION_Mutes: { // access to seq_core_trk[] must be atomic! portENTER_CRITICAL(); seq_core_trk_muted = ((s->pattern_g1 & 0x0f) << 0) | ((s->pattern_g2 & 0x0f) << 4) | ((s->pattern_g3 & 0x0f) << 8) | ((s->pattern_g4 & 0x0f) << 12); portEXIT_CRITICAL(); ++song_pos; again = 1; } break; case SEQ_SONG_ACTION_GuideTrack: { if( s->action_value <= 16 ) seq_song_guide_track = s->action_value; ++song_pos; again = 1; } break; default: if( s->action >= SEQ_SONG_ACTION_Loop1 && s->action <= SEQ_SONG_ACTION_Loop16 ) { song_loop_ctr = 0; song_loop_ctr_max = s->action - SEQ_SONG_ACTION_Loop1; // TODO: implement prefetching until end of step! SEQ_SONG_FetchHlp_PatternChange(0, s->pattern_g1, s->bank_g1, force_immediate_change); SEQ_SONG_FetchHlp_PatternChange(1, s->pattern_g2, s->bank_g2, force_immediate_change); SEQ_SONG_FetchHlp_PatternChange(2, s->pattern_g3, s->bank_g3, force_immediate_change); SEQ_SONG_FetchHlp_PatternChange(3, s->pattern_g4, s->bank_g4, force_immediate_change); } } } while( again ); return 0; // no error }
///////////////////////////////////////////////////////////////////////////// //! This task is running endless in background ///////////////////////////////////////////////////////////////////////////// void APP_Background(void) { const u16 sdcard_check_delay = 1000; u16 sdcard_check_ctr = 0; u8 lun_available = 0; static u8 isInMainPage = 1; SCS_DisplayUpdateInMainPage(0); MBNG_LCD_SpecialCharsReInit(); u32 last_timestamp = MIOS32_TIMESTAMP_Get(); while( 1 ) { //vTaskDelay(1 / portTICK_RATE_MS); // Background task: use timestamp mechanism to generate delay while( MIOS32_TIMESTAMP_Get() == last_timestamp ); last_timestamp = MIOS32_TIMESTAMP_Get(); // call SCS handler MUTEX_LCD_TAKE; MIOS32_LCD_FontInit((u8 *)GLCD_FONT_NORMAL); SCS_Tick(); SCS_DisplayUpdateInMainPage((MBNG_EVENT_MidiLearnModeGet() || MBNG_EVENT_EventLearnIdGet()) ? 1 : 0); // LCD output in mainpage if( SCS_MenuStateGet() == SCS_MENU_STATE_MAINPAGE && !MBNG_EVENT_MidiLearnModeGet() && !MBNG_EVENT_EventLearnIdGet() ) { u8 force = isInMainPage == 0; if( force ) { // page change MBNG_LCD_SpecialCharsReInit(); MBNG_LCD_CursorSet(SCS_LCD_DeviceGet(), SCS_LCD_OffsetXGet(), SCS_LCD_OffsetYGet() + 0); MBNG_LCD_PrintSpaces(SCS_NumMenuItemsGet()*SCS_MENU_ITEM_WIDTH); MBNG_LCD_CursorSet(SCS_LCD_DeviceGet(), SCS_LCD_OffsetXGet(), SCS_LCD_OffsetYGet() + 1); MBNG_LCD_PrintSpaces(SCS_NumMenuItemsGet()*SCS_MENU_ITEM_WIDTH); } MBNG_EVENT_UpdateLCD(force); // handles .NGR file execution MBNG_FILE_R_CheckRequest(); isInMainPage = 1; // static reminder } else { if( isInMainPage && (MBNG_EVENT_MidiLearnModeGet() || MBNG_EVENT_EventLearnIdGet()) ) { SCS_LCD_Update(1); // force display update when learn mode is entered in mainpage } isInMainPage = 0; // static reminder } // handles .NGR file execution MBNG_FILE_R_CheckRequest(); MUTEX_LCD_GIVE; // -> keyboard handler KEYBOARD_Periodic_1mS(); // MIDI In/Out monitor MIDI_PORT_Period1mS(); // call MIDI event tick MBNG_EVENT_Tick(); // each second: check if SD Card (still) available if( msd_state == MSD_DISABLED && ++sdcard_check_ctr >= sdcard_check_delay ) { sdcard_check_ctr = 0; MUTEX_SDCARD_TAKE; s32 status = FILE_CheckSDCard(); if( status == 1 ) { DEBUG_MSG("SD Card connected: %s\n", FILE_VolumeLabel()); // stop sequencer SEQ_BPM_Stop(); // load all file infos MBNG_FILE_LoadAllFiles(1); // including HW info // select the first bank MBNG_EVENT_SelectedBankSet(1); // immediately go to next step sdcard_check_ctr = sdcard_check_delay; } else if( status == 2 ) { DEBUG_MSG("SD Card disconnected\n"); // invalidate all file infos MBNG_FILE_UnloadAllFiles(); // stop sequencer SEQ_BPM_Stop(); // change status MBNG_FILE_StatusMsgSet("No SD Card"); MUTEX_LCD_TAKE; MBNG_LCD_CursorSet(0, 0, 0); MBNG_LCD_PrintString("*** No SD Card *** "); MBNG_LCD_ClearScreenOnNextMessage(); MUTEX_LCD_GIVE; } else if( status == 3 ) { if( !FILE_SDCardAvailable() ) { DEBUG_MSG("SD Card not found\n"); MBNG_FILE_StatusMsgSet("No SD Card"); MUTEX_LCD_TAKE; MBNG_LCD_CursorSet(0, 0, 0); MBNG_LCD_PrintString("*** No SD Card *** "); MBNG_LCD_ClearScreenOnNextMessage(); MUTEX_LCD_GIVE; } else if( !FILE_VolumeAvailable() ) { DEBUG_MSG("ERROR: SD Card contains invalid FAT!\n"); MBNG_FILE_StatusMsgSet("No FAT"); MUTEX_LCD_TAKE; MBNG_LCD_CursorSet(0, 0, 0); MBNG_LCD_PrintString("* No FAT on SD Card * "); MBNG_LCD_ClearScreenOnNextMessage(); MUTEX_LCD_GIVE; } else { MBNG_FILE_StatusMsgSet(NULL); // create the default files if they don't exist on SD Card MBNG_FILE_CreateDefaultFiles(); } hw_enabled = 1; // enable hardware after first read... } MUTEX_SDCARD_GIVE; } // MSD driver if( msd_state != MSD_DISABLED ) { MUTEX_SDCARD_TAKE; switch( msd_state ) { case MSD_SHUTDOWN: // switch back to USB MIDI MIOS32_USB_Init(1); msd_state = MSD_DISABLED; break; case MSD_INIT: // LUN not mounted yet lun_available = 0; // enable MSD USB driver MUTEX_J16_TAKE; if( MSD_Init(0) >= 0 ) msd_state = MSD_READY; else msd_state = MSD_SHUTDOWN; MUTEX_J16_GIVE; break; case MSD_READY: // service MSD USB driver MSD_Periodic_mS(); // this mechanism shuts down the MSD driver if SD card has been unmounted by OS if( lun_available && !MSD_LUN_AvailableGet(0) ) msd_state = MSD_SHUTDOWN; else if( !lun_available && MSD_LUN_AvailableGet(0) ) lun_available = 1; break; } MUTEX_SDCARD_GIVE; } } }
///////////////////////////////////////////////////////////////////////////// // This task is called periodically each mS to handle sequencer requests ///////////////////////////////////////////////////////////////////////////// static void TASK_SEQ(void *pvParameters) { portTickType xLastExecutionTime; u16 sdcard_check_ctr = 0; // Initialise the xLastExecutionTime variable on task entry xLastExecutionTime = xTaskGetTickCount(); while( 1 ) { vTaskDelayUntil(&xLastExecutionTime, 1 / portTICK_RATE_MS); // execute sequencer handler SEQ_Handler(); // send timestamped MIDI events SEQ_MIDI_OUT_Handler(); // each second: check if SD Card (still) available if( ++sdcard_check_ctr >= 1000 ) { sdcard_check_ctr = 0; // use a mutex if multiple tasks access the SD Card! MUTEX_SDCARD_TAKE; s32 status = FILE_CheckSDCard(); if( status == 1 ) { MIOS32_MIDI_SendDebugMessage("SD Card connected: %s\n", FILE_VolumeLabel()); } else if( status == 2 ) { MIOS32_MIDI_SendDebugMessage("SD Card disconnected\n"); // stop sequencer SEQ_BPM_Stop(); // change filename sprintf(MID_FILE_UI_NameGet(), "No SD Card"); } else if( status == 3 ) { if( !FILE_SDCardAvailable() ) { MIOS32_MIDI_SendDebugMessage("SD Card not found\n"); // change filename sprintf(MID_FILE_UI_NameGet(), "No SD Card"); } else if( !FILE_VolumeAvailable() ) { MIOS32_MIDI_SendDebugMessage("ERROR: SD Card contains invalid FAT!\n"); MIOS32_BOARD_LED_Set(0x1, 0x0); // turn off LED // change filename sprintf(MID_FILE_UI_NameGet(), "No FAT"); // stop sequencer SEQ_BPM_Stop(); } else { // change filename sprintf(MID_FILE_UI_NameGet(), "SDCard found"); // if in auto mode and BPM generator is clocked in slave mode: // change to master mode SEQ_BPM_CheckAutoMaster(); // reset sequencer SEQ_Reset(1); // request to play the first file SEQ_PlayFileReq(0); // start sequencer SEQ_BPM_Start(); } } MUTEX_SDCARD_GIVE; } } }