Просмотр исходного кода

file saving (no loading rn, haven't tested but looks about right in hex editor), instrument program editing

LTVA1 3 лет назад
Родитель
Сommit
01daf5fad9

+ 0 - 3
application.fam

@@ -4,9 +4,6 @@ App(
     apptype=FlipperAppType.EXTERNAL,
     entry_point="flizzer_tracker_app",
     cdefines=["APP_FLIZZER_TRACKER"],
-    requires=[
-        "gui", "input"
-    ],
     stack_size=1024,
     order=90,
 	fap_icon="flizzer_tracker.png",

+ 121 - 1
diskop.c

@@ -1 +1,121 @@
-#include "diskop.h"
+#include "diskop.h"
+
+void save_instrument_inner(Stream* stream, Instrument* inst)
+{
+    size_t rwops = stream_write(stream, (uint8_t*)inst->name, sizeof(inst->name));
+    rwops = stream_write(stream, (uint8_t*)&inst->waveform, sizeof(inst->waveform));
+    rwops = stream_write(stream, (uint8_t*)&inst->flags, sizeof(inst->flags));
+    rwops = stream_write(stream, (uint8_t*)&inst->sound_engine_flags, sizeof(inst->sound_engine_flags));
+
+    rwops = stream_write(stream, (uint8_t*)&inst->base_note, sizeof(inst->base_note));
+    rwops = stream_write(stream, (uint8_t*)&inst->finetune, sizeof(inst->finetune));
+
+    rwops = stream_write(stream, (uint8_t*)&inst->slide_speed, sizeof(inst->slide_speed));
+
+    rwops = stream_write(stream, (uint8_t*)&inst->adsr, sizeof(inst->adsr));
+    rwops = stream_write(stream, (uint8_t*)&inst->pw, sizeof(inst->pw));
+
+    if(inst->sound_engine_flags & SE_ENABLE_RING_MOD)
+    {
+        rwops = stream_write(stream, (uint8_t*)&inst->ring_mod, sizeof(inst->ring_mod));
+    }
+
+    if(inst->sound_engine_flags & SE_ENABLE_HARD_SYNC)
+    {
+        rwops = stream_write(stream, (uint8_t*)&inst->hard_sync, sizeof(inst->hard_sync));
+    }
+
+    uint8_t progsteps = 0;
+
+    for(uint8_t i = 0; i < INST_PROG_LEN; i++)
+    {
+        if(inst->program[i] != TE_PROGRAM_NOP)
+        {
+            progsteps = i + 1;
+        }
+    }
+
+    rwops = stream_write(stream, (uint8_t*)&progsteps, sizeof(progsteps));
+
+    if(progsteps > 0)
+    {
+        rwops = stream_write(stream, (uint8_t*)inst->program, sizeof(progsteps) * sizeof(inst->program[0]));
+    }
+
+    rwops = stream_write(stream, (uint8_t*)&inst->program_period, sizeof(inst->program_period));
+
+    if(inst->flags & TE_ENABLE_VIBRATO)
+    {
+        rwops = stream_write(stream, (uint8_t*)&inst->vibrato_speed, sizeof(inst->vibrato_speed));
+        rwops = stream_write(stream, (uint8_t*)&inst->vibrato_depth, sizeof(inst->vibrato_depth));
+        rwops = stream_write(stream, (uint8_t*)&inst->vibrato_delay, sizeof(inst->vibrato_delay));
+    }
+
+    if(inst->flags & TE_ENABLE_PWM)
+    {
+        rwops = stream_write(stream, (uint8_t*)&inst->pwm_speed, sizeof(inst->pwm_speed));
+        rwops = stream_write(stream, (uint8_t*)&inst->pwm_depth, sizeof(inst->pwm_depth));
+        rwops = stream_write(stream, (uint8_t*)&inst->pwm_delay, sizeof(inst->pwm_delay));
+    }
+
+    if(inst->sound_engine_flags & SE_ENABLE_FILTER)
+    {
+        rwops = stream_write(stream, (uint8_t*)&inst->filter_cutoff, sizeof(inst->filter_cutoff));
+        rwops = stream_write(stream, (uint8_t*)&inst->filter_resonance, sizeof(inst->filter_resonance));
+        rwops = stream_write(stream, (uint8_t*)&inst->filter_type, sizeof(inst->filter_type));
+    }
+
+    UNUSED(rwops);
+}
+
+bool save_song(FlizzerTrackerApp* tracker, FuriString* filepath)
+{
+    bool file_removed = storage_simply_remove(tracker->storage, furi_string_get_cstr(filepath)); //just in case
+    bool open_file = file_stream_open(tracker->stream, furi_string_get_cstr(filepath), FSAM_WRITE, FSOM_OPEN_ALWAYS);
+
+    uint8_t version = TRACKER_ENGINE_VERSION;
+    size_t rwops = stream_write(tracker->stream, (uint8_t*)SONG_FILE_SIG, sizeof(SONG_FILE_SIG) - 1);
+    rwops = stream_write(tracker->stream, (uint8_t*)&version, sizeof(uint8_t));
+
+    TrackerSong* song = &tracker->song;
+
+    /*for(uint32_t i = 0; i < 23444; i++)
+    {
+        rwops = stream_write(tracker->stream, (uint8_t*)&song->loop_end, sizeof(uint8_t));
+    }*/
+
+    rwops = stream_write(tracker->stream, (uint8_t*)song->song_name, sizeof(song->song_name));
+    rwops = stream_write(tracker->stream, (uint8_t*)&song->loop_start, sizeof(song->loop_start));
+    rwops = stream_write(tracker->stream, (uint8_t*)&song->loop_end, sizeof(song->loop_end));
+    rwops = stream_write(tracker->stream, (uint8_t*)&song->pattern_length, sizeof(song->pattern_length));
+
+    rwops = stream_write(tracker->stream, (uint8_t*)&song->num_sequence_steps, sizeof(song->num_sequence_steps));
+
+    for(uint16_t i = 0; i < song->num_sequence_steps; i++)
+    {
+        rwops = stream_write(tracker->stream, (uint8_t*)&song->sequence.sequence_step[i], sizeof(song->sequence.sequence_step[0]));
+    }
+
+    rwops = stream_write(tracker->stream, (uint8_t*)&song->num_patterns, sizeof(song->num_patterns));
+
+    for(uint16_t i = 0; i < song->num_patterns; i++)
+    {
+        rwops = stream_write(tracker->stream, (uint8_t*)song->pattern[i].step, sizeof(TrackerSongPatternStep) * (song->pattern_length - 1));
+    }
+
+    rwops = stream_write(tracker->stream, (uint8_t*)&song->num_instruments, sizeof(song->num_instruments));
+
+    for(uint16_t i = 0; i < song->num_instruments; i++)
+    {
+        save_instrument_inner(tracker->stream, song->instrument[i]);
+    }
+
+    file_stream_close(tracker->stream);
+    tracker->is_saving = false;
+    furi_string_free(filepath);
+
+    UNUSED(file_removed);
+    UNUSED(open_file);
+    UNUSED(rwops);
+    return false;
+}

+ 4 - 3
diskop.h

@@ -1,6 +1,7 @@
 #pragma once
 
-#include "sound_engine/sound_engine_defs.h"
-#include "tracker_engine/tracker_engine_defs.h"
+#include "flizzer_tracker.h"
 
-#define INST_FILE_SIG "FZT!INST"
+#define INST_FILE_SIG "FZT!INST"
+
+bool save_song(FlizzerTrackerApp* tracker, FuriString* filepath);

+ 31 - 7
flizzer_tracker.c

@@ -4,8 +4,7 @@
 #include "util.h"
 #include "view/instrument_editor.h"
 #include "view/pattern_editor.h"
-
-#define FLIZZER_TRACKER_FOLDER "/ext/flizzer_tracker"
+#include "diskop.h"
 
 #include <flizzer_tracker_icons.h>
 
@@ -40,6 +39,18 @@ void draw_callback(Canvas *canvas, void *ctx)
 
     canvas_set_color(canvas, ColorXOR);
 
+    if(tracker->is_loading)
+    {
+        canvas_draw_str(canvas, 10, 10, "Loading...");
+        return;
+    }
+
+    if(tracker->is_saving)
+    {
+        canvas_draw_str(canvas, 10, 10, "Saving...");
+        return;
+    }
+
     canvas_set_custom_font(canvas, u8g2_font_tom_thumb_4x6_tr);
 
     switch (tracker->mode)
@@ -96,7 +107,11 @@ bool input_callback(InputEvent *input_event, void *ctx)
     uint32_t final_period = (tracker->was_it_back_keypress ? tracker->period : 0);
 
     FlizzerTrackerEvent event = {.type = EventTypeInput, .input = *input_event, .period = final_period};
-    furi_message_queue_put(tracker->event_queue, &event, FuriWaitForever);
+
+    if(!(tracker->is_loading) && !(tracker->is_saving))
+    {
+        furi_message_queue_put(tracker->event_queue, &event, FuriWaitForever);
+    }
 
     consumed = true;
     return consumed;
@@ -106,6 +121,11 @@ int32_t flizzer_tracker_app(void *p)
 {
     UNUSED(p);
 
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    bool st = storage_simply_mkdir(storage, FLIZZER_TRACKER_FOLDER);
+    UNUSED(st);
+    furi_record_close(RECORD_STORAGE);
+
     FlizzerTrackerApp *tracker = init_tracker(44100, 50, false, 1024);
 
     // Текущее событие типа кастомного типа FlizzerTrackerEvent
@@ -171,19 +191,18 @@ int32_t flizzer_tracker_app(void *p)
         set_instrument(&tracker->song.pattern[1].step[i], 1);
     }
 
-    tracker->song.instrument[0]->base_note = MIDDLE_C;
+    set_default_instrument(tracker->song.instrument[0]);
+    set_default_instrument(tracker->song.instrument[1]);
+
     tracker->song.instrument[0]->adsr.a = 0x2;
     tracker->song.instrument[0]->adsr.d = 0x9;
     tracker->song.instrument[0]->adsr.volume = 0x80;
     tracker->song.instrument[0]->waveform = SE_WAVEFORM_TRIANGLE;
-    tracker->song.instrument[0]->sound_engine_flags |= SE_ENABLE_KEYDOWN_SYNC;
 
-    tracker->song.instrument[1]->base_note = MIDDLE_C;
     tracker->song.instrument[1]->adsr.a = 0x0;
     tracker->song.instrument[1]->adsr.d = 0x3;
     tracker->song.instrument[1]->adsr.volume = 0x18;
     tracker->song.instrument[1]->waveform = SE_WAVEFORM_NOISE;
-    tracker->song.instrument[1]->sound_engine_flags |= SE_ENABLE_KEYDOWN_SYNC;
 
     tracker->tracker_engine.playing = false;
     play();
@@ -202,6 +221,11 @@ int32_t flizzer_tracker_app(void *p)
         {
             process_input_event(tracker, &event);
         }
+
+        if(event.type == EventTypeSaveSong)
+        {
+            save_song(tracker, tracker->filepath);
+        }
     }
 
     stop();

+ 17 - 2
flizzer_tracker.h

@@ -5,10 +5,12 @@
 #include <furi.h>
 #include <gui/gui.h>
 #include <gui/modules/submenu.h>
+#include <gui/modules/widget.h>
 #include <input/input.h>
 #include <notification/notification_messages.h>
 #include <stdio.h>
 #include <toolbox/stream/file_stream.h>
+#include <storage/storage.h>
 #include <u8g2_glue.h>
 
 #include <gui/modules/text_input.h>
@@ -20,9 +22,13 @@
 #include "sound_engine/sound_engine_filter.h"
 #include "tracker_engine/tracker_engine_defs.h"
 
+#define FLIZZER_TRACKER_FOLDER "/ext/flizzer_tracker"
+#define FILE_NAME_LEN 64
+
 typedef enum
 {
     EventTypeInput,
+    EventTypeSaveSong,
 } EventType;
 
 typedef struct
@@ -127,14 +133,16 @@ typedef enum
 {
     VIEW_TRACKER,
     VIEW_KEYBOARD,
-    VIEW_FILE_MANAGER,
     VIEW_SUBMENU_PATTERN,
     VIEW_SUBMENU_INSTRUMENT,
+    VIEW_FILE_OVERWRITE,
 } FlizzerTrackerViews;
 
 typedef enum
 {
     SUBMENU_PATTERN_RETURN,
+    SUBMENU_PATTERN_LOAD_SONG,
+    SUBMENU_PATTERN_SAVE_SONG,
     SUBMENU_PATTERN_EXIT,
 } PatternSubmenuParams;
 
@@ -152,10 +160,14 @@ typedef struct
     TrackerView *tracker_view;
     ViewDispatcher *view_dispatcher;
     TextInput *text_input;
+    Storage* storage;
     Stream *stream;
+    FuriString* filepath;
     DialogsApp *dialogs;
     Submenu *pattern_submenu;
     Submenu *instrument_submenu;
+    Widget* overwrite_file_widget;
+    char filename[FILE_NAME_LEN + 1];
     bool was_it_back_keypress;
     uint32_t current_time;
     uint32_t period;
@@ -168,13 +180,16 @@ typedef struct
     uint8_t selected_param;
 
     uint8_t mode, focus;
-    uint8_t patternx, current_channel, current_digit, program_step, current_instrument, current_note;
+    uint8_t patternx, current_channel, current_digit, program_position, current_program_step, current_instrument, current_note;
 
     uint8_t inst_editor_shift;
 
     bool editing;
     bool was_editing;
 
+    bool is_loading;
+    bool is_saving;
+
     bool quit;
 } FlizzerTrackerApp;
 

