├── .gitattributes ├── images ├── help.png ├── channel_off.png ├── channel_on.png ├── note_release.png ├── checkbox_empty.png ├── checkbox_checked.png ├── flizzer_tracker_module.png └── flizzer_tracker_instrument.png ├── flizzer_tracker.png ├── screenshots ├── inst.png └── pat.png ├── .vscode └── settings.json ├── macros.h ├── wiki_images ├── main_screen.png ├── pattern_row.png ├── sequence_loop.png ├── instrument_editor.png └── instrument_program.png ├── .flipcorg └── gallery │ ├── Screenshot-20230215-154807.png │ └── Screenshot-20230215-155127.png ├── audio_modes.c ├── sound_engine ├── sound_engine_adsr.h ├── sound_engine_osc.h ├── freqs.h ├── sound_engine_filter.h ├── sound_engine.h ├── sound_engine_filter.c ├── freqs.c ├── sound_engine_adsr.c ├── sound_engine_defs.h ├── sound_engine.c └── sound_engine_osc.c ├── README_CATALOG.md ├── view ├── char_array.c ├── opcode_description.h ├── instrument_editor.h ├── pattern_editor.h ├── opcode_description.c └── pattern_editor.c ├── README.md ├── tracker_engine ├── do_effects.h ├── diskop.h ├── tracker_engine.h ├── diskop.c ├── tracker_engine_defs.h └── do_effects.c ├── init_deinit.h ├── input ├── sequence.h ├── instrument.h ├── instrument_program.h ├── pattern.h ├── songinfo.h ├── sequence.c ├── songinfo.c ├── instrument_program.c ├── pattern.c └── instrument.c ├── diskop.h ├── application.fam ├── docs └── changelog.md ├── util.h ├── LICENSE ├── input_event.h ├── flizzer_tracker_hal.h ├── font.h ├── flizzer_tracker.c ├── flizzer_tracker.h ├── .clang-format ├── util.c ├── diskop.c ├── init_deinit.c ├── flizzer_tracker_hal.c └── input_event.c /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /images/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LTVA1/flizzer_tracker/HEAD/images/help.png -------------------------------------------------------------------------------- /flizzer_tracker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LTVA1/flizzer_tracker/HEAD/flizzer_tracker.png -------------------------------------------------------------------------------- /screenshots/inst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LTVA1/flizzer_tracker/HEAD/screenshots/inst.png -------------------------------------------------------------------------------- /screenshots/pat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LTVA1/flizzer_tracker/HEAD/screenshots/pat.png -------------------------------------------------------------------------------- /images/channel_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LTVA1/flizzer_tracker/HEAD/images/channel_off.png -------------------------------------------------------------------------------- /images/channel_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LTVA1/flizzer_tracker/HEAD/images/channel_on.png -------------------------------------------------------------------------------- /images/note_release.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LTVA1/flizzer_tracker/HEAD/images/note_release.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "tracker_engine.h": "c" 4 | } 5 | } -------------------------------------------------------------------------------- /images/checkbox_empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LTVA1/flizzer_tracker/HEAD/images/checkbox_empty.png -------------------------------------------------------------------------------- /macros.h: -------------------------------------------------------------------------------- 1 | #define my_min(a, b) (((a) < (b)) ? (a) : (b)) 2 | #define my_max(a, b) (((a) > (b)) ? (a) : (b)) -------------------------------------------------------------------------------- /images/checkbox_checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LTVA1/flizzer_tracker/HEAD/images/checkbox_checked.png -------------------------------------------------------------------------------- /wiki_images/main_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LTVA1/flizzer_tracker/HEAD/wiki_images/main_screen.png -------------------------------------------------------------------------------- /wiki_images/pattern_row.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LTVA1/flizzer_tracker/HEAD/wiki_images/pattern_row.png -------------------------------------------------------------------------------- /wiki_images/sequence_loop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LTVA1/flizzer_tracker/HEAD/wiki_images/sequence_loop.png -------------------------------------------------------------------------------- /images/flizzer_tracker_module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LTVA1/flizzer_tracker/HEAD/images/flizzer_tracker_module.png -------------------------------------------------------------------------------- /wiki_images/instrument_editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LTVA1/flizzer_tracker/HEAD/wiki_images/instrument_editor.png -------------------------------------------------------------------------------- /wiki_images/instrument_program.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LTVA1/flizzer_tracker/HEAD/wiki_images/instrument_program.png -------------------------------------------------------------------------------- /images/flizzer_tracker_instrument.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LTVA1/flizzer_tracker/HEAD/images/flizzer_tracker_instrument.png -------------------------------------------------------------------------------- /.flipcorg/gallery/Screenshot-20230215-154807.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LTVA1/flizzer_tracker/HEAD/.flipcorg/gallery/Screenshot-20230215-154807.png -------------------------------------------------------------------------------- /.flipcorg/gallery/Screenshot-20230215-155127.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LTVA1/flizzer_tracker/HEAD/.flipcorg/gallery/Screenshot-20230215-155127.png -------------------------------------------------------------------------------- /audio_modes.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | char* audio_modes_text[2] = { 5 | "Internal", 6 | "External", 7 | }; 8 | bool audio_modes_values[2] = {false, true}; -------------------------------------------------------------------------------- /sound_engine/sound_engine_adsr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "sound_engine_defs.h" 4 | 5 | int32_t sound_engine_cycle_and_output_adsr( 6 | int32_t input, 7 | SoundEngine* eng, 8 | SoundEngineADSR* adsr, 9 | uint16_t* flags); -------------------------------------------------------------------------------- /README_CATALOG.md: -------------------------------------------------------------------------------- 1 | # Flizzer Tracker 2 | A Flipper Zero chiptune tracker. Supports 4 channels, external (through PA6 pin) and internal (built-in buzzer) audio output. Each channel has a functionality akin to MOS Technology SID sound chip channel. -------------------------------------------------------------------------------- /view/char_array.c: -------------------------------------------------------------------------------- 1 | const char to_char_array[] = { 2 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 3 | 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 4 | }; -------------------------------------------------------------------------------- /sound_engine/sound_engine_osc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "sound_engine_defs.h" 4 | 5 | uint16_t sound_engine_triangle(uint32_t acc); 6 | 7 | uint16_t 8 | sound_engine_osc(SoundEngine* sound_engine, SoundEngineChannel* channel, uint32_t prev_acc); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flizzer Tracker 2 | A Flipper Zero chiptune tracker. Supports 4 channels, external (through PA6 pin) and internal (built-in buzzer) audio output. Each channel has a functionality akin to MOS Technology SID sound chip channel. 3 | 4 | [Telegram channel](https://t.me/flizzer_tracker) -------------------------------------------------------------------------------- /sound_engine/freqs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define FREQ_TAB_SIZE 12 /* one octave */ 7 | #define NUM_OCTAVES 8 /* 0-7th octaves */ 8 | 9 | extern const uint32_t frequency_table[FREQ_TAB_SIZE]; 10 | 11 | uint32_t get_freq(uint16_t note); -------------------------------------------------------------------------------- /tracker_engine/do_effects.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "tracker_engine_defs.h" 4 | #include 5 | #include 6 | 7 | void do_command( 8 | uint16_t opcode, 9 | TrackerEngine* tracker_engine, 10 | uint8_t channel, 11 | uint8_t tick, 12 | bool from_program); -------------------------------------------------------------------------------- /init_deinit.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "flizzer_tracker.h" 4 | #include "flizzer_tracker_hal.h" 5 | 6 | FlizzerTrackerApp* init_tracker( 7 | uint32_t sample_rate, 8 | uint8_t rate, 9 | bool external_audio_output, 10 | uint32_t audio_buffer_size); 11 | void deinit_tracker(FlizzerTrackerApp* tracker); -------------------------------------------------------------------------------- /view/opcode_description.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../tracker_engine/tracker_engine_defs.h" 4 | #include 5 | 6 | typedef struct { 7 | uint16_t opcode; 8 | uint16_t mask; 9 | char *name, *shortname; 10 | } OpcodeDescription; 11 | 12 | char* get_opcode_description(uint16_t opcode, bool short_description); -------------------------------------------------------------------------------- /input/sequence.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../flizzer_tracker.h" 8 | #include "../sound_engine/sound_engine_defs.h" 9 | #include "../tracker_engine/tracker_engine_defs.h" 10 | #include "../util.h" 11 | 12 | void sequence_edit_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* event); -------------------------------------------------------------------------------- /input/instrument.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../flizzer_tracker.h" 8 | #include "../sound_engine/sound_engine_defs.h" 9 | #include "../tracker_engine/tracker_engine_defs.h" 10 | #include "../util.h" 11 | 12 | void instrument_edit_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* event); -------------------------------------------------------------------------------- /view/instrument_editor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../flizzer_tracker.h" 4 | #include "../tracker_engine/tracker_engine_defs.h" 5 | #include "pattern_editor.h" 6 | 7 | #include 8 | #include 9 | 10 | void draw_instrument_view(Canvas* canvas, FlizzerTrackerApp* tracker); 11 | void draw_instrument_program_view(Canvas* canvas, FlizzerTrackerApp* tracker); -------------------------------------------------------------------------------- /input/instrument_program.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../flizzer_tracker.h" 8 | #include "../sound_engine/sound_engine_defs.h" 9 | #include "../tracker_engine/tracker_engine_defs.h" 10 | #include "../util.h" 11 | 12 | void instrument_program_edit_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* event); -------------------------------------------------------------------------------- /input/pattern.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../flizzer_tracker.h" 8 | #include "../sound_engine/sound_engine_defs.h" 9 | #include "../tracker_engine/tracker_engine_defs.h" 10 | #include "../util.h" 11 | 12 | #define MAX_PATTERNX (2 + 1 + 1 + 3) 13 | 14 | void pattern_edit_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* event); -------------------------------------------------------------------------------- /tracker_engine/diskop.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "tracker_engine.h" 4 | #include "tracker_engine_defs.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | bool load_song(TrackerSong* song, Stream* stream); 11 | bool load_instrument(Instrument* inst, Stream* stream); 12 | void load_instrument_inner(Stream* stream, Instrument* inst, uint8_t version); -------------------------------------------------------------------------------- /input/songinfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "../flizzer_tracker.h" 9 | #include "../sound_engine/sound_engine_defs.h" 10 | #include "../tracker_engine/tracker_engine_defs.h" 11 | #include "../util.h" 12 | 13 | void songinfo_edit_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* event); 14 | void return_from_keyboard_callback(void* ctx); -------------------------------------------------------------------------------- /sound_engine/sound_engine_filter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "sound_engine_defs.h" 4 | 5 | void sound_engine_filter_set_coeff(SoundEngineFilter* flt, uint32_t frequency, uint16_t resonance); 6 | void sound_engine_filter_cycle(SoundEngineFilter* flt, int32_t input); 7 | int32_t sound_engine_output_lowpass(SoundEngineFilter* flt); 8 | int32_t sound_engine_output_highpass(SoundEngineFilter* flt); 9 | int32_t sound_engine_output_bandpass(SoundEngineFilter* flt); -------------------------------------------------------------------------------- /diskop.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "flizzer_tracker.h" 4 | #include "tracker_engine/diskop.h" 5 | 6 | bool save_song(FlizzerTrackerApp* tracker, FuriString* filepath); 7 | bool save_instrument(FlizzerTrackerApp* tracker, FuriString* filepath); 8 | 9 | bool load_song_util(FlizzerTrackerApp* tracker, FuriString* filepath); 10 | bool load_instrument_util(FlizzerTrackerApp* tracker, FuriString* filepath); 11 | 12 | void save_config(FlizzerTrackerApp* tracker); 13 | void load_config(FlizzerTrackerApp* tracker); -------------------------------------------------------------------------------- /application.fam: -------------------------------------------------------------------------------- 1 | App( 2 | appid="flizzer_tracker", 3 | name="Flizzer Tracker", 4 | apptype=FlipperAppType.EXTERNAL, 5 | entry_point="flizzer_tracker_app", 6 | cdefines=["APP_FLIZZER_TRACKER"], 7 | stack_size=2 * 1024, 8 | order=90, 9 | fap_version=(0, 11), 10 | fap_description="An advanced Flipper Zero chiptune tracker with 4 channels", 11 | fap_author="LTVA", 12 | fap_weburl="https://github.com/LTVA1/flizzer_tracker", 13 | fap_icon="flizzer_tracker.png", 14 | fap_icon_assets="images", 15 | fap_category="Media", 16 | ) 17 | -------------------------------------------------------------------------------- /view/pattern_editor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../flizzer_tracker.h" 4 | #include "../tracker_engine/tracker_engine_defs.h" 5 | 6 | #include 7 | #include 8 | 9 | extern const char to_char_array[]; 10 | 11 | void draw_pattern_view(Canvas* canvas, FlizzerTrackerApp* tracker); 12 | void draw_sequence_view(Canvas* canvas, FlizzerTrackerApp* tracker); 13 | void draw_songinfo_view(Canvas* canvas, FlizzerTrackerApp* tracker); 14 | 15 | void draw_generic_n_digit_field( 16 | FlizzerTrackerApp* tracker, 17 | Canvas* canvas, 18 | uint8_t focus, 19 | uint8_t param, 20 | const char* text, 21 | uint8_t x, 22 | uint8_t y, 23 | uint8_t digits); 24 | char to_char(uint8_t number); 25 | char* notename(uint8_t note); -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | # Flizzer Tracker v0.11 # 2 | ## Fixed ## 3 | - Build for new firmware version 4 | 5 | # Flizzer Tracker v0.10 # 6 | ## Fixed ## 7 | - App not responding to keypresses 8 | 9 | # Flizzer Tracker v0.9 # 10 | 11 | ## Added ## 12 | - Vxx effect (detune) 13 | 14 | # Flizzer Tracker v0.8 # 15 | 16 | ## Added ## 17 | - Qxx effect (set tracker engine rate) 18 | 19 | # Flizzer Tracker v0.7 # 20 | 21 | ## Added ## 22 | - Save/load instruments in separate .fzi files 23 | - Pattern editor now occupies full screen when you focus on it 24 | - Copypaste menu (hold Back to open it when focused on pattern editor), operates on whole patterns 25 | 26 | # Flizzer Tracker v0.2-v0.6 # 27 | 28 | - Small fixes for new firmware versions 29 | 30 | # Flizzer Tracker v0.1 # 31 | 32 | - Initial release 33 | -------------------------------------------------------------------------------- /sound_engine/sound_engine.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "freqs.h" 4 | #include "sound_engine_adsr.h" 5 | #include "sound_engine_defs.h" 6 | #include "sound_engine_filter.h" 7 | #include "sound_engine_osc.h" 8 | 9 | void sound_engine_init( 10 | SoundEngine* sound_engine, 11 | uint32_t sample_rate, 12 | bool external_audio_output, 13 | uint32_t audio_buffer_size); 14 | void sound_engine_deinit(SoundEngine* sound_engine); 15 | void sound_engine_set_channel_frequency( 16 | SoundEngine* sound_engine, 17 | SoundEngineChannel* channel, 18 | uint16_t note); 19 | void sound_engine_fill_buffer( 20 | SoundEngine* sound_engine, 21 | uint16_t* audio_buffer, 22 | uint32_t audio_buffer_size); 23 | void sound_engine_enable_gate(SoundEngine* sound_engine, SoundEngineChannel* channel, bool enable); -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "flizzer_tracker.h" 7 | #include "sound_engine/sound_engine_defs.h" 8 | #include "tracker_engine/tracker_engine.h" 9 | #include "tracker_engine/tracker_engine_defs.h" 10 | 11 | #include "macros.h" 12 | 13 | #define clamp(val, add, _min, _max) val = my_min(_max, my_max(_min, (int32_t)val + add)) 14 | #define flipbit(val, bit) \ 15 | { val ^= bit; }; 16 | 17 | void reset_buffer(SoundEngine* sound_engine); 18 | void play_song(FlizzerTrackerApp* tracker, bool from_cursor); 19 | void stop_song(FlizzerTrackerApp* tracker); 20 | 21 | bool is_pattern_empty(TrackerSong* song, uint8_t pattern); 22 | bool check_and_allocate_pattern(TrackerSong* song, uint8_t pattern); 23 | void change_pattern_length(TrackerSong* song, uint16_t new_length); 24 | 25 | bool check_and_allocate_instrument(TrackerSong* song, uint8_t inst); 26 | void set_default_song(FlizzerTrackerApp* tracker); -------------------------------------------------------------------------------- /sound_engine/sound_engine_filter.c: -------------------------------------------------------------------------------- 1 | #include "sound_engine_filter.h" 2 | 3 | void sound_engine_filter_set_coeff(SoundEngineFilter* flt, uint32_t frequency, uint16_t resonance) { 4 | flt->cutoff = (frequency << 5); 5 | flt->resonance = ((int32_t)resonance * 11 / 6) - 200; 6 | } 7 | 8 | void sound_engine_filter_cycle( 9 | SoundEngineFilter* flt, 10 | int32_t input) // don't ask me how it works, stolen from Furnace tracker TSU synth 11 | { 12 | input /= 8; 13 | flt->low = flt->low + ((flt->cutoff * flt->band) >> 16); 14 | flt->high = input - flt->low - (((256 - flt->resonance) * flt->band) >> 8); 15 | flt->band = ((flt->cutoff * flt->high) >> 16) + flt->band; 16 | } 17 | 18 | int32_t sound_engine_output_lowpass(SoundEngineFilter* flt) { 19 | return flt->low * 8; 20 | } 21 | 22 | int32_t sound_engine_output_highpass(SoundEngineFilter* flt) { 23 | return flt->high * 8; 24 | } 25 | 26 | int32_t sound_engine_output_bandpass(SoundEngineFilter* flt) { 27 | return flt->band * 8; 28 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 LTVA1 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tracker_engine/tracker_engine.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "do_effects.h" 4 | #include "tracker_engine_defs.h" 5 | 6 | void tracker_engine_init(TrackerEngine* tracker_engine, uint8_t rate, SoundEngine* sound_engine); 7 | void tracker_engine_deinit(TrackerEngine* tracker_engine, bool free_song); 8 | void tracker_engine_advance_tick(TrackerEngine* tracker_engine); 9 | void tracker_engine_set_song(TrackerEngine* tracker_engine, TrackerSong* song); 10 | void tracker_engine_deinit_song(TrackerSong* song, bool free_song); 11 | void tracker_engine_trigger_instrument_internal( 12 | TrackerEngine* tracker_engine, 13 | uint8_t chan, 14 | Instrument* pinst, 15 | uint16_t note); 16 | 17 | uint8_t tracker_engine_get_note(TrackerSongPatternStep* step); 18 | uint8_t tracker_engine_get_instrument(TrackerSongPatternStep* step); 19 | uint8_t tracker_engine_get_volume(TrackerSongPatternStep* step); 20 | uint16_t tracker_engine_get_command(TrackerSongPatternStep* step); 21 | 22 | void set_note(TrackerSongPatternStep* step, uint8_t note); 23 | void set_instrument(TrackerSongPatternStep* step, uint8_t inst); 24 | void set_volume(TrackerSongPatternStep* step, uint8_t vol); 25 | void set_command(TrackerSongPatternStep* step, uint16_t command); 26 | 27 | void set_default_instrument(Instrument* inst); 28 | void set_empty_pattern(TrackerSongPattern* pattern, uint16_t pattern_length); -------------------------------------------------------------------------------- /input_event.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "flizzer_tracker.h" 8 | #include "sound_engine/sound_engine_defs.h" 9 | #include "tracker_engine/tracker_engine_defs.h" 10 | #include "util.h" 11 | 12 | #include "input/instrument.h" 13 | #include "input/instrument_program.h" 14 | #include "input/pattern.h" 15 | #include "input/sequence.h" 16 | #include "input/songinfo.h" 17 | 18 | extern bool audio_modes_values[]; 19 | extern char* audio_modes_text[]; 20 | 21 | void overwrite_file_widget_yes_input_callback(GuiButtonType result, InputType type, void* ctx); 22 | void overwrite_file_widget_no_input_callback(GuiButtonType result, InputType type, void* ctx); 23 | 24 | void overwrite_instrument_file_widget_yes_input_callback( 25 | GuiButtonType result, 26 | InputType type, 27 | void* ctx); 28 | void overwrite_instrument_file_widget_no_input_callback( 29 | GuiButtonType result, 30 | InputType type, 31 | void* ctx); 32 | 33 | uint32_t submenu_exit_callback(void* context); 34 | uint32_t submenu_settings_exit_callback(void* context); 35 | void submenu_callback(void* context, uint32_t index); 36 | void submenu_copypaste_callback(void* context, uint32_t index); 37 | void audio_output_changed_callback(VariableItem* item); 38 | void process_input_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* event); -------------------------------------------------------------------------------- /sound_engine/freqs.c: -------------------------------------------------------------------------------- 1 | #include "freqs.h" 2 | 3 | const uint32_t frequency_table[FREQ_TAB_SIZE] = { 4 | (uint32_t)(2093.00 * 1024), // 7th octave, the highest in this tracker 5 | (uint32_t)(2217.46 * 1024), // frequency precision is 1 / 1024th of Hz 6 | (uint32_t)(2349.32 * 1024), 7 | (uint32_t)(2489.02 * 1024), 8 | (uint32_t)(2637.02 * 1024), 9 | (uint32_t)(2793.83 * 1024), 10 | (uint32_t)(2959.96 * 1024), 11 | (uint32_t)(3135.96 * 1024), 12 | (uint32_t)(3322.44 * 1024), 13 | (uint32_t)(3520.00 * 1024), 14 | (uint32_t)(3729.31 * 1024), 15 | (uint32_t)(3951.07 * 1024), 16 | }; 17 | 18 | uint32_t get_freq(uint16_t note) { 19 | if(note >= ((FREQ_TAB_SIZE * 8) << 8)) { 20 | return frequency_table[FREQ_TAB_SIZE - 1]; 21 | } 22 | 23 | if((note & 0xff) == 0) { 24 | return frequency_table[((note >> 8) % 12)] / 25 | (2 << (((NUM_OCTAVES) - ((note >> 8) / 12)) - 2)); // wrap to one octave 26 | } 27 | 28 | else { 29 | uint64_t f1 = frequency_table[((note >> 8) % 12)] / 30 | (uint64_t)(2 << (((NUM_OCTAVES) - ((note >> 8) / 12)) - 2)); 31 | uint64_t f2 = frequency_table[(((note >> 8) + 1) % 12)] / 32 | (uint64_t)(2 << (((NUM_OCTAVES) - (((note >> 8) + 1) / 12)) - 2)); 33 | 34 | return f1 + (uint64_t)((f2 - f1) * (uint64_t)(note & 0xff)) / (uint64_t)256; 35 | } 36 | } -------------------------------------------------------------------------------- /flizzer_tracker_hal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "sound_engine/sound_engine.h" 4 | #include "tracker_engine/tracker_engine.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #define SPEAKER_PWM_TIMER TIM16 15 | #define SAMPLE_RATE_TIMER TIM1 16 | #define TRACKER_ENGINE_TIMER TIM2 17 | 18 | #define SPEAKER_PWM_TIMER_CHANNEL LL_TIM_CHANNEL_CH1 19 | 20 | #define TIMER_BASE_CLOCK 64000000 /* CPU frequency, 64 MHz */ 21 | 22 | #define DMA_INSTANCE DMA1, LL_DMA_CHANNEL_1 23 | 24 | void sound_engine_dma_isr(void* ctx); 25 | void tracker_engine_timer_isr(void* ctx); 26 | void sound_engine_init_hardware( 27 | uint32_t sample_rate, 28 | bool external_audio_output, 29 | uint16_t* audio_buffer, 30 | uint32_t audio_buffer_size); 31 | void sound_engine_dma_init(uint32_t address, uint32_t size); 32 | void sound_engine_PWM_timer_init(bool external_audio_output); 33 | void sound_engine_set_audio_output(bool external_audio_output); 34 | void tracker_engine_init_hardware(uint8_t rate); 35 | void tracker_engine_timer_init(uint8_t rate); 36 | void tracker_engine_set_rate(uint8_t rate); 37 | void sound_engine_start(); 38 | void sound_engine_stop(); 39 | void stop(); 40 | void play(); 41 | void tracker_engine_stop(); 42 | void sound_engine_deinit_timer(); 43 | void tracker_engine_start(); -------------------------------------------------------------------------------- /sound_engine/sound_engine_adsr.c: -------------------------------------------------------------------------------- 1 | #include "sound_engine_adsr.h" 2 | 3 | int32_t sound_engine_cycle_and_output_adsr( 4 | int32_t input, 5 | SoundEngine* eng, 6 | SoundEngineADSR* adsr, 7 | uint16_t* flags) { 8 | switch(adsr->envelope_state) { 9 | case ATTACK: { 10 | adsr->envelope += adsr->envelope_speed; 11 | 12 | if(adsr->envelope >= MAX_ADSR) { 13 | adsr->envelope_state = DECAY; 14 | adsr->envelope = MAX_ADSR; 15 | 16 | adsr->envelope_speed = envspd(eng, adsr->d); 17 | } 18 | 19 | break; 20 | } 21 | 22 | case DECAY: { 23 | if(adsr->envelope > ((uint32_t)adsr->s << 17) + adsr->envelope_speed) { 24 | adsr->envelope -= adsr->envelope_speed; 25 | } 26 | 27 | else { 28 | adsr->envelope = (uint32_t)adsr->s << 17; 29 | adsr->envelope_state = (adsr->s == 0) ? RELEASE : SUSTAIN; 30 | 31 | adsr->envelope_speed = envspd(eng, adsr->r); 32 | } 33 | 34 | break; 35 | } 36 | 37 | case SUSTAIN: 38 | case DONE: { 39 | break; 40 | } 41 | 42 | case RELEASE: { 43 | if(adsr->envelope > adsr->envelope_speed) { 44 | adsr->envelope -= adsr->envelope_speed; 45 | } 46 | 47 | else { 48 | adsr->envelope_state = DONE; 49 | *flags &= ~SE_ENABLE_GATE; 50 | adsr->envelope = 0; 51 | } 52 | 53 | break; 54 | } 55 | } 56 | 57 | return (int32_t)((int32_t)input * (int32_t)(adsr->envelope >> 10) / (int32_t)(MAX_ADSR >> 10) * 58 | (int32_t)adsr->volume / (int32_t)MAX_ADSR_VOLUME); 59 | } -------------------------------------------------------------------------------- /font.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | Fontname: -Raccoon-Fixed4x6-Medium-R-Normal--6-60-75-75-P-40-ISO10646-1 5 | Copyright: 6 | Glyphs: 95/203 7 | BBX Build Mode: 0 8 | */ 9 | // this is a modified version with dot and semicolon moved 1 pixel to the left; lowercase symbols removed to save space 10 | // changed "G", "N" and "V" glyphs 11 | const uint8_t u8g2_font_tom_thumb_4x6_tr[610] = 12 | "a\0\2\2\2\3\2\3\4\3\5\0\0\5\0\5\0\1`\0\0\2E\0\4@\62\1\4@\62\2" 13 | "\4@\62\3\4@\62\4\4@\62\5\4@\62\6\4@\62\7\4@\62\10\4@\62\11\4@\62\12" 14 | "\4@\62\13\4@\62\14\4@\62\15\4@\62\16\4@\62\17\4@\62\20\4@\62\21\4@\62\22" 15 | "\4@\62\23\4@\62\24\4@\62\25\4@\62\26\4@\62\27\4@\62\30\4@\62\31\4@\62\32" 16 | "\4@\62\33\4@\62\34\4@\62\35\4@\62\36\4@\62\37\4@\62 \4@\62!\5u\62+" 17 | "\42\6\313\63I\5#\10W\62i\250\241\2$\10Wr#\216\230\0%\10W\62\31\265Q\0&\10" 18 | "W\62J\215\224\4'\5\351\63\2(\6vr\252\14)\7V\62\61%\5*\6O\63\251\3+\7" 19 | "\317ri%\0,\5Jr\12-\5G\63\3.\5E\62\1/\7W\262U\31\1\60\7Wr\313" 20 | "Z\0\61\6Vr\253\1\62\7W\62\32\244r\63\11W\62\32\244\14\26\0\64\7W\62I\215X\65" 21 | "\10W\62#j\260\0\66\7Wrs\244\21\67\7W\62\63\225\21\70\10W\62#\15\65\2\71\10W" 22 | "\62#\215\270\0:\5\315\62);\7Rr\31(\0<\10W\262\251\6\31\4=\6\317\62\33\14>" 23 | "\11W\62\31d\220J\0\77\10W\62\63e\230\0@\7Wr\325\320@A\7Wr\325P*B\10" 24 | "W\62*\255\264\0C\7Wr\263\6\2D\7W\62*Y\13E\7W\62#\216\70F\10W\62#" 25 | "\216\30\1G\7Wr\63\251$H\10W\62I\15\245\2I\7W\62+V\3J\7W\262\245\252\0" 26 | "K\10W\62I\255\244\2L\6W\62\261\71M\10W\62i\14\245\2N\7W\62*\271\2O\7W" 27 | "r\225U\1P\10W\62*\255\30\1Q\7Wr\225\32IR\7W\62*\215US\10Wr\33d" 28 | "\260\0T\7W\62+\266\0U\7W\62\311\225\4V\7W\62\311U\1W\10W\62I\215\241\2X" 29 | "\10W\62I\265T\0Y\10W\62I\225\25\0Z\7W\62\63\225\3[\7W\62#\226\3\134\7\317" 30 | "\62\31d\20]\7W\62\263\34\1^\5\313s\15_\5G\62\3`\5\312\63\61\0\0\0\4\377\377" 31 | "\0"; -------------------------------------------------------------------------------- /sound_engine/sound_engine_defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define NUM_CHANNELS 4 8 | 9 | #define RANDOM_SEED 0xf31782ce 10 | 11 | #define ACC_BITS 23 12 | #define ACC_LENGTH (1 << (ACC_BITS - 1)) 13 | 14 | #define OUTPUT_BITS 16 15 | #define WAVE_AMP (1 << OUTPUT_BITS) 16 | 17 | #define SINE_LUT_SIZE 256 18 | #define SINE_LUT_BITDEPTH 8 19 | 20 | #define MAX_ADSR (0xff << 17) 21 | #define MAX_ADSR_VOLUME 0x80 22 | #define BASE_FREQ 22050 23 | #define envspd(eng, slope) \ 24 | ((slope) != 0 ? \ 25 | (((uint64_t)MAX_ADSR / ((slope) * (slope)*256 / 8)) * BASE_FREQ / eng->sample_rate) : \ 26 | ((uint64_t)MAX_ADSR * BASE_FREQ / eng->sample_rate)) 27 | 28 | typedef enum { 29 | SE_WAVEFORM_NONE = 0, 30 | SE_WAVEFORM_NOISE = 1, 31 | SE_WAVEFORM_PULSE = 2, 32 | SE_WAVEFORM_TRIANGLE = 4, 33 | SE_WAVEFORM_SAW = 8, 34 | SE_WAVEFORM_NOISE_METAL = 16, 35 | SE_WAVEFORM_SINE = 32, 36 | } SoundEngineWaveformType; 37 | 38 | typedef enum { 39 | SE_ENABLE_FILTER = 1, 40 | SE_ENABLE_GATE = 2, 41 | SE_ENABLE_RING_MOD = 4, 42 | SE_ENABLE_HARD_SYNC = 8, 43 | SE_ENABLE_KEYDOWN_SYNC = 16, // sync oscillators on keydown 44 | } SoundEngineFlags; 45 | 46 | typedef enum { 47 | FIL_OUTPUT_LOWPASS = 1, 48 | FIL_OUTPUT_HIGHPASS = 2, 49 | FIL_OUTPUT_BANDPASS = 3, 50 | FIL_OUTPUT_LOW_HIGH = 4, 51 | FIL_OUTPUT_HIGH_BAND = 5, 52 | FIL_OUTPUT_LOW_BAND = 6, 53 | FIL_OUTPUT_LOW_HIGH_BAND = 7, 54 | /* ============ */ 55 | FIL_MODES = 8, 56 | } SoundEngineFilterModes; 57 | 58 | typedef enum { 59 | ATTACK = 1, 60 | DECAY = 2, 61 | SUSTAIN = 3, 62 | RELEASE = 4, 63 | DONE = 5, 64 | } SoundEngineEnvelopeStates; 65 | 66 | typedef struct { 67 | uint8_t a, d, s, r, volume, envelope_state; 68 | uint32_t envelope, envelope_speed; 69 | } SoundEngineADSR; 70 | 71 | typedef struct { 72 | int32_t cutoff, resonance, low, high, band; 73 | } SoundEngineFilter; 74 | 75 | typedef struct { 76 | uint32_t accumulator; 77 | uint32_t frequency; 78 | uint8_t waveform; 79 | uint16_t pw; 80 | uint32_t lfsr; 81 | SoundEngineADSR adsr; 82 | 83 | uint16_t flags; 84 | 85 | uint8_t ring_mod, hard_sync; // 0xff = self 86 | uint8_t sync_bit; 87 | 88 | uint8_t filter_mode; 89 | 90 | SoundEngineFilter filter; 91 | } SoundEngineChannel; 92 | 93 | typedef struct { 94 | SoundEngineChannel channel[NUM_CHANNELS]; 95 | uint32_t sample_rate; 96 | uint16_t* audio_buffer; 97 | uint32_t audio_buffer_size; 98 | bool external_audio_output; 99 | uint8_t sine_lut[SINE_LUT_SIZE]; 100 | 101 | // uint32_t counter; //for debug 102 | } SoundEngine; -------------------------------------------------------------------------------- /flizzer_tracker.c: -------------------------------------------------------------------------------- 1 | #include "flizzer_tracker.h" 2 | #include "diskop.h" 3 | #include "init_deinit.h" 4 | #include "input_event.h" 5 | #include "util.h" 6 | #include "view/instrument_editor.h" 7 | #include "view/pattern_editor.h" 8 | 9 | #include "font.h" 10 | #include 11 | 12 | void draw_callback(Canvas* canvas, void* ctx) { 13 | TrackerViewModel* model = (TrackerViewModel*)ctx; 14 | FlizzerTrackerApp* tracker = (FlizzerTrackerApp*)(model->tracker); 15 | 16 | canvas_set_color(canvas, ColorXOR); 17 | 18 | if(tracker->is_loading || tracker->is_loading_instrument) { 19 | canvas_draw_str(canvas, 10, 10, "Loading..."); 20 | return; 21 | } 22 | 23 | if(tracker->is_saving || tracker->is_saving_instrument) { 24 | canvas_draw_str(canvas, 10, 10, "Saving..."); 25 | return; 26 | } 27 | 28 | if(tracker->showing_help) { 29 | canvas_draw_icon(canvas, 0, 0, &I_help); 30 | return; 31 | } 32 | 33 | canvas_set_custom_u8g2_font(canvas, u8g2_font_tom_thumb_4x6_tr); 34 | 35 | switch(tracker->mode) { 36 | case PATTERN_VIEW: { 37 | if(tracker->tracker_engine.song == NULL) { 38 | stop(); 39 | tracker_engine_set_song(&tracker->tracker_engine, &tracker->song); 40 | } 41 | 42 | if(tracker->focus != EDIT_PATTERN) { 43 | draw_songinfo_view(canvas, tracker); 44 | } 45 | 46 | if(tracker->focus != EDIT_PATTERN) { 47 | draw_sequence_view(canvas, tracker); 48 | } 49 | 50 | draw_pattern_view(canvas, tracker); 51 | break; 52 | } 53 | 54 | case INST_EDITOR_VIEW: { 55 | draw_instrument_view(canvas, tracker); 56 | draw_instrument_program_view(canvas, tracker); 57 | break; 58 | } 59 | 60 | default: 61 | break; 62 | } 63 | } 64 | 65 | bool input_callback(InputEvent* input_event, void* ctx) { 66 | // Проверяем, что контекст не нулевой 67 | furi_assert(ctx); 68 | TrackerView* tracker_view = (TrackerView*)ctx; 69 | FlizzerTrackerApp* tracker = (FlizzerTrackerApp*)(tracker_view->context); 70 | 71 | bool consumed = false; 72 | 73 | if(input_event->key == InputKeyBack && input_event->type == InputTypeShort) { 74 | tracker->period = furi_get_tick() - tracker->current_time; 75 | tracker->current_time = furi_get_tick(); 76 | 77 | tracker->was_it_back_keypress = true; 78 | } 79 | 80 | else if(input_event->type == InputTypeShort || input_event->type == InputTypeLong) { 81 | tracker->was_it_back_keypress = false; 82 | tracker->period = 0; 83 | } 84 | 85 | uint32_t final_period = (tracker->was_it_back_keypress ? tracker->period : 0); 86 | 87 | FlizzerTrackerEvent event = { 88 | .type = EventTypeInput, .input = *input_event, .period = final_period}; 89 | 90 | if(!(tracker->is_loading) && !(tracker->is_saving)) { 91 | if(event.type == EventTypeInput) { 92 | process_input_event(tracker, &event); 93 | } 94 | } 95 | 96 | consumed = true; 97 | return consumed; 98 | } 99 | 100 | int32_t flizzer_tracker_app(void* p) { 101 | UNUSED(p); 102 | 103 | Storage* storage = furi_record_open(RECORD_STORAGE); 104 | bool st = storage_simply_mkdir(storage, APPSDATA_FOLDER); 105 | st = storage_simply_mkdir(storage, FLIZZER_TRACKER_FOLDER); 106 | st = storage_simply_mkdir(storage, FLIZZER_TRACKER_INSTRUMENTS_FOLDER); 107 | UNUSED(st); 108 | furi_record_close(RECORD_STORAGE); 109 | 110 | FlizzerTrackerApp* tracker = init_tracker(44100, 50, true, 1024); 111 | 112 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_TRACKER); 113 | view_dispatcher_run(tracker->view_dispatcher); 114 | 115 | //here program hangs until view_dispatcher_stop() is called! 116 | 117 | stop(); 118 | 119 | save_config(tracker); 120 | 121 | deinit_tracker(tracker); 122 | 123 | return 0; 124 | } -------------------------------------------------------------------------------- /view/opcode_description.c: -------------------------------------------------------------------------------- 1 | #include "opcode_description.h" 2 | #include 3 | 4 | static const OpcodeDescription opcode_desc[] = { 5 | {TE_PROGRAM_LOOP_BEGIN, 0x7f00, "PROGRAM LOOP BEGIN", "L.BEG."}, 6 | {TE_PROGRAM_LOOP_END, 0x7f00, "PROGRAM LOOP END", "L.END"}, 7 | 8 | {TE_PROGRAM_NOP, 0x7fff, "NO OPERATION", ""}, 9 | {TE_PROGRAM_END, 0x7fff, "PROGRAM END", "PR.END"}, 10 | {TE_PROGRAM_JUMP, 0x7f00, "JUMP TO POSITION", "GOTO"}, 11 | 12 | //==================================================== 13 | 14 | {TE_EFFECT_ARPEGGIO, 0x7f00, "RELATIVE ARPEGGIO NOTE", ""}, 15 | {TE_EFFECT_PORTAMENTO_UP, 0x7f00, "PORTAMENTO UP", "PORTUP"}, 16 | {TE_EFFECT_PORTAMENTO_DOWN, 0x7f00, "PORTAMENTO DOWN", "PORTDN"}, 17 | {TE_EFFECT_SLIDE, 0x7f00, "SLIDE", "SLIDE"}, 18 | {TE_EFFECT_VIBRATO, 0x7f00, "VIBRATO", "VIB"}, 19 | {TE_EFFECT_PWM, 0x7f00, "PULSE WIDTH MODIFICATION", "PWM"}, 20 | 21 | {TE_EFFECT_SET_PW, 0x7f00, "SET PULSE WIDTH", "SET PW"}, 22 | {TE_EFFECT_PW_DOWN, 0x7f00, "PULSE WIDTH DOWN", "PWDOWN"}, 23 | {TE_EFFECT_PW_UP, 0x7f00, "PULSE WIDTH UP", "PW UP"}, 24 | {TE_EFFECT_SET_CUTOFF, 0x7f00, "SET FILTER CUTOFF", "F.CUT"}, 25 | 26 | {TE_EFFECT_VOLUME_FADE, 0x7f00, "VOLUME FADE", "V.FADE"}, 27 | {TE_EFFECT_SET_WAVEFORM, 0x7f00, "SET WAVEFORM", "S.WAVE"}, 28 | {TE_EFFECT_SET_VOLUME, 0x7f00, "SET VOLUME", "VOLUME"}, 29 | {TE_EFFECT_SKIP_PATTERN, 0x7f00, "SKIP PATTERN", "P.SKIP"}, 30 | 31 | {TE_EFFECT_EXT_TOGGLE_FILTER, 0x7ff0, "TOGGLE FILTER (0=OFF,1-F=ON)", "T.FILT"}, 32 | {TE_EFFECT_EXT_PORTA_UP, 0x7ff0, "FINE PORTAMENTO UP", "PUP F."}, 33 | {TE_EFFECT_EXT_PORTA_DN, 0x7ff0, "FINE PORTAMENTO DOWN", "PDN F."}, 34 | {TE_EFFECT_EXT_FILTER_MODE, 0x7ff0, "SET FILTER MODE", "F.MODE"}, 35 | {TE_EFFECT_EXT_PATTERN_LOOP, 0x7ff0, "PATTERN LOOP:E60=BEGIN,E6X=END", "PAT.L."}, 36 | {TE_EFFECT_EXT_RETRIGGER, 0x7ff0, "RETRIGGER AT TICK X (X>0)", "RETRIG"}, 37 | {TE_EFFECT_EXT_FINE_VOLUME_DOWN, 0x7ff0, "FINE VOLUME DOWN", "VDN F."}, 38 | {TE_EFFECT_EXT_FINE_VOLUME_UP, 0x7ff0, "FINE VOLUME UP", "VUP F."}, 39 | {TE_EFFECT_EXT_NOTE_CUT, 0x7ff0, "NOTE CUT", "N.CUT"}, 40 | {TE_EFFECT_EXT_NOTE_DELAY, 0x7ff0, "NOTE DELAY", "N.DEL."}, 41 | {TE_EFFECT_EXT_PHASE_RESET, 0x7ff0, "PHASE RESET ON TICK X", "PH.RES."}, 42 | 43 | {TE_EFFECT_SET_SPEED_PROG_PERIOD, 0x7f00, "SET SPEED (PROG.PER.IN PROGRAM)", "P.PER."}, 44 | {TE_EFFECT_CUTOFF_UP, 0x7f00, "FILTER CUTOFF UP", "CUT.UP"}, 45 | {TE_EFFECT_CUTOFF_DOWN, 0x7f00, "FILTER CUTOFF DOWN", "CUT.DN"}, 46 | {TE_EFFECT_SET_RESONANCE, 0x7f00, "SET FILTER RESONANCE", "F.RES."}, 47 | {TE_EFFECT_RESONANCE_UP, 0x7f00, "FILTER RESONANCE UP", "F.R.UP"}, 48 | {TE_EFFECT_RESONANCE_DOWN, 0x7f00, "FILTER RESONANCE DOWN", "F.R.DN"}, 49 | {TE_EFFECT_SET_ATTACK, 0x7f00, "SET ENVELOPE ATTACK", "ADSR A"}, 50 | {TE_EFFECT_SET_DECAY, 0x7f00, "SET ENVELOPE DECAY", "ADSR D"}, 51 | {TE_EFFECT_SET_SUSTAIN, 0x7f00, "SET ENVELOPE SUSTAIN", "ADSR S"}, 52 | {TE_EFFECT_SET_RELEASE, 0x7f00, "SET ENVELOPE RELEASE", "ADSR R"}, 53 | {TE_EFFECT_PROGRAM_RESTART, 0x7f00, "RESTART INSTRUMENT PROGRAM", "P.RES."}, 54 | {TE_EFFECT_SET_RATE, 0x7f00, "SET TRACKER ENGINE RATE (HZ)", "E.RATE"}, 55 | {TE_EFFECT_SET_RING_MOD_SRC, 0x7f00, "SET RING MODULATION SOURCE CH.", "R.SRC"}, 56 | {TE_EFFECT_SET_HARD_SYNC_SRC, 0x7f00, "SET HARD SYNC SOURCE CHANNEL", "S.SRC"}, 57 | {TE_EFFECT_PORTA_UP_SEMITONE, 0x7f00, "PORTAMENTO UP (SEMITONES)", "PU.SEM"}, 58 | {TE_EFFECT_PORTA_DOWN_SEMITONE, 0x7f00, "PORTAMENTO DOWN (SEMITONES)", "PD.SEM"}, 59 | {TE_EFFECT_PITCH, 0x7f00, "DETUNE 80=CENT.,0=-1SEM,FF=+1SEM", "DETUNE"}, 60 | {TE_EFFECT_LEGATO, 0x7f00, "LEGATO", "LEGATO"}, 61 | {TE_EFFECT_ARPEGGIO_ABS, 0x7f00, "ABSOLUTE ARPEGGIO NOTE", ""}, 62 | {TE_EFFECT_TRIGGER_RELEASE, 0x7f00, "TRIGGER RELEASE", "TR.REL"}, 63 | {0, 0, NULL, NULL}, 64 | }; 65 | 66 | char* get_opcode_description(uint16_t opcode, bool short_description) { 67 | for(int i = 0; opcode_desc[i].name != NULL; i++) { 68 | if(opcode_desc[i].opcode == (opcode & opcode_desc[i].mask)) { 69 | return short_description ? opcode_desc[i].shortname : opcode_desc[i].name; 70 | } 71 | } 72 | 73 | return NULL; 74 | } -------------------------------------------------------------------------------- /tracker_engine/diskop.c: -------------------------------------------------------------------------------- 1 | #include "diskop.h" 2 | 3 | void load_instrument_inner(Stream* stream, Instrument* inst, uint8_t version) { 4 | UNUSED(version); 5 | 6 | size_t rwops = stream_read(stream, (uint8_t*)inst->name, sizeof(inst->name)); 7 | rwops = stream_read(stream, (uint8_t*)&inst->waveform, sizeof(inst->waveform)); 8 | rwops = stream_read(stream, (uint8_t*)&inst->flags, sizeof(inst->flags)); 9 | rwops = 10 | stream_read(stream, (uint8_t*)&inst->sound_engine_flags, sizeof(inst->sound_engine_flags)); 11 | 12 | rwops = stream_read(stream, (uint8_t*)&inst->base_note, sizeof(inst->base_note)); 13 | rwops = stream_read(stream, (uint8_t*)&inst->finetune, sizeof(inst->finetune)); 14 | 15 | rwops = stream_read(stream, (uint8_t*)&inst->slide_speed, sizeof(inst->slide_speed)); 16 | 17 | rwops = stream_read(stream, (uint8_t*)&inst->adsr, sizeof(inst->adsr)); 18 | rwops = stream_read(stream, (uint8_t*)&inst->pw, sizeof(inst->pw)); 19 | 20 | if(inst->sound_engine_flags & SE_ENABLE_RING_MOD) { 21 | rwops = stream_read(stream, (uint8_t*)&inst->ring_mod, sizeof(inst->ring_mod)); 22 | } 23 | 24 | if(inst->sound_engine_flags & SE_ENABLE_HARD_SYNC) { 25 | rwops = stream_read(stream, (uint8_t*)&inst->hard_sync, sizeof(inst->hard_sync)); 26 | } 27 | 28 | uint8_t progsteps = 0; 29 | 30 | rwops = stream_read(stream, (uint8_t*)&progsteps, sizeof(progsteps)); 31 | 32 | if(progsteps > 0) { 33 | rwops = stream_read(stream, (uint8_t*)inst->program, progsteps * sizeof(inst->program[0])); 34 | } 35 | 36 | rwops = stream_read(stream, (uint8_t*)&inst->program_period, sizeof(inst->program_period)); 37 | 38 | if(inst->flags & TE_ENABLE_VIBRATO) { 39 | rwops = stream_read(stream, (uint8_t*)&inst->vibrato_speed, sizeof(inst->vibrato_speed)); 40 | rwops = stream_read(stream, (uint8_t*)&inst->vibrato_depth, sizeof(inst->vibrato_depth)); 41 | rwops = stream_read(stream, (uint8_t*)&inst->vibrato_delay, sizeof(inst->vibrato_delay)); 42 | } 43 | 44 | if(inst->flags & TE_ENABLE_PWM) { 45 | rwops = stream_read(stream, (uint8_t*)&inst->pwm_speed, sizeof(inst->pwm_speed)); 46 | rwops = stream_read(stream, (uint8_t*)&inst->pwm_depth, sizeof(inst->pwm_depth)); 47 | rwops = stream_read(stream, (uint8_t*)&inst->pwm_delay, sizeof(inst->pwm_delay)); 48 | } 49 | 50 | if(inst->sound_engine_flags & SE_ENABLE_FILTER) { 51 | rwops = stream_read(stream, (uint8_t*)&inst->filter_cutoff, sizeof(inst->filter_cutoff)); 52 | rwops = 53 | stream_read(stream, (uint8_t*)&inst->filter_resonance, sizeof(inst->filter_resonance)); 54 | rwops = stream_read(stream, (uint8_t*)&inst->filter_type, sizeof(inst->filter_type)); 55 | } 56 | 57 | UNUSED(rwops); 58 | } 59 | 60 | bool load_song_inner(TrackerSong* song, Stream* stream) { 61 | uint8_t version = 0; 62 | size_t rwops = stream_read(stream, (uint8_t*)&version, sizeof(version)); 63 | 64 | if(version > 65 | TRACKER_ENGINE_VERSION) // if song is of newer version this version of tracker engine can't support 66 | { 67 | return false; 68 | } 69 | 70 | tracker_engine_deinit_song(song, false); 71 | memset(song, 0, sizeof(TrackerSong)); 72 | 73 | rwops = stream_read(stream, (uint8_t*)song->song_name, sizeof(song->song_name)); 74 | rwops = stream_read(stream, (uint8_t*)&song->loop_start, sizeof(song->loop_start)); 75 | rwops = stream_read(stream, (uint8_t*)&song->loop_end, sizeof(song->loop_end)); 76 | rwops = stream_read(stream, (uint8_t*)&song->pattern_length, sizeof(song->pattern_length)); 77 | 78 | rwops = stream_read(stream, (uint8_t*)&song->speed, sizeof(song->speed)); 79 | rwops = stream_read(stream, (uint8_t*)&song->rate, sizeof(song->rate)); 80 | 81 | rwops = 82 | stream_read(stream, (uint8_t*)&song->num_sequence_steps, sizeof(song->num_sequence_steps)); 83 | 84 | for(uint16_t i = 0; i < song->num_sequence_steps; i++) { 85 | rwops = stream_read( 86 | stream, 87 | (uint8_t*)&song->sequence.sequence_step[i], 88 | sizeof(song->sequence.sequence_step[0])); 89 | } 90 | 91 | rwops = stream_read(stream, (uint8_t*)&song->num_patterns, sizeof(song->num_patterns)); 92 | 93 | for(uint16_t i = 0; i < song->num_patterns; i++) { 94 | song->pattern[i].step = (TrackerSongPatternStep*)malloc( 95 | sizeof(TrackerSongPatternStep) * (song->pattern_length)); 96 | set_empty_pattern(&song->pattern[i], song->pattern_length); 97 | rwops = stream_read( 98 | stream, 99 | (uint8_t*)song->pattern[i].step, 100 | sizeof(TrackerSongPatternStep) * (song->pattern_length)); 101 | } 102 | 103 | rwops = stream_read(stream, (uint8_t*)&song->num_instruments, sizeof(song->num_instruments)); 104 | 105 | for(uint16_t i = 0; i < song->num_instruments; i++) { 106 | song->instrument[i] = (Instrument*)malloc(sizeof(Instrument)); 107 | set_default_instrument(song->instrument[i]); 108 | load_instrument_inner(stream, song->instrument[i], version); 109 | } 110 | 111 | UNUSED(rwops); 112 | return false; 113 | } 114 | 115 | bool load_song(TrackerSong* song, Stream* stream) { 116 | char header[sizeof(SONG_FILE_SIG) + 2] = {0}; 117 | size_t rwops = stream_read(stream, (uint8_t*)&header, sizeof(SONG_FILE_SIG) - 1); 118 | header[sizeof(SONG_FILE_SIG)] = '\0'; 119 | 120 | if(strcmp(header, SONG_FILE_SIG) == 0) { 121 | bool result = load_song_inner(song, stream); 122 | UNUSED(result); 123 | } 124 | 125 | UNUSED(rwops); 126 | return false; 127 | } -------------------------------------------------------------------------------- /flizzer_tracker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "flizzer_tracker_hal.h" 19 | #include "sound_engine/freqs.h" 20 | #include "sound_engine/sound_engine_defs.h" 21 | #include "sound_engine/sound_engine_filter.h" 22 | #include "tracker_engine/tracker_engine_defs.h" 23 | 24 | #define APPSDATA_FOLDER "/ext/apps_data" 25 | #define FLIZZER_TRACKER_FOLDER "/ext/apps_data/flizzer_tracker" 26 | #define FLIZZER_TRACKER_INSTRUMENTS_FOLDER "/ext/apps_data/flizzer_tracker/instruments" 27 | #define FILE_NAME_LEN 64 28 | 29 | typedef enum { 30 | EventTypeInput, 31 | EventTypeSaveSong, 32 | EventTypeLoadSong, 33 | EventTypeLoadInstrument, 34 | EventTypeSaveInstrument, 35 | EventTypeSetAudioMode, 36 | } EventType; 37 | 38 | typedef struct { 39 | EventType type; 40 | InputEvent input; 41 | uint32_t period; 42 | } FlizzerTrackerEvent; 43 | 44 | typedef enum { 45 | PATTERN_VIEW, 46 | INST_EDITOR_VIEW, 47 | EXPORT_WAV_VIEW, 48 | } TrackerMode; 49 | 50 | typedef enum { 51 | EDIT_PATTERN, 52 | EDIT_SEQUENCE, 53 | EDIT_SONGINFO, 54 | EDIT_INSTRUMENT, 55 | EDIT_PROGRAM, 56 | } TrackerFocus; 57 | 58 | typedef enum { 59 | SI_PATTERNPOS, 60 | SI_SEQUENCEPOS, 61 | SI_SONGSPEED, 62 | SI_SONGRATE, 63 | SI_MASTERVOL, 64 | 65 | SI_SONGNAME, 66 | SI_CURRENTINSTRUMENT, 67 | SI_INSTRUMENTNAME, 68 | /* ======== */ 69 | SI_PARAMS, 70 | } SongInfoParam; 71 | 72 | typedef enum { 73 | INST_CURRENTINSTRUMENT, 74 | INST_INSTRUMENTNAME, 75 | 76 | INST_CURRENT_NOTE, 77 | INST_FINETUNE, 78 | 79 | INST_SLIDESPEED, 80 | INST_SETPW, 81 | INST_PW, 82 | INST_SETCUTOFF, 83 | 84 | INST_WAVE_NOISE, 85 | INST_WAVE_PULSE, 86 | INST_WAVE_TRIANGLE, 87 | INST_WAVE_SAWTOOTH, 88 | INST_WAVE_NOISE_METAL, 89 | INST_WAVE_SINE, 90 | 91 | INST_ATTACK, 92 | INST_DECAY, 93 | INST_SUSTAIN, 94 | INST_RELEASE, 95 | INST_VOLUME, 96 | 97 | INST_ENABLEFILTER, 98 | INST_FILTERCUTOFF, 99 | INST_FILTERRESONANCE, 100 | 101 | INST_FILTERTYPE, 102 | INST_ENABLERINGMOD, 103 | INST_RINGMODSRC, 104 | INST_ENABLEHARDSYNC, 105 | INST_HARDSYNCSRC, 106 | 107 | INST_RETRIGGERONSLIDE, 108 | INST_ENABLEKEYSYNC, 109 | 110 | INST_ENABLEVIBRATO, 111 | INST_VIBRATOSPEED, 112 | INST_VIBRATODEPTH, 113 | INST_VIBRATODELAY, 114 | 115 | INST_ENABLEPWM, 116 | INST_PWMSPEED, 117 | INST_PWMDEPTH, 118 | INST_PWMDELAY, 119 | 120 | INST_PROGRESTART, 121 | INST_PROGRAMEPERIOD, 122 | /* ========= */ 123 | INST_PARAMS, 124 | } InstrumentParam; 125 | 126 | typedef struct { 127 | View* view; 128 | void* context; 129 | } TrackerView; 130 | 131 | typedef enum { 132 | VIEW_TRACKER, 133 | VIEW_KEYBOARD, 134 | VIEW_SUBMENU_PATTERN, 135 | VIEW_SUBMENU_PATTERN_COPYPASTE, 136 | VIEW_SUBMENU_INSTRUMENT, 137 | VIEW_FILE_OVERWRITE, 138 | VIEW_INSTRUMENT_FILE_OVERWRITE, 139 | VIEW_SETTINGS, 140 | } FlizzerTrackerViews; 141 | 142 | typedef enum { 143 | SUBMENU_PATTERN_LOAD_SONG, 144 | SUBMENU_PATTERN_SAVE_SONG, 145 | SUBMENU_PATTERN_SETTINGS, 146 | SUBMENU_PATTERN_HELP, 147 | SUBMENU_PATTERN_EXIT, 148 | } PatternSubmenuParams; 149 | 150 | typedef enum { 151 | SUBMENU_PATTERN_COPYPASTE_COPY, 152 | SUBMENU_PATTERN_COPYPASTE_PASTE, 153 | SUBMENU_PATTERN_COPYPASTE_CUT, 154 | SUBMENU_PATTERN_COPYPASTE_CLEAR, 155 | } PatternCopypasteSubmenuParams; 156 | 157 | typedef enum { 158 | SUBMENU_INSTRUMENT_LOAD, 159 | SUBMENU_INSTRUMENT_SAVE, 160 | SUBMENU_INSTRUMENT_EXIT, 161 | } InstrumentSubmenuParams; 162 | 163 | typedef struct { 164 | NotificationApp* notification; 165 | FuriMessageQueue* event_queue; 166 | Gui* gui; 167 | TrackerView* tracker_view; 168 | ViewDispatcher* view_dispatcher; 169 | TextInput* text_input; 170 | Storage* storage; 171 | Stream* stream; 172 | FuriString* filepath; 173 | DialogsApp* dialogs; 174 | Submenu* pattern_submenu; 175 | Submenu* pattern_copypaste_submenu; 176 | Submenu* instrument_submenu; 177 | VariableItemList* settings_list; 178 | Widget* overwrite_file_widget; 179 | Widget* overwrite_instrument_file_widget; 180 | char filename[FILE_NAME_LEN + 1]; 181 | bool was_it_back_keypress; 182 | uint32_t current_time; 183 | uint32_t period; 184 | 185 | bool external_audio; 186 | 187 | SoundEngine sound_engine; 188 | TrackerEngine tracker_engine; 189 | 190 | TrackerSong song; 191 | 192 | uint8_t selected_param; 193 | 194 | uint8_t mode, focus; 195 | uint8_t patternx, current_channel, current_digit, program_position, current_program_step, 196 | current_instrument, current_note, current_volume; 197 | 198 | uint8_t inst_editor_shift; 199 | 200 | int16_t source_pattern_index; 201 | 202 | bool editing; 203 | bool was_editing; 204 | 205 | bool is_loading; 206 | bool is_saving; 207 | bool is_loading_instrument; 208 | bool is_saving_instrument; 209 | bool showing_help; 210 | 211 | bool cut_pattern; //if we need to clear the pattern we pasted from 212 | 213 | bool quit; 214 | 215 | char eq[2]; 216 | char param[80]; 217 | char value[10]; 218 | } FlizzerTrackerApp; 219 | 220 | typedef struct { 221 | FlizzerTrackerApp* tracker; 222 | } TrackerViewModel; 223 | 224 | void draw_callback(Canvas* canvas, void* ctx); 225 | bool input_callback(InputEvent* input_event, void* ctx); -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: AlwaysBreak 5 | AlignArrayOfStructures: None 6 | AlignConsecutiveMacros: None 7 | AlignConsecutiveAssignments: None 8 | AlignConsecutiveBitFields: None 9 | AlignConsecutiveDeclarations: None 10 | AlignEscapedNewlines: Left 11 | AlignOperands: Align 12 | AlignTrailingComments: false 13 | AllowAllArgumentsOnNextLine: true 14 | AllowAllParametersOfDeclarationOnNextLine: false 15 | AllowShortEnumsOnASingleLine: true 16 | AllowShortBlocksOnASingleLine: Never 17 | AllowShortCaseLabelsOnASingleLine: false 18 | AllowShortFunctionsOnASingleLine: None 19 | AllowShortLambdasOnASingleLine: All 20 | AllowShortIfStatementsOnASingleLine: WithoutElse 21 | AllowShortLoopsOnASingleLine: true 22 | AlwaysBreakAfterDefinitionReturnType: None 23 | AlwaysBreakAfterReturnType: None 24 | AlwaysBreakBeforeMultilineStrings: false 25 | AlwaysBreakTemplateDeclarations: Yes 26 | AttributeMacros: 27 | - __capability 28 | BinPackArguments: false 29 | BinPackParameters: false 30 | BraceWrapping: 31 | AfterCaseLabel: false 32 | AfterClass: false 33 | AfterControlStatement: Never 34 | AfterEnum: false 35 | AfterFunction: false 36 | AfterNamespace: false 37 | AfterObjCDeclaration: false 38 | AfterStruct: false 39 | AfterUnion: false 40 | AfterExternBlock: false 41 | BeforeCatch: false 42 | BeforeElse: false 43 | BeforeLambdaBody: false 44 | BeforeWhile: false 45 | IndentBraces: false 46 | SplitEmptyFunction: true 47 | SplitEmptyRecord: true 48 | SplitEmptyNamespace: true 49 | BreakBeforeBinaryOperators: None 50 | BreakBeforeConceptDeclarations: true 51 | BreakBeforeBraces: Attach 52 | BreakBeforeInheritanceComma: false 53 | BreakInheritanceList: BeforeColon 54 | BreakBeforeTernaryOperators: false 55 | BreakConstructorInitializersBeforeComma: false 56 | BreakConstructorInitializers: BeforeComma 57 | BreakAfterJavaFieldAnnotations: false 58 | BreakStringLiterals: false 59 | ColumnLimit: 99 60 | CommentPragmas: '^ IWYU pragma:' 61 | QualifierAlignment: Leave 62 | CompactNamespaces: false 63 | ConstructorInitializerIndentWidth: 4 64 | ContinuationIndentWidth: 4 65 | Cpp11BracedListStyle: true 66 | DeriveLineEnding: true 67 | DerivePointerAlignment: false 68 | DisableFormat: false 69 | EmptyLineAfterAccessModifier: Never 70 | EmptyLineBeforeAccessModifier: LogicalBlock 71 | ExperimentalAutoDetectBinPacking: false 72 | PackConstructorInitializers: BinPack 73 | BasedOnStyle: '' 74 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 75 | AllowAllConstructorInitializersOnNextLine: true 76 | FixNamespaceComments: false 77 | ForEachMacros: 78 | - foreach 79 | - Q_FOREACH 80 | - BOOST_FOREACH 81 | IfMacros: 82 | - KJ_IF_MAYBE 83 | IncludeBlocks: Preserve 84 | IncludeCategories: 85 | - Regex: '.*' 86 | Priority: 1 87 | SortPriority: 0 88 | CaseSensitive: false 89 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 90 | Priority: 3 91 | SortPriority: 0 92 | CaseSensitive: false 93 | - Regex: '.*' 94 | Priority: 1 95 | SortPriority: 0 96 | CaseSensitive: false 97 | IncludeIsMainRegex: '(Test)?$' 98 | IncludeIsMainSourceRegex: '' 99 | IndentAccessModifiers: false 100 | IndentCaseLabels: false 101 | IndentCaseBlocks: false 102 | IndentGotoLabels: true 103 | IndentPPDirectives: None 104 | IndentExternBlock: AfterExternBlock 105 | IndentRequires: false 106 | IndentWidth: 4 107 | IndentWrappedFunctionNames: true 108 | InsertTrailingCommas: None 109 | JavaScriptQuotes: Leave 110 | JavaScriptWrapImports: true 111 | KeepEmptyLinesAtTheStartOfBlocks: false 112 | LambdaBodyIndentation: Signature 113 | MacroBlockBegin: '' 114 | MacroBlockEnd: '' 115 | MaxEmptyLinesToKeep: 1 116 | NamespaceIndentation: None 117 | ObjCBinPackProtocolList: Auto 118 | ObjCBlockIndentWidth: 4 119 | ObjCBreakBeforeNestedBlockParam: true 120 | ObjCSpaceAfterProperty: true 121 | ObjCSpaceBeforeProtocolList: true 122 | PenaltyBreakAssignment: 10 123 | PenaltyBreakBeforeFirstCallParameter: 30 124 | PenaltyBreakComment: 10 125 | PenaltyBreakFirstLessLess: 0 126 | PenaltyBreakOpenParenthesis: 0 127 | PenaltyBreakString: 10 128 | PenaltyBreakTemplateDeclaration: 10 129 | PenaltyExcessCharacter: 100 130 | PenaltyReturnTypeOnItsOwnLine: 60 131 | PenaltyIndentedWhitespace: 0 132 | PointerAlignment: Left 133 | PPIndentWidth: -1 134 | ReferenceAlignment: Pointer 135 | ReflowComments: false 136 | RemoveBracesLLVM: false 137 | SeparateDefinitionBlocks: Leave 138 | ShortNamespaceLines: 1 139 | SortIncludes: Never 140 | SortJavaStaticImport: Before 141 | SortUsingDeclarations: false 142 | SpaceAfterCStyleCast: false 143 | SpaceAfterLogicalNot: false 144 | SpaceAfterTemplateKeyword: true 145 | SpaceBeforeAssignmentOperators: true 146 | SpaceBeforeCaseColon: false 147 | SpaceBeforeCpp11BracedList: false 148 | SpaceBeforeCtorInitializerColon: true 149 | SpaceBeforeInheritanceColon: true 150 | SpaceBeforeParens: Never 151 | SpaceBeforeParensOptions: 152 | AfterControlStatements: false 153 | AfterForeachMacros: false 154 | AfterFunctionDefinitionName: false 155 | AfterFunctionDeclarationName: false 156 | AfterIfMacros: false 157 | AfterOverloadedOperator: false 158 | BeforeNonEmptyParentheses: false 159 | SpaceAroundPointerQualifiers: Default 160 | SpaceBeforeRangeBasedForLoopColon: true 161 | SpaceInEmptyBlock: false 162 | SpaceInEmptyParentheses: false 163 | SpacesBeforeTrailingComments: 1 164 | SpacesInAngles: Never 165 | SpacesInConditionalStatement: false 166 | SpacesInContainerLiterals: false 167 | SpacesInCStyleCastParentheses: false 168 | SpacesInLineCommentPrefix: 169 | Minimum: 1 170 | Maximum: -1 171 | SpacesInParentheses: false 172 | SpacesInSquareBrackets: false 173 | SpaceBeforeSquareBrackets: false 174 | BitFieldColonSpacing: Both 175 | Standard: c++03 176 | StatementAttributeLikeMacros: 177 | - Q_EMIT 178 | StatementMacros: 179 | - Q_UNUSED 180 | - QT_REQUIRE_VERSION 181 | TabWidth: 4 182 | UseCRLF: false 183 | UseTab: Never 184 | WhitespaceSensitiveMacros: 185 | - STRINGIZE 186 | - PP_STRINGIZE 187 | - BOOST_PP_STRINGIZE 188 | - NS_SWIFT_NAME 189 | - CF_SWIFT_NAME 190 | ... 191 | 192 | -------------------------------------------------------------------------------- /input/sequence.c: -------------------------------------------------------------------------------- 1 | #include "sequence.h" 2 | 3 | void delete_sequence_step(FlizzerTrackerApp* tracker) { 4 | uint8_t sequence_position = tracker->tracker_engine.sequence_position; 5 | uint8_t* pattern = &tracker->tracker_engine.song->sequence.sequence_step[sequence_position] 6 | .pattern_indices[tracker->current_channel]; 7 | *pattern = 0; 8 | } 9 | 10 | void edit_sequence_step(FlizzerTrackerApp* tracker, int8_t delta) { 11 | uint8_t digit = tracker->current_digit; 12 | 13 | uint8_t sequence_position = tracker->tracker_engine.sequence_position; 14 | uint8_t pattern_index = tracker->tracker_engine.song->sequence.sequence_step[sequence_position] 15 | .pattern_indices[tracker->current_channel]; 16 | 17 | uint8_t* pattern = &tracker->tracker_engine.song->sequence.sequence_step[sequence_position] 18 | .pattern_indices[tracker->current_channel]; 19 | uint8_t temp_pattern = *pattern; 20 | 21 | switch(digit) { 22 | case 0: // upper nibble 23 | { 24 | int8_t nibble = ((pattern_index & 0xf0) >> 4); 25 | 26 | if(nibble + delta < 0) { 27 | nibble = 0xf; 28 | } 29 | 30 | else if(nibble + delta > 0xf) { 31 | nibble = 0; 32 | } 33 | 34 | else { 35 | nibble += delta; 36 | } 37 | 38 | temp_pattern &= 0x0f; 39 | temp_pattern |= (nibble << 4); 40 | 41 | break; 42 | } 43 | 44 | case 1: // lower nibble 45 | { 46 | int8_t nibble = (pattern_index & 0x0f); 47 | 48 | if(nibble + delta < 0) { 49 | nibble = 0xf; 50 | } 51 | 52 | else if(nibble + delta > 0xf) { 53 | nibble = 0; 54 | } 55 | 56 | else { 57 | nibble += delta; 58 | } 59 | 60 | temp_pattern &= 0xf0; 61 | temp_pattern |= nibble; 62 | 63 | break; 64 | } 65 | } 66 | 67 | if(check_and_allocate_pattern(&tracker->song, temp_pattern)) { 68 | *pattern = temp_pattern; 69 | } 70 | } 71 | 72 | void sequence_edit_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* event) { 73 | if(event->input.key == InputKeyOk && event->input.type == InputTypeShort && 74 | !tracker->tracker_engine.playing) { 75 | tracker->editing = !tracker->editing; 76 | } 77 | 78 | if(event->input.key == InputKeyOk && event->input.type == InputTypeLong) { 79 | if(!(tracker->editing)) { 80 | if(tracker->tracker_engine.playing) { 81 | stop_song(tracker); 82 | } 83 | 84 | else { 85 | if(tracker->tracker_engine.pattern_position == tracker->song.pattern_length - 1 && 86 | tracker->tracker_engine.sequence_position == 87 | tracker->song.num_sequence_steps - 88 | 1) // if we are at the very end of the song 89 | { 90 | stop_song(tracker); 91 | } 92 | 93 | else { 94 | play_song(tracker, true); 95 | } 96 | } 97 | } 98 | } 99 | 100 | if(event->input.key == InputKeyRight && event->input.type == InputTypeShort) { 101 | tracker->current_digit++; 102 | 103 | if(tracker->current_digit > 1) { 104 | tracker->current_channel++; 105 | 106 | tracker->current_digit = 0; 107 | 108 | if(tracker->current_channel > SONG_MAX_CHANNELS - 1) { 109 | tracker->current_channel = 0; 110 | } 111 | } 112 | } 113 | 114 | if(event->input.key == InputKeyLeft && event->input.type == InputTypeShort) { 115 | tracker->current_digit--; 116 | 117 | if(tracker->current_digit > 1) // unsigned int overflow 118 | { 119 | tracker->current_channel--; 120 | 121 | tracker->current_digit = 1; 122 | 123 | if(tracker->current_channel > SONG_MAX_CHANNELS - 1) // unsigned int overflow 124 | { 125 | tracker->current_channel = SONG_MAX_CHANNELS - 1; 126 | } 127 | } 128 | } 129 | 130 | if(event->input.key == InputKeyDown && event->input.type == InputTypeShort) { 131 | if(!(tracker->editing)) { 132 | tracker->tracker_engine.sequence_position++; 133 | 134 | if(tracker->tracker_engine.sequence_position >= 135 | tracker->tracker_engine.song->num_sequence_steps) { 136 | tracker->tracker_engine.sequence_position = 0; 137 | } 138 | } 139 | 140 | if(tracker->editing) { 141 | edit_sequence_step(tracker, -1); 142 | } 143 | } 144 | 145 | if(event->input.key == InputKeyUp && event->input.type == InputTypeShort) { 146 | if(!(tracker->editing)) { 147 | int16_t temp_sequence_position = tracker->tracker_engine.sequence_position - 1; 148 | 149 | if(temp_sequence_position < 0) { 150 | tracker->tracker_engine.sequence_position = 151 | tracker->tracker_engine.song->num_sequence_steps - 1; 152 | } 153 | 154 | else { 155 | tracker->tracker_engine.sequence_position--; 156 | } 157 | } 158 | 159 | if(tracker->editing) { 160 | edit_sequence_step(tracker, 1); 161 | } 162 | } 163 | 164 | if(event->input.key == InputKeyRight && event->input.type == InputTypeLong && 165 | !(tracker->editing)) // set loop begin or loop end for the song 166 | { 167 | TrackerSong* song = &tracker->song; 168 | 169 | if(song->loop_start == song->loop_end && song->loop_end == 0) // if both are 0 170 | { 171 | song->loop_end = tracker->tracker_engine.sequence_position; 172 | } 173 | 174 | else { 175 | if(tracker->tracker_engine.sequence_position < song->loop_end) { 176 | song->loop_start = tracker->tracker_engine.sequence_position; 177 | } 178 | 179 | if(tracker->tracker_engine.sequence_position > song->loop_start) { 180 | song->loop_end = tracker->tracker_engine.sequence_position; 181 | } 182 | } 183 | } 184 | 185 | if(event->input.key == InputKeyLeft && event->input.type == InputTypeLong && 186 | !(tracker->editing)) // erase loop begin and loop end points 187 | { 188 | TrackerSong* song = &tracker->song; 189 | 190 | song->loop_start = song->loop_end = 0; 191 | } 192 | 193 | if(event->input.key == InputKeyUp && event->input.type == InputTypeLong && 194 | !(tracker->editing)) // jump to the beginning 195 | { 196 | tracker->tracker_engine.sequence_position = 0; 197 | } 198 | 199 | if(event->input.key == InputKeyDown && event->input.type == InputTypeLong && 200 | !(tracker->editing)) // jump to the end 201 | { 202 | tracker->tracker_engine.sequence_position = tracker->song.num_sequence_steps - 1; 203 | } 204 | 205 | if(event->input.key == InputKeyBack && event->input.type == InputTypeShort && 206 | tracker->editing) { 207 | delete_sequence_step(tracker); 208 | } 209 | } -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | #include "macros.h" 3 | 4 | void reset_buffer(SoundEngine* sound_engine) { 5 | for(uint16_t i = 0; i < sound_engine->audio_buffer_size; i++) { 6 | sound_engine->audio_buffer[i] = 512; 7 | } 8 | } 9 | 10 | void stop_song(FlizzerTrackerApp* tracker) { 11 | tracker->tracker_engine.playing = false; 12 | tracker->editing = tracker->was_editing; 13 | 14 | for(int i = 0; i < SONG_MAX_CHANNELS; i++) { 15 | tracker->sound_engine.channel[i].adsr.volume = 0; 16 | tracker->tracker_engine.channel[i].channel_flags &= ~(TEC_PROGRAM_RUNNING); 17 | } 18 | 19 | stop(); 20 | 21 | reset_buffer(&tracker->sound_engine); 22 | } 23 | 24 | void play_song(FlizzerTrackerApp* tracker, bool from_cursor) { 25 | uint16_t temppos = tracker->tracker_engine.pattern_position; 26 | 27 | stop_song(tracker); 28 | 29 | sound_engine_dma_init( 30 | (uint32_t)tracker->sound_engine.audio_buffer, tracker->sound_engine.audio_buffer_size); 31 | 32 | tracker->tracker_engine.playing = true; 33 | 34 | tracker->was_editing = tracker->editing; 35 | tracker->editing = false; 36 | 37 | if(!(from_cursor)) { 38 | tracker->tracker_engine.pattern_position = 0; 39 | temppos = 0; 40 | } 41 | 42 | tracker_engine_timer_init(tracker->song.rate); 43 | 44 | /*sound_engine_init_hardware(tracker->sound_engine.sample_rate, 45 | tracker->sound_engine.external_audio_output, 46 | tracker->sound_engine.audio_buffer, 47 | tracker->sound_engine.audio_buffer_size); 48 | tracker_engine_init_hardware(tracker->song.rate);*/ 49 | 50 | tracker->tracker_engine.current_tick = 0; 51 | tracker_engine_set_song(&tracker->tracker_engine, &tracker->song); 52 | 53 | for(uint8_t i = 0; i < SONG_MAX_CHANNELS; i++) { 54 | bool was_disabled = tracker->tracker_engine.channel[i].channel_flags & TEC_DISABLED; 55 | 56 | memset(&tracker->sound_engine.channel[i], 0, sizeof(SoundEngineChannel)); 57 | memset(&tracker->tracker_engine.channel[i], 0, sizeof(TrackerEngineChannel)); 58 | 59 | if(was_disabled) { 60 | tracker->tracker_engine.channel[i].channel_flags |= TEC_DISABLED; 61 | } 62 | } 63 | 64 | tracker->tracker_engine.pattern_position = temppos; 65 | 66 | play(); 67 | } 68 | 69 | bool is_pattern_empty(TrackerSong* song, uint8_t pattern) { 70 | TrackerSongPattern song_pattern = song->pattern[pattern]; 71 | 72 | for(int i = 0; i < song->pattern_length; i++) { 73 | TrackerSongPatternStep* step = &song_pattern.step[i]; 74 | 75 | if(tracker_engine_get_note(step) != MUS_NOTE_NONE || 76 | tracker_engine_get_instrument(step) != MUS_NOTE_INSTRUMENT_NONE || 77 | tracker_engine_get_volume(step) != MUS_NOTE_VOLUME_NONE || 78 | tracker_engine_get_command(step) != 0) { 79 | return false; 80 | } 81 | } 82 | 83 | return true; 84 | } 85 | 86 | bool check_and_allocate_pattern(TrackerSong* song, uint8_t pattern) { 87 | if(pattern < song->num_patterns) // we can set this pattern since it already exists 88 | { 89 | return true; 90 | } 91 | 92 | else { 93 | if(song->pattern[pattern - 1].step == NULL) 94 | return false; // if we hop through several patterns (e.g. editing upper digit) 95 | 96 | if(!(is_pattern_empty( 97 | song, pattern - 1))) // don't let the user flood the song with empty patterns 98 | { 99 | song->pattern[pattern].step = 100 | malloc(sizeof(TrackerSongPatternStep) * song->pattern_length); 101 | set_empty_pattern(&song->pattern[pattern], song->pattern_length); 102 | song->num_patterns++; 103 | return true; 104 | } 105 | 106 | else { 107 | return false; 108 | } 109 | } 110 | } 111 | 112 | void resize_pattern(TrackerSongPattern* pattern, uint16_t old_length, uint16_t new_length) { 113 | TrackerSongPattern temp; 114 | temp.step = malloc((new_length) * sizeof(TrackerSongPatternStep)); 115 | 116 | set_empty_pattern(&temp, new_length); 117 | memcpy( 118 | temp.step, pattern->step, my_min(old_length, new_length) * sizeof(TrackerSongPatternStep)); 119 | 120 | free(pattern->step); 121 | pattern->step = temp.step; 122 | } 123 | 124 | void change_pattern_length(TrackerSong* song, uint16_t new_length) { 125 | for(int i = 0; i < MAX_PATTERNS; i++) { 126 | if(song->pattern[i].step) { 127 | resize_pattern(&song->pattern[i], song->pattern_length, new_length); 128 | } 129 | } 130 | 131 | song->pattern_length = new_length; 132 | } 133 | 134 | bool is_default_instrument(Instrument* inst) { 135 | Instrument* ref = malloc(sizeof(Instrument)); 136 | set_default_instrument(ref); 137 | bool is_default = memcmp(ref, inst, sizeof(Instrument)) != 0 ? false : true; 138 | free(ref); 139 | return is_default; 140 | } 141 | 142 | bool check_and_allocate_instrument(TrackerSong* song, uint8_t inst) { 143 | if(inst < song->num_instruments) // we can go to this instrument since it already exists 144 | { 145 | return true; 146 | } 147 | 148 | else { 149 | if(inst >= MAX_INSTRUMENTS) return false; 150 | 151 | if(!(is_default_instrument( 152 | song->instrument 153 | [inst - 1]))) // don't let the user flood the song with default instrument 154 | { 155 | song->instrument[inst] = malloc(sizeof(Instrument)); 156 | set_default_instrument(song->instrument[inst]); 157 | song->num_instruments++; 158 | return true; 159 | } 160 | 161 | else { 162 | return false; 163 | } 164 | } 165 | } 166 | 167 | void set_default_song(FlizzerTrackerApp* tracker) { 168 | tracker->tracker_engine.master_volume = 0x80; 169 | 170 | tracker->song.speed = 6; 171 | tracker->song.rate = tracker->tracker_engine.rate; 172 | tracker->song.num_instruments = 1; 173 | tracker->song.num_patterns = 5; 174 | tracker->song.num_sequence_steps = 1; 175 | tracker->song.pattern_length = 64; 176 | 177 | tracker->song.sequence.sequence_step[0].pattern_indices[0] = 1; 178 | tracker->song.sequence.sequence_step[0].pattern_indices[1] = 2; 179 | tracker->song.sequence.sequence_step[0].pattern_indices[2] = 3; 180 | tracker->song.sequence.sequence_step[0].pattern_indices[3] = 4; 181 | 182 | for(int i = 0; i < 5; i++) { 183 | tracker->song.pattern[i].step = malloc(64 * sizeof(TrackerSongPatternStep)); 184 | memset(tracker->song.pattern[i].step, 0, 64 * sizeof(TrackerSongPatternStep)); 185 | } 186 | 187 | for(int i = 0; i < 64; ++i) { 188 | for(int j = 0; j < 5; j++) { 189 | set_note(&tracker->song.pattern[j].step[i], MUS_NOTE_NONE); 190 | 191 | set_instrument(&tracker->song.pattern[j].step[i], MUS_NOTE_INSTRUMENT_NONE); 192 | 193 | set_volume(&tracker->song.pattern[j].step[i], MUS_NOTE_VOLUME_NONE); 194 | } 195 | } 196 | 197 | tracker->song.instrument[0] = malloc(sizeof(Instrument)); 198 | 199 | set_default_instrument(tracker->song.instrument[0]); 200 | 201 | tracker->tracker_engine.playing = false; 202 | } -------------------------------------------------------------------------------- /tracker_engine/tracker_engine_defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../sound_engine/sound_engine_defs.h" 8 | 9 | #define INST_PROG_LEN 16 10 | #define MUS_SONG_NAME_LEN 16 11 | #define MUS_INST_NAME_LEN (MUS_SONG_NAME_LEN - 3) 12 | 13 | #define SONG_MAX_CHANNELS NUM_CHANNELS 14 | #define MAX_INSTRUMENTS 31 15 | #define MAX_PATTERN_LENGTH 256 16 | #define MAX_PATTERNS 256 17 | #define MAX_SEQUENCE_LENGTH 256 18 | 19 | #define MUS_NOTE_NONE 127 20 | #define MUS_NOTE_RELEASE 126 21 | #define MUS_NOTE_CUT 125 22 | 23 | #define MUS_NOTE_INSTRUMENT_NONE 31 24 | #define MUS_NOTE_VOLUME_NONE 31 25 | 26 | #define SONG_FILE_SIG "FZT!SONG" 27 | #define SONG_FILE_EXT ".fzt" 28 | #define INST_FILE_SIG "FZT!INST" 29 | #define INST_FILE_EXT ".fzi" 30 | 31 | #define TRACKER_ENGINE_VERSION 1 32 | 33 | #define MIDDLE_C (12 * 4) 34 | #define MAX_NOTE (12 * 7 + 11) 35 | 36 | typedef enum { 37 | TE_ENABLE_VIBRATO = 1, 38 | TE_ENABLE_PWM = 2, 39 | TE_PROG_NO_RESTART = 4, 40 | TE_SET_CUTOFF = 8, 41 | TE_SET_PW = 16, 42 | TE_RETRIGGER_ON_SLIDE = 32, // call trigger instrument function even if slide command is there 43 | } TrackerEngineFlags; 44 | 45 | typedef enum { 46 | TEC_PLAYING = 1, 47 | TEC_PROGRAM_RUNNING = 2, 48 | TEC_DISABLED = 4, 49 | } TrackerEngineChannelFlags; 50 | 51 | typedef enum { 52 | TE_EFFECT_ARPEGGIO = 0x0000, 53 | TE_EFFECT_PORTAMENTO_UP = 0x0100, 54 | TE_EFFECT_PORTAMENTO_DOWN = 0x0200, 55 | TE_EFFECT_SLIDE = 0x0300, 56 | TE_EFFECT_VIBRATO = 0x0400, 57 | TE_EFFECT_PWM = 0x0500, 58 | TE_EFFECT_SET_PW = 0x0600, 59 | TE_EFFECT_PW_DOWN = 0x0700, 60 | TE_EFFECT_PW_UP = 0x0800, 61 | TE_EFFECT_SET_CUTOFF = 0x0900, 62 | TE_EFFECT_VOLUME_FADE = 0x0a00, 63 | TE_EFFECT_SET_WAVEFORM = 0x0b00, 64 | TE_EFFECT_SET_VOLUME = 0x0c00, 65 | TE_EFFECT_SKIP_PATTERN = 0x0d00, 66 | 67 | TE_EFFECT_EXT = 0x0e00, 68 | TE_EFFECT_EXT_TOGGLE_FILTER = 0x0e00, 69 | TE_EFFECT_EXT_PORTA_UP = 0x0e10, 70 | TE_EFFECT_EXT_PORTA_DN = 0x0e20, 71 | TE_EFFECT_EXT_FILTER_MODE = 0x0e30, 72 | TE_EFFECT_EXT_PATTERN_LOOP = 73 | 0x0e60, // e60 = start, e61-e6f = end and indication how many loops you want 74 | TE_EFFECT_EXT_RETRIGGER = 0x0e90, 75 | TE_EFFECT_EXT_FINE_VOLUME_DOWN = 0x0ea0, 76 | TE_EFFECT_EXT_FINE_VOLUME_UP = 0x0eb0, 77 | TE_EFFECT_EXT_NOTE_CUT = 0x0ec0, 78 | TE_EFFECT_EXT_NOTE_DELAY = 0x0ed0, 79 | TE_EFFECT_EXT_PHASE_RESET = 0x0ef0, 80 | 81 | TE_EFFECT_SET_SPEED_PROG_PERIOD = 0x0f00, 82 | TE_EFFECT_CUTOFF_UP = 0x1000, // Gxx 83 | TE_EFFECT_CUTOFF_DOWN = 0x1100, // Hxx 84 | TE_EFFECT_SET_RESONANCE = 0x1200, // Ixx 85 | TE_EFFECT_RESONANCE_UP = 0x1300, // Jxx 86 | TE_EFFECT_RESONANCE_DOWN = 0x1400, // Kxx 87 | 88 | TE_EFFECT_SET_ATTACK = 0x1500, // Lxx 89 | TE_EFFECT_SET_DECAY = 0x1600, // Mxx 90 | TE_EFFECT_SET_SUSTAIN = 0x1700, // Nxx 91 | TE_EFFECT_SET_RELEASE = 0x1800, // Oxx 92 | TE_EFFECT_PROGRAM_RESTART = 0x1900, // Pxx 93 | 94 | TE_EFFECT_SET_RATE = 0x1a00, //Qxx 95 | 96 | TE_EFFECT_SET_RING_MOD_SRC = 0x1b00, // Rxx 97 | TE_EFFECT_SET_HARD_SYNC_SRC = 0x1c00, // Sxx 98 | 99 | TE_EFFECT_PORTA_UP_SEMITONE = 0x1d00, // Txx 100 | TE_EFFECT_PORTA_DOWN_SEMITONE = 0x1e00, // Uxx 101 | TE_EFFECT_PITCH = 0x1f00, //Vxx 102 | /* 103 | TE_EFFECT_ = 0x2000, //Wxx 104 | */ 105 | 106 | TE_EFFECT_LEGATO = 0x2100, // Xxx 107 | TE_EFFECT_ARPEGGIO_ABS = 0x2200, // Yxx 108 | TE_EFFECT_TRIGGER_RELEASE = 0x2300, // Zxx 109 | 110 | /* These effects work only in instrument program */ 111 | TE_PROGRAM_LOOP_BEGIN = 0x7d00, 112 | TE_PROGRAM_LOOP_END = 0x7e00, 113 | TE_PROGRAM_JUMP = 0x7f00, 114 | TE_PROGRAM_NOP = 0x7ffe, 115 | TE_PROGRAM_END = 0x7fff, 116 | } EffectCommandsOpcodes; 117 | 118 | typedef struct { 119 | uint8_t a, d, s, r, volume; 120 | } InstrumentAdsr; 121 | 122 | typedef struct { 123 | char name[MUS_INST_NAME_LEN + 1]; 124 | 125 | uint8_t waveform; 126 | uint16_t flags; 127 | uint16_t sound_engine_flags; 128 | 129 | uint8_t slide_speed; 130 | 131 | InstrumentAdsr adsr; 132 | 133 | uint8_t ring_mod, hard_sync; // 0xff = self 134 | 135 | uint8_t pw; // store only one byte since we don't have the luxury of virtually unlimited memory! 136 | 137 | uint16_t program 138 | [INST_PROG_LEN]; // MSB is unite bit (indicates this and next command must be executed at once) 139 | uint8_t program_period; 140 | 141 | uint8_t vibrato_speed, vibrato_depth, vibrato_delay; 142 | uint8_t pwm_speed, pwm_depth, pwm_delay; 143 | 144 | uint8_t filter_cutoff, filter_resonance, filter_type; 145 | 146 | uint8_t base_note; 147 | int8_t finetune; 148 | } Instrument; 149 | 150 | typedef struct { 151 | Instrument* instrument; 152 | 153 | uint16_t flags; 154 | 155 | uint8_t channel_flags; 156 | 157 | uint16_t note, target_note, last_note, fixed_note; 158 | int16_t finetune_note; 159 | int16_t arpeggio_note; 160 | 161 | uint8_t volume; 162 | 163 | uint8_t program_counter, program_tick, program_loop, program_period; 164 | 165 | uint16_t filter_cutoff, filter_resonance; 166 | uint8_t filter_type; 167 | 168 | uint8_t vibrato_speed, vibrato_depth, vibrato_delay; 169 | uint8_t pwm_speed, pwm_depth, pwm_delay; 170 | 171 | uint32_t vibrato_position, pwm_position; // basically accumulators 172 | 173 | uint8_t extarp1, extarp2; 174 | 175 | uint16_t pw; 176 | 177 | uint8_t slide_speed; 178 | } TrackerEngineChannel; 179 | 180 | typedef struct { 181 | uint8_t note; // MSB is used for instrument number MSB 182 | uint8_t inst_vol; // high nibble + MSB from note = instrument, low nibble = 4 volume LSBs 183 | uint16_t command; // MSB used as volume MSB 184 | } TrackerSongPatternStep; 185 | 186 | typedef struct { 187 | TrackerSongPatternStep* step; 188 | } TrackerSongPattern; 189 | 190 | typedef struct { 191 | uint8_t pattern_indices[SONG_MAX_CHANNELS]; 192 | } TrackerSongSequenceStep; 193 | 194 | typedef struct { 195 | TrackerSongSequenceStep sequence_step[MAX_SEQUENCE_LENGTH]; 196 | } TrackerSongSequence; 197 | 198 | typedef struct { 199 | Instrument* instrument[MAX_INSTRUMENTS]; 200 | TrackerSongPattern pattern[MAX_PATTERNS]; 201 | TrackerSongSequence sequence; 202 | 203 | uint8_t num_patterns, num_instruments; 204 | uint16_t num_sequence_steps; 205 | uint16_t pattern_length; 206 | 207 | char song_name[MUS_SONG_NAME_LEN + 1]; 208 | uint8_t speed, rate; 209 | 210 | uint8_t loop_start, loop_end; 211 | } TrackerSong; 212 | 213 | typedef struct { 214 | TrackerEngineChannel channel[SONG_MAX_CHANNELS]; 215 | 216 | TrackerSong* song; 217 | SoundEngine* sound_engine; 218 | 219 | uint16_t pattern_position, sequence_position; 220 | int16_t current_tick; 221 | uint16_t absolute_position; // sequence_position * pattern_length + pattern_position 222 | 223 | uint8_t speed, rate; 224 | uint8_t master_volume; 225 | 226 | bool playing; // if we reach the end of the song and song does not loop we just stop there 227 | 228 | bool in_loop; // for E6X (pattern loop) command 229 | uint8_t loops_left; 230 | 231 | // uint32_t counter; //for debug 232 | } TrackerEngine; -------------------------------------------------------------------------------- /input/songinfo.c: -------------------------------------------------------------------------------- 1 | #include "songinfo.h" 2 | 3 | #include "../diskop.h" 4 | 5 | void edit_songinfo_param(FlizzerTrackerApp* tracker, uint8_t selected_param, int8_t delta) { 6 | if(!(tracker->current_digit)) { 7 | delta *= 16; 8 | } 9 | 10 | switch(selected_param) { 11 | case SI_PATTERNPOS: { 12 | uint16_t new_length = tracker->song.pattern_length; 13 | 14 | if((int16_t)new_length + (int16_t)delta > 0 && 15 | (int16_t)new_length + (int16_t)delta <= 0x100) { 16 | new_length += delta; 17 | change_pattern_length(&tracker->song, new_length); 18 | 19 | if(tracker->tracker_engine.pattern_position >= new_length) { 20 | tracker->tracker_engine.pattern_position = new_length - 1; 21 | } 22 | } 23 | 24 | break; 25 | } 26 | 27 | case SI_SEQUENCEPOS: { 28 | if((int16_t)tracker->song.num_sequence_steps + (int16_t)delta > 0 && 29 | (int16_t)tracker->song.num_sequence_steps + (int16_t)delta <= 0x100) { 30 | tracker->song.num_sequence_steps += delta; 31 | 32 | if(tracker->tracker_engine.sequence_position >= tracker->song.num_sequence_steps) { 33 | tracker->tracker_engine.sequence_position = tracker->song.num_sequence_steps - 1; 34 | } 35 | } 36 | 37 | break; 38 | } 39 | 40 | case SI_SONGSPEED: { 41 | if((int16_t)tracker->song.speed + (int16_t)delta > 1 && 42 | (int16_t)tracker->song.speed + (int16_t)delta <= 0xff) { 43 | tracker->song.speed += delta; 44 | } 45 | 46 | break; 47 | } 48 | 49 | case SI_SONGRATE: { 50 | if((int16_t)tracker->song.rate + (int16_t)delta > 1 && 51 | (int16_t)tracker->song.rate + (int16_t)delta <= 0xff) { 52 | tracker->song.rate += delta; 53 | } 54 | 55 | break; 56 | } 57 | 58 | case SI_MASTERVOL: { 59 | if((int16_t)tracker->tracker_engine.master_volume + (int16_t)delta > 0 && 60 | (int16_t)tracker->tracker_engine.master_volume + (int16_t)delta <= 0xff) { 61 | tracker->tracker_engine.master_volume += delta; 62 | } 63 | 64 | break; 65 | } 66 | 67 | case SI_SONGNAME: { 68 | text_input_set_header_text(tracker->text_input, "Song name:"); 69 | text_input_set_result_callback( 70 | tracker->text_input, 71 | return_from_keyboard_callback, 72 | tracker, 73 | (char*)&tracker->song.song_name, 74 | MUS_SONG_NAME_LEN + 1, 75 | false); 76 | 77 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_KEYBOARD); 78 | break; 79 | } 80 | 81 | case SI_CURRENTINSTRUMENT: { 82 | int16_t inst = tracker->current_instrument; 83 | 84 | int8_t inst_delta = delta > 0 ? 1 : -1; 85 | 86 | inst += inst_delta; 87 | 88 | clamp(inst, 0, 0, tracker->song.num_instruments - 1); 89 | 90 | tracker->current_instrument = inst; 91 | 92 | break; 93 | } 94 | 95 | case SI_INSTRUMENTNAME: { 96 | text_input_set_header_text(tracker->text_input, "Instrument name:"); 97 | text_input_set_result_callback( 98 | tracker->text_input, 99 | return_from_keyboard_callback, 100 | tracker, 101 | (char*)&tracker->song.instrument[tracker->current_instrument]->name, 102 | MUS_INST_NAME_LEN + 1, 103 | false); 104 | 105 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_KEYBOARD); 106 | break; 107 | } 108 | 109 | default: 110 | break; 111 | } 112 | } 113 | 114 | void songinfo_edit_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* event) { 115 | if(event->input.key == InputKeyOk && event->input.type == InputTypeShort && 116 | !tracker->tracker_engine.playing) { 117 | tracker->editing = !tracker->editing; 118 | } 119 | 120 | if(event->input.key == InputKeyOk && event->input.type == InputTypeLong) { 121 | if(!(tracker->editing)) { 122 | if(tracker->tracker_engine.playing) { 123 | stop_song(tracker); 124 | } 125 | 126 | else { 127 | if(tracker->tracker_engine.pattern_position == tracker->song.pattern_length - 1 && 128 | tracker->tracker_engine.sequence_position == 129 | tracker->song.num_sequence_steps - 130 | 1) // if we are at the very end of the song 131 | { 132 | stop_song(tracker); 133 | } 134 | 135 | else { 136 | play_song(tracker, true); 137 | } 138 | } 139 | } 140 | } 141 | 142 | if(event->input.key == InputKeyRight && event->input.type == InputTypeShort) { 143 | switch(tracker->selected_param) { 144 | default: { 145 | tracker->current_digit++; 146 | 147 | if(tracker->current_digit > 1) { 148 | tracker->selected_param++; 149 | 150 | tracker->current_digit = 0; 151 | 152 | if(tracker->selected_param > SI_PARAMS - 1) { 153 | tracker->selected_param = 0; 154 | } 155 | } 156 | 157 | break; 158 | } 159 | 160 | case SI_CURRENTINSTRUMENT: 161 | case SI_SONGNAME: 162 | case SI_INSTRUMENTNAME: { 163 | tracker->selected_param++; 164 | 165 | tracker->current_digit = 0; 166 | 167 | if(tracker->selected_param > SI_PARAMS - 1) { 168 | tracker->selected_param = 0; 169 | } 170 | 171 | break; 172 | } 173 | } 174 | } 175 | 176 | if(event->input.key == InputKeyLeft && event->input.type == InputTypeShort) { 177 | switch(tracker->selected_param) { 178 | default: { 179 | tracker->current_digit--; 180 | 181 | if(tracker->current_digit > 1) // unsigned int overflow 182 | { 183 | tracker->selected_param--; 184 | 185 | tracker->current_digit = 1; 186 | 187 | if(tracker->selected_param > SI_PARAMS - 1) // unsigned int overflow 188 | { 189 | tracker->selected_param = SI_PARAMS - 1; 190 | } 191 | } 192 | 193 | break; 194 | } 195 | 196 | case SI_CURRENTINSTRUMENT: 197 | case SI_SONGNAME: 198 | case SI_INSTRUMENTNAME: { 199 | tracker->selected_param--; 200 | 201 | tracker->current_digit = 0; 202 | 203 | if(tracker->selected_param > SI_PARAMS - 1) // unsigned int overflow 204 | { 205 | tracker->selected_param = SI_PARAMS - 1; 206 | } 207 | 208 | break; 209 | } 210 | } 211 | } 212 | 213 | if(event->input.key == InputKeyDown && event->input.type == InputTypeShort) { 214 | if(tracker->editing) { 215 | edit_songinfo_param(tracker, tracker->selected_param, -1); 216 | } 217 | } 218 | 219 | if(event->input.key == InputKeyUp && event->input.type == InputTypeShort) { 220 | if(tracker->editing) { 221 | edit_songinfo_param(tracker, tracker->selected_param, 1); 222 | } 223 | } 224 | } -------------------------------------------------------------------------------- /sound_engine/sound_engine.c: -------------------------------------------------------------------------------- 1 | #include "sound_engine.h" 2 | #include "../flizzer_tracker_hal.h" 3 | 4 | #include 5 | 6 | #define PI 3.1415 7 | 8 | void sound_engine_init( 9 | SoundEngine* sound_engine, 10 | uint32_t sample_rate, 11 | bool external_audio_output, 12 | uint32_t audio_buffer_size) { 13 | if(sound_engine->audio_buffer) { 14 | free(sound_engine->audio_buffer); 15 | } 16 | 17 | memset(sound_engine, 0, sizeof(SoundEngine)); 18 | 19 | sound_engine->audio_buffer = malloc(audio_buffer_size * sizeof(sound_engine->audio_buffer[0])); 20 | memset(sound_engine->audio_buffer, 0, sizeof(SoundEngine)); 21 | sound_engine->audio_buffer_size = audio_buffer_size; 22 | sound_engine->sample_rate = sample_rate; 23 | sound_engine->external_audio_output = external_audio_output; 24 | 25 | for(int i = 0; i < NUM_CHANNELS; ++i) { 26 | sound_engine->channel[i].lfsr = RANDOM_SEED; 27 | } 28 | 29 | for(int i = 0; i < SINE_LUT_SIZE; ++i) { 30 | sound_engine->sine_lut[i] = (uint8_t)((sinf(i / 64.0 * PI) + 1.0) * 127.0); 31 | } 32 | 33 | furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL); 34 | 35 | furi_hal_interrupt_set_isr_ex( 36 | FuriHalInterruptIdDma1Ch1, 37 | FuriHalInterruptPriorityHighest, 38 | sound_engine_dma_isr, 39 | sound_engine); 40 | 41 | sound_engine_init_hardware( 42 | sample_rate, external_audio_output, sound_engine->audio_buffer, audio_buffer_size); 43 | } 44 | 45 | void sound_engine_deinit(SoundEngine* sound_engine) { 46 | free(sound_engine->audio_buffer); 47 | 48 | if(!(sound_engine->external_audio_output)) { 49 | if(furi_hal_speaker_is_mine()) { 50 | furi_hal_speaker_release(); 51 | } 52 | } 53 | 54 | else { 55 | furi_hal_gpio_init(&gpio_ext_pa6, GpioModeAnalog, GpioPullNo, GpioSpeedLow); 56 | } 57 | 58 | furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL); 59 | sound_engine_stop(); 60 | sound_engine_deinit_timer(); 61 | } 62 | 63 | void sound_engine_set_channel_frequency( 64 | SoundEngine* sound_engine, 65 | SoundEngineChannel* channel, 66 | uint16_t note) { 67 | uint32_t frequency = get_freq(note); 68 | 69 | if(frequency != 0) { 70 | channel->frequency = (uint64_t)(ACC_LENGTH) / (uint64_t)1024 * (uint64_t)(frequency) / 71 | (uint64_t)sound_engine->sample_rate; 72 | } 73 | 74 | else { 75 | channel->frequency = 0; 76 | } 77 | } 78 | 79 | void sound_engine_enable_gate(SoundEngine* sound_engine, SoundEngineChannel* channel, bool enable) { 80 | if(enable) { 81 | channel->adsr.envelope = 0; 82 | channel->adsr.envelope_speed = envspd(sound_engine, channel->adsr.a); 83 | channel->adsr.envelope_state = ATTACK; 84 | 85 | channel->flags |= SE_ENABLE_GATE; 86 | 87 | if(channel->flags & SE_ENABLE_KEYDOWN_SYNC) { 88 | channel->accumulator = 0; 89 | } 90 | } 91 | 92 | else { 93 | channel->adsr.envelope_state = RELEASE; 94 | channel->adsr.envelope_speed = envspd(sound_engine, channel->adsr.r); 95 | } 96 | } 97 | 98 | void sound_engine_fill_buffer( 99 | SoundEngine* sound_engine, 100 | uint16_t* audio_buffer, 101 | uint32_t audio_buffer_size) { 102 | int32_t channel_output[NUM_CHANNELS]; 103 | int32_t channel_output_final[NUM_CHANNELS]; 104 | 105 | for(uint32_t i = 0; i < audio_buffer_size; ++i) { 106 | int32_t output = WAVE_AMP * 2; 107 | 108 | for(uint32_t chan = 0; chan < NUM_CHANNELS; ++chan) { 109 | SoundEngineChannel* channel = &sound_engine->channel[chan]; 110 | 111 | if(channel->frequency > 0) { 112 | channel->sync_bit = 0; 113 | uint32_t prev_acc = channel->accumulator; 114 | 115 | channel->accumulator += channel->frequency; 116 | 117 | channel->sync_bit |= (channel->accumulator > ACC_LENGTH ? 1 : 0); 118 | 119 | channel->accumulator &= ACC_LENGTH - 1; 120 | 121 | if(channel->flags & SE_ENABLE_HARD_SYNC) { 122 | uint8_t hard_sync_src = channel->hard_sync == 0xff ? chan : channel->hard_sync; 123 | 124 | if(sound_engine->channel[hard_sync_src].sync_bit) { 125 | channel->accumulator = 0; 126 | } 127 | } 128 | 129 | channel_output[chan] = 130 | sound_engine_osc(sound_engine, channel, prev_acc) - WAVE_AMP / 2; 131 | 132 | if(channel->flags & SE_ENABLE_RING_MOD) { 133 | uint8_t ring_mod_src = channel->ring_mod == 0xff ? chan : channel->ring_mod; 134 | channel_output[chan] = 135 | channel_output[chan] * channel_output[ring_mod_src] / WAVE_AMP; 136 | } 137 | 138 | channel_output_final[chan] = sound_engine_cycle_and_output_adsr( 139 | channel_output[chan], sound_engine, &channel->adsr, &channel->flags); 140 | 141 | if(channel->flags & SE_ENABLE_FILTER) { 142 | if(channel->filter_mode != 0) { 143 | sound_engine_filter_cycle(&channel->filter, channel_output_final[chan]); 144 | 145 | switch(channel->filter_mode) { 146 | case FIL_OUTPUT_LOWPASS: { 147 | channel_output_final[chan] = 148 | sound_engine_output_lowpass(&channel->filter); 149 | break; 150 | } 151 | 152 | case FIL_OUTPUT_HIGHPASS: { 153 | channel_output_final[chan] = 154 | sound_engine_output_highpass(&channel->filter); 155 | break; 156 | } 157 | 158 | case FIL_OUTPUT_BANDPASS: { 159 | channel_output_final[chan] = 160 | sound_engine_output_bandpass(&channel->filter); 161 | break; 162 | } 163 | 164 | case FIL_OUTPUT_LOW_HIGH: { 165 | channel_output_final[chan] = 166 | sound_engine_output_lowpass(&channel->filter) + 167 | sound_engine_output_highpass(&channel->filter); 168 | break; 169 | } 170 | 171 | case FIL_OUTPUT_HIGH_BAND: { 172 | channel_output_final[chan] = 173 | sound_engine_output_highpass(&channel->filter) + 174 | sound_engine_output_bandpass(&channel->filter); 175 | break; 176 | } 177 | 178 | case FIL_OUTPUT_LOW_BAND: { 179 | channel_output_final[chan] = 180 | sound_engine_output_lowpass(&channel->filter) + 181 | sound_engine_output_bandpass(&channel->filter); 182 | break; 183 | } 184 | 185 | case FIL_OUTPUT_LOW_HIGH_BAND: { 186 | channel_output_final[chan] = 187 | sound_engine_output_lowpass(&channel->filter) + 188 | sound_engine_output_highpass(&channel->filter) + 189 | sound_engine_output_bandpass(&channel->filter); 190 | break; 191 | } 192 | } 193 | } 194 | } 195 | 196 | output += channel_output_final[chan]; 197 | } 198 | } 199 | 200 | //audio_buffer[i] = output / (64 * 4); 201 | audio_buffer[i] = output >> 8; 202 | } 203 | } -------------------------------------------------------------------------------- /input/instrument_program.c: -------------------------------------------------------------------------------- 1 | #include "instrument_program.h" 2 | #include "../macros.h" 3 | 4 | void instrument_program_edit_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* event) { 5 | if(event->input.key == InputKeyOk && event->input.type == InputTypeShort) { 6 | tracker->editing = !(tracker->editing); 7 | return; 8 | } 9 | 10 | if(event->input.key == InputKeyRight && event->input.type == InputTypeShort && 11 | tracker->editing) { 12 | tracker->current_digit = my_min(2, tracker->current_digit + 1); 13 | return; 14 | } 15 | 16 | if(event->input.key == InputKeyOk && event->input.type == InputTypeLong && tracker->editing) { 17 | Instrument* inst = tracker->song.instrument[tracker->current_instrument]; 18 | 19 | if(tracker->current_program_step < INST_PROG_LEN - 1) { 20 | if((inst->program[tracker->current_program_step] & 0x7fff) < TE_PROGRAM_LOOP_BEGIN && 21 | ((inst->program[tracker->current_program_step + 1] & 0x7fff) < 22 | TE_PROGRAM_LOOP_BEGIN || 23 | (inst->program[tracker->current_program_step + 1] & 0x7f00) == 24 | TE_PROGRAM_LOOP_END)) // so we can unite with loop end as in klystrack 25 | { 26 | inst->program[tracker->current_program_step] ^= 0x8000; // flipping unite bit 27 | } 28 | } 29 | 30 | return; 31 | } 32 | 33 | if(event->input.key == InputKeyLeft && event->input.type == InputTypeShort && 34 | tracker->editing) { 35 | tracker->current_digit = fmax(0, (int16_t)tracker->current_digit - 1); 36 | return; 37 | } 38 | 39 | if(event->input.key == InputKeyBack && event->input.type == InputTypeShort && 40 | tracker->editing) { 41 | Instrument* inst = tracker->song.instrument[tracker->current_instrument]; 42 | inst->program[tracker->current_program_step] = TE_PROGRAM_NOP; 43 | } 44 | 45 | if(event->input.key == InputKeyUp && event->input.type == InputTypeShort) { 46 | if(!(tracker->editing)) { 47 | if((int16_t)tracker->current_program_step - 1 >= 0) { 48 | tracker->current_program_step--; 49 | 50 | if(tracker->program_position > tracker->current_program_step) { 51 | tracker->program_position = tracker->current_program_step; 52 | } 53 | } 54 | 55 | else { 56 | tracker->current_program_step = INST_PROG_LEN - 1; 57 | 58 | tracker->program_position = INST_PROG_LEN - 1 - 7; 59 | } 60 | } 61 | 62 | if(tracker->editing) { 63 | Instrument* inst = tracker->song.instrument[tracker->current_instrument]; 64 | uint16_t opcode = inst->program[tracker->current_program_step]; 65 | 66 | switch(tracker->current_digit) { 67 | case 0: // MSB 68 | { 69 | uint8_t param = ((opcode & 0x7f00) >> 8); 70 | 71 | if(param < 0xff) { 72 | param++; 73 | } 74 | 75 | if((inst->program[tracker->current_program_step] & 0x7fff) == TE_PROGRAM_NOP) { 76 | param = 0; 77 | inst->program[tracker->current_program_step] = 0; 78 | } 79 | 80 | param &= 0x7f; 81 | 82 | inst->program[tracker->current_program_step] &= 0x80ff; 83 | inst->program[tracker->current_program_step] |= ((uint16_t)param << 8); 84 | 85 | break; 86 | } 87 | 88 | case 1: // upper digit of param, e.g. eXx 89 | { 90 | int8_t nibble = ((opcode & 0x00f0) >> 4); 91 | 92 | if(nibble + 1 <= 0xf) { 93 | nibble++; 94 | } 95 | 96 | else { 97 | nibble = 0; 98 | } 99 | 100 | inst->program[tracker->current_program_step] &= 0xff0f; 101 | inst->program[tracker->current_program_step] |= (nibble << 4); 102 | 103 | break; 104 | } 105 | 106 | case 2: // lower digit of param, e.g. exX 107 | { 108 | int8_t nibble = (opcode & 0x000f); 109 | 110 | if(nibble + 1 <= 0xf) { 111 | nibble++; 112 | } 113 | 114 | else { 115 | nibble = 0; 116 | } 117 | 118 | inst->program[tracker->current_program_step] &= 0xfff0; 119 | inst->program[tracker->current_program_step] |= nibble; 120 | 121 | break; 122 | } 123 | 124 | default: 125 | break; 126 | } 127 | } 128 | 129 | return; 130 | } 131 | 132 | if(event->input.key == InputKeyDown && event->input.type == InputTypeShort) { 133 | if(!(tracker->editing)) { 134 | if(tracker->current_program_step + 1 < INST_PROG_LEN) { 135 | tracker->current_program_step++; 136 | 137 | if(tracker->program_position < tracker->current_program_step - 7) { 138 | tracker->program_position = tracker->current_program_step - 7; 139 | } 140 | } 141 | 142 | else { 143 | tracker->current_program_step = 0; 144 | 145 | tracker->program_position = 0; 146 | } 147 | } 148 | 149 | if(tracker->editing) { 150 | Instrument* inst = tracker->song.instrument[tracker->current_instrument]; 151 | uint16_t opcode = inst->program[tracker->current_program_step]; 152 | 153 | switch(tracker->current_digit) { 154 | case 0: // MSB 155 | { 156 | uint8_t param = ((opcode & 0x7f00) >> 8); 157 | 158 | if(param < (TE_PROGRAM_JUMP >> 8) && param > 0) { 159 | param--; 160 | 161 | inst->program[tracker->current_program_step] &= 0x80ff; 162 | inst->program[tracker->current_program_step] |= ((uint16_t)param << 8); 163 | } 164 | 165 | if((inst->program[tracker->current_program_step] & 0x7f00) == TE_PROGRAM_JUMP && 166 | (inst->program[tracker->current_program_step] & 0x7fff) != TE_PROGRAM_END && 167 | (inst->program[tracker->current_program_step] & 0x7fff) != TE_PROGRAM_NOP) { 168 | inst->program[tracker->current_program_step] = 169 | TE_PROGRAM_LOOP_END | 170 | (inst->program[tracker->current_program_step] & 0x8000); 171 | } 172 | 173 | if((inst->program[tracker->current_program_step] & 0x7fff) == TE_PROGRAM_END) { 174 | // param = (TE_PROGRAM_JUMP >> 8); 175 | inst->program[tracker->current_program_step] = 176 | TE_PROGRAM_JUMP | (inst->program[tracker->current_program_step] & 0x8000); 177 | } 178 | 179 | if((inst->program[tracker->current_program_step] & 0x7fff) == TE_PROGRAM_NOP) { 180 | // param = (TE_PROGRAM_END >> 8); 181 | inst->program[tracker->current_program_step] = 182 | TE_PROGRAM_END | (inst->program[tracker->current_program_step] & 0x8000); 183 | } 184 | 185 | if((inst->program[tracker->current_program_step] & 0x7f00) == 186 | (TE_PROGRAM_LOOP_BEGIN - 0x100)) { 187 | // param = (TE_PROGRAM_END >> 8); 188 | inst->program[tracker->current_program_step] = 189 | TE_EFFECT_TRIGGER_RELEASE | 190 | (inst->program[tracker->current_program_step] & 0x8000); 191 | } 192 | 193 | break; 194 | } 195 | 196 | case 1: // upper digit of param, e.g. eXx 197 | { 198 | int8_t nibble = ((opcode & 0x00f0) >> 4); 199 | 200 | if(nibble - 1 >= 0) { 201 | nibble--; 202 | } 203 | 204 | else { 205 | nibble = 0xf; 206 | } 207 | 208 | inst->program[tracker->current_program_step] &= 0xff0f; 209 | inst->program[tracker->current_program_step] |= (nibble << 4); 210 | 211 | break; 212 | } 213 | 214 | case 2: // lower digit of param, e.g. exX 215 | { 216 | int8_t nibble = (opcode & 0x000f); 217 | 218 | if(nibble - 1 >= 0) { 219 | nibble--; 220 | } 221 | 222 | else { 223 | nibble = 0xf; 224 | } 225 | 226 | inst->program[tracker->current_program_step] &= 0xfff0; 227 | inst->program[tracker->current_program_step] |= nibble; 228 | 229 | break; 230 | } 231 | 232 | default: 233 | break; 234 | } 235 | } 236 | 237 | return; 238 | } 239 | } -------------------------------------------------------------------------------- /diskop.c: -------------------------------------------------------------------------------- 1 | #include "diskop.h" 2 | 3 | #define CFG_FILENAME "settings.cfg" 4 | 5 | void save_instrument_inner(Stream* stream, Instrument* inst) { 6 | size_t rwops = stream_write(stream, (uint8_t*)inst->name, sizeof(inst->name)); 7 | rwops = stream_write(stream, (uint8_t*)&inst->waveform, sizeof(inst->waveform)); 8 | rwops = stream_write(stream, (uint8_t*)&inst->flags, sizeof(inst->flags)); 9 | rwops = stream_write( 10 | stream, (uint8_t*)&inst->sound_engine_flags, sizeof(inst->sound_engine_flags)); 11 | 12 | rwops = stream_write(stream, (uint8_t*)&inst->base_note, sizeof(inst->base_note)); 13 | rwops = stream_write(stream, (uint8_t*)&inst->finetune, sizeof(inst->finetune)); 14 | 15 | rwops = stream_write(stream, (uint8_t*)&inst->slide_speed, sizeof(inst->slide_speed)); 16 | 17 | rwops = stream_write(stream, (uint8_t*)&inst->adsr, sizeof(inst->adsr)); 18 | rwops = stream_write(stream, (uint8_t*)&inst->pw, sizeof(inst->pw)); 19 | 20 | if(inst->sound_engine_flags & SE_ENABLE_RING_MOD) { 21 | rwops = stream_write(stream, (uint8_t*)&inst->ring_mod, sizeof(inst->ring_mod)); 22 | } 23 | 24 | if(inst->sound_engine_flags & SE_ENABLE_HARD_SYNC) { 25 | rwops = stream_write(stream, (uint8_t*)&inst->hard_sync, sizeof(inst->hard_sync)); 26 | } 27 | 28 | uint8_t progsteps = 0; 29 | 30 | for(uint8_t i = 0; i < INST_PROG_LEN; i++) { 31 | if((inst->program[i] & 0x7fff) != TE_PROGRAM_NOP) { 32 | progsteps = i + 1; 33 | } 34 | } 35 | 36 | rwops = stream_write(stream, (uint8_t*)&progsteps, sizeof(progsteps)); 37 | 38 | if(progsteps > 0) { 39 | rwops = 40 | stream_write(stream, (uint8_t*)inst->program, progsteps * sizeof(inst->program[0])); 41 | } 42 | 43 | rwops = stream_write(stream, (uint8_t*)&inst->program_period, sizeof(inst->program_period)); 44 | 45 | if(inst->flags & TE_ENABLE_VIBRATO) { 46 | rwops = stream_write(stream, (uint8_t*)&inst->vibrato_speed, sizeof(inst->vibrato_speed)); 47 | rwops = stream_write(stream, (uint8_t*)&inst->vibrato_depth, sizeof(inst->vibrato_depth)); 48 | rwops = stream_write(stream, (uint8_t*)&inst->vibrato_delay, sizeof(inst->vibrato_delay)); 49 | } 50 | 51 | if(inst->flags & TE_ENABLE_PWM) { 52 | rwops = stream_write(stream, (uint8_t*)&inst->pwm_speed, sizeof(inst->pwm_speed)); 53 | rwops = stream_write(stream, (uint8_t*)&inst->pwm_depth, sizeof(inst->pwm_depth)); 54 | rwops = stream_write(stream, (uint8_t*)&inst->pwm_delay, sizeof(inst->pwm_delay)); 55 | } 56 | 57 | if(inst->sound_engine_flags & SE_ENABLE_FILTER) { 58 | rwops = stream_write(stream, (uint8_t*)&inst->filter_cutoff, sizeof(inst->filter_cutoff)); 59 | rwops = stream_write( 60 | stream, (uint8_t*)&inst->filter_resonance, sizeof(inst->filter_resonance)); 61 | rwops = stream_write(stream, (uint8_t*)&inst->filter_type, sizeof(inst->filter_type)); 62 | } 63 | 64 | UNUSED(rwops); 65 | } 66 | 67 | bool save_instrument(FlizzerTrackerApp* tracker, FuriString* filepath) { 68 | bool file_removed = 69 | storage_simply_remove(tracker->storage, furi_string_get_cstr(filepath)); // just in case 70 | bool open_file = file_stream_open( 71 | tracker->stream, furi_string_get_cstr(filepath), FSAM_WRITE, FSOM_OPEN_ALWAYS); 72 | 73 | uint8_t version = TRACKER_ENGINE_VERSION; 74 | size_t rwops = 75 | stream_write(tracker->stream, (uint8_t*)INST_FILE_SIG, sizeof(INST_FILE_SIG) - 1); 76 | rwops = stream_write(tracker->stream, (uint8_t*)&version, sizeof(uint8_t)); 77 | 78 | Instrument* inst = tracker->song.instrument[tracker->current_instrument]; 79 | 80 | save_instrument_inner(tracker->stream, inst); 81 | 82 | file_stream_close(tracker->stream); 83 | tracker->is_saving_instrument = false; 84 | furi_string_free(filepath); 85 | 86 | UNUSED(file_removed); 87 | UNUSED(open_file); 88 | UNUSED(rwops); 89 | return false; 90 | } 91 | 92 | bool save_song(FlizzerTrackerApp* tracker, FuriString* filepath) { 93 | bool file_removed = 94 | storage_simply_remove(tracker->storage, furi_string_get_cstr(filepath)); // just in case 95 | bool open_file = file_stream_open( 96 | tracker->stream, furi_string_get_cstr(filepath), FSAM_WRITE, FSOM_OPEN_ALWAYS); 97 | 98 | uint8_t version = TRACKER_ENGINE_VERSION; 99 | size_t rwops = 100 | stream_write(tracker->stream, (uint8_t*)SONG_FILE_SIG, sizeof(SONG_FILE_SIG) - 1); 101 | rwops = stream_write(tracker->stream, (uint8_t*)&version, sizeof(uint8_t)); 102 | 103 | TrackerSong* song = &tracker->song; 104 | 105 | /*for(uint32_t i = 0; i < 23444; i++) 106 | { 107 | rwops = stream_write(tracker->stream, (uint8_t*)&song->loop_end, sizeof(uint8_t)); 108 | }*/ 109 | 110 | rwops = stream_write(tracker->stream, (uint8_t*)song->song_name, sizeof(song->song_name)); 111 | rwops = stream_write(tracker->stream, (uint8_t*)&song->loop_start, sizeof(song->loop_start)); 112 | rwops = stream_write(tracker->stream, (uint8_t*)&song->loop_end, sizeof(song->loop_end)); 113 | rwops = stream_write( 114 | tracker->stream, (uint8_t*)&song->pattern_length, sizeof(song->pattern_length)); 115 | 116 | rwops = stream_write(tracker->stream, (uint8_t*)&song->speed, sizeof(song->speed)); 117 | rwops = stream_write(tracker->stream, (uint8_t*)&song->rate, sizeof(song->rate)); 118 | 119 | rwops = stream_write( 120 | tracker->stream, (uint8_t*)&song->num_sequence_steps, sizeof(song->num_sequence_steps)); 121 | 122 | for(uint16_t i = 0; i < song->num_sequence_steps; i++) { 123 | rwops = stream_write( 124 | tracker->stream, 125 | (uint8_t*)&song->sequence.sequence_step[i], 126 | sizeof(song->sequence.sequence_step[0])); 127 | } 128 | 129 | rwops = 130 | stream_write(tracker->stream, (uint8_t*)&song->num_patterns, sizeof(song->num_patterns)); 131 | 132 | for(uint16_t i = 0; i < song->num_patterns; i++) { 133 | rwops = stream_write( 134 | tracker->stream, 135 | (uint8_t*)song->pattern[i].step, 136 | sizeof(TrackerSongPatternStep) * (song->pattern_length)); 137 | } 138 | 139 | rwops = stream_write( 140 | tracker->stream, (uint8_t*)&song->num_instruments, sizeof(song->num_instruments)); 141 | 142 | for(uint16_t i = 0; i < song->num_instruments; i++) { 143 | save_instrument_inner(tracker->stream, song->instrument[i]); 144 | } 145 | 146 | file_stream_close(tracker->stream); 147 | tracker->is_saving = false; 148 | furi_string_free(filepath); 149 | 150 | UNUSED(file_removed); 151 | UNUSED(open_file); 152 | UNUSED(rwops); 153 | return false; 154 | } 155 | 156 | bool load_song_util(FlizzerTrackerApp* tracker, FuriString* filepath) { 157 | bool open_file = file_stream_open( 158 | tracker->stream, furi_string_get_cstr(filepath), FSAM_READ, FSOM_OPEN_ALWAYS); 159 | 160 | bool result = load_song(&tracker->song, tracker->stream); 161 | 162 | tracker->is_loading = false; 163 | file_stream_close(tracker->stream); 164 | furi_string_free(filepath); 165 | UNUSED(open_file); 166 | return result; 167 | } 168 | 169 | bool load_instrument_disk(TrackerSong* song, uint8_t inst, Stream* stream) { 170 | set_default_instrument(song->instrument[inst]); 171 | 172 | char header[sizeof(INST_FILE_SIG) + 2] = {0}; 173 | size_t rwops = stream_read(stream, (uint8_t*)&header, sizeof(INST_FILE_SIG) - 1); 174 | header[sizeof(INST_FILE_SIG)] = '\0'; 175 | 176 | uint8_t version = 0; 177 | 178 | if(strcmp(header, INST_FILE_SIG) == 0) { 179 | rwops = stream_read(stream, (uint8_t*)&version, sizeof(version)); 180 | 181 | if(version <= TRACKER_ENGINE_VERSION) { 182 | load_instrument_inner(stream, song->instrument[inst], version); 183 | } 184 | } 185 | 186 | UNUSED(rwops); 187 | return false; 188 | } 189 | 190 | bool load_instrument_util(FlizzerTrackerApp* tracker, FuriString* filepath) { 191 | bool open_file = file_stream_open( 192 | tracker->stream, furi_string_get_cstr(filepath), FSAM_READ, FSOM_OPEN_ALWAYS); 193 | 194 | bool result = 195 | load_instrument_disk(&tracker->song, tracker->current_instrument, tracker->stream); 196 | 197 | tracker->is_loading_instrument = false; 198 | file_stream_close(tracker->stream); 199 | furi_string_free(filepath); 200 | UNUSED(open_file); 201 | return result; 202 | } 203 | 204 | void save_config(FlizzerTrackerApp* tracker) { 205 | // stream_read_line 206 | FuriString* filepath = furi_string_alloc(); 207 | FuriString* config_line = furi_string_alloc(); 208 | furi_string_cat_printf(filepath, "%s/%s", FLIZZER_TRACKER_FOLDER, CFG_FILENAME); 209 | 210 | bool open_file = file_stream_open( 211 | tracker->stream, furi_string_get_cstr(filepath), FSAM_WRITE, FSOM_OPEN_ALWAYS); 212 | UNUSED(open_file); 213 | 214 | furi_string_cat_printf( 215 | config_line, "%s = %s\n", "external_audio", tracker->external_audio ? "true" : "false"); 216 | stream_write_string(tracker->stream, config_line); 217 | 218 | file_stream_close(tracker->stream); 219 | furi_string_free(filepath); 220 | furi_string_free(config_line); 221 | } 222 | 223 | void load_config(FlizzerTrackerApp* tracker) { 224 | FuriString* filepath = furi_string_alloc(); 225 | FuriString* config_line = furi_string_alloc(); 226 | furi_string_cat_printf(filepath, "%s/%s", FLIZZER_TRACKER_FOLDER, CFG_FILENAME); 227 | 228 | bool open_file = file_stream_open( 229 | tracker->stream, furi_string_get_cstr(filepath), FSAM_READ, FSOM_OPEN_ALWAYS); 230 | UNUSED(open_file); 231 | 232 | stream_read_line(tracker->stream, config_line); 233 | 234 | sscanf( 235 | furi_string_get_cstr(config_line), "%s%s%s", tracker->param, tracker->eq, tracker->value); 236 | 237 | if(strcmp(tracker->param, "external_audio") == 0) { 238 | if(strcmp(tracker->value, "false") == 0) { 239 | tracker->external_audio = false; 240 | // strcpy(tracker->value, "false_"); 241 | } 242 | 243 | if(strcmp(tracker->value, "true") == 0) { 244 | tracker->external_audio = true; 245 | // strcpy(tracker->value, "true_"); 246 | } 247 | 248 | sound_engine_init( 249 | &tracker->sound_engine, 250 | tracker->sound_engine.sample_rate, 251 | tracker->external_audio, 252 | tracker->sound_engine.audio_buffer_size); 253 | // sound_engine_set_audio_output(tracker->external_audio); 254 | } 255 | 256 | file_stream_close(tracker->stream); 257 | furi_string_free(filepath); 258 | furi_string_free(config_line); 259 | } -------------------------------------------------------------------------------- /init_deinit.c: -------------------------------------------------------------------------------- 1 | #include "init_deinit.h" 2 | #include "input_event.h" 3 | 4 | #include "diskop.h" 5 | 6 | #define AUDIO_MODES_COUNT 2 7 | 8 | static void tracker_redraw_update_model(void* context) { 9 | FlizzerTrackerApp* tracker = context; 10 | 11 | view_commit_model(tracker->tracker_view->view, true); 12 | } 13 | 14 | TrackerView* tracker_view_alloc(FlizzerTrackerApp* tracker) { 15 | TrackerView* tracker_view = malloc(sizeof(TrackerView)); 16 | tracker_view->view = view_alloc(); 17 | tracker_view->context = tracker; 18 | view_set_context(tracker_view->view, tracker_view); 19 | view_allocate_model(tracker_view->view, ViewModelTypeLockFree, sizeof(TrackerViewModel)); 20 | view_set_draw_callback(tracker_view->view, draw_callback); 21 | view_set_input_callback(tracker_view->view, input_callback); 22 | 23 | view_dispatcher_set_event_callback_context(tracker->view_dispatcher, tracker); 24 | view_dispatcher_set_tick_event_callback( 25 | tracker->view_dispatcher, tracker_redraw_update_model, 250); 26 | 27 | return tracker_view; 28 | } 29 | 30 | void tracker_view_free(TrackerView* tracker_view) { 31 | furi_assert(tracker_view); 32 | view_free(tracker_view->view); 33 | free(tracker_view); 34 | } 35 | 36 | uint8_t my_value_index_bool( 37 | const bool value, 38 | const bool values[], 39 | uint8_t 40 | values_count) // why the fuck it gives unresolved symbol if I include it from toolbox???!!! 41 | { 42 | uint8_t index = 0; 43 | 44 | for(uint8_t i = 0; i < values_count; i++) { 45 | if(value == values[i]) { 46 | index = i; 47 | break; 48 | } 49 | } 50 | 51 | return index; 52 | } 53 | 54 | FlizzerTrackerApp* init_tracker( 55 | uint32_t sample_rate, 56 | uint8_t rate, 57 | bool external_audio_output, 58 | uint32_t audio_buffer_size) { 59 | FlizzerTrackerApp* tracker = malloc(sizeof(FlizzerTrackerApp)); 60 | memset(tracker, 0, sizeof(FlizzerTrackerApp)); 61 | 62 | tracker->external_audio = external_audio_output; 63 | 64 | sound_engine_init( 65 | &tracker->sound_engine, sample_rate, external_audio_output, audio_buffer_size); 66 | tracker_engine_init(&tracker->tracker_engine, rate, &tracker->sound_engine); 67 | 68 | tracker->tracker_engine.song = &tracker->song; 69 | 70 | tracker->current_note = MIDDLE_C; 71 | 72 | // Очередь событий на 8 элементов размера FlizzerTrackerEvent 73 | tracker->event_queue = furi_message_queue_alloc(8, sizeof(FlizzerTrackerEvent)); 74 | 75 | tracker->gui = furi_record_open(RECORD_GUI); 76 | tracker->view_dispatcher = view_dispatcher_alloc(); 77 | 78 | tracker->tracker_view = tracker_view_alloc(tracker); 79 | 80 | with_view_model( 81 | tracker->tracker_view->view, TrackerViewModel * model, { model->tracker = tracker; }, true); 82 | 83 | view_dispatcher_add_view(tracker->view_dispatcher, VIEW_TRACKER, tracker->tracker_view->view); 84 | view_dispatcher_attach_to_gui( 85 | tracker->view_dispatcher, tracker->gui, ViewDispatcherTypeFullscreen); 86 | 87 | tracker->storage = furi_record_open(RECORD_STORAGE); 88 | tracker->stream = file_stream_alloc(tracker->storage); 89 | 90 | tracker->text_input = text_input_alloc(); 91 | view_dispatcher_add_view( 92 | tracker->view_dispatcher, VIEW_KEYBOARD, text_input_get_view(tracker->text_input)); 93 | 94 | tracker->pattern_submenu = submenu_alloc(); 95 | tracker->pattern_copypaste_submenu = submenu_alloc(); 96 | tracker->instrument_submenu = submenu_alloc(); 97 | 98 | view_set_previous_callback(submenu_get_view(tracker->pattern_submenu), submenu_exit_callback); 99 | view_set_previous_callback( 100 | submenu_get_view(tracker->pattern_copypaste_submenu), submenu_exit_callback); 101 | view_set_previous_callback( 102 | submenu_get_view(tracker->instrument_submenu), submenu_exit_callback); 103 | 104 | submenu_add_item( 105 | tracker->pattern_submenu, 106 | "Load song", 107 | SUBMENU_PATTERN_LOAD_SONG, 108 | submenu_callback, 109 | tracker); 110 | submenu_add_item( 111 | tracker->pattern_submenu, 112 | "Save song", 113 | SUBMENU_PATTERN_SAVE_SONG, 114 | submenu_callback, 115 | tracker); 116 | submenu_add_item( 117 | tracker->pattern_submenu, "Settings", SUBMENU_PATTERN_SETTINGS, submenu_callback, tracker); 118 | submenu_add_item( 119 | tracker->pattern_submenu, "Help", SUBMENU_PATTERN_HELP, submenu_callback, tracker); 120 | submenu_add_item( 121 | tracker->pattern_submenu, "Exit", SUBMENU_PATTERN_EXIT, submenu_callback, tracker); 122 | 123 | submenu_add_item( 124 | tracker->instrument_submenu, 125 | "Load instrument", 126 | SUBMENU_INSTRUMENT_LOAD, 127 | submenu_callback, 128 | tracker); 129 | submenu_add_item( 130 | tracker->instrument_submenu, 131 | "Save instrument", 132 | SUBMENU_INSTRUMENT_SAVE, 133 | submenu_callback, 134 | tracker); 135 | submenu_add_item( 136 | tracker->instrument_submenu, "Exit", SUBMENU_INSTRUMENT_EXIT, submenu_callback, tracker); 137 | 138 | submenu_add_item( 139 | tracker->pattern_copypaste_submenu, 140 | "Copy", 141 | SUBMENU_PATTERN_COPYPASTE_COPY, 142 | submenu_copypaste_callback, 143 | tracker); 144 | submenu_add_item( 145 | tracker->pattern_copypaste_submenu, 146 | "Paste", 147 | SUBMENU_PATTERN_COPYPASTE_PASTE, 148 | submenu_copypaste_callback, 149 | tracker); 150 | submenu_add_item( 151 | tracker->pattern_copypaste_submenu, 152 | "Cut", 153 | SUBMENU_PATTERN_COPYPASTE_CUT, 154 | submenu_copypaste_callback, 155 | tracker); 156 | submenu_add_item( 157 | tracker->pattern_copypaste_submenu, 158 | "Clear", 159 | SUBMENU_PATTERN_COPYPASTE_CLEAR, 160 | submenu_copypaste_callback, 161 | tracker); 162 | 163 | view_dispatcher_add_view( 164 | tracker->view_dispatcher, 165 | VIEW_SUBMENU_PATTERN, 166 | submenu_get_view(tracker->pattern_submenu)); 167 | view_dispatcher_add_view( 168 | tracker->view_dispatcher, 169 | VIEW_SUBMENU_PATTERN_COPYPASTE, 170 | submenu_get_view(tracker->pattern_copypaste_submenu)); 171 | view_dispatcher_add_view( 172 | tracker->view_dispatcher, 173 | VIEW_SUBMENU_INSTRUMENT, 174 | submenu_get_view(tracker->instrument_submenu)); 175 | 176 | load_config(tracker); 177 | 178 | tracker->settings_list = variable_item_list_alloc(); 179 | View* view = variable_item_list_get_view(tracker->settings_list); 180 | view_set_previous_callback(view, submenu_settings_exit_callback); 181 | 182 | VariableItem* item; 183 | uint8_t value_index; 184 | 185 | item = variable_item_list_add( 186 | tracker->settings_list, 187 | "Audio output", 188 | AUDIO_MODES_COUNT, 189 | audio_output_changed_callback, 190 | tracker); 191 | value_index = 192 | my_value_index_bool(tracker->external_audio, audio_modes_values, AUDIO_MODES_COUNT); 193 | variable_item_set_current_value_index(item, value_index); 194 | variable_item_set_current_value_text(item, audio_modes_text[value_index]); 195 | 196 | view_dispatcher_add_view(tracker->view_dispatcher, VIEW_SETTINGS, view); 197 | 198 | tracker->overwrite_file_widget = widget_alloc(); 199 | 200 | widget_add_button_element( 201 | tracker->overwrite_file_widget, 202 | GuiButtonTypeLeft, 203 | "No", 204 | (ButtonCallback)overwrite_file_widget_no_input_callback, 205 | tracker); 206 | widget_add_button_element( 207 | tracker->overwrite_file_widget, 208 | GuiButtonTypeRight, 209 | "Yes", 210 | (ButtonCallback)overwrite_file_widget_yes_input_callback, 211 | tracker); 212 | 213 | widget_add_text_scroll_element( 214 | tracker->overwrite_file_widget, 215 | 0, 216 | 0, 217 | 128, 218 | 64, 219 | "This song file already exists,\n do you want to overwrite it?"); 220 | 221 | view_dispatcher_add_view( 222 | tracker->view_dispatcher, 223 | VIEW_FILE_OVERWRITE, 224 | widget_get_view(tracker->overwrite_file_widget)); 225 | 226 | tracker->overwrite_instrument_file_widget = widget_alloc(); 227 | 228 | widget_add_button_element( 229 | tracker->overwrite_instrument_file_widget, 230 | GuiButtonTypeLeft, 231 | "No", 232 | (ButtonCallback)overwrite_instrument_file_widget_no_input_callback, 233 | tracker); 234 | widget_add_button_element( 235 | tracker->overwrite_instrument_file_widget, 236 | GuiButtonTypeRight, 237 | "Yes", 238 | (ButtonCallback)overwrite_instrument_file_widget_yes_input_callback, 239 | tracker); 240 | 241 | widget_add_text_scroll_element( 242 | tracker->overwrite_instrument_file_widget, 243 | 0, 244 | 0, 245 | 128, 246 | 64, 247 | "This instrument file already\nexists, do you want to\noverwrite it?"); 248 | 249 | view_dispatcher_add_view( 250 | tracker->view_dispatcher, 251 | VIEW_INSTRUMENT_FILE_OVERWRITE, 252 | widget_get_view(tracker->overwrite_instrument_file_widget)); 253 | 254 | tracker->notification = furi_record_open(RECORD_NOTIFICATION); 255 | notification_message(tracker->notification, &sequence_display_backlight_enforce_on); 256 | 257 | set_default_song(tracker); 258 | 259 | tracker->focus = EDIT_SONGINFO; 260 | tracker->source_pattern_index = -1; 261 | 262 | return tracker; 263 | } 264 | 265 | void deinit_tracker(FlizzerTrackerApp* tracker) { 266 | notification_message(tracker->notification, &sequence_display_backlight_enforce_auto); 267 | furi_record_close(RECORD_NOTIFICATION); 268 | 269 | // Специальная очистка памяти, занимаемой очередью 270 | furi_message_queue_free(tracker->event_queue); 271 | 272 | view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_SETTINGS); 273 | view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_FILE_OVERWRITE); 274 | view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_SUBMENU_INSTRUMENT); 275 | view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_INSTRUMENT_FILE_OVERWRITE); 276 | view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_SUBMENU_PATTERN_COPYPASTE); 277 | view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_SUBMENU_PATTERN); 278 | view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_KEYBOARD); 279 | view_dispatcher_remove_view(tracker->view_dispatcher, VIEW_TRACKER); 280 | 281 | text_input_free(tracker->text_input); 282 | 283 | variable_item_list_free(tracker->settings_list); 284 | 285 | submenu_free(tracker->pattern_submenu); 286 | submenu_free(tracker->pattern_copypaste_submenu); 287 | submenu_free(tracker->instrument_submenu); 288 | 289 | widget_free(tracker->overwrite_file_widget); 290 | widget_free(tracker->overwrite_instrument_file_widget); 291 | 292 | view_dispatcher_free(tracker->view_dispatcher); 293 | 294 | tracker_view_free(tracker->tracker_view); 295 | furi_record_close(RECORD_GUI); 296 | 297 | stream_free(tracker->stream); 298 | furi_record_close(RECORD_STORAGE); 299 | 300 | sound_engine_deinit(&tracker->sound_engine); 301 | 302 | if(tracker->tracker_engine.song == NULL) { 303 | tracker_engine_set_song(&tracker->tracker_engine, &tracker->song); 304 | } 305 | 306 | tracker_engine_deinit(&tracker->tracker_engine, false); 307 | 308 | free(tracker); 309 | } -------------------------------------------------------------------------------- /flizzer_tracker_hal.c: -------------------------------------------------------------------------------- 1 | #include "flizzer_tracker_hal.h" 2 | #include "flizzer_tracker.h" 3 | 4 | void sound_engine_dma_isr(void* ctx) { 5 | SoundEngine* sound_engine = (SoundEngine*)ctx; 6 | 7 | // sound_engine->counter++; 8 | 9 | // half of transfer 10 | if(LL_DMA_IsActiveFlag_HT1(DMA1)) { 11 | LL_DMA_ClearFlag_HT1(DMA1); 12 | // fill first half of buffer 13 | uint16_t* audio_buffer = sound_engine->audio_buffer; 14 | uint32_t audio_buffer_length = sound_engine->audio_buffer_size / 2; 15 | sound_engine_fill_buffer(sound_engine, audio_buffer, audio_buffer_length); 16 | } 17 | 18 | // transfer complete 19 | if(LL_DMA_IsActiveFlag_TC1(DMA1)) { 20 | LL_DMA_ClearFlag_TC1(DMA1); 21 | // fill second half of buffer 22 | uint32_t audio_buffer_length = sound_engine->audio_buffer_size / 2; 23 | uint16_t* audio_buffer = &sound_engine->audio_buffer[audio_buffer_length]; 24 | sound_engine_fill_buffer(sound_engine, audio_buffer, audio_buffer_length); 25 | } 26 | } 27 | 28 | void tracker_engine_timer_isr( 29 | void* ctx) // the tracker engine interrupt is of higher priority than sound engine one so it can run at the middle of filling the buffer, thus allowing sample-accurate tight effect timing 30 | { 31 | TrackerEngine* tracker_engine = (TrackerEngine*)ctx; 32 | // tracker_engine->counter++; 33 | 34 | if(LL_TIM_IsActiveFlag_UPDATE(TRACKER_ENGINE_TIMER)) { 35 | LL_TIM_ClearFlag_UPDATE(TRACKER_ENGINE_TIMER); 36 | tracker_engine_advance_tick(tracker_engine); 37 | } 38 | } 39 | 40 | void sound_engine_PWM_timer_init(bool external_audio_output) // external audio on pin PA6 41 | { 42 | if(external_audio_output) { 43 | /*if(furi_hal_speaker_is_mine()) { 44 | furi_hal_speaker_release(); 45 | }*/ 46 | 47 | //LL_TIM_DisableAllOutputs(SPEAKER_PWM_TIMER); 48 | //LL_TIM_DisableCounter(SPEAKER_PWM_TIMER); 49 | 50 | if(!(furi_hal_speaker_is_mine())) { 51 | if(furi_hal_speaker_acquire(1000)) { 52 | LL_TIM_DisableAllOutputs(SPEAKER_PWM_TIMER); 53 | LL_TIM_DisableCounter(SPEAKER_PWM_TIMER); 54 | 55 | LL_TIM_InitTypeDef TIM_InitStruct = {0}; 56 | LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; 57 | 58 | TIM_InitStruct.Prescaler = 0; 59 | TIM_InitStruct.Autoreload = 60 | 1023; // 10-bit PWM resolution at around 60 kHz PWM rate 61 | TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; 62 | LL_TIM_Init(SPEAKER_PWM_TIMER, &TIM_InitStruct); 63 | 64 | TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1; 65 | TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE; 66 | TIM_OC_InitStruct.CompareValue = 0; 67 | LL_TIM_OC_Init(SPEAKER_PWM_TIMER, SPEAKER_PWM_TIMER_CHANNEL, &TIM_OC_InitStruct); 68 | 69 | SPEAKER_PWM_TIMER->CNT = 0; 70 | 71 | LL_TIM_EnableAllOutputs(SPEAKER_PWM_TIMER); 72 | LL_TIM_EnableCounter(SPEAKER_PWM_TIMER); 73 | } 74 | } 75 | 76 | furi_hal_gpio_init(&gpio_ext_pa6, GpioModeAnalog, GpioPullNo, GpioSpeedLow); 77 | 78 | LL_TIM_InitTypeDef TIM_InitStruct = {0}; 79 | LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; 80 | 81 | TIM_InitStruct.Prescaler = 0; 82 | TIM_InitStruct.Autoreload = 1023; // 10-bit PWM resolution at around 60 kHz PWM rate 83 | TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; 84 | LL_TIM_Init(SPEAKER_PWM_TIMER, &TIM_InitStruct); 85 | 86 | TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1; 87 | TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE; 88 | TIM_OC_InitStruct.CompareValue = 0; 89 | LL_TIM_OC_Init(SPEAKER_PWM_TIMER, SPEAKER_PWM_TIMER_CHANNEL, &TIM_OC_InitStruct); 90 | 91 | SPEAKER_PWM_TIMER->CNT = 0; 92 | 93 | LL_TIM_EnableAllOutputs(SPEAKER_PWM_TIMER); 94 | LL_TIM_EnableCounter(SPEAKER_PWM_TIMER); 95 | 96 | furi_hal_gpio_init(&gpio_ext_pa6, GpioModeAnalog, GpioPullNo, GpioSpeedLow); 97 | //furi_hal_gpio_init_ex(&gpio_ext_pa6, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn14TIM16); 98 | } 99 | 100 | else { 101 | if(!(furi_hal_speaker_is_mine())) { 102 | if(furi_hal_speaker_acquire(1000)) { 103 | LL_TIM_DisableAllOutputs(SPEAKER_PWM_TIMER); 104 | LL_TIM_DisableCounter(SPEAKER_PWM_TIMER); 105 | 106 | LL_TIM_InitTypeDef TIM_InitStruct = {0}; 107 | LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; 108 | 109 | TIM_InitStruct.Prescaler = 0; 110 | TIM_InitStruct.Autoreload = 111 | 1023; // 10-bit PWM resolution at around 60 kHz PWM rate 112 | TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; 113 | LL_TIM_Init(SPEAKER_PWM_TIMER, &TIM_InitStruct); 114 | 115 | TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1; 116 | TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE; 117 | TIM_OC_InitStruct.CompareValue = 0; 118 | LL_TIM_OC_Init(SPEAKER_PWM_TIMER, SPEAKER_PWM_TIMER_CHANNEL, &TIM_OC_InitStruct); 119 | 120 | SPEAKER_PWM_TIMER->CNT = 0; 121 | 122 | LL_TIM_EnableAllOutputs(SPEAKER_PWM_TIMER); 123 | LL_TIM_EnableCounter(SPEAKER_PWM_TIMER); 124 | } 125 | } 126 | 127 | furi_hal_gpio_init(&gpio_ext_pa6, GpioModeAnalog, GpioPullNo, GpioSpeedLow); 128 | //furi_hal_gpio_init_ex(&gpio_ext_pa6, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn14TIM16); 129 | } 130 | 131 | furi_hal_gpio_init_ex( 132 | &gpio_ext_pa6, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn14TIM16); 133 | } 134 | 135 | void sound_engine_set_audio_output(bool external_audio_output) { 136 | if(external_audio_output) { 137 | furi_hal_gpio_init_ex( 138 | &gpio_ext_pa6, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn14TIM16); 139 | 140 | if(furi_hal_speaker_is_mine()) { 141 | furi_hal_speaker_release(); 142 | } 143 | } 144 | 145 | else { 146 | if(!(furi_hal_speaker_is_mine())) { 147 | bool unu = furi_hal_speaker_acquire(1000); 148 | UNUSED(unu); 149 | } 150 | 151 | furi_hal_gpio_init(&gpio_ext_pa6, GpioModeAnalog, GpioPullNo, GpioSpeedLow); 152 | } 153 | } 154 | 155 | void sound_engine_timer_init(uint32_t sample_rate) // external audio on pin PA6 156 | { 157 | if(!furi_hal_bus_is_enabled(FuriHalBusTIM1)) { 158 | furi_hal_bus_enable(FuriHalBusTIM1); 159 | } 160 | 161 | LL_TIM_InitTypeDef TIM_InitStruct = {0}; 162 | LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; 163 | 164 | TIM_InitStruct.Prescaler = 0; 165 | TIM_InitStruct.Autoreload = 166 | TIMER_BASE_CLOCK / sample_rate - 1; // to support various sample rates 167 | TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; 168 | LL_TIM_Init(SAMPLE_RATE_TIMER, &TIM_InitStruct); 169 | 170 | TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1; 171 | TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE; 172 | LL_TIM_OC_Init(SAMPLE_RATE_TIMER, SPEAKER_PWM_TIMER_CHANNEL, &TIM_OC_InitStruct); 173 | 174 | LL_TIM_EnableAllOutputs(SAMPLE_RATE_TIMER); 175 | 176 | SAMPLE_RATE_TIMER->CNT = 0; 177 | } 178 | 179 | void tracker_engine_timer_init(uint8_t rate) // 0-255 hz 180 | { 181 | if(!furi_hal_bus_is_enabled(FuriHalBusTIM2)) { 182 | furi_hal_bus_enable(FuriHalBusTIM2); 183 | } 184 | 185 | LL_TIM_InitTypeDef TIM_InitStruct = {0}; 186 | LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; 187 | 188 | TIM_InitStruct.Prescaler = 0; // using 32-bit timer 189 | TIM_InitStruct.Autoreload = 190 | (uint32_t)TIMER_BASE_CLOCK / (uint32_t)rate - 1; // to support various tracker engine rates 191 | TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; 192 | LL_TIM_Init(TRACKER_ENGINE_TIMER, &TIM_InitStruct); 193 | 194 | TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1; 195 | TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE; 196 | LL_TIM_OC_Init(TRACKER_ENGINE_TIMER, SPEAKER_PWM_TIMER_CHANNEL, &TIM_OC_InitStruct); 197 | 198 | LL_TIM_EnableIT_UPDATE(TRACKER_ENGINE_TIMER); 199 | 200 | TRACKER_ENGINE_TIMER->CNT = 0; 201 | } 202 | 203 | void tracker_engine_set_rate(uint8_t rate) { 204 | if(!furi_hal_bus_is_enabled(FuriHalBusTIM2)) { 205 | furi_hal_bus_enable(FuriHalBusTIM2); 206 | } 207 | 208 | LL_TIM_InitTypeDef TIM_InitStruct = {0}; 209 | 210 | TIM_InitStruct.Prescaler = 0; // using 32-bit timer 211 | TIM_InitStruct.Autoreload = 212 | (uint32_t)TIMER_BASE_CLOCK / (uint32_t)rate - 1; // to support various tracker engine rates 213 | TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; 214 | LL_TIM_Init(TRACKER_ENGINE_TIMER, &TIM_InitStruct); 215 | 216 | TRACKER_ENGINE_TIMER->CNT = 0; 217 | } 218 | 219 | void tracker_engine_init_hardware(uint8_t rate) { 220 | tracker_engine_timer_init(rate); 221 | } 222 | 223 | void sound_engine_dma_init(uint32_t address, uint32_t size) { 224 | uint32_t dma_dst = (uint32_t) & (SPEAKER_PWM_TIMER->CCR1); 225 | 226 | LL_DMA_ConfigAddresses(DMA_INSTANCE, address, dma_dst, LL_DMA_DIRECTION_MEMORY_TO_PERIPH); 227 | LL_DMA_SetDataLength(DMA_INSTANCE, size); 228 | 229 | LL_DMA_SetPeriphRequest(DMA_INSTANCE, LL_DMAMUX_REQ_TIM1_UP); 230 | LL_DMA_SetDataTransferDirection(DMA_INSTANCE, LL_DMA_DIRECTION_MEMORY_TO_PERIPH); 231 | LL_DMA_SetChannelPriorityLevel(DMA_INSTANCE, LL_DMA_PRIORITY_VERYHIGH); 232 | LL_DMA_SetMode(DMA_INSTANCE, LL_DMA_MODE_CIRCULAR); 233 | LL_DMA_SetPeriphIncMode(DMA_INSTANCE, LL_DMA_PERIPH_NOINCREMENT); 234 | LL_DMA_SetMemoryIncMode(DMA_INSTANCE, LL_DMA_MEMORY_INCREMENT); 235 | LL_DMA_SetPeriphSize(DMA_INSTANCE, LL_DMA_PDATAALIGN_HALFWORD); 236 | LL_DMA_SetMemorySize(DMA_INSTANCE, LL_DMA_MDATAALIGN_HALFWORD); 237 | 238 | LL_DMA_EnableIT_TC(DMA_INSTANCE); 239 | LL_DMA_EnableIT_HT(DMA_INSTANCE); 240 | } 241 | 242 | void sound_engine_init_hardware( 243 | uint32_t sample_rate, 244 | bool external_audio_output, 245 | uint16_t* audio_buffer, 246 | uint32_t audio_buffer_size) { 247 | sound_engine_dma_init((uint32_t)audio_buffer, audio_buffer_size); 248 | sound_engine_timer_init(sample_rate); 249 | sound_engine_PWM_timer_init(external_audio_output); 250 | } 251 | 252 | void sound_engine_dma_start() { 253 | LL_DMA_EnableChannel(DMA_INSTANCE); 254 | LL_TIM_EnableDMAReq_UPDATE(SAMPLE_RATE_TIMER); 255 | } 256 | 257 | void sound_engine_dma_stop() { 258 | LL_DMA_DisableChannel(DMA_INSTANCE); 259 | } 260 | 261 | void sound_engine_start() { 262 | SAMPLE_RATE_TIMER->CNT = 0; 263 | LL_TIM_EnableCounter(SAMPLE_RATE_TIMER); 264 | 265 | sound_engine_dma_start(); 266 | } 267 | 268 | void sound_engine_stop() { 269 | LL_TIM_DisableAllOutputs(SAMPLE_RATE_TIMER); 270 | LL_TIM_DisableCounter(SAMPLE_RATE_TIMER); 271 | 272 | sound_engine_dma_stop(); 273 | } 274 | 275 | void sound_engine_deinit_timer() { 276 | LL_TIM_DisableAllOutputs(SAMPLE_RATE_TIMER); 277 | LL_TIM_DisableAllOutputs(SPEAKER_PWM_TIMER); 278 | 279 | LL_TIM_DisableCounter(SPEAKER_PWM_TIMER); 280 | 281 | if(furi_hal_speaker_is_mine()) { 282 | furi_hal_speaker_release(); 283 | } 284 | 285 | if(furi_hal_bus_is_enabled(FuriHalBusTIM2)) { 286 | furi_hal_bus_disable(FuriHalBusTIM2); 287 | } 288 | if(furi_hal_bus_is_enabled(FuriHalBusTIM1)) { 289 | furi_hal_bus_disable(FuriHalBusTIM1); 290 | } 291 | } 292 | 293 | void tracker_engine_start() { 294 | TRACKER_ENGINE_TIMER->CNT = 0; 295 | 296 | LL_TIM_EnableAllOutputs(TRACKER_ENGINE_TIMER); 297 | LL_TIM_EnableCounter(TRACKER_ENGINE_TIMER); 298 | } 299 | 300 | void tracker_engine_stop() { 301 | LL_TIM_DisableAllOutputs(TRACKER_ENGINE_TIMER); 302 | LL_TIM_DisableCounter(TRACKER_ENGINE_TIMER); 303 | } 304 | 305 | void play() { 306 | tracker_engine_start(); 307 | sound_engine_start(); 308 | } 309 | 310 | void stop() { 311 | sound_engine_stop(); 312 | tracker_engine_stop(); 313 | } -------------------------------------------------------------------------------- /input/pattern.c: -------------------------------------------------------------------------------- 1 | #include "pattern.h" 2 | 3 | uint8_t get_field(uint8_t patternx) { 4 | uint8_t field = 0; 5 | 6 | if(patternx <= 1) field = 0; 7 | if(patternx == 2) field = 1; 8 | if(patternx == 3) field = 2; 9 | if(patternx > 3) field = 3; 10 | 11 | return field; 12 | } 13 | 14 | void edit_note( 15 | FlizzerTrackerApp* tracker, 16 | TrackerSongPatternStep* step, 17 | int8_t delta) // here we need data about last note if we place a new note 18 | { 19 | int16_t note = tracker_engine_get_note(step); 20 | 21 | if(note == MUS_NOTE_RELEASE) { 22 | if(delta < 0) { 23 | set_note(step, MUS_NOTE_CUT); 24 | } 25 | 26 | return; 27 | } 28 | 29 | if(note == MUS_NOTE_CUT) { 30 | if(delta > 0) { 31 | set_note(step, MUS_NOTE_RELEASE); 32 | } 33 | 34 | return; 35 | } 36 | 37 | if(note == MUS_NOTE_NONE) { 38 | note = 39 | tracker->current_note; // remember which note we entered earlier and use it as reference 40 | } 41 | 42 | clamp(note, delta, 0, MAX_NOTE); 43 | 44 | set_note(step, (uint8_t)note); 45 | set_instrument(step, tracker->current_instrument); 46 | 47 | tracker->current_note = (uint8_t)note; 48 | } 49 | 50 | void edit_instrument(FlizzerTrackerApp* tracker, TrackerSongPatternStep* step, int8_t delta) { 51 | int16_t inst = tracker_engine_get_instrument(step); 52 | 53 | if(inst == MUS_NOTE_INSTRUMENT_NONE) { 54 | if(delta > 0) { 55 | inst = tracker->current_instrument; 56 | } 57 | 58 | else { 59 | inst = MUS_NOTE_INSTRUMENT_NONE - 1; 60 | } 61 | } 62 | 63 | clamp(inst, delta, 0, tracker->song.num_instruments - 1); 64 | tracker->current_instrument = inst; // remember last instrument 65 | set_instrument(step, (uint8_t)inst); 66 | } 67 | 68 | void edit_volume(FlizzerTrackerApp* tracker, TrackerSongPatternStep* step, int8_t delta) { 69 | int16_t vol = tracker_engine_get_volume(step); 70 | 71 | vol = tracker->current_volume; 72 | 73 | if(vol + delta < 0) { 74 | vol = MUS_NOTE_VOLUME_NONE - 1 - delta; 75 | } 76 | 77 | if(vol + delta >= MUS_NOTE_VOLUME_NONE) { 78 | vol = 0 - delta; 79 | } 80 | 81 | clamp(vol, delta, 0, MUS_NOTE_VOLUME_NONE - 1); 82 | 83 | set_volume(step, (uint8_t)vol); 84 | 85 | tracker->current_volume = vol; 86 | } 87 | 88 | void edit_command(TrackerSongPatternStep* step, uint8_t digit, int8_t delta) { 89 | int32_t command = tracker_engine_get_command(step); 90 | 91 | switch(digit) { 92 | case 0: // upper 7 bits 93 | { 94 | int16_t fx_name = ((command & 0x7f00) >> 8); 95 | 96 | if(fx_name + delta > 35) // loop 97 | { // 0-9 and then A-Z 98 | fx_name = 0; 99 | } 100 | 101 | else if(fx_name + delta < 0) { 102 | fx_name = 35; 103 | } 104 | 105 | else { 106 | fx_name += delta; 107 | } 108 | 109 | command &= 0x00ff; 110 | 111 | command |= (fx_name << 8); 112 | 113 | set_command(step, (uint16_t)command); 114 | 115 | break; 116 | } 117 | 118 | case 1: // upper digit of command param 119 | { 120 | int8_t upper_digit = ((command & 0x00f0) >> 4); 121 | 122 | if(upper_digit + delta > 0xf) // loop 123 | { 124 | upper_digit = 0; 125 | } 126 | 127 | else if(upper_digit + delta < 0) { 128 | upper_digit = 0xf; 129 | } 130 | 131 | else { 132 | upper_digit += delta; 133 | } 134 | 135 | command &= 0xff0f; 136 | 137 | command |= (upper_digit << 4); 138 | 139 | set_command(step, (uint16_t)command); 140 | 141 | break; 142 | } 143 | 144 | case 2: // lower digit of command param 145 | { 146 | int8_t lower_digit = (command & 0x000f); 147 | 148 | if(lower_digit + delta > 0xf) // loop 149 | { 150 | lower_digit = 0; 151 | } 152 | 153 | else if(lower_digit + delta < 0) { 154 | lower_digit = 0xf; 155 | } 156 | 157 | else { 158 | lower_digit += delta; 159 | } 160 | 161 | command &= 0xfff0; 162 | 163 | command |= lower_digit; 164 | 165 | set_command(step, (uint16_t)command); 166 | 167 | break; 168 | } 169 | 170 | default: 171 | break; 172 | } 173 | } 174 | 175 | void delete_field(TrackerSongPatternStep* step, uint8_t field) { 176 | switch(field) { 177 | case 0: // note 178 | { 179 | set_note(step, MUS_NOTE_NONE); 180 | set_instrument(step, MUS_NOTE_INSTRUMENT_NONE); // also delete instrument 181 | break; 182 | } 183 | 184 | case 1: // instrument 185 | { 186 | set_instrument(step, MUS_NOTE_INSTRUMENT_NONE); 187 | break; 188 | } 189 | 190 | case 2: // volume 191 | { 192 | set_volume(step, MUS_NOTE_VOLUME_NONE); 193 | break; 194 | } 195 | 196 | case 3: // command 197 | { 198 | set_command(step, 0); 199 | break; 200 | } 201 | 202 | default: 203 | break; 204 | } 205 | } 206 | 207 | void edit_pattern_step(FlizzerTrackerApp* tracker, TrackerSongPatternStep* step, int8_t delta) { 208 | switch(get_field(tracker->patternx)) { 209 | case 0: // note 210 | { 211 | if(tracker->patternx) // editing octave 212 | { 213 | edit_note(tracker, step, 12 * delta); 214 | } 215 | 216 | else // editing note 217 | { 218 | edit_note(tracker, step, delta); 219 | } 220 | 221 | break; 222 | } 223 | 224 | case 1: // instrument 225 | { 226 | edit_instrument(tracker, step, delta); 227 | break; 228 | } 229 | 230 | case 2: // volume 231 | { 232 | edit_volume(tracker, step, delta); 233 | break; 234 | } 235 | 236 | case 3: // command 237 | { 238 | uint8_t digit = 0; 239 | if(tracker->patternx == 4) digit = 0; 240 | if(tracker->patternx == 5) digit = 1; 241 | if(tracker->patternx == 6) digit = 2; 242 | edit_command(step, digit, delta); 243 | break; 244 | } 245 | 246 | default: 247 | break; 248 | } 249 | } 250 | 251 | void pattern_edit_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* event) { 252 | if(event->input.key == InputKeyLeft && event->input.type == InputTypeLong && 253 | !(tracker->editing)) { 254 | flipbit( 255 | tracker->tracker_engine.channel[tracker->current_channel].channel_flags, TEC_DISABLED); 256 | return; 257 | } 258 | 259 | if(event->input.key == InputKeyDown && event->input.type == InputTypeLong && 260 | !(tracker->editing)) { 261 | tracker->tracker_engine.pattern_position = 262 | tracker->tracker_engine.song->pattern_length - 1; // go to pattern last row 263 | return; 264 | } 265 | 266 | if(event->input.key == InputKeyUp && event->input.type == InputTypeLong && 267 | !(tracker->editing)) { 268 | tracker->tracker_engine.pattern_position = 0; // return to pattern 1st row 269 | return; 270 | } 271 | 272 | uint8_t sequence_position = tracker->tracker_engine.sequence_position; 273 | uint8_t current_pattern = 274 | tracker->tracker_engine.song->sequence.sequence_step[sequence_position] 275 | .pattern_indices[tracker->current_channel]; 276 | uint16_t pattern_step = tracker->tracker_engine.pattern_position; 277 | 278 | uint16_t pattern_length = tracker->tracker_engine.song->pattern_length; 279 | 280 | TrackerSongPattern* pattern = &tracker->tracker_engine.song->pattern[current_pattern]; 281 | 282 | TrackerSongPatternStep* step = NULL; 283 | 284 | if(pattern_step < pattern_length) { 285 | step = &pattern->step[pattern_step]; 286 | } 287 | 288 | if(!(step)) return; 289 | 290 | if(event->input.key == InputKeyOk && event->input.type == InputTypeShort && 291 | !tracker->tracker_engine.playing) { 292 | tracker->editing = !tracker->editing; 293 | 294 | if(tracker->editing) { 295 | // stop_song(tracker); 296 | } 297 | } 298 | 299 | if(event->input.key == InputKeyOk && event->input.type == InputTypeLong) { 300 | if(!(tracker->editing)) { 301 | if(tracker->tracker_engine.playing) { 302 | stop_song(tracker); 303 | } 304 | 305 | else { 306 | if(tracker->tracker_engine.pattern_position == tracker->song.pattern_length - 1 && 307 | tracker->tracker_engine.sequence_position == 308 | tracker->song.num_sequence_steps - 309 | 1) // if we are at the very end of the song 310 | { 311 | stop_song(tracker); 312 | } 313 | 314 | else { 315 | play_song(tracker, true); 316 | } 317 | } 318 | } 319 | 320 | else { 321 | if(get_field(tracker->patternx) == 0) { 322 | set_note(step, MUS_NOTE_RELEASE); 323 | } 324 | } 325 | } 326 | 327 | if(event->input.key == InputKeyRight && event->input.type == InputTypeShort) { 328 | tracker->patternx++; 329 | 330 | if(tracker->patternx > MAX_PATTERNX - 1) { 331 | tracker->current_channel++; 332 | 333 | tracker->patternx = 0; 334 | 335 | if(tracker->current_channel > SONG_MAX_CHANNELS - 1) { 336 | tracker->current_channel = 0; 337 | } 338 | } 339 | } 340 | 341 | if(event->input.key == InputKeyLeft && event->input.type == InputTypeShort) { 342 | tracker->patternx--; 343 | 344 | if(tracker->patternx > MAX_PATTERNX - 1) // unsigned int overflow 345 | { 346 | tracker->current_channel--; 347 | 348 | tracker->patternx = MAX_PATTERNX - 1; 349 | 350 | if(tracker->current_channel > SONG_MAX_CHANNELS - 1) // unsigned int overflow 351 | { 352 | tracker->current_channel = SONG_MAX_CHANNELS - 1; 353 | } 354 | } 355 | } 356 | 357 | if(event->input.key == InputKeyDown && event->input.type == InputTypeShort) { 358 | if(!(tracker->editing)) { 359 | tracker->tracker_engine.pattern_position++; 360 | 361 | if(tracker->tracker_engine.pattern_position > 362 | tracker->tracker_engine.song->pattern_length - 1 && 363 | tracker->tracker_engine.sequence_position < 364 | tracker->tracker_engine.song->num_sequence_steps - 1) { 365 | tracker->tracker_engine.pattern_position = 0; 366 | tracker->tracker_engine.sequence_position++; 367 | } 368 | 369 | else if( 370 | tracker->tracker_engine.pattern_position > 371 | tracker->tracker_engine.song->pattern_length - 1) { 372 | tracker->tracker_engine.pattern_position = 373 | tracker->tracker_engine.song->pattern_length - 1; 374 | } 375 | } 376 | 377 | if(tracker->editing) { 378 | edit_pattern_step(tracker, step, -1); 379 | } 380 | } 381 | 382 | if(event->input.key == InputKeyUp && event->input.type == InputTypeShort) { 383 | if(!(tracker->editing)) { 384 | int16_t temp_pattern_position = tracker->tracker_engine.pattern_position - 1; 385 | 386 | if(temp_pattern_position < 0) { 387 | if(tracker->tracker_engine.sequence_position > 0) { 388 | tracker->tracker_engine.sequence_position--; 389 | tracker->tracker_engine.pattern_position = 390 | tracker->tracker_engine.song->pattern_length - 1; 391 | } 392 | } 393 | 394 | else { 395 | tracker->tracker_engine.pattern_position--; 396 | } 397 | } 398 | 399 | if(tracker->editing) { 400 | edit_pattern_step(tracker, step, 1); 401 | } 402 | } 403 | 404 | if(event->input.key == InputKeyBack && event->input.type == InputTypeShort && 405 | tracker->editing) { 406 | uint8_t field = get_field(tracker->patternx); 407 | 408 | delete_field(step, field); 409 | } 410 | } -------------------------------------------------------------------------------- /sound_engine/sound_engine_osc.c: -------------------------------------------------------------------------------- 1 | #include "sound_engine_osc.h" 2 | 3 | static inline uint16_t sound_engine_pulse(uint32_t acc, uint32_t pw) // 0-FFF pulse width range 4 | { 5 | return ( 6 | ((acc >> (((uint32_t)ACC_BITS - 17))) >= ((pw == 0xfff ? pw + 1 : pw) << 4) ? 7 | (WAVE_AMP - 1) : 8 | 0)); 9 | } 10 | 11 | static inline uint16_t sound_engine_saw(uint32_t acc) { 12 | return (acc >> (ACC_BITS - OUTPUT_BITS - 1)) & (WAVE_AMP - 1); 13 | } 14 | 15 | uint16_t sound_engine_triangle(uint32_t acc) { 16 | return ( 17 | (((acc & (ACC_LENGTH / 2)) ? ~acc : acc) >> (ACC_BITS - OUTPUT_BITS - 2)) & 18 | (WAVE_AMP * 2 - 1)); 19 | } 20 | 21 | static inline uint16_t sound_engine_sine(uint32_t acc, SoundEngine* sound_engine) { 22 | return ( 23 | (uint16_t)sound_engine->sine_lut[(acc >> (ACC_BITS - SINE_LUT_BITDEPTH))] 24 | << (OUTPUT_BITS - SINE_LUT_BITDEPTH)); 25 | } 26 | 27 | inline static void shift_lfsr(uint32_t* v, uint32_t tap_0, uint32_t tap_1) { 28 | typedef uint32_t T; 29 | const T zero = (T)(0); 30 | const T lsb = zero + (T)(1); 31 | const T feedback = ((lsb << (tap_0)) ^ (lsb << (tap_1))); 32 | 33 | *v = (*v >> 1) ^ ((zero - (*v & lsb)) & feedback); 34 | } 35 | 36 | static inline uint16_t sound_engine_noise(SoundEngineChannel* channel, uint32_t prev_acc) { 37 | if((prev_acc & (ACC_LENGTH / 32)) != (channel->accumulator & (ACC_LENGTH / 32))) { 38 | if(channel->waveform & SE_WAVEFORM_NOISE_METAL) { 39 | shift_lfsr(&channel->lfsr, 14, 8); 40 | channel->lfsr &= (1 << (14 + 1)) - 1; 41 | } 42 | 43 | else { 44 | shift_lfsr(&channel->lfsr, 22, 17); 45 | channel->lfsr &= (1 << (22 + 1)) - 1; 46 | } 47 | } 48 | 49 | return (channel->lfsr) & (WAVE_AMP - 1); 50 | } 51 | 52 | uint16_t 53 | sound_engine_osc(SoundEngine* sound_engine, SoundEngineChannel* channel, uint32_t prev_acc) { 54 | switch(channel->waveform) { 55 | case SE_WAVEFORM_NOISE: 56 | case SE_WAVEFORM_NOISE_METAL: 57 | case(SE_WAVEFORM_NOISE | SE_WAVEFORM_NOISE_METAL): { 58 | return sound_engine_noise(channel, prev_acc); 59 | break; 60 | } 61 | 62 | case SE_WAVEFORM_PULSE: { 63 | return sound_engine_pulse(channel->accumulator, channel->pw); 64 | break; 65 | } 66 | 67 | case SE_WAVEFORM_TRIANGLE: { 68 | return sound_engine_triangle(channel->accumulator); 69 | break; 70 | } 71 | 72 | case SE_WAVEFORM_SAW: { 73 | return sound_engine_saw(channel->accumulator); 74 | break; 75 | } 76 | 77 | case SE_WAVEFORM_SINE: { 78 | return sound_engine_sine(channel->accumulator, sound_engine); 79 | break; 80 | } 81 | 82 | case(SE_WAVEFORM_PULSE | SE_WAVEFORM_NOISE): 83 | case(SE_WAVEFORM_PULSE | SE_WAVEFORM_NOISE_METAL): 84 | case(SE_WAVEFORM_PULSE | SE_WAVEFORM_NOISE | SE_WAVEFORM_NOISE_METAL): { 85 | return sound_engine_pulse(channel->accumulator, channel->pw) & 86 | sound_engine_noise(channel, prev_acc); 87 | } 88 | 89 | case(SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_NOISE): 90 | case(SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_NOISE_METAL): 91 | case(SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_NOISE | SE_WAVEFORM_NOISE_METAL): { 92 | return sound_engine_triangle(channel->accumulator) & sound_engine_noise(channel, prev_acc); 93 | } 94 | 95 | case(SE_WAVEFORM_PULSE | SE_WAVEFORM_TRIANGLE): { 96 | return sound_engine_pulse(channel->accumulator, channel->pw) & 97 | sound_engine_triangle(channel->accumulator); 98 | } 99 | 100 | case(SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_PULSE | SE_WAVEFORM_NOISE): 101 | case(SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_PULSE | SE_WAVEFORM_NOISE_METAL): 102 | case(SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_PULSE | SE_WAVEFORM_NOISE | SE_WAVEFORM_NOISE_METAL): { 103 | return sound_engine_pulse(channel->accumulator, channel->pw) & 104 | sound_engine_noise(channel, prev_acc) & sound_engine_triangle(channel->accumulator); 105 | } 106 | 107 | case(SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE): 108 | case(SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE_METAL): 109 | case(SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE | SE_WAVEFORM_NOISE_METAL): { 110 | return sound_engine_saw(channel->accumulator) & sound_engine_noise(channel, prev_acc); 111 | } 112 | 113 | case(SE_WAVEFORM_PULSE | SE_WAVEFORM_SAW): { 114 | return sound_engine_pulse(channel->accumulator, channel->pw) & 115 | sound_engine_saw(channel->accumulator); 116 | } 117 | 118 | case(SE_WAVEFORM_PULSE | SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE): 119 | case(SE_WAVEFORM_PULSE | SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE_METAL): 120 | case(SE_WAVEFORM_PULSE | SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE | SE_WAVEFORM_NOISE_METAL): { 121 | return sound_engine_pulse(channel->accumulator, channel->pw) & 122 | sound_engine_saw(channel->accumulator) & sound_engine_noise(channel, prev_acc); 123 | } 124 | 125 | case(SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_SAW): { 126 | return sound_engine_triangle(channel->accumulator) & 127 | sound_engine_saw(channel->accumulator); 128 | } 129 | 130 | case(SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE): 131 | case(SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE_METAL): 132 | case(SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE | SE_WAVEFORM_NOISE_METAL): { 133 | return sound_engine_triangle(channel->accumulator) & 134 | sound_engine_saw(channel->accumulator) & sound_engine_noise(channel, prev_acc); 135 | } 136 | 137 | case(SE_WAVEFORM_PULSE | SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_SAW): { 138 | return sound_engine_pulse(channel->accumulator, channel->pw) & 139 | sound_engine_triangle(channel->accumulator) & 140 | sound_engine_saw(channel->accumulator); 141 | } 142 | 143 | case(SE_WAVEFORM_PULSE | SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE): 144 | case(SE_WAVEFORM_PULSE | SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE_METAL): 145 | case( 146 | SE_WAVEFORM_PULSE | SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE | 147 | SE_WAVEFORM_NOISE_METAL): { 148 | return sound_engine_pulse(channel->accumulator, channel->pw) & 149 | sound_engine_triangle(channel->accumulator) & 150 | sound_engine_saw(channel->accumulator) & sound_engine_noise(channel, prev_acc); 151 | } 152 | 153 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_NOISE): 154 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_NOISE_METAL): 155 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_NOISE | SE_WAVEFORM_NOISE_METAL): { 156 | return sound_engine_sine(channel->accumulator, sound_engine) & 157 | sound_engine_noise(channel, prev_acc); 158 | } 159 | 160 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_PULSE): { 161 | return sound_engine_pulse(channel->accumulator, channel->pw) & 162 | sound_engine_sine(channel->accumulator, sound_engine); 163 | } 164 | 165 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_PULSE | SE_WAVEFORM_NOISE): 166 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_PULSE | SE_WAVEFORM_NOISE_METAL): 167 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_PULSE | SE_WAVEFORM_NOISE | SE_WAVEFORM_NOISE_METAL): { 168 | return sound_engine_pulse(channel->accumulator, channel->pw) & 169 | sound_engine_sine(channel->accumulator, sound_engine) & 170 | sound_engine_noise(channel, prev_acc); 171 | } 172 | 173 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_TRIANGLE): { 174 | return sound_engine_triangle(channel->accumulator) & 175 | sound_engine_sine(channel->accumulator, sound_engine); 176 | } 177 | 178 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_NOISE): 179 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_NOISE_METAL): 180 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_NOISE | SE_WAVEFORM_NOISE_METAL): { 181 | return sound_engine_triangle(channel->accumulator) & 182 | sound_engine_sine(channel->accumulator, sound_engine) & 183 | sound_engine_noise(channel, prev_acc); 184 | } 185 | 186 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_PULSE): { 187 | return sound_engine_pulse(channel->accumulator, channel->pw) & 188 | sound_engine_triangle(channel->accumulator) & 189 | sound_engine_sine(channel->accumulator, sound_engine); 190 | } 191 | 192 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_PULSE | SE_WAVEFORM_NOISE): 193 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_PULSE | SE_WAVEFORM_NOISE_METAL): 194 | case( 195 | SE_WAVEFORM_SINE | SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_PULSE | SE_WAVEFORM_NOISE | 196 | SE_WAVEFORM_NOISE_METAL): { 197 | return sound_engine_pulse(channel->accumulator, channel->pw) & 198 | sound_engine_triangle(channel->accumulator) & 199 | sound_engine_sine(channel->accumulator, sound_engine) & 200 | sound_engine_noise(channel, prev_acc); 201 | } 202 | 203 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_SAW): { 204 | return sound_engine_saw(channel->accumulator) & 205 | sound_engine_sine(channel->accumulator, sound_engine); 206 | } 207 | 208 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE): 209 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE_METAL): 210 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE | SE_WAVEFORM_NOISE_METAL): { 211 | return sound_engine_saw(channel->accumulator) & 212 | sound_engine_sine(channel->accumulator, sound_engine) & 213 | sound_engine_noise(channel, prev_acc); 214 | } 215 | 216 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_PULSE | SE_WAVEFORM_SAW): { 217 | return sound_engine_pulse(channel->accumulator, channel->pw) & 218 | sound_engine_saw(channel->accumulator) & 219 | sound_engine_sine(channel->accumulator, sound_engine); 220 | } 221 | 222 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_PULSE | SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE): 223 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_PULSE | SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE_METAL): 224 | case( 225 | SE_WAVEFORM_SINE | SE_WAVEFORM_PULSE | SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE | 226 | SE_WAVEFORM_NOISE_METAL): { 227 | return sound_engine_pulse(channel->accumulator, channel->pw) & 228 | sound_engine_saw(channel->accumulator) & 229 | sound_engine_sine(channel->accumulator, sound_engine) & 230 | sound_engine_noise(channel, prev_acc); 231 | } 232 | 233 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_SAW): { 234 | return sound_engine_saw(channel->accumulator) & 235 | sound_engine_triangle(channel->accumulator) & 236 | sound_engine_sine(channel->accumulator, sound_engine); 237 | } 238 | 239 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE): 240 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE_METAL): 241 | case( 242 | SE_WAVEFORM_SINE | SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_SAW | SE_WAVEFORM_NOISE | 243 | SE_WAVEFORM_NOISE_METAL): { 244 | return sound_engine_saw(channel->accumulator) & 245 | sound_engine_triangle(channel->accumulator) & 246 | sound_engine_sine(channel->accumulator, sound_engine) & 247 | sound_engine_noise(channel, prev_acc); 248 | } 249 | 250 | case(SE_WAVEFORM_SINE | SE_WAVEFORM_PULSE | SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_SAW): { 251 | return sound_engine_saw(channel->accumulator) & 252 | sound_engine_pulse(channel->accumulator, channel->pw) & 253 | sound_engine_triangle(channel->accumulator) & 254 | sound_engine_sine(channel->accumulator, sound_engine); 255 | } 256 | 257 | case( 258 | SE_WAVEFORM_SINE | SE_WAVEFORM_PULSE | SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_SAW | 259 | SE_WAVEFORM_NOISE): 260 | case( 261 | SE_WAVEFORM_SINE | SE_WAVEFORM_PULSE | SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_SAW | 262 | SE_WAVEFORM_NOISE_METAL): 263 | case( 264 | SE_WAVEFORM_SINE | SE_WAVEFORM_PULSE | SE_WAVEFORM_TRIANGLE | SE_WAVEFORM_SAW | 265 | SE_WAVEFORM_NOISE | SE_WAVEFORM_NOISE_METAL): { 266 | return sound_engine_saw(channel->accumulator) & 267 | sound_engine_pulse(channel->accumulator, channel->pw) & 268 | sound_engine_triangle(channel->accumulator) & 269 | sound_engine_sine(channel->accumulator, sound_engine) & 270 | sound_engine_noise(channel, prev_acc); 271 | } 272 | 273 | default: 274 | break; 275 | } 276 | 277 | return WAVE_AMP / 2; 278 | } -------------------------------------------------------------------------------- /tracker_engine/do_effects.c: -------------------------------------------------------------------------------- 1 | #include "do_effects.h" 2 | #include 3 | 4 | #include "../sound_engine/sound_engine.h" 5 | #include "../sound_engine/sound_engine_filter.h" 6 | #include "tracker_engine.h" 7 | 8 | #include "../flizzer_tracker_hal.h" 9 | 10 | void do_command( 11 | uint16_t opcode, 12 | TrackerEngine* tracker_engine, 13 | uint8_t channel, 14 | uint8_t tick, 15 | bool from_program) { 16 | UNUSED(from_program); 17 | 18 | TrackerEngineChannel* te_channel = &tracker_engine->channel[channel]; 19 | SoundEngineChannel* se_channel = &tracker_engine->sound_engine->channel[channel]; 20 | 21 | switch(opcode & 0x7f00) { 22 | case TE_EFFECT_ARPEGGIO: { 23 | if(tick == 0) { 24 | if(te_channel->fixed_note != 0xffff) { 25 | te_channel->note = te_channel->last_note; 26 | te_channel->fixed_note = 0xffff; 27 | } 28 | 29 | if((opcode & 0xff) == 0xf0) 30 | te_channel->arpeggio_note = te_channel->extarp1; 31 | else if((opcode & 0xff) == 0xf1) 32 | te_channel->arpeggio_note = te_channel->extarp2; 33 | else 34 | te_channel->arpeggio_note = (opcode & 0xff); 35 | } 36 | break; 37 | } 38 | 39 | case TE_EFFECT_PORTAMENTO_UP: { 40 | uint32_t prev = te_channel->note; 41 | 42 | te_channel->note += ((opcode & 0xff) << 2); 43 | if(prev > te_channel->note) te_channel->note = 0xffff; 44 | 45 | te_channel->target_note = te_channel->note; 46 | break; 47 | } 48 | 49 | case TE_EFFECT_PORTAMENTO_DOWN: { 50 | int32_t prev = te_channel->note; 51 | 52 | te_channel->note -= ((opcode & 0xff) << 2); 53 | if(prev < te_channel->note) te_channel->note = 0; 54 | 55 | te_channel->target_note = te_channel->note; 56 | break; 57 | } 58 | 59 | case TE_EFFECT_VIBRATO: { 60 | if(tick == 0) { 61 | if(opcode & 0xff) { 62 | te_channel->flags |= TE_ENABLE_VIBRATO; 63 | 64 | te_channel->vibrato_speed = (opcode & 0xf0); 65 | te_channel->vibrato_depth = ((opcode & 0x0f) << 4); 66 | } 67 | 68 | else { 69 | te_channel->flags &= ~(TE_ENABLE_VIBRATO); 70 | } 71 | } 72 | 73 | break; 74 | } 75 | 76 | case TE_EFFECT_PWM: { 77 | if(tick == 0) { 78 | if(opcode & 0xff) { 79 | te_channel->flags |= TE_ENABLE_PWM; 80 | 81 | te_channel->pwm_speed = (opcode & 0xf0); 82 | te_channel->pwm_depth = ((opcode & 0x0f) << 4); 83 | } 84 | 85 | else { 86 | te_channel->flags &= ~(TE_ENABLE_PWM); 87 | } 88 | } 89 | 90 | break; 91 | } 92 | 93 | case TE_EFFECT_SET_PW: { 94 | if(tick == 0) { 95 | te_channel->pw = ((opcode & 0xff) << 4); 96 | } 97 | 98 | break; 99 | } 100 | 101 | case TE_EFFECT_PW_UP: { 102 | int16_t temp_pw = te_channel->pw + (int16_t)(opcode & 0xff); 103 | 104 | if(temp_pw < 0) temp_pw = 0; 105 | if(temp_pw > 0xfff) temp_pw = 0xfff; 106 | 107 | te_channel->pw = temp_pw; 108 | 109 | break; 110 | } 111 | 112 | case TE_EFFECT_PW_DOWN: { 113 | int16_t temp_pw = te_channel->pw - (int16_t)(opcode & 0xff); 114 | 115 | if(temp_pw < 0) temp_pw = 0; 116 | if(temp_pw > 0xfff) temp_pw = 0xfff; 117 | 118 | te_channel->pw = temp_pw; 119 | 120 | break; 121 | } 122 | 123 | case TE_EFFECT_SET_CUTOFF: { 124 | if(tick == 0) { 125 | te_channel->filter_cutoff = ((opcode & 0xff) << 3); 126 | sound_engine_filter_set_coeff( 127 | &se_channel->filter, te_channel->filter_cutoff, te_channel->filter_resonance); 128 | } 129 | 130 | break; 131 | } 132 | 133 | case TE_EFFECT_VOLUME_FADE: { 134 | if(!(te_channel->channel_flags & TEC_DISABLED)) { 135 | te_channel->volume -= (opcode & 0xf); 136 | if(te_channel->volume > MAX_ADSR_VOLUME) te_channel->volume = 0; 137 | te_channel->volume += ((opcode >> 4) & 0xf); 138 | if(te_channel->volume > MAX_ADSR_VOLUME) te_channel->volume = MAX_ADSR_VOLUME; 139 | 140 | se_channel->adsr.volume = (int32_t)te_channel->volume; 141 | se_channel->adsr.volume = (int32_t)se_channel->adsr.volume * 142 | (int32_t)tracker_engine->master_volume / MAX_ADSR_VOLUME; 143 | } 144 | 145 | break; 146 | } 147 | 148 | case TE_EFFECT_SET_WAVEFORM: { 149 | if(tick == 0) { 150 | se_channel->waveform = (opcode & 0x3f); 151 | } 152 | 153 | break; 154 | } 155 | 156 | case TE_EFFECT_SET_VOLUME: { 157 | if(tick == 0) { 158 | if(!(te_channel->channel_flags & TEC_DISABLED)) { 159 | te_channel->volume = opcode & 0xff; 160 | 161 | se_channel->adsr.volume = (int32_t)te_channel->volume; 162 | se_channel->adsr.volume = (int32_t)se_channel->adsr.volume * 163 | (int32_t)tracker_engine->master_volume / MAX_ADSR_VOLUME; 164 | } 165 | } 166 | 167 | break; 168 | } 169 | 170 | case TE_EFFECT_EXT: { 171 | switch(opcode & 0x7ff0) { 172 | case TE_EFFECT_EXT_TOGGLE_FILTER: { 173 | if(tick == 0) { 174 | if(opcode & 0xf) { 175 | se_channel->flags |= SE_ENABLE_FILTER; 176 | } 177 | 178 | else { 179 | se_channel->flags &= ~SE_ENABLE_FILTER; 180 | } 181 | } 182 | 183 | break; 184 | } 185 | 186 | case TE_EFFECT_EXT_PORTA_DN: { 187 | if(tick == 0) { 188 | int32_t prev = te_channel->note; 189 | 190 | te_channel->note -= (opcode & 0xf); 191 | if(prev < te_channel->note) te_channel->note = 0; 192 | 193 | te_channel->target_note = te_channel->note; 194 | } 195 | 196 | break; 197 | } 198 | 199 | case TE_EFFECT_EXT_PORTA_UP: { 200 | if(tick == 0) { 201 | uint32_t prev = te_channel->note; 202 | 203 | te_channel->note += (opcode & 0xf); 204 | if(prev > te_channel->note) te_channel->note = 0xffff; 205 | 206 | te_channel->target_note = te_channel->note; 207 | } 208 | 209 | break; 210 | } 211 | 212 | case TE_EFFECT_EXT_FILTER_MODE: { 213 | if(tick == 0) { 214 | se_channel->filter_mode = (opcode & 0xf); 215 | } 216 | 217 | break; 218 | } 219 | 220 | case TE_EFFECT_EXT_RETRIGGER: { 221 | if((opcode & 0xf) > 0 && (tick % (opcode & 0xf)) == 0) { 222 | uint8_t prev_vol_tr = te_channel->volume; 223 | uint8_t prev_vol_cyd = se_channel->adsr.volume; 224 | tracker_engine_trigger_instrument_internal( 225 | tracker_engine, channel, te_channel->instrument, te_channel->last_note); 226 | te_channel->volume = prev_vol_tr; 227 | se_channel->adsr.volume = prev_vol_cyd; 228 | } 229 | 230 | break; 231 | } 232 | 233 | case TE_EFFECT_EXT_FINE_VOLUME_DOWN: { 234 | if(tick == 0) { 235 | te_channel->volume -= (opcode & 0xf); 236 | 237 | if(te_channel->volume > MAX_ADSR_VOLUME) te_channel->volume = 0; 238 | 239 | se_channel->adsr.volume = (int32_t)te_channel->volume; 240 | se_channel->adsr.volume = (int32_t)se_channel->adsr.volume * 241 | (int32_t)tracker_engine->master_volume / MAX_ADSR_VOLUME; 242 | } 243 | 244 | break; 245 | } 246 | 247 | case TE_EFFECT_EXT_FINE_VOLUME_UP: { 248 | if(tick == 0) { 249 | te_channel->volume += (opcode & 0xf); 250 | 251 | if(te_channel->volume > MAX_ADSR_VOLUME) te_channel->volume = MAX_ADSR_VOLUME; 252 | 253 | se_channel->adsr.volume = (int32_t)te_channel->volume; 254 | se_channel->adsr.volume = (int32_t)se_channel->adsr.volume * 255 | (int32_t)tracker_engine->master_volume / MAX_ADSR_VOLUME; 256 | } 257 | 258 | break; 259 | } 260 | 261 | case TE_EFFECT_EXT_NOTE_CUT: { 262 | if((opcode & 0xf) <= tick) { 263 | se_channel->adsr.volume = 0; 264 | te_channel->volume = 0; 265 | } 266 | 267 | break; 268 | } 269 | 270 | case TE_EFFECT_EXT_PHASE_RESET: { 271 | if(tick == (opcode & 0xf)) { 272 | se_channel->accumulator = 0; 273 | se_channel->lfsr = RANDOM_SEED; 274 | } 275 | 276 | break; 277 | } 278 | } 279 | 280 | break; 281 | } 282 | 283 | case TE_EFFECT_SET_SPEED_PROG_PERIOD: { 284 | if(tick == 0) { 285 | if(from_program) { 286 | te_channel->program_period = opcode & 0xff; 287 | } 288 | 289 | else { 290 | tracker_engine->song->speed = opcode & 0xff; 291 | } 292 | } 293 | 294 | break; 295 | } 296 | 297 | case TE_EFFECT_CUTOFF_UP: { 298 | te_channel->filter_cutoff += (opcode & 0xff); 299 | 300 | if(te_channel->filter_cutoff > 0x7ff) { 301 | te_channel->filter_cutoff = 0x7ff; 302 | } 303 | 304 | sound_engine_filter_set_coeff( 305 | &se_channel->filter, te_channel->filter_cutoff, te_channel->filter_resonance); 306 | 307 | break; 308 | } 309 | 310 | case TE_EFFECT_CUTOFF_DOWN: { 311 | te_channel->filter_cutoff -= (opcode & 0xff); 312 | 313 | if(te_channel->filter_cutoff > 0x7ff) // unsigned int overflow 314 | { 315 | te_channel->filter_cutoff = 0; 316 | } 317 | 318 | sound_engine_filter_set_coeff( 319 | &se_channel->filter, te_channel->filter_cutoff, te_channel->filter_resonance); 320 | 321 | break; 322 | } 323 | 324 | case TE_EFFECT_SET_RESONANCE: { 325 | if(tick == 0) { 326 | te_channel->filter_resonance = (opcode & 0xff); 327 | sound_engine_filter_set_coeff( 328 | &se_channel->filter, te_channel->filter_cutoff, te_channel->filter_resonance); 329 | } 330 | 331 | break; 332 | } 333 | 334 | case TE_EFFECT_RESONANCE_UP: { 335 | te_channel->filter_resonance += (opcode & 0xff); 336 | 337 | if(te_channel->filter_resonance > 0xff) { 338 | te_channel->filter_resonance = 0xff; 339 | } 340 | 341 | sound_engine_filter_set_coeff( 342 | &se_channel->filter, te_channel->filter_cutoff, te_channel->filter_resonance); 343 | break; 344 | } 345 | 346 | case TE_EFFECT_RESONANCE_DOWN: { 347 | te_channel->filter_resonance -= (opcode & 0xff); 348 | 349 | if(te_channel->filter_resonance > 0xff) { 350 | te_channel->filter_resonance = 0; 351 | } 352 | 353 | sound_engine_filter_set_coeff( 354 | &se_channel->filter, te_channel->filter_cutoff, te_channel->filter_resonance); 355 | break; 356 | } 357 | 358 | case TE_EFFECT_SET_RING_MOD_SRC: { 359 | if(tick == 0) { 360 | se_channel->ring_mod = (opcode & 0xff); 361 | } 362 | 363 | break; 364 | } 365 | 366 | case TE_EFFECT_SET_HARD_SYNC_SRC: { 367 | if(tick == 0) { 368 | se_channel->hard_sync = (opcode & 0xff); 369 | } 370 | 371 | break; 372 | } 373 | 374 | case TE_EFFECT_SET_ATTACK: { 375 | if(tick == 0) { 376 | se_channel->adsr.a = (opcode & 0xff); 377 | 378 | if(se_channel->adsr.envelope_state == ATTACK) { 379 | se_channel->adsr.envelope_speed = 380 | envspd(tracker_engine->sound_engine, se_channel->adsr.a); 381 | } 382 | } 383 | 384 | break; 385 | } 386 | 387 | case TE_EFFECT_SET_DECAY: { 388 | if(tick == 0) { 389 | se_channel->adsr.d = (opcode & 0xff); 390 | 391 | if(se_channel->adsr.envelope_state == DECAY) { 392 | se_channel->adsr.envelope_speed = 393 | envspd(tracker_engine->sound_engine, se_channel->adsr.d); 394 | } 395 | } 396 | 397 | break; 398 | } 399 | 400 | case TE_EFFECT_SET_SUSTAIN: { 401 | if(tick == 0) { 402 | se_channel->adsr.s = (opcode & 0xff); 403 | } 404 | 405 | break; 406 | } 407 | 408 | case TE_EFFECT_SET_RELEASE: { 409 | if(tick == 0) { 410 | se_channel->adsr.r = (opcode & 0xff); 411 | 412 | if(se_channel->adsr.envelope_state == RELEASE) { 413 | se_channel->adsr.envelope_speed = 414 | envspd(tracker_engine->sound_engine, se_channel->adsr.r); 415 | } 416 | } 417 | 418 | break; 419 | } 420 | 421 | case TE_EFFECT_PROGRAM_RESTART: { 422 | if(tick == 0) { 423 | te_channel->program_counter = 0; 424 | te_channel->program_loop = 0; 425 | te_channel->program_period = 0; 426 | te_channel->program_tick = 0; 427 | } 428 | 429 | break; 430 | } 431 | 432 | case TE_EFFECT_SET_RATE: { 433 | if(tick == 0 && (opcode & 0xff) > 0) { 434 | tracker_engine_set_rate(opcode & 0xff); 435 | } 436 | 437 | break; 438 | } 439 | 440 | case TE_EFFECT_PORTA_UP_SEMITONE: { 441 | uint32_t prev = te_channel->note; 442 | 443 | te_channel->note += ((opcode & 0xff) << 8); 444 | if(prev > te_channel->note) te_channel->note = 0xffff; 445 | 446 | te_channel->target_note = te_channel->note; 447 | break; 448 | } 449 | 450 | case TE_EFFECT_PORTA_DOWN_SEMITONE: { 451 | int32_t prev = te_channel->note; 452 | 453 | te_channel->note -= ((opcode & 0xff) << 8); 454 | if(prev < te_channel->note) te_channel->note = 0; 455 | 456 | te_channel->target_note = te_channel->note; 457 | break; 458 | } 459 | 460 | case TE_EFFECT_PITCH: { 461 | te_channel->finetune_note = ((int16_t)(opcode & 0xff) - 0x80) * 2; 462 | break; 463 | } 464 | 465 | case TE_EFFECT_ARPEGGIO_ABS: { 466 | te_channel->arpeggio_note = 0; 467 | te_channel->fixed_note = ((opcode & 0xff) << 8); 468 | 469 | break; 470 | } 471 | 472 | case TE_EFFECT_TRIGGER_RELEASE: { 473 | if(tick == (opcode & 0xff)) 474 | { 475 | sound_engine_enable_gate(tracker_engine->sound_engine, se_channel, 0); 476 | } 477 | 478 | break; 479 | } 480 | 481 | default: 482 | break; 483 | } 484 | } -------------------------------------------------------------------------------- /input/instrument.c: -------------------------------------------------------------------------------- 1 | #include "instrument.h" 2 | #include "songinfo.h" 3 | 4 | void edit_instrument_param(FlizzerTrackerApp* tracker, uint8_t selected_param, int8_t delta) { 5 | if(!(tracker->current_digit)) { 6 | delta *= 16; 7 | } 8 | 9 | Instrument* inst = tracker->song.instrument[tracker->current_instrument]; 10 | 11 | switch(selected_param) { 12 | case INST_CURRENTINSTRUMENT: { 13 | int16_t inst = tracker->current_instrument; 14 | 15 | int8_t inst_delta = delta > 0 ? 1 : -1; 16 | 17 | inst += inst_delta; 18 | 19 | clamp(inst, 0, 0, tracker->song.num_instruments); 20 | 21 | if(check_and_allocate_instrument(&tracker->song, (uint8_t)inst)) { 22 | tracker->current_instrument = inst; 23 | } 24 | 25 | break; 26 | } 27 | 28 | case INST_INSTRUMENTNAME: { 29 | text_input_set_header_text(tracker->text_input, "Instrument name:"); 30 | text_input_set_result_callback( 31 | tracker->text_input, 32 | return_from_keyboard_callback, 33 | tracker, 34 | (char*)&inst->name, 35 | MUS_INST_NAME_LEN + 1, 36 | false); 37 | 38 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_KEYBOARD); 39 | break; 40 | } 41 | 42 | case INST_CURRENT_NOTE: { 43 | int8_t note_delta = 0; 44 | 45 | if(delta < 0) { 46 | if(tracker->current_digit) { 47 | note_delta = -12; 48 | } 49 | 50 | else { 51 | note_delta = -1; 52 | } 53 | } 54 | 55 | if(delta > 0) { 56 | if(tracker->current_digit) { 57 | note_delta = 12; 58 | } 59 | 60 | else { 61 | note_delta = 1; 62 | } 63 | } 64 | 65 | clamp(inst->base_note, note_delta, 0, MAX_NOTE); 66 | 67 | break; 68 | } 69 | 70 | case INST_FINETUNE: { 71 | int8_t fine_delta = 0; 72 | 73 | if(delta < 0) { 74 | if(tracker->current_digit) { 75 | fine_delta = -1; 76 | } 77 | 78 | else { 79 | fine_delta = -10; 80 | } 81 | } 82 | 83 | if(delta > 0) { 84 | if(tracker->current_digit) { 85 | fine_delta = 1; 86 | } 87 | 88 | else { 89 | fine_delta = 10; 90 | } 91 | } 92 | 93 | inst->finetune += fine_delta; 94 | 95 | break; 96 | } 97 | 98 | case INST_SLIDESPEED: { 99 | if((int16_t)inst->slide_speed + (int16_t)delta >= 0 && 100 | (int16_t)inst->slide_speed + (int16_t)delta <= 0xff) { 101 | inst->slide_speed += delta; 102 | } 103 | 104 | break; 105 | } 106 | 107 | case INST_SETPW: { 108 | flipbit(inst->flags, TE_SET_PW); 109 | break; 110 | } 111 | 112 | case INST_PW: { 113 | if((int16_t)inst->pw + (int16_t)delta >= 0 && (int16_t)inst->pw + (int16_t)delta <= 0xff) { 114 | inst->pw += delta; 115 | } 116 | 117 | break; 118 | } 119 | 120 | case INST_SETCUTOFF: { 121 | flipbit(inst->flags, TE_SET_CUTOFF); 122 | break; 123 | } 124 | 125 | case INST_WAVE_NOISE: { 126 | flipbit(inst->waveform, SE_WAVEFORM_NOISE); 127 | break; 128 | } 129 | 130 | case INST_WAVE_PULSE: { 131 | flipbit(inst->waveform, SE_WAVEFORM_PULSE); 132 | break; 133 | } 134 | 135 | case INST_WAVE_TRIANGLE: { 136 | flipbit(inst->waveform, SE_WAVEFORM_TRIANGLE); 137 | break; 138 | } 139 | 140 | case INST_WAVE_SAWTOOTH: { 141 | flipbit(inst->waveform, SE_WAVEFORM_SAW); 142 | break; 143 | } 144 | 145 | case INST_WAVE_NOISE_METAL: { 146 | flipbit(inst->waveform, SE_WAVEFORM_NOISE_METAL); 147 | break; 148 | } 149 | 150 | case INST_WAVE_SINE: { 151 | flipbit(inst->waveform, SE_WAVEFORM_SINE); 152 | break; 153 | } 154 | 155 | case INST_ATTACK: { 156 | if((int16_t)inst->adsr.a + (int16_t)delta >= 0 && 157 | (int16_t)inst->adsr.a + (int16_t)delta <= 0xff) { 158 | inst->adsr.a += delta; 159 | } 160 | 161 | break; 162 | } 163 | 164 | case INST_DECAY: { 165 | if((int16_t)inst->adsr.d + (int16_t)delta >= 0 && 166 | (int16_t)inst->adsr.d + (int16_t)delta <= 0xff) { 167 | inst->adsr.d += delta; 168 | } 169 | 170 | break; 171 | } 172 | 173 | case INST_SUSTAIN: { 174 | if((int16_t)inst->adsr.s + (int16_t)delta >= 0 && 175 | (int16_t)inst->adsr.s + (int16_t)delta <= 0xff) { 176 | inst->adsr.s += delta; 177 | } 178 | 179 | break; 180 | } 181 | 182 | case INST_RELEASE: { 183 | if((int16_t)inst->adsr.r + (int16_t)delta >= 0 && 184 | (int16_t)inst->adsr.r + (int16_t)delta <= 0xff) { 185 | inst->adsr.r += delta; 186 | } 187 | 188 | break; 189 | } 190 | 191 | case INST_VOLUME: { 192 | if((int16_t)inst->adsr.volume + (int16_t)delta >= 0 && 193 | (int16_t)inst->adsr.volume + (int16_t)delta <= 0xff) { 194 | inst->adsr.volume += delta; 195 | } 196 | 197 | break; 198 | } 199 | 200 | case INST_ENABLEFILTER: { 201 | flipbit(inst->sound_engine_flags, SE_ENABLE_FILTER); 202 | break; 203 | } 204 | 205 | case INST_FILTERCUTOFF: { 206 | if((int16_t)inst->filter_cutoff + (int16_t)delta >= 0 && 207 | (int16_t)inst->filter_cutoff + (int16_t)delta <= 0xff) { 208 | inst->filter_cutoff += delta; 209 | } 210 | 211 | break; 212 | } 213 | 214 | case INST_FILTERRESONANCE: { 215 | if((int16_t)inst->filter_resonance + (int16_t)delta >= 0 && 216 | (int16_t)inst->filter_resonance + (int16_t)delta <= 0xff) { 217 | inst->filter_resonance += delta; 218 | } 219 | 220 | break; 221 | } 222 | 223 | case INST_FILTERTYPE: { 224 | int8_t flt_delta = (delta > 0 ? 1 : -1); 225 | 226 | if((int16_t)inst->filter_type + (int16_t)flt_delta >= 0 && 227 | (int16_t)inst->filter_type + (int16_t)flt_delta < FIL_MODES) { 228 | inst->filter_type += flt_delta; 229 | } 230 | 231 | break; 232 | } 233 | 234 | case INST_ENABLERINGMOD: { 235 | flipbit(inst->sound_engine_flags, SE_ENABLE_RING_MOD); 236 | break; 237 | } 238 | 239 | case INST_RINGMODSRC: { 240 | if((int16_t)inst->ring_mod + (int16_t)delta >= 0 && 241 | (int16_t)inst->ring_mod + (int16_t)delta < SONG_MAX_CHANNELS) { 242 | inst->ring_mod += delta; 243 | } 244 | 245 | if((int16_t)inst->ring_mod + (int16_t)delta < 0) { 246 | inst->ring_mod = 0xff; // 0xff = self 247 | } 248 | 249 | if((int16_t)inst->ring_mod == 0xff && (int16_t)delta > 0) { 250 | inst->ring_mod = 0; 251 | } 252 | 253 | break; 254 | } 255 | 256 | case INST_ENABLEHARDSYNC: { 257 | flipbit(inst->sound_engine_flags, SE_ENABLE_HARD_SYNC); 258 | break; 259 | } 260 | 261 | case INST_HARDSYNCSRC: { 262 | if((int16_t)inst->hard_sync + (int16_t)delta >= 0 && 263 | (int16_t)inst->hard_sync + (int16_t)delta < SONG_MAX_CHANNELS) { 264 | inst->hard_sync += delta; 265 | } 266 | 267 | if((int16_t)inst->hard_sync + (int16_t)delta < 0) { 268 | inst->hard_sync = 0xff; // 0xff = self 269 | } 270 | 271 | if((int16_t)inst->hard_sync == 0xff && (int16_t)delta > 0) { 272 | inst->hard_sync = 0; 273 | } 274 | 275 | break; 276 | } 277 | 278 | case INST_RETRIGGERONSLIDE: { 279 | flipbit(inst->flags, TE_RETRIGGER_ON_SLIDE); 280 | break; 281 | } 282 | 283 | case INST_ENABLEKEYSYNC: { 284 | flipbit(inst->sound_engine_flags, SE_ENABLE_KEYDOWN_SYNC); 285 | break; 286 | } 287 | 288 | case INST_ENABLEVIBRATO: { 289 | flipbit(inst->flags, TE_ENABLE_VIBRATO); 290 | break; 291 | } 292 | 293 | case INST_VIBRATOSPEED: { 294 | if((int16_t)inst->vibrato_speed + (int16_t)delta >= 0 && 295 | (int16_t)inst->vibrato_speed + (int16_t)delta <= 0xff) { 296 | inst->vibrato_speed += delta; 297 | } 298 | 299 | break; 300 | } 301 | 302 | case INST_VIBRATODEPTH: { 303 | if((int16_t)inst->vibrato_depth + (int16_t)delta >= 0 && 304 | (int16_t)inst->vibrato_depth + (int16_t)delta <= 0xff) { 305 | inst->vibrato_depth += delta; 306 | } 307 | 308 | break; 309 | } 310 | 311 | case INST_VIBRATODELAY: { 312 | if((int16_t)inst->vibrato_delay + (int16_t)delta >= 0 && 313 | (int16_t)inst->vibrato_delay + (int16_t)delta <= 0xff) { 314 | inst->vibrato_delay += delta; 315 | } 316 | 317 | break; 318 | } 319 | 320 | case INST_ENABLEPWM: { 321 | flipbit(inst->flags, TE_ENABLE_PWM); 322 | break; 323 | } 324 | 325 | case INST_PWMSPEED: { 326 | if((int16_t)inst->pwm_speed + (int16_t)delta >= 0 && 327 | (int16_t)inst->pwm_speed + (int16_t)delta <= 0xff) { 328 | inst->pwm_speed += delta; 329 | } 330 | 331 | break; 332 | } 333 | 334 | case INST_PWMDEPTH: { 335 | if((int16_t)inst->pwm_depth + (int16_t)delta >= 0 && 336 | (int16_t)inst->pwm_depth + (int16_t)delta <= 0xff) { 337 | inst->pwm_depth += delta; 338 | } 339 | 340 | break; 341 | } 342 | 343 | case INST_PWMDELAY: { 344 | if((int16_t)inst->pwm_delay + (int16_t)delta >= 0 && 345 | (int16_t)inst->pwm_delay + (int16_t)delta <= 0xff) { 346 | inst->pwm_delay += delta; 347 | } 348 | 349 | break; 350 | } 351 | 352 | case INST_PROGRESTART: { 353 | flipbit(inst->flags, TE_PROG_NO_RESTART); 354 | break; 355 | } 356 | 357 | case INST_PROGRAMEPERIOD: { 358 | if((int16_t)inst->program_period + (int16_t)delta >= 0 && 359 | (int16_t)inst->program_period + (int16_t)delta <= 0xff) { 360 | inst->program_period += delta; 361 | } 362 | 363 | break; 364 | } 365 | } 366 | } 367 | 368 | void instrument_edit_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* event) { 369 | if(event->input.key == InputKeyOk && event->input.type == InputTypeShort && 370 | !tracker->tracker_engine.playing) { 371 | tracker->editing = !(tracker->editing); 372 | return; 373 | } 374 | 375 | if(event->input.key == InputKeyOk && event->input.type == InputTypeLong && !tracker->editing) { 376 | reset_buffer(&tracker->sound_engine); 377 | tracker_engine_set_song(&tracker->tracker_engine, NULL); 378 | 379 | for(int i = 1; i < SONG_MAX_CHANNELS; i++) { 380 | tracker->tracker_engine.channel[i].channel_flags &= TEC_PLAYING; 381 | tracker->tracker_engine.sound_engine->channel[i].frequency = 0; 382 | tracker->tracker_engine.sound_engine->channel[i].waveform = 0; 383 | } 384 | 385 | Instrument* inst = tracker->song.instrument[tracker->current_instrument]; 386 | tracker_engine_trigger_instrument_internal( 387 | &tracker->tracker_engine, 0, inst, (MIDDLE_C << 8)); 388 | tracker->tracker_engine.playing = true; 389 | play(); 390 | return; 391 | } 392 | 393 | if(event->input.key == InputKeyOk && event->input.type == InputTypeRelease && 394 | !tracker->editing) { 395 | SoundEngineChannel* se_channel = &tracker->sound_engine.channel[0]; 396 | sound_engine_enable_gate(&tracker->sound_engine, se_channel, false); 397 | return; 398 | } 399 | 400 | if(event->input.key == InputKeyRight && event->input.type == InputTypeShort) { 401 | switch(tracker->selected_param) { 402 | default: { 403 | tracker->current_digit++; 404 | 405 | if(tracker->current_digit > 1) { 406 | tracker->selected_param++; 407 | 408 | tracker->current_digit = 0; 409 | 410 | if(tracker->selected_param > INST_PARAMS - 1) { 411 | tracker->selected_param = 0; 412 | } 413 | } 414 | 415 | break; 416 | } 417 | 418 | case INST_CURRENTINSTRUMENT: 419 | case INST_INSTRUMENTNAME: 420 | case INST_SETPW: 421 | case INST_SETCUTOFF: 422 | case INST_WAVE_NOISE: 423 | case INST_WAVE_PULSE: 424 | case INST_WAVE_TRIANGLE: 425 | case INST_WAVE_SAWTOOTH: 426 | case INST_WAVE_NOISE_METAL: 427 | case INST_WAVE_SINE: 428 | case INST_ENABLEFILTER: 429 | case INST_FILTERTYPE: 430 | case INST_ENABLERINGMOD: 431 | case INST_RINGMODSRC: 432 | case INST_ENABLEHARDSYNC: 433 | case INST_HARDSYNCSRC: 434 | case INST_RETRIGGERONSLIDE: 435 | case INST_ENABLEKEYSYNC: 436 | case INST_ENABLEVIBRATO: 437 | case INST_ENABLEPWM: 438 | case INST_PROGRESTART: { 439 | tracker->selected_param++; 440 | 441 | tracker->current_digit = 1; 442 | 443 | if(tracker->selected_param > INST_PARAMS - 1) { 444 | tracker->selected_param = 0; 445 | } 446 | 447 | break; 448 | } 449 | } 450 | } 451 | 452 | if(event->input.key == InputKeyLeft && event->input.type == InputTypeShort) { 453 | switch(tracker->selected_param) { 454 | default: { 455 | tracker->current_digit--; 456 | 457 | if(tracker->current_digit > 1) // unsigned int overflow 458 | { 459 | tracker->selected_param--; 460 | 461 | tracker->current_digit = 1; 462 | 463 | if(tracker->selected_param > INST_PARAMS - 1) // unsigned int overflow 464 | { 465 | tracker->selected_param = INST_PARAMS - 1; 466 | } 467 | } 468 | 469 | break; 470 | } 471 | 472 | case INST_CURRENTINSTRUMENT: 473 | case INST_INSTRUMENTNAME: 474 | case INST_SETPW: 475 | case INST_SETCUTOFF: 476 | case INST_WAVE_NOISE: 477 | case INST_WAVE_PULSE: 478 | case INST_WAVE_TRIANGLE: 479 | case INST_WAVE_SAWTOOTH: 480 | case INST_WAVE_NOISE_METAL: 481 | case INST_WAVE_SINE: 482 | case INST_ENABLEFILTER: 483 | case INST_FILTERTYPE: 484 | case INST_ENABLERINGMOD: 485 | case INST_RINGMODSRC: 486 | case INST_ENABLEHARDSYNC: 487 | case INST_HARDSYNCSRC: 488 | case INST_RETRIGGERONSLIDE: 489 | case INST_ENABLEKEYSYNC: 490 | case INST_ENABLEVIBRATO: 491 | case INST_ENABLEPWM: 492 | case INST_PROGRESTART: { 493 | tracker->selected_param--; 494 | 495 | tracker->current_digit = 1; 496 | 497 | if(tracker->selected_param > INST_PARAMS - 1) // unsigned int overflow 498 | { 499 | tracker->selected_param = INST_PARAMS - 1; 500 | } 501 | 502 | break; 503 | } 504 | } 505 | 506 | return; 507 | } 508 | 509 | if(event->input.key == InputKeyDown && event->input.type == InputTypeShort) { 510 | if(tracker->editing) { 511 | edit_instrument_param(tracker, tracker->selected_param, -1); 512 | } 513 | 514 | return; 515 | } 516 | 517 | if(event->input.key == InputKeyUp && event->input.type == InputTypeShort) { 518 | if(tracker->editing) { 519 | edit_instrument_param(tracker, tracker->selected_param, 1); 520 | } 521 | 522 | return; 523 | } 524 | 525 | if(tracker->selected_param > INST_VIBRATODELAY) { 526 | tracker->inst_editor_shift = 6; 527 | } 528 | 529 | if(tracker->selected_param > INST_PWMDELAY) { 530 | tracker->inst_editor_shift = 12; 531 | } 532 | 533 | if(tracker->selected_param < INST_CURRENT_NOTE) { 534 | tracker->inst_editor_shift = 0; 535 | } 536 | } -------------------------------------------------------------------------------- /view/pattern_editor.c: -------------------------------------------------------------------------------- 1 | #include "pattern_editor.h" 2 | #include "../macros.h" 3 | 4 | #include 5 | 6 | #define PATTERN_EDITOR_Y ((tracker->focus == EDIT_PATTERN) ? 4 : (64 - (6 * 5) - 1)) 7 | 8 | static const char* notenames[] = { 9 | "C-", 10 | "C#", 11 | "D-", 12 | "D#", 13 | "E-", 14 | "F-", 15 | "F#", 16 | "G-", 17 | "G#", 18 | "A-", 19 | "A#", 20 | "B-", 21 | }; 22 | 23 | char* notename(uint8_t note) { 24 | static char buffer[6]; 25 | 26 | if(note == MUS_NOTE_CUT) { 27 | snprintf(buffer, sizeof(buffer), "%s", "OFF"); 28 | return buffer; 29 | } 30 | 31 | if(note == MUS_NOTE_RELEASE) { 32 | snprintf(buffer, sizeof(buffer), "%s", " "); 33 | return buffer; 34 | } 35 | 36 | if(note == 0xf0) // external arpeggio notes 37 | { 38 | snprintf(buffer, sizeof(buffer), "%s", "EXT.0"); 39 | return buffer; 40 | } 41 | 42 | if(note == 0xf1) { 43 | snprintf(buffer, sizeof(buffer), "%s", "EXT.1"); 44 | return buffer; 45 | } 46 | 47 | else { 48 | uint8_t final_note = my_min(12 * 7 + 11, note); 49 | snprintf(buffer, sizeof(buffer), "%s%d", notenames[final_note % 12], final_note / 12); 50 | } 51 | 52 | return buffer; 53 | } 54 | 55 | char to_char(uint8_t number) { 56 | return to_char_array[number]; 57 | } 58 | 59 | void draw_pattern_view(Canvas* canvas, FlizzerTrackerApp* tracker) { 60 | char command_buffer[6] = {0}; 61 | char buffer[11] = {0}; 62 | 63 | canvas_draw_line(canvas, 0, PATTERN_EDITOR_Y, 127, PATTERN_EDITOR_Y); 64 | 65 | for(int i = 0; i < SONG_MAX_CHANNELS; ++i) { 66 | uint8_t sequence_position = tracker->tracker_engine.sequence_position; 67 | uint8_t current_pattern = 68 | tracker->tracker_engine.song->sequence.sequence_step[sequence_position] 69 | .pattern_indices[i]; 70 | uint16_t pattern_step = tracker->tracker_engine.pattern_position; 71 | 72 | uint16_t pattern_length = tracker->tracker_engine.song->pattern_length; 73 | 74 | TrackerSongPattern* pattern = &tracker->tracker_engine.song->pattern[current_pattern]; 75 | 76 | for(uint8_t pos = 0; pos < ((tracker->focus == EDIT_PATTERN) ? 9 : 5); ++pos) { 77 | TrackerSongPatternStep* step = NULL; 78 | 79 | if(pattern_step - ((tracker->focus == EDIT_PATTERN) ? 4 : 2) + pos >= 0 && 80 | pattern_step - ((tracker->focus == EDIT_PATTERN) ? 4 : 2) + pos < pattern_length) { 81 | step = 82 | &pattern->step[pattern_step + pos - ((tracker->focus == EDIT_PATTERN) ? 4 : 2)]; 83 | } 84 | 85 | uint8_t string_x = i * 32; 86 | uint8_t string_y = 87 | PATTERN_EDITOR_Y + 6 * pos + 6 + ((tracker->focus == EDIT_PATTERN) ? 3 : 1); 88 | 89 | if(step) { 90 | uint8_t note = tracker_engine_get_note(step); 91 | uint8_t inst = tracker_engine_get_instrument(step); 92 | uint8_t vol = tracker_engine_get_volume(step); 93 | uint16_t command = tracker_engine_get_command(step); 94 | 95 | char inst_ch = to_char(inst); 96 | char vol_ch = to_char(vol); 97 | char command_ch = to_char(command >> 8); 98 | 99 | if(inst == MUS_NOTE_INSTRUMENT_NONE) { 100 | inst_ch = '-'; 101 | } 102 | 103 | if(vol == MUS_NOTE_VOLUME_NONE) { 104 | vol_ch = '-'; 105 | } 106 | 107 | if(command == 0) { 108 | snprintf(command_buffer, sizeof(command_buffer), "---"); 109 | } 110 | 111 | else { 112 | snprintf( 113 | command_buffer, 114 | sizeof(command_buffer), 115 | "%c%02X", 116 | command_ch, 117 | (command & 0xff)); 118 | } 119 | 120 | snprintf( 121 | buffer, 122 | sizeof(buffer), 123 | "%s%c%c%s", 124 | (note == MUS_NOTE_NONE ? "---" : notename(note)), 125 | inst_ch, 126 | vol_ch, 127 | command_buffer); 128 | 129 | canvas_draw_str(canvas, string_x, string_y, buffer); 130 | 131 | if(note == MUS_NOTE_RELEASE) { 132 | canvas_draw_icon(canvas, string_x, string_y - 5, &I_note_release); 133 | } 134 | } 135 | } 136 | } 137 | 138 | if(tracker->editing && tracker->focus == EDIT_PATTERN) { 139 | uint16_t x = tracker->current_channel * 32 + tracker->patternx * 4 + 140 | (tracker->patternx > 0 ? 4 : 0) - 1; 141 | uint16_t y = PATTERN_EDITOR_Y + 6 * ((tracker->focus == EDIT_PATTERN) ? 4 : 2) + 142 | ((tracker->focus == EDIT_PATTERN) ? 3 : 1); 143 | 144 | canvas_draw_box(canvas, x, y, (tracker->patternx > 0 ? 5 : 9), 7); 145 | } 146 | 147 | if(!(tracker->editing) && tracker->focus == EDIT_PATTERN) { 148 | uint16_t x = tracker->current_channel * 32 + tracker->patternx * 4 + 149 | (tracker->patternx > 0 ? 4 : 0) - 1; 150 | uint16_t y = PATTERN_EDITOR_Y + 6 * ((tracker->focus == EDIT_PATTERN) ? 4 : 2) + 151 | ((tracker->focus == EDIT_PATTERN) ? 3 : 1); 152 | 153 | canvas_draw_frame(canvas, x, y, (tracker->patternx > 0 ? 5 : 9), 7); 154 | } 155 | 156 | canvas_set_color(canvas, ColorBlack); 157 | 158 | for(int i = 1; i < SONG_MAX_CHANNELS; ++i) { 159 | for(int y = PATTERN_EDITOR_Y + 1; y < 64; y += 2) { 160 | canvas_draw_dot(canvas, i * 32 - 1, y); 161 | } 162 | } 163 | 164 | for(int i = 0; i < SONG_MAX_CHANNELS; ++i) { 165 | if(tracker->tracker_engine.channel[i].channel_flags & TEC_DISABLED) { 166 | canvas_draw_icon(canvas, 13 + 32 * i, PATTERN_EDITOR_Y - 3, &I_channel_off); 167 | } 168 | 169 | else { 170 | canvas_draw_icon(canvas, 13 + 32 * i, PATTERN_EDITOR_Y - 3, &I_channel_on); 171 | } 172 | } 173 | 174 | canvas_set_color(canvas, ColorXOR); 175 | } 176 | 177 | #define SEQ_SLIDER_X (4 * (4 * 2 + 1) + 2) 178 | #define SEQ_SLIDER_Y (32) 179 | 180 | void draw_sequence_view(Canvas* canvas, FlizzerTrackerApp* tracker) { 181 | char buffer[4]; 182 | 183 | uint8_t sequence_position = tracker->tracker_engine.sequence_position; 184 | TrackerSong* song = &tracker->song; 185 | 186 | for(int pos = sequence_position - 2; pos < sequence_position + 3; pos++) { 187 | if(pos >= 0 && pos < tracker->song.num_sequence_steps) { 188 | for(int i = 0; i < SONG_MAX_CHANNELS; ++i) { 189 | uint8_t current_pattern = 190 | tracker->tracker_engine.song->sequence.sequence_step[pos].pattern_indices[i]; 191 | 192 | uint8_t x = i * (4 * 2 + 1) + 3; 193 | uint8_t y = (pos - (sequence_position - 2)) * 6 + 5; 194 | 195 | snprintf(buffer, sizeof(buffer), "%02X", current_pattern); 196 | canvas_draw_str(canvas, x, y, buffer); 197 | } 198 | } 199 | } 200 | 201 | if(song->loop_start != 0 || song->loop_end != 0) { 202 | canvas_set_color(canvas, ColorBlack); 203 | 204 | for(int pos = sequence_position - 2; pos < sequence_position + 3; pos++) { 205 | if(pos >= 0 && pos < tracker->song.num_sequence_steps) { 206 | if(pos == song->loop_start) { 207 | int16_t y = (pos - (sequence_position - 2)) * 6; 208 | 209 | canvas_draw_line(canvas, 0, fmax(y, 0), 1, fmax(y, 0)); 210 | canvas_draw_line(canvas, 0, fmax(y, 0), 0, fmax(y + 4, 0)); 211 | } 212 | 213 | if(pos > song->loop_start && pos < song->loop_end) { 214 | int16_t y = (pos - (sequence_position - 2)) * 6; 215 | 216 | canvas_draw_line(canvas, 0, fmax(y - 1, 0), 0, fmax(y + 4, 0)); 217 | } 218 | 219 | if(pos == song->loop_end) { 220 | int16_t y = (pos - (sequence_position - 2)) * 6; 221 | 222 | canvas_draw_line(canvas, 0, fmax(y + 4, 0), 1, fmax(y + 4, 0)); 223 | canvas_draw_line(canvas, 0, fmax(y - 1, 0), 0, fmax(y + 4, 0)); 224 | 225 | break; 226 | } 227 | } 228 | } 229 | 230 | canvas_set_color(canvas, ColorXOR); 231 | } 232 | 233 | canvas_set_color(canvas, ColorBlack); 234 | 235 | canvas_draw_line(canvas, SEQ_SLIDER_X, 0, SEQ_SLIDER_X + 2, 0); 236 | canvas_draw_line(canvas, SEQ_SLIDER_X, SEQ_SLIDER_Y, SEQ_SLIDER_X + 2, SEQ_SLIDER_Y); 237 | 238 | canvas_draw_line(canvas, SEQ_SLIDER_X, 0, SEQ_SLIDER_X, SEQ_SLIDER_Y); 239 | canvas_draw_line(canvas, SEQ_SLIDER_X + 2, 0, SEQ_SLIDER_X + 2, SEQ_SLIDER_Y); 240 | 241 | uint8_t start_pos = 242 | sequence_position * (SEQ_SLIDER_Y - 2) / tracker->song.num_sequence_steps + 1; 243 | uint8_t slider_length = (SEQ_SLIDER_Y - 2) / tracker->song.num_sequence_steps + 1; 244 | 245 | canvas_draw_line( 246 | canvas, SEQ_SLIDER_X + 1, start_pos, SEQ_SLIDER_X + 1, (start_pos + slider_length)); 247 | 248 | canvas_set_color(canvas, ColorXOR); 249 | 250 | if(tracker->editing && tracker->focus == EDIT_SEQUENCE) { 251 | uint8_t x = tracker->current_channel * (4 + 4 + 1) + (tracker->current_digit ? 4 : 0) + 2; 252 | uint8_t y = 11; 253 | 254 | canvas_draw_box(canvas, x, y, 5, 7); 255 | } 256 | 257 | if(!(tracker->editing) && tracker->focus == EDIT_SEQUENCE) { 258 | uint8_t x = tracker->current_channel * (4 + 4 + 1) + (tracker->current_digit ? 4 : 0) + 2; 259 | uint8_t y = 11; 260 | 261 | canvas_draw_frame(canvas, x, y, 5, 7); 262 | } 263 | } 264 | 265 | #define member_size(type, member) sizeof(((type*)0)->member) 266 | 267 | #define SONG_HEADER_SIZE \ 268 | (member_size(TrackerSong, song_name) + member_size(TrackerSong, speed) + \ 269 | member_size(TrackerSong, rate) + member_size(TrackerSong, loop_start) + \ 270 | member_size(TrackerSong, loop_end) + member_size(TrackerSong, num_patterns) + \ 271 | member_size(TrackerSong, num_sequence_steps) + member_size(TrackerSong, num_instruments) + \ 272 | member_size(TrackerSong, pattern_length)) 273 | 274 | uint32_t calculate_song_size(TrackerSong* song) { 275 | uint32_t song_size = 276 | SONG_HEADER_SIZE + sizeof(Instrument) * song->num_instruments + 277 | sizeof(TrackerSongPatternStep) * song->num_patterns * song->pattern_length + 278 | sizeof(TrackerSongSequenceStep) * song->num_sequence_steps; 279 | return song_size; 280 | } 281 | 282 | void draw_generic_n_digit_field( 283 | FlizzerTrackerApp* tracker, 284 | Canvas* canvas, 285 | uint8_t focus, 286 | uint8_t param, 287 | const char* text, 288 | uint8_t x, 289 | uint8_t y, 290 | uint8_t digits) // last 1-2 symbols are digits we are editing 291 | { 292 | canvas_draw_str(canvas, x, y, text); 293 | 294 | if(tracker->focus == focus && tracker->selected_param == param && tracker->editing) { 295 | bool select_string = true; 296 | 297 | if(tracker->focus == EDIT_SONGINFO) { 298 | if(param != SI_SONGNAME && param != SI_INSTRUMENTNAME) { 299 | select_string = false; 300 | } 301 | } 302 | 303 | if(tracker->focus == EDIT_INSTRUMENT) { 304 | if(param != INST_INSTRUMENTNAME) { 305 | select_string = false; 306 | } 307 | } 308 | 309 | if(!(select_string)) { 310 | if(tracker->focus == EDIT_INSTRUMENT && param == INST_CURRENTINSTRUMENT) { 311 | canvas_draw_box(canvas, x + strlen(text) * 4 - digits * 4 - 1, y - 6, 5, 7); 312 | } 313 | 314 | else { 315 | canvas_draw_box( 316 | canvas, 317 | x + strlen(text) * 4 - digits * 4 + tracker->current_digit * 4 - 1, 318 | y - 6, 319 | 5, 320 | 7); 321 | } 322 | } 323 | 324 | else { 325 | canvas_draw_box(canvas, x - 1, y - 6, fmax(5, strlen(text) * 4 + 1), 7); 326 | } 327 | } 328 | 329 | if(tracker->focus == focus && tracker->selected_param == param && !(tracker->editing)) { 330 | bool select_string = true; 331 | 332 | if(tracker->focus == EDIT_SONGINFO) { 333 | if(param != SI_SONGNAME && param != SI_INSTRUMENTNAME) { 334 | select_string = false; 335 | } 336 | } 337 | 338 | if(tracker->focus == EDIT_INSTRUMENT) { 339 | if(param != INST_INSTRUMENTNAME) { 340 | select_string = false; 341 | } 342 | } 343 | 344 | if(!(select_string)) { 345 | if(tracker->focus == EDIT_INSTRUMENT && param == INST_CURRENTINSTRUMENT) { 346 | canvas_draw_frame(canvas, x + strlen(text) * 4 - digits * 4 - 1, y - 6, 5, 7); 347 | } 348 | 349 | else { 350 | canvas_draw_frame( 351 | canvas, 352 | x + strlen(text) * 4 - digits * 4 + tracker->current_digit * 4 - 1, 353 | y - 6, 354 | 5, 355 | 7); 356 | } 357 | } 358 | 359 | else { 360 | canvas_draw_frame(canvas, x - 1, y - 6, fmax(5, strlen(text) * 4 + 1), 7); 361 | } 362 | } 363 | } 364 | 365 | void draw_songinfo_view(Canvas* canvas, FlizzerTrackerApp* tracker) { 366 | char buffer[30]; 367 | 368 | snprintf( 369 | buffer, 370 | sizeof(buffer), 371 | "PAT.P.%02X/%02X", 372 | tracker->tracker_engine.pattern_position, 373 | tracker->song.pattern_length - 1); 374 | draw_generic_n_digit_field(tracker, canvas, EDIT_SONGINFO, SI_PATTERNPOS, buffer, 42, 5, 2); 375 | snprintf( 376 | buffer, 377 | sizeof(buffer), 378 | "SEQ.P.%02X/%02X", 379 | tracker->tracker_engine.sequence_position, 380 | tracker->song.num_sequence_steps - 1); 381 | draw_generic_n_digit_field(tracker, canvas, EDIT_SONGINFO, SI_SEQUENCEPOS, buffer, 42, 11, 2); 382 | snprintf(buffer, sizeof(buffer), "SPD.%02X", tracker->song.speed); 383 | draw_generic_n_digit_field(tracker, canvas, EDIT_SONGINFO, SI_SONGSPEED, buffer, 42, 17, 2); 384 | snprintf(buffer, sizeof(buffer), "RATE %02X", tracker->song.rate); 385 | draw_generic_n_digit_field( 386 | tracker, canvas, EDIT_SONGINFO, SI_SONGRATE, buffer, 42 + 4 * 7, 17, 2); 387 | snprintf(buffer, sizeof(buffer), "VOL %02X", tracker->tracker_engine.master_volume); 388 | draw_generic_n_digit_field( 389 | tracker, canvas, EDIT_SONGINFO, SI_MASTERVOL, buffer, 42 + 4 * 7 + 4 * 8, 17, 2); 390 | 391 | snprintf(buffer, sizeof(buffer), "SONG:"); 392 | canvas_draw_str(canvas, 42, 23, buffer); 393 | snprintf(buffer, sizeof(buffer), "%s", tracker->song.song_name); 394 | draw_generic_n_digit_field( 395 | tracker, canvas, EDIT_SONGINFO, SI_SONGNAME, buffer, 42 + 4 * 5, 23, 1); 396 | 397 | snprintf(buffer, sizeof(buffer), "INST:%c", to_char(tracker->current_instrument)); 398 | draw_generic_n_digit_field( 399 | tracker, canvas, EDIT_SONGINFO, SI_CURRENTINSTRUMENT, buffer, 42, 29, 1); 400 | snprintf( 401 | buffer, sizeof(buffer), "%s", tracker->song.instrument[tracker->current_instrument]->name); 402 | draw_generic_n_digit_field( 403 | tracker, canvas, EDIT_SONGINFO, SI_INSTRUMENTNAME, buffer, 42 + 4 * 7, 29, 1); 404 | 405 | uint32_t song_size = calculate_song_size(&tracker->song); 406 | uint32_t free_bytes = memmgr_get_free_heap(); 407 | canvas_draw_line(canvas, 128 - 4 * 10 - 2, 0, 128 - 4 * 10 - 2, 10); 408 | 409 | char song_size_buffer[19]; 410 | char free_bytes_buffer[19]; 411 | 412 | if(song_size > 9999) { 413 | snprintf( 414 | song_size_buffer, 415 | sizeof(song_size_buffer), 416 | "TUNE:%ld%c%01ldK", 417 | song_size / 1024, 418 | '.', 419 | (song_size % 1024) / 103); 420 | } 421 | 422 | else { 423 | snprintf(song_size_buffer, sizeof(song_size_buffer), "TUNE:%ld", song_size); 424 | } 425 | 426 | if(free_bytes > 9999) { 427 | snprintf( 428 | free_bytes_buffer, 429 | sizeof(song_size_buffer), 430 | "FREE:%ld%c%01ldK", 431 | free_bytes / 1024, 432 | '.', 433 | (free_bytes % 1024) / 103); 434 | } 435 | 436 | else { 437 | snprintf(free_bytes_buffer, sizeof(song_size_buffer), "FREE:%ld", free_bytes); 438 | } 439 | 440 | canvas_draw_str(canvas, 128 - 4 * 10, 5, song_size_buffer); 441 | canvas_draw_str(canvas, 128 - 4 * 10, 11, free_bytes_buffer); 442 | } -------------------------------------------------------------------------------- /input_event.c: -------------------------------------------------------------------------------- 1 | #include "input_event.h" 2 | 3 | #include "diskop.h" 4 | 5 | #include 6 | 7 | #define AUDIO_MODES_COUNT 2 8 | 9 | void return_from_keyboard_callback(void* ctx) { 10 | FlizzerTrackerApp* tracker = (FlizzerTrackerApp*)ctx; 11 | 12 | if(!tracker->is_loading && !tracker->is_saving && !tracker->is_loading_instrument && 13 | !tracker->is_saving_instrument) { 14 | uint8_t string_length = 0; 15 | char* string = NULL; 16 | 17 | if(tracker->focus == EDIT_SONGINFO && tracker->mode == PATTERN_VIEW) { 18 | switch(tracker->selected_param) { 19 | case SI_SONGNAME: { 20 | string_length = MUS_SONG_NAME_LEN; 21 | string = (char*)&tracker->song.song_name; 22 | break; 23 | } 24 | 25 | case SI_INSTRUMENTNAME: { 26 | string_length = MUS_INST_NAME_LEN; 27 | string = (char*)&tracker->song.instrument[tracker->current_instrument]->name; 28 | break; 29 | } 30 | } 31 | } 32 | 33 | if(tracker->focus == EDIT_INSTRUMENT && tracker->mode == INST_EDITOR_VIEW) { 34 | switch(tracker->selected_param) { 35 | case INST_INSTRUMENTNAME: { 36 | string_length = MUS_INST_NAME_LEN; 37 | string = (char*)&tracker->song.instrument[tracker->current_instrument]->name; 38 | break; 39 | } 40 | } 41 | } 42 | 43 | if(string == NULL || string_length == 0) return; 44 | 45 | for(uint8_t i = 0; i < string_length; 46 | i++) // I tinyfied the font by deleting lowercase chars, and I don't like the lowercase chars of any 3x5 pixels font 47 | { 48 | string[i] = toupper(string[i]); 49 | } 50 | } 51 | 52 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_TRACKER); 53 | 54 | if(tracker->is_saving) { 55 | stop_song(tracker); 56 | 57 | tracker->filepath = furi_string_alloc(); 58 | furi_string_cat_printf( 59 | tracker->filepath, "%s/%s%s", FLIZZER_TRACKER_FOLDER, tracker->filename, SONG_FILE_EXT); 60 | 61 | if(storage_file_exists(tracker->storage, furi_string_get_cstr(tracker->filepath))) { 62 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_FILE_OVERWRITE); 63 | return; 64 | } 65 | 66 | else { 67 | save_song(tracker, tracker->filepath); 68 | } 69 | } 70 | 71 | if(tracker->is_saving_instrument) { 72 | stop_song(tracker); 73 | 74 | tracker->filepath = furi_string_alloc(); 75 | furi_string_cat_printf( 76 | tracker->filepath, 77 | "%s/%s%s", 78 | FLIZZER_TRACKER_INSTRUMENTS_FOLDER, 79 | tracker->filename, 80 | INST_FILE_EXT); 81 | 82 | if(storage_file_exists(tracker->storage, furi_string_get_cstr(tracker->filepath))) { 83 | view_dispatcher_switch_to_view( 84 | tracker->view_dispatcher, VIEW_INSTRUMENT_FILE_OVERWRITE); 85 | return; 86 | } 87 | 88 | else { 89 | save_instrument(tracker, tracker->filepath); 90 | } 91 | } 92 | } 93 | 94 | void overwrite_file_widget_yes_input_callback(GuiButtonType result, InputType type, void* ctx) { 95 | UNUSED(result); 96 | 97 | FlizzerTrackerApp* tracker = (FlizzerTrackerApp*)ctx; 98 | 99 | if(type == InputTypeShort) { 100 | tracker->is_saving = true; 101 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_TRACKER); 102 | 103 | save_song(tracker, tracker->filepath); 104 | } 105 | } 106 | 107 | void overwrite_file_widget_no_input_callback(GuiButtonType result, InputType type, void* ctx) { 108 | UNUSED(result); 109 | 110 | FlizzerTrackerApp* tracker = (FlizzerTrackerApp*)ctx; 111 | 112 | if(type == InputTypeShort) { 113 | tracker->is_saving = false; 114 | furi_string_free(tracker->filepath); 115 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_TRACKER); 116 | } 117 | } 118 | 119 | void overwrite_instrument_file_widget_yes_input_callback( 120 | GuiButtonType result, 121 | InputType type, 122 | void* ctx) { 123 | UNUSED(result); 124 | 125 | FlizzerTrackerApp* tracker = (FlizzerTrackerApp*)ctx; 126 | 127 | if(type == InputTypeShort) { 128 | tracker->is_saving_instrument = true; 129 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_TRACKER); 130 | // save_song(tracker, tracker->filepath); 131 | static FlizzerTrackerEvent event = { 132 | .type = EventTypeSaveInstrument, .input = {{0}}, .period = 0}; 133 | furi_message_queue_put(tracker->event_queue, &event, FuriWaitForever); 134 | } 135 | } 136 | 137 | void overwrite_instrument_file_widget_no_input_callback( 138 | GuiButtonType result, 139 | InputType type, 140 | void* ctx) { 141 | UNUSED(result); 142 | 143 | FlizzerTrackerApp* tracker = (FlizzerTrackerApp*)ctx; 144 | 145 | if(type == InputTypeShort) { 146 | tracker->is_saving_instrument = false; 147 | furi_string_free(tracker->filepath); 148 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_TRACKER); 149 | } 150 | } 151 | 152 | uint32_t submenu_settings_exit_callback(void* context) { 153 | UNUSED(context); 154 | return VIEW_SUBMENU_PATTERN; 155 | } 156 | 157 | uint32_t submenu_exit_callback(void* context) { 158 | UNUSED(context); 159 | return VIEW_TRACKER; 160 | } 161 | 162 | void submenu_callback(void* context, uint32_t index) { 163 | FlizzerTrackerApp* tracker = (FlizzerTrackerApp*)context; 164 | 165 | switch(tracker->mode) { 166 | case PATTERN_VIEW: { 167 | switch(index) { 168 | case SUBMENU_PATTERN_EXIT: { 169 | tracker->quit = true; 170 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_TRACKER); 171 | view_dispatcher_stop(tracker->view_dispatcher); 172 | break; 173 | } 174 | 175 | case SUBMENU_PATTERN_HELP: { 176 | tracker->showing_help = true; 177 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_TRACKER); 178 | break; 179 | } 180 | 181 | case SUBMENU_PATTERN_SAVE_SONG: { 182 | text_input_set_header_text(tracker->text_input, "Song filename:"); 183 | memset(&tracker->filename, 0, FILE_NAME_LEN); 184 | text_input_set_result_callback( 185 | tracker->text_input, 186 | return_from_keyboard_callback, 187 | tracker, 188 | (char*)&tracker->filename, 189 | FILE_NAME_LEN, 190 | true); 191 | 192 | tracker->is_saving = true; 193 | 194 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_KEYBOARD); 195 | break; 196 | } 197 | 198 | case SUBMENU_PATTERN_LOAD_SONG: { 199 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_TRACKER); 200 | 201 | stop_song(tracker); 202 | 203 | tracker->tracker_engine.sequence_position = tracker->tracker_engine.pattern_position = 204 | tracker->current_instrument = 0; 205 | 206 | tracker->dialogs = furi_record_open(RECORD_DIALOGS); 207 | tracker->is_loading = true; 208 | 209 | FuriString* path; 210 | path = furi_string_alloc(); 211 | furi_string_set(path, FLIZZER_TRACKER_FOLDER); 212 | 213 | DialogsFileBrowserOptions browser_options; 214 | dialog_file_browser_set_basic_options( 215 | &browser_options, SONG_FILE_EXT, &I_flizzer_tracker_module); 216 | browser_options.base_path = FLIZZER_TRACKER_FOLDER; 217 | browser_options.hide_ext = false; 218 | 219 | bool ret = dialog_file_browser_show(tracker->dialogs, path, path, &browser_options); 220 | 221 | furi_record_close(RECORD_DIALOGS); 222 | 223 | const char* cpath = furi_string_get_cstr(path); 224 | 225 | if(ret && strcmp(&cpath[strlen(cpath) - 4], SONG_FILE_EXT) == 0) { 226 | bool result = load_song_util(tracker, path); 227 | UNUSED(result); 228 | } 229 | 230 | else { 231 | furi_string_free(path); 232 | tracker->is_loading = false; 233 | } 234 | break; 235 | } 236 | 237 | case SUBMENU_PATTERN_SETTINGS: { 238 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_SETTINGS); 239 | break; 240 | } 241 | 242 | default: 243 | break; 244 | } 245 | 246 | break; 247 | } 248 | 249 | case INST_EDITOR_VIEW: { 250 | switch(index) { 251 | case SUBMENU_INSTRUMENT_EXIT: { 252 | tracker->quit = true; 253 | 254 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_TRACKER); 255 | view_dispatcher_stop(tracker->view_dispatcher); 256 | break; 257 | } 258 | 259 | case SUBMENU_INSTRUMENT_SAVE: { 260 | text_input_set_header_text(tracker->text_input, "Instrument filename:"); 261 | memset(&tracker->filename, 0, FILE_NAME_LEN); 262 | text_input_set_result_callback( 263 | tracker->text_input, 264 | return_from_keyboard_callback, 265 | tracker, 266 | (char*)&tracker->filename, 267 | FILE_NAME_LEN, 268 | true); 269 | 270 | tracker->is_saving_instrument = true; 271 | 272 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_KEYBOARD); 273 | break; 274 | } 275 | 276 | case SUBMENU_INSTRUMENT_LOAD: { 277 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_TRACKER); 278 | 279 | stop_song(tracker); 280 | 281 | tracker->dialogs = furi_record_open(RECORD_DIALOGS); 282 | tracker->is_loading_instrument = true; 283 | 284 | FuriString* path; 285 | path = furi_string_alloc(); 286 | furi_string_set(path, FLIZZER_TRACKER_INSTRUMENTS_FOLDER); 287 | 288 | DialogsFileBrowserOptions browser_options; 289 | dialog_file_browser_set_basic_options( 290 | &browser_options, INST_FILE_EXT, &I_flizzer_tracker_instrument); 291 | browser_options.base_path = FLIZZER_TRACKER_FOLDER; 292 | browser_options.hide_ext = false; 293 | 294 | bool ret = dialog_file_browser_show(tracker->dialogs, path, path, &browser_options); 295 | 296 | furi_record_close(RECORD_DIALOGS); 297 | 298 | const char* cpath = furi_string_get_cstr(path); 299 | 300 | if(ret && strcmp(&cpath[strlen(cpath) - 4], INST_FILE_EXT) == 0) { 301 | bool result = load_instrument_util(tracker, path); 302 | UNUSED(result); 303 | } 304 | 305 | else { 306 | furi_string_free(path); 307 | tracker->is_loading = false; 308 | } 309 | break; 310 | } 311 | 312 | default: 313 | break; 314 | } 315 | 316 | break; 317 | } 318 | 319 | default: 320 | break; 321 | } 322 | } 323 | 324 | void submenu_copypaste_callback(void* context, uint32_t index) { 325 | FlizzerTrackerApp* tracker = (FlizzerTrackerApp*)context; 326 | 327 | uint8_t sequence_position = tracker->tracker_engine.sequence_position; 328 | uint8_t current_pattern_index = 329 | tracker->tracker_engine.song->sequence.sequence_step[sequence_position] 330 | .pattern_indices[tracker->current_channel]; 331 | 332 | TrackerSongPattern* source_pattern; 333 | 334 | if(tracker->source_pattern_index >= 0) { 335 | source_pattern = &tracker->song.pattern[tracker->source_pattern_index]; 336 | } 337 | 338 | TrackerSongPattern* current_pattern = &tracker->song.pattern[current_pattern_index]; 339 | 340 | uint16_t pattern_length = tracker->tracker_engine.song->pattern_length; 341 | 342 | switch(index) { 343 | case SUBMENU_PATTERN_COPYPASTE_COPY: { 344 | tracker->source_pattern_index = current_pattern_index; 345 | tracker->cut_pattern = false; 346 | break; 347 | } 348 | 349 | case SUBMENU_PATTERN_COPYPASTE_PASTE: { 350 | if(tracker->source_pattern_index >= 0) { 351 | memcpy( 352 | current_pattern->step, 353 | source_pattern->step, 354 | sizeof(TrackerSongPatternStep) * pattern_length); 355 | 356 | if(tracker->cut_pattern) { 357 | set_empty_pattern(source_pattern, pattern_length); 358 | tracker->cut_pattern = false; 359 | } 360 | } 361 | break; 362 | } 363 | 364 | case SUBMENU_PATTERN_COPYPASTE_CUT: { 365 | tracker->source_pattern_index = current_pattern_index; 366 | tracker->cut_pattern = true; 367 | break; 368 | } 369 | 370 | case SUBMENU_PATTERN_COPYPASTE_CLEAR: { 371 | set_empty_pattern(current_pattern, pattern_length); 372 | break; 373 | } 374 | 375 | default: 376 | break; 377 | } 378 | 379 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_TRACKER); 380 | } 381 | 382 | void audio_output_changed_callback(VariableItem* item) { 383 | FlizzerTrackerApp* tracker = (FlizzerTrackerApp*)variable_item_get_context(item); 384 | uint8_t index = variable_item_get_current_value_index(item); 385 | variable_item_set_current_value_text(item, audio_modes_text[(index > 1 ? 1 : index)]); 386 | 387 | if(tracker) { 388 | tracker->external_audio = (bool)index; 389 | 390 | tracker->external_audio = audio_modes_values[(index > 1 ? 1 : index)]; 391 | 392 | sound_engine_PWM_timer_init(tracker->external_audio); 393 | 394 | tracker->sound_engine.external_audio_output = tracker->external_audio; 395 | } 396 | } 397 | 398 | void cycle_focus(FlizzerTrackerApp* tracker) { 399 | switch(tracker->mode) { 400 | case PATTERN_VIEW: { 401 | tracker->focus++; 402 | 403 | if(tracker->focus > EDIT_SONGINFO) { 404 | tracker->focus = EDIT_PATTERN; 405 | } 406 | 407 | break; 408 | } 409 | 410 | case INST_EDITOR_VIEW: { 411 | tracker->focus++; 412 | 413 | if(tracker->focus > EDIT_PROGRAM) { 414 | tracker->focus = EDIT_INSTRUMENT; 415 | 416 | if(tracker->current_digit > 1) { 417 | tracker->current_digit = 1; 418 | } 419 | } 420 | 421 | break; 422 | } 423 | 424 | default: 425 | break; 426 | } 427 | } 428 | 429 | void cycle_view(FlizzerTrackerApp* tracker) { 430 | if(tracker->mode == PATTERN_VIEW) { 431 | tracker->mode = INST_EDITOR_VIEW; 432 | tracker->focus = EDIT_INSTRUMENT; 433 | 434 | tracker->selected_param = 0; 435 | tracker->current_digit = 0; 436 | 437 | return; 438 | } 439 | 440 | if(tracker->mode == INST_EDITOR_VIEW) { 441 | tracker->mode = PATTERN_VIEW; 442 | tracker->focus = EDIT_PATTERN; 443 | 444 | if(tracker->tracker_engine.song == NULL) { 445 | stop_song(tracker); 446 | tracker_engine_set_song(&tracker->tracker_engine, &tracker->song); 447 | } 448 | 449 | tracker->selected_param = 0; 450 | tracker->current_digit = 0; 451 | 452 | return; 453 | } 454 | } 455 | 456 | void process_input_event(FlizzerTrackerApp* tracker, FlizzerTrackerEvent* event) { 457 | if(event->input.key == InputKeyBack && event->input.type == InputTypeShort && 458 | tracker->showing_help) { 459 | tracker->showing_help = false; 460 | return; 461 | } 462 | 463 | if(tracker->showing_help || tracker->is_loading || tracker->is_saving || 464 | tracker->is_loading_instrument || tracker->is_saving_instrument) 465 | return; //do not react until these are finished 466 | 467 | if(event->input.key == InputKeyBack && event->input.type == InputTypeShort && 468 | event->period > 0 && event->period < 300 && !(tracker->editing)) { 469 | cycle_view(tracker); 470 | stop_song(tracker); 471 | return; 472 | } 473 | 474 | else if( 475 | event->input.key == InputKeyBack && event->input.type == InputTypeShort && 476 | !(tracker->editing)) { 477 | cycle_focus(tracker); 478 | //stop_song(tracker); 479 | return; 480 | } 481 | 482 | if(event->input.key == InputKeyBack && event->input.type == InputTypeLong) { 483 | switch(tracker->mode) { 484 | case PATTERN_VIEW: { 485 | if(tracker->focus == EDIT_PATTERN) { 486 | submenu_set_selected_item( 487 | tracker->pattern_copypaste_submenu, SUBMENU_PATTERN_COPYPASTE_COPY); 488 | view_dispatcher_switch_to_view( 489 | tracker->view_dispatcher, VIEW_SUBMENU_PATTERN_COPYPASTE); 490 | } 491 | 492 | else { 493 | submenu_set_selected_item(tracker->pattern_submenu, SUBMENU_PATTERN_LOAD_SONG); 494 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_SUBMENU_PATTERN); 495 | } 496 | break; 497 | } 498 | 499 | case INST_EDITOR_VIEW: { 500 | submenu_set_selected_item(tracker->instrument_submenu, SUBMENU_INSTRUMENT_LOAD); 501 | view_dispatcher_switch_to_view(tracker->view_dispatcher, VIEW_SUBMENU_INSTRUMENT); 502 | break; 503 | } 504 | 505 | default: 506 | break; 507 | } 508 | 509 | return; 510 | } 511 | 512 | switch(tracker->focus) { 513 | case EDIT_PATTERN: { 514 | pattern_edit_event(tracker, event); 515 | break; 516 | } 517 | 518 | case EDIT_SEQUENCE: { 519 | sequence_edit_event(tracker, event); 520 | break; 521 | } 522 | 523 | case EDIT_SONGINFO: { 524 | songinfo_edit_event(tracker, event); 525 | break; 526 | } 527 | 528 | case EDIT_INSTRUMENT: { 529 | instrument_edit_event(tracker, event); 530 | break; 531 | } 532 | 533 | case EDIT_PROGRAM: { 534 | instrument_program_edit_event(tracker, event); 535 | break; 536 | } 537 | 538 | default: 539 | break; 540 | } 541 | } --------------------------------------------------------------------------------