diff --git a/application.fam b/application.fam index cba99d9..7e81b6b 100644 --- a/application.fam +++ b/application.fam @@ -12,5 +12,5 @@ App( fap_weburl="https://github.com/LTVA1/flizzer_tracker", fap_icon="flizzer_tracker.png", fap_icon_assets="images", - fap_category="Media_Extra", + fap_category="Media", ) diff --git a/diskop.c b/diskop.c index 146282f..b6b8131 100644 --- a/diskop.c +++ b/diskop.c @@ -61,6 +61,44 @@ void save_instrument_inner(Stream* stream, Instrument* inst) { rwops = stream_write(stream, (uint8_t*)&inst->filter_type, sizeof(inst->filter_type)); } + if(inst->sound_engine_flags & SE_ENABLE_SAMPLE) { + rwops = stream_write(stream, (uint8_t*)&inst->sample, sizeof(inst->sample)); + } + + UNUSED(rwops); +} + +void save_sample_inner(Stream* stream, SoundEngineDPCMsample* sample) { + size_t rwops = stream_write(stream, (uint8_t*)sample->name, sizeof(sample->name)); + rwops = stream_write(stream, (uint8_t*)&sample->flags, sizeof(sample->flags)); + rwops = stream_write( + stream, + (uint8_t*)&sample->initial_delta_counter_position, + sizeof(sample->initial_delta_counter_position)); + + uint32_t length = sample->length; + + if(sample->data == NULL) { + length = 0; + } + + rwops = stream_write(stream, (uint8_t*)&length, sizeof(length)); + + if(sample->flags & SE_SAMPLE_LOOP) { + rwops = stream_write(stream, (uint8_t*)&sample->loop_start, sizeof(sample->loop_start)); + rwops = stream_write(stream, (uint8_t*)&sample->loop_end, sizeof(sample->loop_end)); + rwops = stream_write( + stream, + (uint8_t*)&sample->delta_counter_position_on_loop_start, + sizeof(sample->delta_counter_position_on_loop_start)); + } + + if(length > 0) { + for(uint32_t i = 0; i < sample->length / 8 + 1; i++) { + rwops = stream_write(stream, &sample->data[i], 1); + } + } + UNUSED(rwops); } @@ -143,6 +181,12 @@ bool save_song(FlizzerTrackerApp* tracker, FuriString* filepath) { save_instrument_inner(tracker->stream, song->instrument[i]); } + rwops = stream_write(tracker->stream, (uint8_t*)&song->num_samples, sizeof(song->num_samples)); + + for(uint16_t i = 0; i < song->num_samples; i++) { + save_sample_inner(tracker->stream, song->samples[i]); + } + file_stream_close(tracker->stream); tracker->is_saving = false; furi_string_free(filepath); @@ -166,6 +210,174 @@ bool load_song_util(FlizzerTrackerApp* tracker, FuriString* filepath) { return result; } +uint8_t get_sample(uint16_t num_channels, uint16_t bit_depth, Stream* stream) { + uint8_t sample = 32; + + bool fuck; + + if(bit_depth == 8 && num_channels == 1) //8 bit unsigned mono + { + uint8_t temp = 0; + fuck = stream_read(stream, (uint8_t*)&temp, sizeof(temp)); + + sample = temp / 4; + return sample; //scaled to 0-63 + } + + if(bit_depth == 8 && num_channels == 2) //8 bit unsigned stereo + { + uint8_t temp_r = 0; + uint8_t temp_l = 0; + fuck = stream_read(stream, (uint8_t*)&temp_r, sizeof(temp_r)); + fuck = stream_read(stream, (uint8_t*)&temp_l, sizeof(temp_l)); + + sample = ((uint16_t)temp_r + temp_l) / 2 / 4; + return sample; //scaled to 0-63 + } + + if(bit_depth == 16 && num_channels == 1) //16 bit signed mono + { + int16_t temp = 0; + fuck = stream_read(stream, (uint8_t*)&temp, sizeof(temp)); + + sample = ((int32_t)temp + 32767) / 256 / 4; + return sample; //scaled to 0-63 + } + + if(bit_depth == 16 && num_channels == 2) //16 bit signed stereo + { + int16_t temp_r = 0; + int16_t temp_l = 0; + fuck = stream_read(stream, (uint8_t*)&temp_r, sizeof(temp_r)); + fuck = stream_read(stream, (uint8_t*)&temp_l, sizeof(temp_l)); + + sample = ((int32_t)temp_r + (int32_t)temp_l + 32767 + 32767) / 2 / 4 / 256; + return sample; //scaled to 0-63 + } + + return sample; //scaled to 0-63 + UNUSED(fuck); +} + +void import_sample_inner(Stream* stream, SoundEngineDPCMsample* sample, FlizzerTrackerApp* tracker) { + uint16_t num_channels = 0; + uint16_t bit_depth = 0; + uint32_t sample_size = 0; + + bool fuck = stream_seek(stream, 22, StreamOffsetFromStart); + fuck = stream_read(stream, (uint8_t*)&num_channels, sizeof(num_channels)); + + fuck = stream_seek(stream, 34, StreamOffsetFromStart); + fuck = stream_read(stream, (uint8_t*)&bit_depth, sizeof(bit_depth)); + + char data_sig[5]; + data_sig[4] = '\0'; + + uint16_t pos = 0; + + while(strcmp(data_sig, "data") != + 0) //because not all wav files' headers are 44 bytes long! ffmpeg no exception + { + fuck = stream_seek(stream, pos, StreamOffsetFromStart); + fuck = stream_read(stream, (uint8_t*)data_sig, 4); + pos++; + } + + fuck = stream_seek( + stream, + pos + 3, + StreamOffsetFromStart); //read wav file size (offset 3 because we got one more byr from pos++ above) + fuck = stream_read(stream, (uint8_t*)&sample_size, sizeof(sample_size)); + + pos = stream_tell(stream); + + tracker->bit_depth = bit_depth; + tracker->num_channels = num_channels; + tracker->length = sample_size; + + uint32_t num_samples = sample_size / (bit_depth / 8) / num_channels; + + if(num_samples / 8 > + memmgr_get_free_heap() - 5 * 1024) //the resulting DPCM sample is too big to fit in RAM + { + return; + } + + if(sample->loop_end >= num_samples) { + sample->loop_end = num_samples - 1; + } + + if(sample->loop_start >= num_samples) { + sample->loop_start = 0; + } + + sample->length = num_samples; + + uint8_t sample_value = get_sample(num_channels, bit_depth, stream); + + sample->initial_delta_counter_position = sample_value; + + //fuck = stream_seek(stream, pos, StreamOffsetFromStart); + + if(sample->data) { + free(sample->data); + } + + sample->data = (uint8_t*)malloc(sizeof(uint8_t) * num_samples / 8 + 1); + memset(sample->data, 0, sizeof(uint8_t) * num_samples / 8 + 1); + + int8_t delta_counter = sample->initial_delta_counter_position; + + for(tracker->sample_import_progress = 0; tracker->sample_import_progress < num_samples; + tracker->sample_import_progress++) //read other samples + { + sample_value = get_sample(num_channels, bit_depth, stream); + + if(delta_counter <= sample_value) { + if(delta_counter < 63) { + delta_counter++; + sample->data[tracker->sample_import_progress / 8] |= + (1 << (tracker->sample_import_progress & 7)); + } + + else { + delta_counter--; + } + } + + else { + if(delta_counter > 0) + delta_counter--; + + else { + delta_counter++; + sample->data[tracker->sample_import_progress / 8] |= + (1 << (tracker->sample_import_progress & 7)); + } + } + } + + recalculate_dpcm_sample_delta_counter_at_loop_start(sample); + + UNUSED(fuck); + UNUSED(sample); +} + +bool load_sample_util(FlizzerTrackerApp* tracker, FuriString* filepath) { + bool open_file = file_stream_open( + tracker->stream, furi_string_get_cstr(filepath), FSAM_READ, FSOM_OPEN_ALWAYS); + + import_sample_inner(tracker->stream, tracker->song.samples[tracker->current_sample], tracker); + + tracker->is_loading_sample = false; + + file_stream_close(tracker->stream); + furi_string_free(filepath); + + UNUSED(open_file); + return true; +} + bool load_instrument_disk(TrackerSong* song, uint8_t inst, Stream* stream) { set_default_instrument(song->instrument[inst]); diff --git a/diskop.h b/diskop.h index ed8b52a..59d3848 100644 --- a/diskop.h +++ b/diskop.h @@ -8,6 +8,7 @@ bool save_instrument(FlizzerTrackerApp* tracker, FuriString* filepath); bool load_song_util(FlizzerTrackerApp* tracker, FuriString* filepath); bool load_instrument_util(FlizzerTrackerApp* tracker, FuriString* filepath); +bool load_sample_util(FlizzerTrackerApp* tracker, FuriString* filepath); void save_config(FlizzerTrackerApp* tracker); void load_config(FlizzerTrackerApp* tracker); \ No newline at end of file diff --git a/flizzer_tracker.c b/flizzer_tracker.c index 2c0729a..a226eb8 100644 --- a/flizzer_tracker.c +++ b/flizzer_tracker.c @@ -5,6 +5,7 @@ #include "util.h" #include "view/instrument_editor.h" #include "view/pattern_editor.h" +#include "view/sample_editor.h" #include "font.h" #include @@ -20,6 +21,23 @@ void draw_callback(Canvas* canvas, void* ctx) { return; } + if(tracker->is_loading_sample) { + canvas_draw_str(canvas, 10, 10, "Importing sample..."); + + canvas_draw_frame(canvas, 0, 20, 128, 10); + + uint8_t progress = 0; + + if(tracker->song.samples[tracker->current_sample]->length > 0) { + progress = (uint64_t)tracker->sample_import_progress * 126 / + tracker->song.samples[tracker->current_sample]->length; + } + + canvas_draw_box(canvas, 1, 21, progress, 8); + + return; + } + if(tracker->is_saving || tracker->is_saving_instrument) { canvas_draw_str(canvas, 10, 10, "Saving..."); return; @@ -57,6 +75,11 @@ void draw_callback(Canvas* canvas, void* ctx) { break; } + case SAMPLE_EDITOR_VIEW: { + draw_sample_view(canvas, tracker); + break; + } + default: break; } @@ -107,6 +130,44 @@ int32_t flizzer_tracker_app(void* p) { FlizzerTrackerApp* tracker = init_tracker(44100, 50, true, 1024); + //tracker->song.samples[0] = (SoundEngineDPCMsample*)malloc(sizeof(SoundEngineDPCMsample)); + + //memset(tracker->song.samples[0], 0, sizeof(SoundEngineDPCMsample)); + + tracker->song.num_samples = 1; + tracker->song.samples[0]->data = (uint8_t*)malloc(16); + + strcpy((char*)&tracker->song.samples[0]->name, "TEXT"); + + tracker->song.samples[0]->initial_delta_counter_position = 32; + tracker->song.samples[0]->delta_counter_position_on_loop_start = 32; + tracker->song.samples[0]->length = 8 * 16; + tracker->song.samples[0]->flags |= SE_SAMPLE_LOOP; + tracker->song.samples[0]->loop_start = 0; + tracker->song.samples[0]->loop_end = 8 * 16 - 1; + + for(uint8_t i = 0; i < 4; i++) { + tracker->song.samples[0]->data[i] = 0; + } + + for(uint8_t i = 4; i < 12; i++) { + tracker->song.samples[0]->data[i] = 0xff; + } + + for(uint8_t i = 12; i < 16; i++) { + tracker->song.samples[0]->data[i] = 0; + } + + tracker->song.instrument[0]->waveform = 0; + + tracker->song.instrument[0]->adsr.a = 0; + tracker->song.instrument[0]->adsr.s = 0xff; + + tracker->song.instrument[0]->sample = 0; + tracker->song.instrument[0]->sample_pointer = tracker->song.samples[0]; + tracker->song.instrument[0]->sound_engine_flags |= SE_ENABLE_SAMPLE | + SE_SAMPLE_OVERRIDE_ENVELOPE; + // Текущее событие типа кастомного типа FlizzerTrackerEvent FlizzerTrackerEvent event; @@ -159,6 +220,7 @@ int32_t flizzer_tracker_app(void* p) { if(ret && strcmp(&cpath[strlen(cpath) - 4], SONG_FILE_EXT) == 0) { bool result = load_song_util(tracker, path); + tracker->current_sample = 0; UNUSED(result); } @@ -197,7 +259,39 @@ int32_t flizzer_tracker_app(void* p) { else { furi_string_free(path); - tracker->is_loading = false; + tracker->is_loading_instrument = false; + } + } + + if(event.type == EventTypeLoadSample) { + stop_song(tracker); + + tracker->dialogs = furi_record_open(RECORD_DIALOGS); + tracker->is_loading_sample = true; + + FuriString* path; + path = furi_string_alloc(); + furi_string_set(path, FLIZZER_TRACKER_FOLDER); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, ".wav", &I_wav_icon); + browser_options.base_path = FLIZZER_TRACKER_FOLDER; + browser_options.hide_ext = false; + + bool ret = dialog_file_browser_show(tracker->dialogs, path, path, &browser_options); + + furi_record_close(RECORD_DIALOGS); + + const char* cpath = furi_string_get_cstr(path); + + if(ret && strcmp(&cpath[strlen(cpath) - 4], ".wav") == 0) { + bool result = load_sample_util(tracker, path); + UNUSED(result); + } + + else { + furi_string_free(path); + tracker->is_loading_sample = false; } } diff --git a/flizzer_tracker.h b/flizzer_tracker.h index 97269a9..80e8a57 100644 --- a/flizzer_tracker.h +++ b/flizzer_tracker.h @@ -34,6 +34,7 @@ typedef enum { EventTypeLoadInstrument, EventTypeSaveInstrument, EventTypeSetAudioMode, + EventTypeLoadSample, } EventType; typedef struct { @@ -45,6 +46,7 @@ typedef struct { typedef enum { PATTERN_VIEW, INST_EDITOR_VIEW, + SAMPLE_EDITOR_VIEW, EXPORT_WAV_VIEW, } TrackerMode; @@ -54,6 +56,7 @@ typedef enum { EDIT_SONGINFO, EDIT_INSTRUMENT, EDIT_PROGRAM, + EDIT_SAMPLE, } TrackerFocus; typedef enum { @@ -119,11 +122,27 @@ typedef enum { INST_PWMDELAY, INST_PROGRESTART, + + INST_ENABLESAMPLE, + INST_SAMPLENUMBER, + INST_SAMPLEOVERRIDEVOLUMEENVELOPE, + INST_SAMPLELOCKTOBASENOTE, + INST_PROGRAMEPERIOD, /* ========= */ INST_PARAMS, } InstrumentParam; +typedef enum { + SAMPLE_NUMBER, + SAMPLE_NAME, + SAMPLE_LOOP, + SAMPLE_LOOP_START, + SAMPLE_LOOP_END, + /* ======== */ + SAMPLE_PARAMS, +} SampleParam; + typedef struct { View* view; void* context; @@ -135,6 +154,7 @@ typedef enum { VIEW_SUBMENU_PATTERN, VIEW_SUBMENU_PATTERN_COPYPASTE, VIEW_SUBMENU_INSTRUMENT, + VIEW_SUBMENU_SAMPLE, VIEW_FILE_OVERWRITE, VIEW_INSTRUMENT_FILE_OVERWRITE, VIEW_SETTINGS, @@ -161,6 +181,11 @@ typedef enum { SUBMENU_INSTRUMENT_EXIT, } InstrumentSubmenuParams; +typedef enum { + SUBMENU_SAMPLE_LOAD, + SUBMENU_SAMPLE_EXIT, +} SampleSubmenuParams; + typedef struct { NotificationApp* notification; FuriMessageQueue* event_queue; @@ -175,6 +200,7 @@ typedef struct { Submenu* pattern_submenu; Submenu* pattern_copypaste_submenu; Submenu* instrument_submenu; + Submenu* sample_submenu; VariableItemList* settings_list; Widget* overwrite_file_widget; Widget* overwrite_instrument_file_widget; @@ -200,6 +226,8 @@ typedef struct { int16_t source_pattern_index; + uint8_t current_sample; + bool editing; bool was_editing; @@ -207,6 +235,7 @@ typedef struct { bool is_saving; bool is_loading_instrument; bool is_saving_instrument; + bool is_loading_sample; bool showing_help; bool cut_pattern; //if we need to clear the pattern we pasted from @@ -216,6 +245,11 @@ typedef struct { char eq[2]; char param[80]; char value[10]; + + uint32_t sample_import_progress; + + uint16_t sample_rate, bit_depth, num_channels; + uint32_t length; } FlizzerTrackerApp; typedef struct { diff --git a/flizzer_tracker_hal.c b/flizzer_tracker_hal.c index a23753a..d229598 100644 --- a/flizzer_tracker_hal.c +++ b/flizzer_tracker_hal.c @@ -40,12 +40,61 @@ void tracker_engine_timer_isr( void sound_engine_PWM_timer_init(bool external_audio_output) // external audio on pin PA6 { if(external_audio_output) { - furi_hal_gpio_init_ex( - &gpio_ext_pa6, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn14TIM16); - - if(furi_hal_speaker_is_mine()) { + /*if(furi_hal_speaker_is_mine()) { furi_hal_speaker_release(); + }*/ + + //LL_TIM_DisableAllOutputs(SPEAKER_PWM_TIMER); + //LL_TIM_DisableCounter(SPEAKER_PWM_TIMER); + + if(!(furi_hal_speaker_is_mine())) { + if(furi_hal_speaker_acquire(1000)) { + LL_TIM_DisableAllOutputs(SPEAKER_PWM_TIMER); + LL_TIM_DisableCounter(SPEAKER_PWM_TIMER); + + LL_TIM_InitTypeDef TIM_InitStruct = {0}; + LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; + + TIM_InitStruct.Prescaler = 0; + TIM_InitStruct.Autoreload = + 1023; // 10-bit PWM resolution at around 60 kHz PWM rate + TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; + LL_TIM_Init(SPEAKER_PWM_TIMER, &TIM_InitStruct); + + TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1; + TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE; + TIM_OC_InitStruct.CompareValue = 0; + LL_TIM_OC_Init(SPEAKER_PWM_TIMER, SPEAKER_PWM_TIMER_CHANNEL, &TIM_OC_InitStruct); + + SPEAKER_PWM_TIMER->CNT = 0; + + LL_TIM_EnableAllOutputs(SPEAKER_PWM_TIMER); + LL_TIM_EnableCounter(SPEAKER_PWM_TIMER); + } } + + furi_hal_gpio_init(&gpio_ext_pa6, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + LL_TIM_InitTypeDef TIM_InitStruct = {0}; + LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; + + TIM_InitStruct.Prescaler = 0; + TIM_InitStruct.Autoreload = 1023; // 10-bit PWM resolution at around 60 kHz PWM rate + TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; + LL_TIM_Init(SPEAKER_PWM_TIMER, &TIM_InitStruct); + + TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1; + TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE; + TIM_OC_InitStruct.CompareValue = 0; + LL_TIM_OC_Init(SPEAKER_PWM_TIMER, SPEAKER_PWM_TIMER_CHANNEL, &TIM_OC_InitStruct); + + SPEAKER_PWM_TIMER->CNT = 0; + + LL_TIM_EnableAllOutputs(SPEAKER_PWM_TIMER); + LL_TIM_EnableCounter(SPEAKER_PWM_TIMER); + + furi_hal_gpio_init(&gpio_ext_pa6, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + //furi_hal_gpio_init_ex(&gpio_ext_pa6, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn14TIM16); } else { @@ -76,7 +125,11 @@ void sound_engine_PWM_timer_init(bool external_audio_output) // external audio o } furi_hal_gpio_init(&gpio_ext_pa6, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + //furi_hal_gpio_init_ex(&gpio_ext_pa6, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn14TIM16); } + + furi_hal_gpio_init_ex( + &gpio_ext_pa6, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn14TIM16); } void sound_engine_set_audio_output(bool external_audio_output) { diff --git a/images/wav_icon.png b/images/wav_icon.png new file mode 100644 index 0000000..e527a9f Binary files /dev/null and b/images/wav_icon.png differ diff --git a/init_deinit.c b/init_deinit.c index 33e42fe..7544699 100644 --- a/init_deinit.c +++ b/init_deinit.c @@ -84,12 +84,14 @@ FlizzerTrackerApp* init_tracker( tracker->pattern_submenu = submenu_alloc(); tracker->pattern_copypaste_submenu = submenu_alloc(); tracker->instrument_submenu = submenu_alloc(); + tracker->sample_submenu = submenu_alloc(); view_set_previous_callback(submenu_get_view(tracker->pattern_submenu), submenu_exit_callback); view_set_previous_callback( submenu_get_view(tracker->pattern_copypaste_submenu), submenu_exit_callback); view_set_previous_callback( submenu_get_view(tracker->instrument_submenu), submenu_exit_callback); + view_set_previous_callback(submenu_get_view(tracker->sample_submenu), submenu_exit_callback); submenu_add_item( tracker->pattern_submenu, @@ -150,6 +152,15 @@ FlizzerTrackerApp* init_tracker( submenu_copypaste_callback, tracker); + submenu_add_item( + tracker->sample_submenu, + "Load sample", + SUBMENU_SAMPLE_LOAD, + sample_submenu_callback, + tracker); + submenu_add_item( + tracker->sample_submenu, "Exit", SUBMENU_SAMPLE_EXIT, sample_submenu_callback, tracker); + view_dispatcher_add_view( tracker->view_dispatcher, VIEW_SUBMENU_PATTERN, @@ -162,6 +173,8 @@ FlizzerTrackerApp* init_tracker( tracker->view_dispatcher, VIEW_SUBMENU_INSTRUMENT, submenu_get_view(tracker->instrument_submenu)); + view_dispatcher_add_view( + tracker->view_dispatcher, VIEW_SUBMENU_SAMPLE, submenu_get_view(tracker->sample_submenu)); load_config(tracker); @@ -262,8 +275,10 @@ void deinit_tracker(FlizzerTrackerApp* tracker) { view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_SETTINGS); view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_FILE_OVERWRITE); view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_SUBMENU_INSTRUMENT); + view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_INSTRUMENT_FILE_OVERWRITE); view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_SUBMENU_PATTERN_COPYPASTE); view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_SUBMENU_PATTERN); + view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_SUBMENU_SAMPLE); view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_KEYBOARD); view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_TRACKER); @@ -274,6 +289,7 @@ void deinit_tracker(FlizzerTrackerApp* tracker) { submenu_free(tracker->pattern_submenu); submenu_free(tracker->pattern_copypaste_submenu); submenu_free(tracker->instrument_submenu); + submenu_free(tracker->sample_submenu); widget_free(tracker->overwrite_file_widget); widget_free(tracker->overwrite_instrument_file_widget); diff --git a/input/instrument.c b/input/instrument.c index cb55ebb..214ac8b 100644 --- a/input/instrument.c +++ b/input/instrument.c @@ -354,6 +354,33 @@ void edit_instrument_param(FlizzerTrackerApp* tracker, uint8_t selected_param, i break; } + case INST_ENABLESAMPLE: { + flipbit(inst->sound_engine_flags, SE_ENABLE_SAMPLE); + break; + } + + case INST_SAMPLENUMBER: { + if((int16_t)inst->sample + (int16_t)delta >= 0 && + (int16_t)inst->sample + (int16_t)delta <= MAX_DPCM_SAMPLES - 1) { + inst->sample += delta; + } + + if(tracker->song.samples[inst->sample]->data) { + tracker->song.num_samples = inst->sample; + } + break; + } + + case INST_SAMPLEOVERRIDEVOLUMEENVELOPE: { + flipbit(inst->sound_engine_flags, SE_SAMPLE_OVERRIDE_ENVELOPE); + break; + } + + case INST_SAMPLELOCKTOBASENOTE: { + flipbit(inst->flags, TE_SAMPLE_LOCK_TO_BASE_NOTE); + break; + } + case INST_PROGRAMEPERIOD: { if((int16_t)inst->program_period + (int16_t)delta >= 0 && (int16_t)inst->program_period + (int16_t)delta <= 0xff) { @@ -377,9 +404,13 @@ void instrument_edit_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* even tracker_engine_set_song(&tracker->tracker_engine, NULL); for(int i = 1; i < SONG_MAX_CHANNELS; i++) { - tracker->tracker_engine.channel[i].channel_flags &= TEC_PLAYING; + tracker->tracker_engine.channel[i].channel_flags &= ~(TEC_PLAYING); tracker->tracker_engine.sound_engine->channel[i].frequency = 0; tracker->tracker_engine.sound_engine->channel[i].waveform = 0; + + if(tracker->tracker_engine.sound_engine->channel[i].sample) { + tracker->tracker_engine.sound_engine->channel[i].sample->playing = false; + } } Instrument* inst = tracker->song.instrument[tracker->current_instrument]; @@ -435,6 +466,9 @@ void instrument_edit_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* even case INST_ENABLEKEYSYNC: case INST_ENABLEVIBRATO: case INST_ENABLEPWM: + case INST_ENABLESAMPLE: + case INST_SAMPLEOVERRIDEVOLUMEENVELOPE: + case INST_SAMPLELOCKTOBASENOTE: case INST_PROGRESTART: { tracker->selected_param++; @@ -489,6 +523,9 @@ void instrument_edit_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* even case INST_ENABLEKEYSYNC: case INST_ENABLEVIBRATO: case INST_ENABLEPWM: + case INST_ENABLESAMPLE: + case INST_SAMPLEOVERRIDEVOLUMEENVELOPE: + case INST_SAMPLELOCKTOBASENOTE: case INST_PROGRESTART: { tracker->selected_param--; @@ -530,6 +567,10 @@ void instrument_edit_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* even tracker->inst_editor_shift = 12; } + if(tracker->selected_param > INST_PROGRESTART) { + tracker->inst_editor_shift = 18; + } + if(tracker->selected_param < INST_CURRENT_NOTE) { tracker->inst_editor_shift = 0; } diff --git a/input/sample.c b/input/sample.c new file mode 100644 index 0000000..e4b09f7 --- /dev/null +++ b/input/sample.c @@ -0,0 +1,224 @@ +#include "sample.h" +#include "songinfo.h" + +void edit_sample_param(FlizzerTrackerApp* tracker, uint8_t selected_param, int8_t init_delta) { + int32_t delta = init_delta; + + if(selected_param == SAMPLE_LOOP_END || selected_param == SAMPLE_LOOP_START) { + delta *= (int32_t)(1 << ((5 - (int32_t)tracker->current_digit) * 4)); + } + + if(selected_param == SAMPLE_NAME) { + delta *= (int32_t)(1 << ((1 - (int32_t)tracker->current_digit) * 4)); + } + + switch(selected_param) { + case SAMPLE_NUMBER: { + int8_t temp = tracker->current_sample + delta; + + if(temp < MAX_DPCM_SAMPLES && temp >= 0) { + if(temp == 0 || + (temp > 0 && + tracker->song.samples[temp - 1]->data)) //do not allow to make empty samples + { + tracker->current_sample = temp; + } + } + + break; + } + + case SAMPLE_NAME: { + text_input_set_header_text(tracker->text_input, "Sample name:"); + text_input_set_result_callback( + tracker->text_input, + return_from_keyboard_callback, + tracker, + (char*)&tracker->song.samples[tracker->current_sample]->name, + WAVE_NAME_LEN, + false); + + view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_KEYBOARD); + break; + } + + case SAMPLE_LOOP: { + flipbit(tracker->song.samples[tracker->current_sample]->flags, SE_SAMPLE_LOOP); + break; + } + + case SAMPLE_LOOP_START: { + int32_t temp = tracker->song.samples[tracker->current_sample]->loop_start; + + temp += delta; + + if(temp >= 0) { + if((uint32_t)temp < tracker->song.samples[tracker->current_sample]->length - 1 && + (uint32_t)temp < tracker->song.samples[tracker->current_sample]->loop_end) { + tracker->song.samples[tracker->current_sample]->loop_start = temp; + } + } + + recalculate_dpcm_sample_delta_counter_at_loop_start( + tracker->song.samples[tracker->current_sample]); + + break; + } + + case SAMPLE_LOOP_END: { + int32_t temp = tracker->song.samples[tracker->current_sample]->loop_end; + + temp += delta; + + if(temp >= 0) { + if((uint32_t)temp < tracker->song.samples[tracker->current_sample]->length && + (uint32_t)temp > tracker->song.samples[tracker->current_sample]->loop_start) { + tracker->song.samples[tracker->current_sample]->loop_end = temp; + } + } + + break; + } + + default: + break; + } +} + +void sample_edit_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* event) { + if(event->input.key == InputKeyOk && event->input.type == InputTypeShort && + !tracker->tracker_engine.playing) { + tracker->editing = !(tracker->editing); + return; + } + + if(event->input.key == InputKeyRight && event->input.type == InputTypeShort) { + switch(tracker->selected_param) { + default: { + tracker->current_digit++; + + if(tracker->current_digit > 0) { + tracker->selected_param++; + + tracker->current_digit = 0; + + if(tracker->selected_param > SAMPLE_PARAMS - 1) { + tracker->selected_param = 0; + } + } + + break; + } + + case SAMPLE_NUMBER: { + tracker->current_digit++; + + if(tracker->current_digit > 1) { + tracker->selected_param++; + + tracker->current_digit = 0; + + if(tracker->selected_param > SAMPLE_PARAMS - 1) { + tracker->selected_param = 0; + } + } + + break; + } + + case SAMPLE_LOOP_START: + case SAMPLE_LOOP_END: { + tracker->current_digit++; + + if(tracker->current_digit > 5) { + tracker->selected_param++; + + tracker->current_digit = 0; + + if(tracker->selected_param > SAMPLE_PARAMS - 1) { + tracker->selected_param = 0; + } + } + + break; + } + } + } + + if(event->input.key == InputKeyLeft && event->input.type == InputTypeShort) { + switch(tracker->selected_param) { + default: { + tracker->current_digit--; + + if(tracker->current_digit > 0) // unsigned int overflow + { + tracker->selected_param--; + + tracker->current_digit = 0; + + if(tracker->selected_param > SAMPLE_PARAMS - 1) // unsigned int overflow + { + tracker->selected_param = SAMPLE_PARAMS - 1; + } + } + + break; + } + + case SAMPLE_NUMBER: { + tracker->current_digit--; + + if(tracker->current_digit > 1) // unsigned int overflow + { + tracker->selected_param--; + + tracker->current_digit = 0; + + if(tracker->selected_param > SAMPLE_PARAMS - 1) // unsigned int overflow + { + tracker->selected_param = SAMPLE_PARAMS - 1; + } + } + + break; + } + + case SAMPLE_LOOP_START: + case SAMPLE_LOOP_END: { + tracker->current_digit--; + + if(tracker->current_digit > 5) // unsigned int overflow + { + tracker->selected_param--; + + tracker->current_digit = 0; + + if(tracker->selected_param > SAMPLE_PARAMS - 1) // unsigned int overflow + { + tracker->selected_param = SAMPLE_PARAMS - 1; + } + } + + break; + } + } + + return; + } + + if(event->input.key == InputKeyDown && event->input.type == InputTypeShort) { + if(tracker->editing) { + edit_sample_param(tracker, tracker->selected_param, -1); + } + + return; + } + + if(event->input.key == InputKeyUp && event->input.type == InputTypeShort) { + if(tracker->editing) { + edit_sample_param(tracker, tracker->selected_param, 1); + } + + return; + } +} \ No newline at end of file diff --git a/input/sample.h b/input/sample.h new file mode 100644 index 0000000..be1de4a --- /dev/null +++ b/input/sample.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include +#include + +#include "../flizzer_tracker.h" +#include "../sound_engine/sound_engine_defs.h" +#include "../tracker_engine/tracker_engine_defs.h" +#include "../util.h" + +void sample_edit_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* event); \ No newline at end of file diff --git a/input_event.c b/input_event.c index 55b9e7d..dbb9211 100644 --- a/input_event.c +++ b/input_event.c @@ -38,6 +38,16 @@ void return_from_keyboard_callback(void* ctx) { } } + if(tracker->focus == EDIT_SAMPLE && tracker->mode == SAMPLE_EDITOR_VIEW) { + switch(tracker->selected_param) { + case SAMPLE_NAME: { + string_length = WAVE_NAME_LEN; + string = (char*)&tracker->song.samples[tracker->current_sample]->name; + break; + } + } + } + if(string == NULL || string_length == 0) return; for(uint8_t i = 0; i < string_length; @@ -335,6 +345,37 @@ void submenu_copypaste_callback(void* context, uint32_t index) { view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_TRACKER); } +void sample_submenu_callback(void* context, uint32_t index) { + FlizzerTrackerApp* tracker = (FlizzerTrackerApp*)context; + + switch(index) { + case SUBMENU_SAMPLE_LOAD: { + FlizzerTrackerEvent event = {.type = EventTypeLoadSample, .input = {{0}}, .period = 0}; + furi_message_queue_put(tracker->event_queue, &event, FuriWaitForever); + break; + } + + case SUBMENU_SAMPLE_EXIT: { + tracker->quit = true; + + static InputEvent inevent = {.sequence = 0, .key = InputKeyLeft, .type = InputTypeMAX}; + FlizzerTrackerEvent event = { + .type = EventTypeInput, + .input = inevent, + .period = + 0}; // making an event so tracker does not wait for next keypress and exits immediately + furi_message_queue_put(tracker->event_queue, &event, FuriWaitForever); + view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_TRACKER); + break; + } + + default: + break; + } + + view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_TRACKER); +} + void audio_output_changed_callback(VariableItem* item) { FlizzerTrackerApp* tracker = (FlizzerTrackerApp*)variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -398,8 +439,18 @@ void cycle_view(FlizzerTrackerApp* tracker) { } if(tracker->mode == INST_EDITOR_VIEW) { + tracker->mode = SAMPLE_EDITOR_VIEW; + tracker->focus = EDIT_SAMPLE; + + tracker->selected_param = 0; + tracker->current_digit = 0; + + return; + } + + if(tracker->mode == SAMPLE_EDITOR_VIEW) { tracker->mode = PATTERN_VIEW; - tracker->focus = EDIT_PATTERN; + tracker->focus = EDIT_SONGINFO; if(tracker->tracker_engine.song == NULL) { stop_song(tracker); @@ -421,7 +472,8 @@ void process_input_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* event) } if(tracker->showing_help || tracker->is_loading || tracker->is_saving || - tracker->is_loading_instrument || tracker->is_saving_instrument) + tracker->is_loading_instrument || tracker->is_saving_instrument || + tracker->is_loading_sample) return; //do not react until these are finished if(event->input.key == InputKeyBack && event->input.type == InputTypeShort && @@ -462,6 +514,12 @@ void process_input_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* event) break; } + case SAMPLE_EDITOR_VIEW: { + submenu_set_selected_item(tracker->sample_submenu, SUBMENU_SAMPLE_LOAD); + view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_SUBMENU_SAMPLE); + break; + } + default: break; } @@ -495,6 +553,11 @@ void process_input_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* event) break; } + case EDIT_SAMPLE: { + sample_edit_event(tracker, event); + break; + } + default: break; } diff --git a/input_event.h b/input_event.h index c2b1950..dcba9a8 100644 --- a/input_event.h +++ b/input_event.h @@ -11,6 +11,7 @@ #include "input/instrument.h" #include "input/instrument_program.h" +#include "input/sample.h" #include "input/pattern.h" #include "input/sequence.h" #include "input/songinfo.h" @@ -36,5 +37,6 @@ uint32_t submenu_exit_callback(void* context); uint32_t submenu_settings_exit_callback(void* context); void submenu_callback(void* context, uint32_t index); void submenu_copypaste_callback(void* context, uint32_t index); +void sample_submenu_callback(void* context, uint32_t index); void audio_output_changed_callback(VariableItem* item); void process_input_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* event); \ No newline at end of file diff --git a/sound_engine/sound_engine.c b/sound_engine/sound_engine.c index 4908c92..35793d6 100644 --- a/sound_engine/sound_engine.c +++ b/sound_engine/sound_engine.c @@ -34,6 +34,8 @@ void sound_engine_init( sound_engine->sine_lut[i] = (uint8_t)((sinf(i / 64.0 * PI) + 1.0) * 127.0); } + furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL); + furi_hal_interrupt_set_isr_ex( FuriHalInterruptIdDma1Ch1, 15, sound_engine_dma_isr, sound_engine); @@ -54,7 +56,7 @@ void sound_engine_deinit(SoundEngine* sound_engine) { furi_hal_gpio_init(&gpio_ext_pa6, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } - furi_hal_interrupt_set_isr_ex(FuriHalInterruptIdDma1Ch1, 13, NULL, NULL); + furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL); sound_engine_stop(); sound_engine_deinit_timer(); } @@ -108,6 +110,8 @@ void sound_engine_fill_buffer( SoundEngineChannel* channel = &sound_engine->channel[chan]; if(channel->frequency > 0) { + channel->sync_bit = 0; + uint32_t prev_acc = channel->accumulator; channel->accumulator += channel->frequency; @@ -127,6 +131,22 @@ void sound_engine_fill_buffer( channel_output[chan] = sound_engine_osc(sound_engine, channel, prev_acc) - WAVE_AMP / 2; + if(channel->flags & SE_ENABLE_SAMPLE) { + channel->sample->accumulator += channel->sample->frequency; + } + + if((channel->flags & SE_ENABLE_SAMPLE) && + !(channel->flags & SE_SAMPLE_OVERRIDE_ENVELOPE)) { + int32_t dpcm = 0; + + for(uint8_t i = 0; i < (channel->sample->accumulator >> (DPCM_ACC_BITS - 1)); + i++) { + dpcm = sound_engine_get_dpcm(channel->sample, true); + } + + channel_output[chan] += dpcm; + } + if(channel->flags & SE_ENABLE_RING_MOD) { uint8_t ring_mod_src = channel->ring_mod == 0xff ? i : channel->ring_mod; channel_output[chan] = @@ -134,7 +154,23 @@ void sound_engine_fill_buffer( } channel_output_final[chan] = sound_engine_cycle_and_output_adsr( - channel_output[chan], sound_engine, &channel->adsr, &channel->flags); + channel_output[chan], sound_engine, &channel->adsr, channel, &channel->flags); + + if((channel->flags & SE_ENABLE_SAMPLE) && + (channel->flags & SE_SAMPLE_OVERRIDE_ENVELOPE)) { + int32_t dpcm = 0; + + for(uint8_t i = 0; i < (channel->sample->accumulator >> (DPCM_ACC_BITS - 1)); + i++) { + dpcm = sound_engine_get_dpcm(channel->sample, true); + } + + channel_output_final[chan] += dpcm * channel->adsr.volume / MAX_ADSR_VOLUME; + } + + if(channel->flags & SE_ENABLE_SAMPLE) { + channel->sample->accumulator &= DPCM_ACC_LENGTH - 1; + } if(channel->flags & SE_ENABLE_FILTER) { if(channel->filter_mode != 0) { diff --git a/sound_engine/sound_engine.h b/sound_engine/sound_engine.h index a16d02f..7203706 100644 --- a/sound_engine/sound_engine.h +++ b/sound_engine/sound_engine.h @@ -5,6 +5,7 @@ #include "sound_engine_defs.h" #include "sound_engine_filter.h" #include "sound_engine_osc.h" +#include "sound_engine_dpcm.h" void sound_engine_init( SoundEngine* sound_engine, diff --git a/sound_engine/sound_engine_adsr.c b/sound_engine/sound_engine_adsr.c index 21eb8f7..a6ffdc5 100644 --- a/sound_engine/sound_engine_adsr.c +++ b/sound_engine/sound_engine_adsr.c @@ -4,6 +4,7 @@ int32_t sound_engine_cycle_and_output_adsr( int32_t input, SoundEngine* eng, SoundEngineADSR* adsr, + SoundEngineChannel* ch, uint16_t* flags) { switch(adsr->envelope_state) { case ATTACK: { @@ -45,14 +46,25 @@ int32_t sound_engine_cycle_and_output_adsr( } else { - adsr->envelope_state = DONE; - *flags &= ~SE_ENABLE_GATE; - adsr->envelope = 0; + if(!ch->sample) { + adsr->envelope_state = DONE; + *flags &= ~SE_ENABLE_GATE; + adsr->envelope = 0; + } + + if(ch->sample && (!(ch->sample->flags & SE_SAMPLE_LOOP))) { + ch->sample->playing = false; + + adsr->envelope_state = DONE; + *flags &= ~SE_ENABLE_GATE; + adsr->envelope = 0; + } } break; } } - return (int32_t)((int32_t)input * (int32_t)(adsr->envelope >> 10) / (int32_t)(MAX_ADSR >> 10) * (int32_t)adsr->volume / (int32_t)MAX_ADSR_VOLUME); + return (int32_t)((int32_t)input * (int32_t)(adsr->envelope >> 10) / (int32_t)(MAX_ADSR >> 10) * + (int32_t)adsr->volume / (int32_t)MAX_ADSR_VOLUME); } \ No newline at end of file diff --git a/sound_engine/sound_engine_adsr.h b/sound_engine/sound_engine_adsr.h index 9b915c6..32188da 100644 --- a/sound_engine/sound_engine_adsr.h +++ b/sound_engine/sound_engine_adsr.h @@ -6,4 +6,5 @@ int32_t sound_engine_cycle_and_output_adsr( int32_t input, SoundEngine* eng, SoundEngineADSR* adsr, + SoundEngineChannel* ch, uint16_t* flags); \ No newline at end of file diff --git a/sound_engine/sound_engine_defs.h b/sound_engine/sound_engine_defs.h index bfc9b14..a08a4e3 100644 --- a/sound_engine/sound_engine_defs.h +++ b/sound_engine/sound_engine_defs.h @@ -11,12 +11,19 @@ #define ACC_BITS 23 #define ACC_LENGTH (1 << (ACC_BITS - 1)) +#define DPCM_ACC_BITS 16 +#define DPCM_ACC_LENGTH (1 << (DPCM_ACC_BITS - 1)) + #define OUTPUT_BITS 16 #define WAVE_AMP (1 << OUTPUT_BITS) #define SINE_LUT_SIZE 256 #define SINE_LUT_BITDEPTH 8 +#define WAVE_NAME_LEN (32 - 8 + 1) +#define DELTA_COUNTER_MIDDLE ((1 << 6) / 2) +#define DELTA_COUNTER_MAX ((1 << 6) - 1) + #define MAX_ADSR (0xff << 17) #define MAX_ADSR_VOLUME 0x80 #define BASE_FREQ 22050 @@ -41,8 +48,14 @@ typedef enum { SE_ENABLE_RING_MOD = 4, SE_ENABLE_HARD_SYNC = 8, SE_ENABLE_KEYDOWN_SYNC = 16, // sync oscillators on keydown + SE_ENABLE_SAMPLE = 32, + SE_SAMPLE_OVERRIDE_ENVELOPE = 64, } SoundEngineFlags; +typedef enum { + SE_SAMPLE_LOOP = 1, +} SoundEngineDPCMsampleFlags; + typedef enum { FIL_OUTPUT_LOWPASS = 1, FIL_OUTPUT_HIGHPASS = 2, @@ -72,6 +85,21 @@ typedef struct { int32_t cutoff, resonance, low, high, band; } SoundEngineFilter; +typedef struct { + uint32_t length, loop_start, loop_end, position; + uint8_t delta_counter; //0-63 + uint8_t initial_delta_counter_position; + uint8_t delta_counter_position_on_loop_start; + char name[WAVE_NAME_LEN]; + uint8_t* data; + + uint8_t flags; + bool playing; + + uint32_t accumulator; + uint32_t frequency; +} SoundEngineDPCMsample; + typedef struct { uint32_t accumulator; uint32_t frequency; @@ -88,6 +116,8 @@ typedef struct { uint8_t filter_mode; SoundEngineFilter filter; + + SoundEngineDPCMsample* sample; } SoundEngineChannel; typedef struct { diff --git a/sound_engine/sound_engine_dpcm.c b/sound_engine/sound_engine_dpcm.c new file mode 100644 index 0000000..649d8c7 --- /dev/null +++ b/sound_engine/sound_engine_dpcm.c @@ -0,0 +1,47 @@ +#include "sound_engine_dpcm.h" + +int32_t sound_engine_get_dpcm(SoundEngineDPCMsample* sample, bool advance) { + if(!(sample->playing)) return 0; + + if(advance) { + if(sample->flags & SE_SAMPLE_LOOP) { + if(sample->position > sample->loop_end) { + sample->position = sample->loop_start; + sample->delta_counter = sample->delta_counter_position_on_loop_start; + } + } + + if(sample->position > sample->length) { + sample->playing = false; + return 0; + } + + if(sample->data[sample->position >> 3] & (1 << (sample->position & 7))) { + sample->delta_counter++; + } + + else { + sample->delta_counter--; + } + + sample->position++; + } + + return (int32_t)(sample->delta_counter << (8 + 2)) - (DELTA_COUNTER_MIDDLE << (8 + 2)); +} + +void recalculate_dpcm_sample_delta_counter_at_loop_start(SoundEngineDPCMsample* sample) { + uint8_t delta_counter = sample->initial_delta_counter_position; + + for(uint32_t i = 0; i < sample->loop_start; i++) { + if(sample->data[i >> 3] & (1 << (i & 7))) { + delta_counter++; + } + + else { + delta_counter--; + } + } + + sample->delta_counter_position_on_loop_start = delta_counter; +} \ No newline at end of file diff --git a/sound_engine/sound_engine_dpcm.h b/sound_engine/sound_engine_dpcm.h new file mode 100644 index 0000000..8c3606e --- /dev/null +++ b/sound_engine/sound_engine_dpcm.h @@ -0,0 +1,6 @@ +#pragma once + +#include "sound_engine_defs.h" + +int32_t sound_engine_get_dpcm(SoundEngineDPCMsample* sample, bool advance); +void recalculate_dpcm_sample_delta_counter_at_loop_start(SoundEngineDPCMsample* sample); \ No newline at end of file diff --git a/tracker_engine/diskop.c b/tracker_engine/diskop.c index 46de567..5c49d29 100644 --- a/tracker_engine/diskop.c +++ b/tracker_engine/diskop.c @@ -54,7 +54,51 @@ void load_instrument_inner(Stream* stream, Instrument* inst, uint8_t version) { rwops = stream_read(stream, (uint8_t*)&inst->filter_type, sizeof(inst->filter_type)); } + if(version > 1) { + if(inst->sound_engine_flags & SE_ENABLE_SAMPLE) { + rwops = stream_read(stream, (uint8_t*)&inst->sample, sizeof(inst->sample)); + } + } + + UNUSED(rwops); +} + +void load_sample_inner(Stream* stream, SoundEngineDPCMsample* sample, uint8_t version) { + size_t rwops = stream_read(stream, (uint8_t*)sample->name, sizeof(sample->name)); + rwops = stream_read(stream, (uint8_t*)&sample->flags, sizeof(sample->flags)); + rwops = stream_read( + stream, + (uint8_t*)&sample->initial_delta_counter_position, + sizeof(sample->initial_delta_counter_position)); + + rwops = stream_read(stream, (uint8_t*)&sample->length, sizeof(sample->length)); + + if(sample->data) { + free(sample->data); + } + + if(sample->flags & SE_SAMPLE_LOOP) { + rwops = stream_read(stream, (uint8_t*)&sample->loop_start, sizeof(sample->loop_start)); + rwops = stream_read(stream, (uint8_t*)&sample->loop_end, sizeof(sample->loop_end)); + rwops = stream_read( + stream, + (uint8_t*)&sample->delta_counter_position_on_loop_start, + sizeof(sample->delta_counter_position_on_loop_start)); + } + + if(sample->length > 0) { + sample->data = (uint8_t*)malloc(sample->length / 8 + 1); + + for(uint32_t i = 0; i < sample->length / 8 + 1; i += 64) { + rwops = stream_read( + stream, + &sample->data[i], + ((i + 64) < (sample->length / 8 + 1)) ? 64 : (sample->length / 8 + 1 - i)); + } + } + UNUSED(rwops); + UNUSED(version); } bool load_song_inner(TrackerSong* song, Stream* stream) { @@ -106,6 +150,40 @@ bool load_song_inner(TrackerSong* song, Stream* stream) { song->instrument[i] = (Instrument*)malloc(sizeof(Instrument)); set_default_instrument(song->instrument[i]); load_instrument_inner(stream, song->instrument[i], version); + + if(song->instrument[i]->sound_engine_flags & SE_ENABLE_SAMPLE) { + if(song->samples[i] == NULL) { + song->samples[i] = (SoundEngineDPCMsample*)malloc(sizeof(SoundEngineDPCMsample)); + memset(song->samples[i], 0, sizeof(SoundEngineDPCMsample)); + } + + song->instrument[i]->sample_pointer = song->samples[i]; + } + } + + if(version > 1) { + rwops = stream_read(stream, &song->num_samples, sizeof(song->num_samples)); + + if(song->num_samples == 0) { + song->samples[0] = (SoundEngineDPCMsample*)malloc(sizeof(SoundEngineDPCMsample)); + memset(song->samples[0], 0, sizeof(SoundEngineDPCMsample)); + } + + for(uint8_t i = 0; i < song->num_samples; i++) { + if(song->samples[i] == NULL) { + song->samples[i] = (SoundEngineDPCMsample*)malloc(sizeof(SoundEngineDPCMsample)); + memset(song->samples[i], 0, sizeof(SoundEngineDPCMsample)); + } + + load_sample_inner(stream, song->samples[i], version); + } + } + + else { + if(song->samples[0] == NULL) { + song->samples[0] = (SoundEngineDPCMsample*)malloc(sizeof(SoundEngineDPCMsample)); + memset(song->samples[0], 0, sizeof(SoundEngineDPCMsample)); + } } UNUSED(rwops); diff --git a/tracker_engine/diskop.h b/tracker_engine/diskop.h index bed7c01..e282261 100644 --- a/tracker_engine/diskop.h +++ b/tracker_engine/diskop.h @@ -9,4 +9,5 @@ bool load_song(TrackerSong* song, Stream* stream); bool load_instrument(Instrument* inst, Stream* stream); -void load_instrument_inner(Stream* stream, Instrument* inst, uint8_t version); \ No newline at end of file +void load_instrument_inner(Stream* stream, Instrument* inst, uint8_t version); +void load_sample_inner(Stream* stream, SoundEngineDPCMsample* sample, uint8_t version); \ No newline at end of file diff --git a/tracker_engine/do_effects.c b/tracker_engine/do_effects.c index a556840..ab0db15 100644 --- a/tracker_engine/do_effects.c +++ b/tracker_engine/do_effects.c @@ -427,6 +427,19 @@ void do_command( break; } + case TE_EFFECT_SET_DPCM_SAMPLE: { + if(tick == 0) { + if(tracker_engine->song->samples[(opcode & 0xff)] != 0) { + se_channel->sample = tracker_engine->song->samples[(opcode & 0xff)]; + se_channel->sample->delta_counter = + se_channel->sample->initial_delta_counter_position; + se_channel->sample->position = 0; + } + } + + break; + } + case TE_EFFECT_PORTA_UP_SEMITONE: { uint32_t prev = te_channel->note; diff --git a/tracker_engine/tracker_engine.c b/tracker_engine/tracker_engine.c index 402a6d6..c52ad03 100644 --- a/tracker_engine/tracker_engine.c +++ b/tracker_engine/tracker_engine.c @@ -21,12 +21,25 @@ void tracker_engine_deinit_song(TrackerSong* song, bool free_song) { for(int i = 0; i < MAX_PATTERNS; i++) { if(song->pattern[i].step != NULL) { free(song->pattern[i].step); + song->pattern[i].step = NULL; } } for(int i = 0; i < MAX_INSTRUMENTS; i++) { if(song->instrument[i] != NULL) { free(song->instrument[i]); + song->instrument[i] = NULL; + } + } + + for(int i = 0; i < MAX_DPCM_SAMPLES; i++) { + if(song->samples[i] != NULL) { + if(song->samples[i]->data) { + free(song->samples[i]->data); + } + + free(song->samples[i]); + song->samples[i] = NULL; } } @@ -196,6 +209,54 @@ void tracker_engine_trigger_instrument_internal( se_channel->lfsr = RANDOM_SEED; } + if((pinst->sound_engine_flags & SE_ENABLE_SAMPLE) && + !(te_channel->channel_flags & TEC_DISABLED)) { + if(tracker_engine->song) { + if(tracker_engine->song->samples[pinst->sample] != 0) { + se_channel->sample = tracker_engine->song->samples[pinst->sample]; + se_channel->sample->delta_counter = + se_channel->sample->initial_delta_counter_position; + se_channel->sample->position = 0; + se_channel->sample->playing = true; + + se_channel->sample->accumulator = 0; + + if(te_channel->flags & TE_SAMPLE_LOCK_TO_BASE_NOTE) { + se_channel->sample->frequency = DPCM_ACC_LENGTH; + } + + else { + se_channel->sample->frequency = + (uint64_t)get_freq(te_channel->note) * DPCM_ACC_LENGTH / + get_freq((pinst->base_note << 8) + pinst->finetune); + } + } + } + + else { + if(pinst->sample_pointer != 0) { + te_channel->sample = pinst->sample_pointer; + se_channel->sample = te_channel->sample; + se_channel->sample->delta_counter = + se_channel->sample->initial_delta_counter_position; + se_channel->sample->position = 0; + se_channel->sample->playing = true; + + se_channel->sample->accumulator = 0; + + if(te_channel->flags & TE_SAMPLE_LOCK_TO_BASE_NOTE) { + se_channel->sample->frequency = DPCM_ACC_LENGTH; + } + + else { + se_channel->sample->frequency = + (uint64_t)get_freq(te_channel->note) * DPCM_ACC_LENGTH / + get_freq((pinst->base_note << 8) + pinst->finetune); + } + } + } + } + if(pinst->flags & TE_SET_CUTOFF) { te_channel->filter_cutoff = ((uint16_t)pinst->filter_cutoff << 3); te_channel->filter_resonance = (uint16_t)pinst->filter_resonance; @@ -227,13 +288,13 @@ void tracker_engine_trigger_instrument_internal( se_channel->adsr.d = pinst->adsr.d; se_channel->adsr.s = pinst->adsr.s; se_channel->adsr.r = pinst->adsr.r; - se_channel->adsr.volume = pinst->adsr.volume; - se_channel->adsr.volume = (int32_t)se_channel->adsr.volume * - (int32_t)tracker_engine->master_volume / MAX_ADSR_VOLUME; + se_channel->adsr.volume = + pinst->adsr.volume * (int32_t)tracker_engine->master_volume / MAX_ADSR_VOLUME; + //se_channel->adsr.volume = (int32_t)se_channel->adsr.volume; - te_channel->volume = pinst->adsr.volume; te_channel->volume = - (int32_t)te_channel->volume * (int32_t)tracker_engine->master_volume / MAX_ADSR_VOLUME; + pinst->adsr.volume * (int32_t)tracker_engine->master_volume / MAX_ADSR_VOLUME; + //te_channel->volume = (int32_t)te_channel->volume; sound_engine_enable_gate( tracker_engine->sound_engine, &tracker_engine->sound_engine->channel[chan], true); @@ -449,11 +510,27 @@ void tracker_engine_advance_channel(TrackerEngine* tracker_engine, uint8_t chan) chn_note = ((12 * 7 + 11) << 8); // highest note is B-7 } + if(te_channel->instrument) { + if(te_channel->instrument->sample_pointer) { + if(te_channel->flags & TE_SAMPLE_LOCK_TO_BASE_NOTE) { + te_channel->instrument->sample_pointer->frequency = DPCM_ACC_LENGTH; + } + + else { + te_channel->instrument->sample_pointer->frequency = + (uint64_t)get_freq(chn_note) * DPCM_ACC_LENGTH / + get_freq( + (te_channel->instrument->base_note << 8) + + te_channel->instrument->finetune); + } + } + } + tracker_engine_set_note(tracker_engine, chan, (uint16_t)chn_note, false); } if(tracker_engine->channel[chan].channel_flags & - TEC_DISABLED) // so we can't set some non-zero volme from inst program too + TEC_DISABLED) // so we can't set some non-zero volume from inst program too { tracker_engine->sound_engine->channel[chan].adsr.volume = 0; } diff --git a/tracker_engine/tracker_engine_defs.h b/tracker_engine/tracker_engine_defs.h index ef92b1a..8ea4f2e 100644 --- a/tracker_engine/tracker_engine_defs.h +++ b/tracker_engine/tracker_engine_defs.h @@ -12,6 +12,7 @@ #define SONG_MAX_CHANNELS NUM_CHANNELS #define MAX_INSTRUMENTS 31 +#define MAX_DPCM_SAMPLES 32 #define MAX_PATTERN_LENGTH 256 #define MAX_PATTERNS 256 #define MAX_SEQUENCE_LENGTH 256 @@ -28,7 +29,7 @@ #define INST_FILE_SIG "FZT!INST" #define INST_FILE_EXT ".fzi" -#define TRACKER_ENGINE_VERSION 1 +#define TRACKER_ENGINE_VERSION 2 #define MIDDLE_C (12 * 4) #define MAX_NOTE (12 * 7 + 11) @@ -40,6 +41,7 @@ typedef enum { TE_SET_CUTOFF = 8, TE_SET_PW = 16, TE_RETRIGGER_ON_SLIDE = 32, // call trigger instrument function even if slide command is there + TE_SAMPLE_LOCK_TO_BASE_NOTE = 64, } TrackerEngineFlags; typedef enum { @@ -90,9 +92,8 @@ typedef enum { TE_EFFECT_SET_SUSTAIN = 0x1700, // Nxx TE_EFFECT_SET_RELEASE = 0x1800, // Oxx TE_EFFECT_PROGRAM_RESTART = 0x1900, // Pxx - /* - TE_EFFECT_ = 0x1a00, //Qxx - */ + + TE_EFFECT_SET_DPCM_SAMPLE = 0x1a00, //Qxx TE_EFFECT_SET_RING_MOD_SRC = 0x1b00, // Rxx TE_EFFECT_SET_HARD_SYNC_SRC = 0x1c00, // Sxx @@ -146,6 +147,9 @@ typedef struct { uint8_t base_note; int8_t finetune; + + uint8_t sample; + SoundEngineDPCMsample* sample_pointer; } Instrument; typedef struct { @@ -175,6 +179,9 @@ typedef struct { uint16_t pw; uint8_t slide_speed; + + uint8_t sample_index; + SoundEngineDPCMsample* sample; } TrackerEngineChannel; typedef struct { @@ -208,6 +215,9 @@ typedef struct { uint8_t speed, rate; uint8_t loop_start, loop_end; + + SoundEngineDPCMsample* samples[MAX_DPCM_SAMPLES]; + uint8_t num_samples; } TrackerSong; typedef struct { diff --git a/util.c b/util.c index bbf3119..d9fa211 100644 --- a/util.c +++ b/util.c @@ -198,5 +198,10 @@ void set_default_song(FlizzerTrackerApp* tracker) { set_default_instrument(tracker->song.instrument[0]); + for(uint8_t i = 0; i < MAX_DPCM_SAMPLES; i++) { + tracker->song.samples[i] = (SoundEngineDPCMsample*)malloc(sizeof(SoundEngineDPCMsample)); + memset(tracker->song.samples[i], 0, sizeof(SoundEngineDPCMsample)); + } + tracker->tracker_engine.playing = false; } \ No newline at end of file diff --git a/view/instrument_editor.c b/view/instrument_editor.c index b931a5d..8872ea5 100644 --- a/view/instrument_editor.c +++ b/view/instrument_editor.c @@ -140,6 +140,10 @@ static const char* instrument_editor_params_description[] = { "PWM DEPTH", "PWM DELAY (IN TICKS)", "DON'T RESTART PROGRAM ON KEYDOWN", + "ENABLE DPCM SAMPLE", + "SAMPLE NUMBER", + "SAMPLE:OVERRIDE VOLUME ENVELOPE", + "SAMPLE:LOCK TO BASE NOTE", "PROG.PERIOD (00 = PROGRAM OFF)", }; @@ -220,30 +224,40 @@ void draw_instrument_view(Canvas* canvas, FlizzerTrackerApp* tracker) { } } - draw_inst_text_two_digits( - tracker, - canvas, - EDIT_INSTRUMENT, - INST_SLIDESPEED, - "SL.SPD:", - 0, - 17 - shift, - inst->slide_speed); + if(shift < 18) { + draw_inst_text_two_digits( + tracker, + canvas, + EDIT_INSTRUMENT, + INST_SLIDESPEED, + "SL.SPD:", + 0, + 17 - shift, + inst->slide_speed); - draw_inst_flag( - tracker, canvas, EDIT_INSTRUMENT, INST_SETPW, "PW:", 36, 17 - shift, inst->flags, TE_SET_PW); - draw_inst_text_two_digits( - tracker, canvas, EDIT_INSTRUMENT, INST_PW, "", 54, 17 - shift, inst->pw); - draw_inst_flag( - tracker, - canvas, - EDIT_INSTRUMENT, - INST_SETCUTOFF, - "CUT", - 61, - 17 - shift, - inst->flags, - TE_SET_CUTOFF); + draw_inst_flag( + tracker, + canvas, + EDIT_INSTRUMENT, + INST_SETPW, + "PW:", + 36, + 17 - shift, + inst->flags, + TE_SET_PW); + draw_inst_text_two_digits( + tracker, canvas, EDIT_INSTRUMENT, INST_PW, "", 54, 17 - shift, inst->pw); + draw_inst_flag( + tracker, + canvas, + EDIT_INSTRUMENT, + INST_SETCUTOFF, + "CUT", + 61, + 17 - shift, + inst->flags, + TE_SET_CUTOFF); + } draw_inst_flag( tracker, @@ -484,6 +498,51 @@ void draw_instrument_view(Canvas* canvas, FlizzerTrackerApp* tracker) { TE_PROG_NO_RESTART); } + if(shift >= 18) { + draw_inst_flag( + tracker, + canvas, + EDIT_INSTRUMENT, + INST_ENABLESAMPLE, + "SAMPLE:", + 0, + 65 + 6 - shift, + inst->sound_engine_flags, + SE_ENABLE_SAMPLE); + + draw_inst_text_two_digits( + tracker, + canvas, + EDIT_INSTRUMENT, + INST_SAMPLENUMBER, + "", + 34, + 65 + 6 - shift, + inst->sample); + + draw_inst_flag( + tracker, + canvas, + EDIT_INSTRUMENT, + INST_SAMPLEOVERRIDEVOLUMEENVELOPE, + "OENV", + 41, + 65 + 6 - shift, + inst->sound_engine_flags, + SE_SAMPLE_OVERRIDE_ENVELOPE); + + draw_inst_flag( + tracker, + canvas, + EDIT_INSTRUMENT, + INST_SAMPLELOCKTOBASENOTE, + "L", + 64, + 65 + 6 - shift, + inst->flags, + TE_SAMPLE_LOCK_TO_BASE_NOTE); + } + draw_inst_text_two_digits( tracker, canvas, diff --git a/view/instrument_editor.h b/view/instrument_editor.h index b8656c0..03fb267 100644 --- a/view/instrument_editor.h +++ b/view/instrument_editor.h @@ -8,4 +8,14 @@ #include void draw_instrument_view(Canvas* canvas, FlizzerTrackerApp* tracker); -void draw_instrument_program_view(Canvas* canvas, FlizzerTrackerApp* tracker); \ No newline at end of file +void draw_instrument_program_view(Canvas* canvas, FlizzerTrackerApp* tracker); +void draw_inst_flag( + FlizzerTrackerApp* tracker, + Canvas* canvas, + uint8_t focus, + uint8_t param, + const char* text, + uint8_t x, + uint8_t y, + uint16_t flags, + uint16_t mask); \ No newline at end of file diff --git a/view/opcode_description.c b/view/opcode_description.c index f42ef20..159d6c7 100644 --- a/view/opcode_description.c +++ b/view/opcode_description.c @@ -51,6 +51,9 @@ static const OpcodeDescription opcode_desc[] = { {TE_EFFECT_SET_SUSTAIN, 0x7f00, "SET ENVELOPE SUSTAIN", "ADSR S"}, {TE_EFFECT_SET_RELEASE, 0x7f00, "SET ENVELOPE RELEASE", "ADSR R"}, {TE_EFFECT_PROGRAM_RESTART, 0x7f00, "RESTART INSTRUMENT PROGRAM", "P.RES."}, + + {TE_EFFECT_SET_DPCM_SAMPLE, 0x7f00, "SET DPCM SAMPLE", "SAMPLE"}, + {TE_EFFECT_SET_RING_MOD_SRC, 0x7f00, "SET RING MODULATION SOURCE CH.", "R.SRC"}, {TE_EFFECT_SET_HARD_SYNC_SRC, 0x7f00, "SET HARD SYNC SOURCE CHANNEL", "S.SRC"}, {TE_EFFECT_PORTA_UP_SEMITONE, 0x7f00, "PORTAMENTO UP (SEMITONES)", "PU.SEM"}, diff --git a/view/pattern_editor.c b/view/pattern_editor.c index 19c62d9..d8450cb 100644 --- a/view/pattern_editor.c +++ b/view/pattern_editor.c @@ -276,6 +276,12 @@ uint32_t calculate_song_size(TrackerSong* song) { SONG_HEADER_SIZE + sizeof(Instrument) * song->num_instruments + sizeof(TrackerSongPatternStep) * song->num_patterns * song->pattern_length + sizeof(TrackerSongSequenceStep) * song->num_sequence_steps; + + song_size += sizeof(SoundEngineDPCMsample) * song->num_samples; + + for(uint8_t i = 0; i < song->num_samples; i++) { + song_size += (song->samples[i]->length >> 3); + } return song_size; } @@ -306,6 +312,12 @@ void draw_generic_n_digit_field( } } + if(tracker->focus == EDIT_SAMPLE) { + if(param != SAMPLE_NAME) { + select_string = false; + } + } + if(!(select_string)) { if(tracker->focus == EDIT_INSTRUMENT && param == INST_CURRENTINSTRUMENT) { canvas_draw_box(canvas, x + strlen(text) * 4 - digits * 4 - 1, y - 6, 5, 7); @@ -341,6 +353,12 @@ void draw_generic_n_digit_field( } } + if(tracker->focus == EDIT_SAMPLE) { + if(param != SAMPLE_NAME) { + select_string = false; + } + } + if(!(select_string)) { if(tracker->focus == EDIT_INSTRUMENT && param == INST_CURRENTINSTRUMENT) { canvas_draw_frame(canvas, x + strlen(text) * 4 - digits * 4 - 1, y - 6, 5, 7); diff --git a/view/sample_editor.c b/view/sample_editor.c new file mode 100644 index 0000000..efd6963 --- /dev/null +++ b/view/sample_editor.c @@ -0,0 +1,70 @@ +#include "sample_editor.h" + +void draw_sample_view(Canvas* canvas, FlizzerTrackerApp* tracker) { + canvas_draw_line(canvas, 0, 32, 127, 32); + + char buffer[30]; + + snprintf( + buffer, + sizeof(buffer), + "%d %d %ld", + tracker->bit_depth, + tracker->num_channels, + tracker->length); + canvas_draw_str(canvas, 0, 10, buffer); + + snprintf(buffer, sizeof(buffer), "WAVE:%02X", tracker->current_sample); + draw_generic_n_digit_field(tracker, canvas, EDIT_SAMPLE, SAMPLE_NUMBER, buffer, 0, 39, 2); + + snprintf(buffer, sizeof(buffer), "%s", tracker->song.samples[tracker->current_sample]->name); + draw_generic_n_digit_field(tracker, canvas, EDIT_SAMPLE, SAMPLE_NAME, buffer, 4 * 8, 39, 1); + + snprintf( + buffer, + sizeof(buffer), + "LENGTH:%06lX", + tracker->song.samples[tracker->current_sample]->length); + canvas_draw_str(canvas, 0, 39 + 6, buffer); + + snprintf( + buffer, + sizeof(buffer), + "SIZE:%06lX BYTES", + (tracker->song.samples[tracker->current_sample]->length >> 3)); + canvas_draw_str(canvas, 63 - 2, 39 + 6, buffer); + + draw_inst_flag( + tracker, + canvas, + EDIT_SAMPLE, + SAMPLE_LOOP, + "LOOP", + 0, + 39 + 6 + 6, + tracker->song.samples[tracker->current_sample]->flags, + SE_SAMPLE_LOOP); + + snprintf( + buffer, + sizeof(buffer), + "START:%06lX", + tracker->song.samples[tracker->current_sample]->loop_start); + draw_generic_n_digit_field( + tracker, canvas, EDIT_SAMPLE, SAMPLE_LOOP_START, buffer, 4 * 7, 39 + 6 + 6, 6); + + snprintf( + buffer, + sizeof(buffer), + "END:%06lX", + tracker->song.samples[tracker->current_sample]->loop_end); + draw_generic_n_digit_field( + tracker, + canvas, + EDIT_SAMPLE, + SAMPLE_LOOP_END, + buffer, + 4 * 6 + 4 * (6 + 6 + 2), + 39 + 6 + 6, + 6); +} \ No newline at end of file diff --git a/view/sample_editor.h b/view/sample_editor.h new file mode 100644 index 0000000..d880577 --- /dev/null +++ b/view/sample_editor.h @@ -0,0 +1,11 @@ +#pragma once + +#include "../flizzer_tracker.h" +#include "../tracker_engine/tracker_engine_defs.h" +#include "instrument_editor.h" +#include "pattern_editor.h" + +#include +#include + +void draw_sample_view(Canvas* canvas, FlizzerTrackerApp* tracker); \ No newline at end of file