+ 30 - 0
init_deinit.c

@@ -47,6 +47,9 @@ FlizzerTrackerApp *init_tracker(uint32_t sample_rate, uint8_t rate, bool externa
     with_view_model(
         tracker->tracker_view->view, TrackerViewModel * model, { model->tracker = tracker; }, true);
 
+    tracker->storage = furi_record_open(RECORD_STORAGE);
+    tracker->stream = file_stream_alloc(tracker->storage);
+
     tracker->text_input = text_input_alloc();
     view_dispatcher_add_view(tracker->view_dispatcher, VIEW_KEYBOARD, text_input_get_view(tracker->text_input));
 
@@ -54,6 +57,8 @@ FlizzerTrackerApp *init_tracker(uint32_t sample_rate, uint8_t rate, bool externa
     tracker->instrument_submenu = submenu_alloc();
 
     submenu_add_item(tracker->pattern_submenu, "Return", SUBMENU_PATTERN_RETURN, submenu_callback, tracker);
+    submenu_add_item(tracker->pattern_submenu, "Load song", SUBMENU_PATTERN_LOAD_SONG, submenu_callback, tracker);
+    submenu_add_item(tracker->pattern_submenu, "Save song", SUBMENU_PATTERN_SAVE_SONG, submenu_callback, tracker);
     submenu_add_item(tracker->pattern_submenu, "Exit", SUBMENU_PATTERN_EXIT, submenu_callback, tracker);
 
     submenu_add_item(tracker->instrument_submenu, "Return", SUBMENU_INSTRUMENT_RETURN, submenu_callback, tracker);
@@ -62,6 +67,25 @@ FlizzerTrackerApp *init_tracker(uint32_t sample_rate, uint8_t rate, bool externa
     view_dispatcher_add_view(tracker->view_dispatcher, VIEW_SUBMENU_PATTERN, submenu_get_view(tracker->pattern_submenu));
     view_dispatcher_add_view(tracker->view_dispatcher, VIEW_SUBMENU_INSTRUMENT, submenu_get_view(tracker->instrument_submenu));
 
+    tracker->overwrite_file_widget = widget_alloc();
+
+    widget_add_button_element(
+        tracker->overwrite_file_widget,
+        GuiButtonTypeLeft,
+        "No",
+        (ButtonCallback)overwrite_file_widget_no_input_callback,
+        tracker);
+    widget_add_button_element(
+        tracker->overwrite_file_widget,
+        GuiButtonTypeRight,
+        "Yes",
+        (ButtonCallback)overwrite_file_widget_yes_input_callback,
+        tracker);
+
+    widget_add_text_scroll_element(tracker->overwrite_file_widget, 0, 0, 128, 64, "This song file already exists,\n do you want to overwrite it?");
+
+    view_dispatcher_add_view(tracker->view_dispatcher, VIEW_FILE_OVERWRITE, widget_get_view(tracker->overwrite_file_widget));
+
     tracker->notification = furi_record_open(RECORD_NOTIFICATION);
     notification_message(tracker->notification, &sequence_display_backlight_enforce_on);
 
@@ -76,6 +100,7 @@ void deinit_tracker(FlizzerTrackerApp *tracker)
     // Специальная очистка памяти, занимаемой очередью
     furi_message_queue_free(tracker->event_queue);
 
+    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_SUBMENU_PATTERN);
     view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_KEYBOARD);
@@ -86,11 +111,16 @@ void deinit_tracker(FlizzerTrackerApp *tracker)
     submenu_free(tracker->pattern_submenu);
     submenu_free(tracker->instrument_submenu);
 
+    widget_free(tracker->overwrite_file_widget);
+
     view_dispatcher_free(tracker->view_dispatcher);
 
     tracker_view_free(tracker->tracker_view);
     furi_record_close(RECORD_GUI);
 
+    stream_free(tracker->stream);
+    furi_record_close(RECORD_STORAGE);
+
     sound_engine_deinit(&tracker->sound_engine);
 
     if (tracker->tracker_engine.song == NULL)

+ 7 - 13
input/instrument.c

@@ -16,22 +16,16 @@ void edit_instrument_param(FlizzerTrackerApp *tracker, uint8_t selected_param, i
         {
             int16_t inst = tracker->current_instrument;
 
-            if (inst + delta >= MUS_NOTE_INSTRUMENT_NONE)
-            {
-                if (delta > 0)
-                {
-                    inst = 0;
-                }
+            int8_t inst_delta = delta > 0 ? 1 : -1;
 
-                else
-                {
-                    inst = MUS_NOTE_INSTRUMENT_NONE - 1;
-                }
-            }
+            inst += inst_delta;
 
-            clamp(inst, delta, 0, tracker->song.num_instruments - 1);
+            clamp(inst, 0, 0, tracker->song.num_instruments);
 
-            tracker->current_instrument = inst;
+            if(check_and_allocate_instrument(&tracker->song, (uint8_t)inst))
+            {
+                tracker->current_instrument = inst;
+            }
 
             break;
         }

+ 245 - 2
input/instrument_program.c

@@ -2,6 +2,249 @@
 
 void instrument_program_edit_event(FlizzerTrackerApp *tracker, FlizzerTrackerEvent *event)
 {
-    UNUSED(tracker);
-    UNUSED(event);
+    if (event->input.key == InputKeyOk && event->input.type == InputTypeShort)
+    {
+        tracker->editing = !(tracker->editing);
+        return;
+    }
+
+    if (event->input.key == InputKeyRight && event->input.type == InputTypeShort && tracker->editing)
+    {
+        tracker->current_digit = fmin(2, tracker->current_digit + 1);
+        return;
+    }
+
+    if (event->input.key == InputKeyOk && event->input.type == InputTypeLong && tracker->editing)
+    {
+        Instrument* inst = tracker->song.instrument[tracker->current_instrument];
+
+        if(tracker->current_program_step < INST_PROG_LEN - 1)
+        {
+            if((inst->program[tracker->current_program_step] & 0x7fff) < TE_PROGRAM_LOOP_BEGIN && ((inst->program[tracker->current_program_step + 1] & 0x7fff) < TE_PROGRAM_LOOP_BEGIN || (inst->program[tracker->current_program_step + 1] & 0x7f00) == TE_PROGRAM_LOOP_END)) //so we can unite with loop end as in klystrack
+            {
+                inst->program[tracker->current_program_step] ^= 0x8000; //flipping unite bit
+            }
+        }
+
+        return;
+    }
+
+    if (event->input.key == InputKeyLeft && event->input.type == InputTypeShort && tracker->editing)
+    {
+        tracker->current_digit = fmax(0, (int16_t)tracker->current_digit - 1);
+        return;
+    }
+
+    if (event->input.key == InputKeyBack && event->input.type == InputTypeShort && tracker->editing)
+    {
+        Instrument* inst = tracker->song.instrument[tracker->current_instrument];
+        inst->program[tracker->current_program_step] = TE_PROGRAM_NOP;
+    }
+
+    if (event->input.key == InputKeyUp && event->input.type == InputTypeShort)
+    {
+        if(!(tracker->editing))
+        {
+            if((int16_t)tracker->current_program_step - 1 >= 0)
+            {
+                tracker->current_program_step--;
+
+                if(tracker->program_position > tracker->current_program_step)
+                {
+                    tracker->program_position = tracker->current_program_step;
+                }
+            }
+
+            else
+            {
+                tracker->current_program_step = INST_PROG_LEN - 1;
+
+                tracker->program_position = INST_PROG_LEN - 1 - 7;
+            }
+        }
+
+        if(tracker->editing)
+        {
+            Instrument* inst = tracker->song.instrument[tracker->current_instrument];
+            uint16_t opcode = inst->program[tracker->current_program_step];
+
+            switch(tracker->current_digit)
+            {
+                case 0: //MSB
+                {
+                    uint8_t param = ((opcode & 0x7f00) >> 8);
+
+                    if(param < 0xff)
+                    {
+                        param++;
+                    }
+
+                    if((inst->program[tracker->current_program_step] & 0x7fff) == TE_PROGRAM_NOP)
+                    {
+                        param = 0;
+                        inst->program[tracker->current_program_step] = 0;
+                    }
+
+                    param &= 0x7f;
+
+                    inst->program[tracker->current_program_step] &= 0x80ff;
+                    inst->program[tracker->current_program_step] |= ((uint16_t)param << 8);
+
+                    break;
+                }
+
+                case 1: //upper digit of param, e.g. eXx
+                {
+                    int8_t nibble = ((opcode & 0x00f0) >> 4);
+
+                    if(nibble + 1 < 0xf)
+                    {
+                        nibble++;
+                    }
+
+                    else
+                    {
+                        nibble = 0;
+                    }
+
+                    inst->program[tracker->current_program_step] &= 0xff0f;
+                    inst->program[tracker->current_program_step] |= (nibble << 4);
+
+                    break;
+                }
+
+                case 2: //lower digit of param, e.g. exX
+                {
+                    int8_t nibble = (opcode & 0x000f);
+
+                    if(nibble + 1 < 0xf)
+                    {
+                        nibble++;
+                    }
+
+                    else
+                    {
+                        nibble = 0;
+                    }
+
+                    inst->program[tracker->current_program_step] &= 0xfff0;
+                    inst->program[tracker->current_program_step] |= nibble;
+
+                    break;
+                }
+
+                default: break;
+            }
+        }
+
+        return;
+    }
+
+    if (event->input.key == InputKeyDown && event->input.type == InputTypeShort)
+    {
+        if(!(tracker->editing))
+        {
+            if(tracker->current_program_step + 1 < INST_PROG_LEN)
+            {
+                tracker->current_program_step++;
+
+                if(tracker->program_position < tracker->current_program_step - 8)
+                {
+                    tracker->program_position = tracker->current_program_step - 8;
+                }
+            }
+
+            else
+            {
+                tracker->current_program_step = 0;
+
+                tracker->program_position = 0;
+            }
+        }
+
+        if(tracker->editing)
+        {
+            Instrument* inst = tracker->song.instrument[tracker->current_instrument];
+            uint16_t opcode = inst->program[tracker->current_program_step];
+
+            switch(tracker->current_digit)
+            {
+                case 0: //MSB
+                {
+                    uint8_t param = ((opcode & 0x7f00) >> 8);
+
+                    if(param < (TE_PROGRAM_JUMP >> 8) && param > 0)
+                    {
+                        param--;
+
+                        inst->program[tracker->current_program_step] &= 0x80ff;
+                        inst->program[tracker->current_program_step] |= ((uint16_t)param << 8);
+                    }
+
+                    if((inst->program[tracker->current_program_step] & 0x7f00) == TE_PROGRAM_JUMP && (inst->program[tracker->current_program_step] & 0x7fff) != TE_PROGRAM_END && (inst->program[tracker->current_program_step] & 0x7fff) != TE_PROGRAM_NOP)
+                    {
+                        inst->program[tracker->current_program_step] = TE_PROGRAM_LOOP_END | (inst->program[tracker->current_program_step] & 0x8000);
+                    }
+
+                    if((inst->program[tracker->current_program_step] & 0x7fff) == TE_PROGRAM_END)
+                    {
+                        //param = (TE_PROGRAM_JUMP >> 8);
+                        inst->program[tracker->current_program_step] = TE_PROGRAM_JUMP | (inst->program[tracker->current_program_step] & 0x8000);
+                    }
+
+                    if((inst->program[tracker->current_program_step] & 0x7fff) == TE_PROGRAM_NOP)
+                    {
+                        //param = (TE_PROGRAM_END >> 8);
+                        inst->program[tracker->current_program_step] = TE_PROGRAM_END | (inst->program[tracker->current_program_step] & 0x8000);
+                    }
+
+                    break;
+                }
+
+                case 1: //upper digit of param, e.g. eXx
+                {
+                    int8_t nibble = ((opcode & 0x00f0) >> 4);
+
+                    if(nibble - 1 >= 0)
+                    {
+                        nibble--;
+                    }
+
+                    else
+                    {
+                        nibble = 0xf;
+                    }
+
+                    inst->program[tracker->current_program_step] &= 0xff0f;
+                    inst->program[tracker->current_program_step] |= (nibble << 4);
+
+                    break;
+                }
+
+                case 2: //lower digit of param, e.g. exX
+                {
+                    int8_t nibble = (opcode & 0x000f);
+
+                    if(nibble - 1 >= 0)
+                    {
+                        nibble--;
+                    }
+
+                    else
+                    {
+                        nibble = 0xf;
+                    }
+
+                    inst->program[tracker->current_program_step] &= 0xfff0;
+                    inst->program[tracker->current_program_step] |= nibble;
+
+                    break;
+                }
+
+                default: break;
+            }
+        }
+
+        return;
+    }
 }

+ 2 - 1
input/pattern.c

@@ -70,7 +70,7 @@ void edit_instrument(FlizzerTrackerApp *tracker, TrackerSongPatternStep *step, i
         }
     }
 
-    clamp(inst, delta, 0, MUS_NOTE_INSTRUMENT_NONE - 1);
+    clamp(inst, delta, 0, tracker->song.num_instruments - 1);
     tracker->current_instrument = inst; // remember last instrument
     set_instrument(step, (uint8_t)inst);
 }
@@ -199,6 +199,7 @@ void delete_field(TrackerSongPatternStep *step, uint8_t field)
         case 0: // note
         {
             set_note(step, MUS_NOTE_NONE);
+            set_instrument(step, MUS_NOTE_INSTRUMENT_NONE); //also delete instrument
             break;
         }
 

+ 59 - 42
input/songinfo.c

@@ -1,54 +1,80 @@
 #include "songinfo.h"
 
+#include "../diskop.h"
+
 void return_from_keyboard_callback(void *ctx)
 {
     FlizzerTrackerApp *tracker = (FlizzerTrackerApp *)ctx;
 
-    uint8_t string_length = 0;
-    char *string = NULL;
-
-    if (tracker->focus == EDIT_SONGINFO && tracker->mode == PATTERN_VIEW)
+    if(!tracker->is_loading && !tracker->is_saving)
     {
-        switch (tracker->selected_param)
+        uint8_t string_length = 0;
+        char *string = NULL;
+
+        if (tracker->focus == EDIT_SONGINFO && tracker->mode == PATTERN_VIEW)
         {
-            case SI_SONGNAME:
+            switch (tracker->selected_param)
             {
-                string_length = MUS_SONG_NAME_LEN;
-                string = (char *)&tracker->song.song_name;
-                break;
-            }
+                case SI_SONGNAME:
+                {
+                    string_length = MUS_SONG_NAME_LEN;
+                    string = (char *)&tracker->song.song_name;
+                    break;
+                }
 
-            case SI_INSTRUMENTNAME:
-            {
-                string_length = MUS_INST_NAME_LEN;
-                string = (char *)&tracker->song.instrument[tracker->current_instrument]->name;
-                break;
+                case SI_INSTRUMENTNAME:
+                {
+                    string_length = MUS_INST_NAME_LEN;
+                    string = (char *)&tracker->song.instrument[tracker->current_instrument]->name;
+                    break;
+                }
             }
         }
-    }
 
-    if (tracker->focus == EDIT_INSTRUMENT && tracker->mode == INST_EDITOR_VIEW)
-    {
-        switch (tracker->selected_param)
+        if (tracker->focus == EDIT_INSTRUMENT && tracker->mode == INST_EDITOR_VIEW)
         {
-            case INST_INSTRUMENTNAME:
+            switch (tracker->selected_param)
             {
-                string_length = MUS_INST_NAME_LEN;
-                string = (char *)&tracker->song.instrument[tracker->current_instrument]->name;
-                break;
+                case INST_INSTRUMENTNAME:
+                {
+                    string_length = MUS_INST_NAME_LEN;
+                    string = (char *)&tracker->song.instrument[tracker->current_instrument]->name;
+                    break;
+                }
             }
         }
-    }
 
-    if (string == NULL || string_length == 0)
-        return;
+        if (string == NULL || string_length == 0)
+            return;
 
-    for (uint8_t i = 0; i < string_length; i++) // I tinyfied the font by deleting lowercase chars, and I don't like the lowercase chars of any 3x5 pixels font
-    {
-        string[i] = toupper(string[i]);
+        for (uint8_t i = 0; i < string_length; i++) // I tinyfied the font by deleting lowercase chars, and I don't like the lowercase chars of any 3x5 pixels font
+        {
+            string[i] = toupper(string[i]);
+        }
     }
 
     view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_TRACKER);
+
+    if(tracker->is_saving)
+    {
+        //storage_file_exists(storage, NFC_TEST_DICT_PATH);
+        tracker->filepath = furi_string_alloc();
+        furi_string_cat_printf(tracker->filepath, "%s/%s%s", FLIZZER_TRACKER_FOLDER, tracker->filename, SONG_FILE_EXT);
+
+        if(storage_file_exists(tracker->storage, furi_string_get_cstr(tracker->filepath)))
+        {
+            view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_FILE_OVERWRITE);
+            return;
+        }
+
+        else
+        {
+            FlizzerTrackerEvent event = {.type = EventTypeSaveSong, .input = {0}, .period = 0};
+            furi_message_queue_put(tracker->event_queue, &event, FuriWaitForever);
+            //bool song_saved = save_song(tracker, tracker->filepath);
+            //UNUSED(song_saved);
+        }
+    }
 }
 
 void edit_songinfo_param(FlizzerTrackerApp *tracker, uint8_t selected_param, int8_t delta)
@@ -75,7 +101,7 @@ void edit_songinfo_param(FlizzerTrackerApp *tracker, uint8_t selected_param, int
 
         case SI_SEQUENCEPOS:
         {
-            if ((int16_t)tracker->song.num_sequence_steps + (int16_t)delta > 1 && (int16_t)tracker->song.num_sequence_steps + (int16_t)delta <= 0xff)
+            if ((int16_t)tracker->song.num_sequence_steps + (int16_t)delta > 0 && (int16_t)tracker->song.num_sequence_steps + (int16_t)delta <= 0x100)
             {
                 tracker->song.num_sequence_steps += delta;
             }
@@ -126,20 +152,11 @@ void edit_songinfo_param(FlizzerTrackerApp *tracker, uint8_t selected_param, int
         {
             int16_t inst = tracker->current_instrument;
 
-            if (inst + delta >= MUS_NOTE_INSTRUMENT_NONE)
-            {
-                if (delta > 0)
-                {
-                    inst = 0;
-                }
+            int8_t inst_delta = delta > 0 ? 1 : -1;
 
-                else
-                {
-                    inst = MUS_NOTE_INSTRUMENT_NONE - 1;
-                }
-            }
+            inst += inst_delta;
 
-            clamp(inst, delta, 0, tracker->song.num_instruments - 1);
+            clamp(inst, 0, 0, tracker->song.num_instruments - 1);
 
             tracker->current_instrument = inst;
 

+ 45 - 0
input_event.c

@@ -1,5 +1,37 @@
 #include "input_event.h"
 
+#include "diskop.h"
+
+void overwrite_file_widget_yes_input_callback(GuiButtonType result, InputType type, void* ctx)
+{
+    UNUSED(result);
+
+    FlizzerTrackerApp* tracker = (FlizzerTrackerApp*)ctx;
+
+    if(type == InputTypeShort)
+    {
+        tracker->is_saving = true;
+        view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_TRACKER);
+        //save_song(tracker, tracker->filepath);
+        static FlizzerTrackerEvent event = {.type = EventTypeSaveSong, .input = {0}, .period = 0};
+        furi_message_queue_put(tracker->event_queue, &event, FuriWaitForever);
+    }
+}
+
+void overwrite_file_widget_no_input_callback(GuiButtonType result, InputType type, void* ctx)
+{
+    UNUSED(result);
+
+    FlizzerTrackerApp* tracker = (FlizzerTrackerApp*)ctx;
+
+    if(type == InputTypeShort)
+    {
+        tracker->is_saving = false;
+        furi_string_free(tracker->filepath);
+        view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_TRACKER);
+    }
+}
+
 void submenu_callback(void *context, uint32_t index)
 {
     FlizzerTrackerApp *tracker = (FlizzerTrackerApp *)context;
@@ -27,6 +59,17 @@ void submenu_callback(void *context, uint32_t index)
                     break;
                 }
 
+                case SUBMENU_PATTERN_SAVE_SONG:
+                {
+                    text_input_set_header_text(tracker->text_input, "Song filename:");
+                    memset(&tracker->filename, 0, FILE_NAME_LEN);
+                    text_input_set_result_callback(tracker->text_input, return_from_keyboard_callback, tracker, (char *)&tracker->filename, FILE_NAME_LEN, true);
+
+                    tracker->is_saving = true;
+
+                    view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_KEYBOARD);
+                }
+
                 default:
                     break;
             }
@@ -152,12 +195,14 @@ void process_input_event(FlizzerTrackerApp *tracker, FlizzerTrackerEvent *event)
         {
             case PATTERN_VIEW:
             {
+                submenu_set_selected_item(tracker->pattern_submenu, SUBMENU_PATTERN_RETURN);
                 view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_SUBMENU_PATTERN);
                 break;
             }
 
             case INST_EDITOR_VIEW:
             {
+                submenu_set_selected_item(tracker->instrument_submenu, SUBMENU_INSTRUMENT_RETURN);
                 view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_SUBMENU_INSTRUMENT);
                 break;
             }

+ 3 - 0
input_event.h

@@ -15,5 +15,8 @@
 #include "input/sequence.h"
 #include "input/songinfo.h"
 
+void overwrite_file_widget_yes_input_callback(GuiButtonType result, InputType type, void* ctx);
+void overwrite_file_widget_no_input_callback(GuiButtonType result, InputType type, void* ctx);
+
 void submenu_callback(void *context, uint32_t index);
 void process_input_event(FlizzerTrackerApp *tracker, FlizzerTrackerEvent *event);

+ 12 - 0
sound_engine/sound_engine.c

@@ -7,6 +7,18 @@
 
 void sound_engine_init(SoundEngine *sound_engine, uint32_t sample_rate, bool external_audio_output, uint32_t audio_buffer_size)
 {
+    if(sound_engine->audio_buffer)
+    {
+        free(sound_engine->audio_buffer);
+    }
+
+    if(sound_engine->sine_lut)
+    {
+        free(sound_engine->sine_lut);
+    }
+
+    memset(sound_engine, 0, sizeof(SoundEngine));
+
     sound_engine->audio_buffer = malloc(audio_buffer_size * sizeof(sound_engine->audio_buffer[0]));
     memset(sound_engine->audio_buffer, 0, sizeof(SoundEngine));
     sound_engine->audio_buffer_size = audio_buffer_size;

+ 6 - 10
tracker_engine/tracker_engine.c

@@ -6,6 +6,8 @@
 
 void tracker_engine_init(TrackerEngine *tracker_engine, uint8_t rate, SoundEngine *sound_engine)
 {
+    memset(tracker_engine, 0, sizeof(TrackerEngine));
+
     furi_hal_interrupt_set_isr_ex(FuriHalInterruptIdTIM2, 14, tracker_engine_timer_isr, (void *)tracker_engine);
     tracker_engine_init_hardware(rate);
 
@@ -233,7 +235,7 @@ void tracker_engine_advance_tick(TrackerEngine *tracker_engine)
 
         if (tracker_engine->song)
         {
-            uint8_t sequence_position = tracker_engine->sequence_position;
+            uint16_t sequence_position = tracker_engine->sequence_position;
             uint8_t current_pattern = song->sequence.sequence_step[sequence_position].pattern_indices[chan];
             uint8_t pattern_step = tracker_engine->pattern_position;
 
@@ -313,20 +315,14 @@ void tracker_engine_advance_tick(TrackerEngine *tracker_engine)
             {
                 tracker_engine->pattern_position = 0;
 
-                uint16_t temp_sequence_position = tracker_engine->sequence_position;
-                temp_sequence_position++;
+                tracker_engine->sequence_position++;
 
-                if (temp_sequence_position >= song->num_sequence_steps)
+                if (tracker_engine->sequence_position >= song->num_sequence_steps)
                 {
                     tracker_engine->playing = false; // TODO: add song loop handling
-                    // tracker_engine->sequence_position--;
+                    tracker_engine->sequence_position--;
                     tracker_engine->pattern_position = song->pattern_length - 1;
                 }
-
-                else
-                {
-                    tracker_engine->sequence_position++;
-                }
             }
         }
     }

+ 26 - 3
tracker_engine/tracker_engine_defs.h

@@ -23,6 +23,7 @@
 #define MUS_NOTE_VOLUME_NONE 31
 
 #define SONG_FILE_SIG "FZT!SONG"
+#define SONG_FILE_EXT ".fzt"
 
 #define TRACKER_ENGINE_VERSION 1
 
@@ -36,8 +37,7 @@ typedef enum
     TE_PROG_NO_RESTART = 4,
     TE_SET_CUTOFF = 8,
     TE_SET_PW = 16,
-    TE_KEY_SYNC = 32,           // sync oscillators on keydown
-    TE_RETRIGGER_ON_SLIDE = 64, // call trigger instrument function even if slide command is there
+    TE_RETRIGGER_ON_SLIDE = 32, // call trigger instrument function even if slide command is there
 } TrackerEngineFlags;
 
 typedef enum
@@ -47,6 +47,28 @@ typedef enum
     TEC_DISABLED = 4,
 } TrackerEngineChannelFlags;
 
+typedef enum
+{
+    TE_EFFECT_ARPEGGIO = 0x0000,
+    TE_EFFECT_PORTAMENTO_UP = 0x0100,
+    TE_EFFECT_PORTAMENTO_DOWN = 0x0200,
+    TE_EFFECT_SLIDE = 0x0300,
+    TE_EFFECT_VIBRATO = 0x0400,
+    /* TODO: add smth there */
+    TE_EFFECT_VOLUME_FADE = 0x0a00,
+    TE_EFFECT_SET_VOLUME = 0x0c00,
+    TE_EFFECT_SKIP_PATTERN = 0x0d00,
+    TE_EFFECT_EXT = 0x0e00,
+    /* TODO: add 0exy effects here */
+    TE_EFFECT_SET_SPEED_PROG_PERIOD = 0x0f00,
+    /* These effects work only in instrument program */
+    TE_PROGRAM_LOOP_BEGIN = 0x7d00,
+    TE_PROGRAM_LOOP_END = 0x7e00,
+    TE_PROGRAM_JUMP = 0x7f00,
+    TE_PROGRAM_NOP = 0x7ffe,
+    TE_PROGRAM_END = 0x7fff,
+} EffectCommandsOpcodes;
+
 typedef struct
 {
     uint8_t a, d, s, r, volume;
@@ -138,7 +160,8 @@ typedef struct
     TrackerSongPattern pattern[MAX_PATTERNS];
     TrackerSongSequence sequence;
 
-    uint8_t num_patterns, num_sequence_steps, num_instruments;
+    uint8_t num_patterns, num_instruments;
+    uint16_t num_sequence_steps;
     uint16_t pattern_length;
 
     char song_name[MUS_SONG_NAME_LEN + 1];

+ 57 - 0
util.c

@@ -148,4 +148,61 @@ void change_pattern_length(TrackerSong *song, uint16_t new_length)
     }
 
     song->pattern_length = new_length;
+}
+
+void set_default_instrument(Instrument* inst)
+{
+    memset(inst, 0, sizeof(Instrument));
+
+    inst->flags = TE_SET_CUTOFF | TE_SET_PW;
+    inst->sound_engine_flags = SE_ENABLE_KEYDOWN_SYNC;
+
+    inst->base_note = MIDDLE_C;
+
+    inst->waveform = SE_WAVEFORM_PULSE;
+    inst->pw = 0x80;
+
+    inst->adsr.a = 0x4;
+    inst->adsr.d = 0x28;
+    inst->adsr.volume = 0x80;
+
+    for(int i = 0; i < INST_PROG_LEN; i++)
+    {
+        inst->program[i] = TE_PROGRAM_NOP;
+    }
+}
+
+bool is_default_instrument(Instrument* inst)
+{
+    Instrument* ref = malloc(sizeof(Instrument));
+    set_default_instrument(ref);
+    bool is_default = memcmp(ref, inst, sizeof(Instrument)) != 0 ? false : true;
+    free(ref);
+    return is_default;
+}
+
+bool check_and_allocate_instrument(TrackerSong *song, uint8_t inst)
+{
+    if (inst < song->num_instruments) // we can go to this instrument since it already exists
+    {
+        return true;
+    }
+
+    else
+    {
+        if(inst >= MAX_INSTRUMENTS) return false;
+
+        if (!(is_default_instrument(song->instrument[inst - 1]))) // don't let the user flood the song with default instrument
+        {
+            song->instrument[inst] = malloc(sizeof(Instrument));
+            set_default_instrument(song->instrument[inst]);
+            song->num_instruments++;
+            return true;
+        }
+
+        else
+        {
+            return false;
+        }
+    }
 }

+ 4 - 1
util.h

@@ -25,4 +25,7 @@ void stop_song(FlizzerTrackerApp *tracker);
 void set_empty_pattern(TrackerSongPattern *pattern, uint16_t pattern_length);
 bool is_pattern_empty(TrackerSong *song, uint8_t pattern);
 bool check_and_allocate_pattern(TrackerSong *song, uint8_t pattern);
-void change_pattern_length(TrackerSong *song, uint16_t new_length);
+void change_pattern_length(TrackerSong *song, uint16_t new_length);
+
+void set_default_instrument(Instrument* inst);
+bool check_and_allocate_instrument(TrackerSong *song, uint8_t inst);

+ 39 - 0
view/char_array.c

@@ -0,0 +1,39 @@
+const char to_char_array[] =
+    {
+        '0',
+        '1',
+        '2',
+        '3',
+        '4',
+        '5',
+        '6',
+        '7',
+        '8',
+        '9',
+        'A',
+        'B',
+        'C',
+        'D',
+        'E',
+        'F',
+        'G',
+        'H',
+        'I',
+        'J',
+        'K',
+        'L',
+        'M',
+        'N',
+        'O',
+        'P',
+        'Q',
+        'R',
+        'S',
+        'T',
+        'U',
+        'V',
+        'W',
+        'X',
+        'Y',
+        'Z',
+};

+ 111 - 7
view/instrument_editor.c

@@ -1,6 +1,8 @@
 #include "instrument_editor.h"
 #include "pattern_editor.h"
 
+#include "opcode_description.h"
+
 #include <flizzer_tracker_icons.h>
 
 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)
@@ -86,7 +88,7 @@ static const char *instrument_editor_params_description[] =
         "ENABLE HARD SYNC",
         "HARDSYNC SOURCE CHANNEL (F=SELF)",
         "RETRIGGER INSTRUMENT ON SLIDE",
-        "SYNC OSCLLATORS ON KEYDOWN",
+        "SYNC OSCILLATORS ON KEYDOWN",
         "ENABLE VIBRATO",
         "VIBRATO SPEED",
         "VIBRATO DEPTH",
@@ -156,7 +158,7 @@ void draw_instrument_view(Canvas *canvas, FlizzerTrackerApp *tracker)
 
     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", 62, 17 - shift, inst->flags, TE_SET_CUTOFF);
+    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_WAVE_NOISE, "N", 0, 23 - shift, inst->waveform, SE_WAVEFORM_NOISE);
     draw_inst_flag(tracker, canvas, EDIT_INSTRUMENT, INST_WAVE_PULSE, "P", 10, 23 - shift, inst->waveform, SE_WAVEFORM_PULSE);
@@ -204,11 +206,11 @@ void draw_instrument_view(Canvas *canvas, FlizzerTrackerApp *tracker)
         draw_inst_text_two_digits(tracker, canvas, EDIT_INSTRUMENT, INST_PWMDELAY, "DEL:", 52, 59 - shift, inst->pwm_delay);
     }
 
-    draw_inst_text_two_digits(tracker, canvas, EDIT_INSTRUMENT, INST_PROGRAMEPERIOD, "P.PERIOD:", 84, 56, inst->program_period);
+    draw_inst_text_two_digits(tracker, canvas, EDIT_INSTRUMENT, INST_PROGRAMEPERIOD, "P.PERIOD:", 81, 56, inst->program_period);
 
     canvas_draw_line(canvas, 0, 57, 127, 57);
-    canvas_draw_line(canvas, 82, 0, 82, 56);
-    canvas_draw_line(canvas, 83, 49, 127, 49);
+    canvas_draw_line(canvas, 79, 0, 79, 56);
+    canvas_draw_line(canvas, 80, 49, 127, 49);
 
     if (tracker->focus == EDIT_INSTRUMENT)
     {
@@ -216,8 +218,110 @@ void draw_instrument_view(Canvas *canvas, FlizzerTrackerApp *tracker)
     }
 }
 
+char command_get_char(uint16_t command)
+{
+    if((command >> 8) < 36)
+    {
+        return to_char_array[(command >> 8)];
+    }
+
+    if(command == TE_PROGRAM_END)
+    {
+        return ':';
+    }
+
+    if((command & 0xff00) == TE_PROGRAM_JUMP)
+    {
+        return '^';
+    }
+
+    if((command & 0xff00) == TE_PROGRAM_LOOP_END)
+    {
+        return '>';
+    }
+
+    if((command & 0xff00) == TE_PROGRAM_LOOP_BEGIN)
+    {
+        return '<';
+    }
+
+    return '?';
+}
+
+void draw_program_step(Canvas *canvas, uint8_t y, FlizzerTrackerApp *tracker, uint8_t index)
+{
+    char buffer[15];
+
+    Instrument* inst = tracker->song.instrument[tracker->current_instrument];
+    uint16_t opcode = inst->program[index];
+
+    if(opcode != TE_PROGRAM_NOP)
+    {
+        snprintf(buffer, sizeof(buffer), "%01X %c%02X %s", index, command_get_char(opcode & 0x7fff), (opcode & 0xff), get_opcode_description(opcode, true) ? get_opcode_description(opcode, true) : "");
+
+        if(opcode & 0x8000)
+        {
+            if(index == 0)
+            {
+                canvas_draw_line(canvas, 84 + 4 * 4 + 2, y, 84 + 4 * 4 + 2, y - 3);
+                canvas_draw_dot(canvas, 84 + 4 * 4 + 1, y - 4);
+            }
+
+            if(index > 0 && !(inst->program[index - 1] & 0x8000))
+            {
+                canvas_draw_line(canvas, 84 + 4 * 4 + 2, y, 84 + 4 * 4 + 2, y - 3);
+                canvas_draw_dot(canvas, 84 + 4 * 4 + 1, y - 4);
+            }
+
+            if(index > 0 && (inst->program[index - 1] & 0x8000))
+            {
+                canvas_draw_line(canvas, 84 + 4 * 4 + 2, y, 84 + 4 * 4 + 2, y - 5);
+            }
+        }
+
+        else
+        {
+            if(index > 0 && (inst->program[index - 1] & 0x8000))
+            {
+                canvas_draw_line(canvas, 84 + 4 * 4 + 2, y - 3, 84 + 4 * 4 + 2, y - 5);
+                canvas_draw_dot(canvas, 84 + 4 * 4 + 1, y - 2);
+            }
+        }
+    }
+
+    else
+    {
+        snprintf(buffer, sizeof(buffer), "%01X ---", index);
+    }
+
+    canvas_draw_str(canvas, 81, y, buffer);
+}
+
 void draw_instrument_program_view(Canvas *canvas, FlizzerTrackerApp *tracker)
 {
-    UNUSED(canvas);
-    UNUSED(tracker);
+    Instrument* inst = tracker->song.instrument[tracker->current_instrument];
+
+    for(uint8_t i = tracker->program_position; i < fmin(INST_PROG_LEN, tracker->program_position + 8); i++)
+    {
+        draw_program_step(canvas, 6 + 6 * i - tracker->program_position * 6, tracker, i);
+
+        if(i == tracker->current_program_step && tracker->focus == EDIT_PROGRAM)
+        {
+            if(tracker->editing)
+            {
+                canvas_draw_box(canvas, 80 + 8 + tracker->current_digit * 4, 6 * i - tracker->program_position * 6, 5, 7);
+            }
+
+            else
+            {
+                canvas_draw_box(canvas, 80, 6 * i - tracker->program_position * 6, 5, 7);
+            }
+        }
+    }
+
+    if (tracker->focus == EDIT_PROGRAM)
+    {
+        uint16_t opcode = (inst->program[tracker->current_program_step] & 0x7fff);
+        canvas_draw_str(canvas, 0, 64, get_opcode_description(opcode, false) ? get_opcode_description(opcode, false) : "");
+    }
 }

+ 1 - 0
view/instrument_editor.h

@@ -2,6 +2,7 @@
 
 #include "../flizzer_tracker.h"
 #include "../tracker_engine/tracker_engine_defs.h"
+#include "pattern_editor.h"
 
 #include <furi.h>
 #include <gui/gui.h>

+ 33 - 0
view/opcode_description.c

@@ -0,0 +1,33 @@
+#include "opcode_description.h"
+
+static const OpcodeDescription opcode_desc[] =
+    {
+        {TE_EFFECT_ARPEGGIO, 0x7f00, "RELATIVE ARPEGGIO NOTE", ""},
+        {TE_EFFECT_PORTAMENTO_UP, 0x7f00, "PORTAMENTO UP", "PORTUP"},
+        {TE_EFFECT_PORTAMENTO_DOWN, 0x7f00, "PORTAMENTO DOWN", "PORTDN"},
+        {TE_EFFECT_SLIDE, 0x7f00, "SLIDE", "SLIDE"},
+        {TE_EFFECT_VIBRATO, 0x7f00, "VIBRATO", "VIB"},
+        {TE_EFFECT_VOLUME_FADE, 0x7f00, "VOLUME FADE", "V.FADE"},
+        {TE_EFFECT_SKIP_PATTERN, 0x7f00, "SKIP PATTERN", "P.SKIP"},
+        {TE_EFFECT_SET_SPEED_PROG_PERIOD, 0x7f00, "SET SPEED (PROG.PER.IN PROGRAM)", "P.PER."},
+        {TE_PROGRAM_LOOP_BEGIN, 0x7f00, "PROGRAM LOOP BEGIN", "L.BEG."},
+        {TE_PROGRAM_LOOP_END, 0x7f00, "PROGRAM LOOP END", "L.END"},
+
+        {TE_PROGRAM_NOP, 0x7fff, "NO OPERATION", ""},
+        {TE_PROGRAM_END, 0x7fff, "PROGRAM END", "PR.END"},
+        {TE_PROGRAM_JUMP, 0x7f00, "JUMP TO POSITION", "GOTO"},
+        {0, 0, NULL, NULL},
+};
+
+char *get_opcode_description(uint16_t opcode, bool short_description)
+{
+    for (int i = 0; opcode_desc[i].name != NULL; i++)
+    {
+        if (opcode_desc[i].opcode == (opcode & opcode_desc[i].mask))
+        {
+            return short_description ? opcode_desc[i].shortname : opcode_desc[i].name;
+        }
+    }
+
+    return NULL;
+}

+ 13 - 0
view/opcode_description.h

@@ -0,0 +1,13 @@
+#pragma once
+
+#include <stdio.h>
+#include "../tracker_engine/tracker_engine_defs.h"
+
+typedef struct
+{
+    uint16_t opcode;
+    uint16_t mask;
+    char *name, *shortname;
+} OpcodeDescription;
+
+char *get_opcode_description(uint16_t opcode, bool short_description);

+ 1 - 41
view/pattern_editor.c

@@ -44,46 +44,6 @@ char *notename(uint8_t note)
     return buffer;
 }
 
-const char to_char_array[] =
-    {
-        '0',
-        '1',
-        '2',
-        '3',
-        '4',
-        '5',
-        '6',
-        '7',
-        '8',
-        '9',
-        'A',
-        'B',
-        'C',
-        'D',
-        'E',
-        'F',
-        'G',
-        'H',
-        'I',
-        'J',
-        'K',
-        'L',
-        'M',
-        'N',
-        'O',
-        'P',
-        'Q',
-        'R',
-        'S',
-        'T',
-        'U',
-        'V',
-        'W',
-        'X',
-        'Y',
-        'Z',
-};
-
 char to_char(uint8_t number)
 {
     return to_char_array[number];
@@ -307,7 +267,7 @@ void draw_songinfo_view(Canvas *canvas, FlizzerTrackerApp *tracker)
 
     snprintf(buffer, sizeof(buffer), "PAT.P.%02X/%02X", tracker->tracker_engine.pattern_position, tracker->song.pattern_length - 1);
     draw_generic_n_digit_field(tracker, canvas, EDIT_SONGINFO, SI_PATTERNPOS, buffer, 42, 5, 2);
-    snprintf(buffer, sizeof(buffer), "SEQ.P.%02X/%02X", tracker->tracker_engine.sequence_position, tracker->song.num_sequence_steps);
+    snprintf(buffer, sizeof(buffer), "SEQ.P.%02X/%02X", tracker->tracker_engine.sequence_position, tracker->song.num_sequence_steps - 1);
     draw_generic_n_digit_field(tracker, canvas, EDIT_SONGINFO, SI_SEQUENCEPOS, buffer, 42, 11, 2);
     snprintf(buffer, sizeof(buffer), "SPD.%02X", tracker->song.speed);
     draw_generic_n_digit_field(tracker, canvas, EDIT_SONGINFO, SI_SONGSPEED, buffer, 42, 17, 2);

+ 2 - 0
view/pattern_editor.h

@@ -6,6 +6,8 @@
 #include <furi.h>
 #include <gui/gui.h>
 
+extern const char to_char_array[];
+
 void draw_pattern_view(Canvas *canvas, FlizzerTrackerApp *tracker);
 void draw_sequence_view(Canvas *canvas, FlizzerTrackerApp *tracker);
 void draw_songinfo_view(Canvas *canvas, FlizzerTrackerApp *tracker);