mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-07-18 00:38:10 -07:00
Zero Tracker
This commit is contained in:
@@ -33,6 +33,7 @@ Thank you to all the supporters!
|
||||
- Added: [Power: Show battery percentile while charging #178 (By TQMatvey)](https://github.com/DarkFlippers/unleashed-firmware/pull/178)
|
||||
- Added: [Fix U2F HID vulnerability #2042 (By nminaylov)](https://github.com/flipperdevices/flipperzero-firmware/pull/2042)
|
||||
- Updated: [WiFi (Marauder) (By 0xchocolate)](https://github.com/0xchocolate/flipperzero-firmware-with-wifi-marauder-companion)
|
||||
- Added: [Zero Tracker (By DrZZlo13)](https://github.com/DrZlo13/flipper-zero-music-tracker)
|
||||
|
||||
## Install from Release
|
||||
FLASH STOCK FIRST BEFORE UPDATING TO CUSTOM FIRMWARE!
|
||||
@@ -247,6 +248,7 @@ $ ./fbt resources icons dolphin_ext
|
||||
- [WiFi (Marauder) (By 0xchocolate)](https://github.com/0xchocolate/flipperzero-firmware-with-wifi-marauder-companion) `Req: ESP32 WITH MARAUDER FLASHED`
|
||||
- [WiFi Scanner v.0.4 (By SequoiaSan)](https://github.com/SequoiaSan/FlipperZero-WiFi-Scanner_Module-ESP8266) `Req: ESP8266 or ESP32`
|
||||
- [Wii EC Analyser (By csBlueChip)](https://github.com/csBlueChip/FlipperZero_WiiEC)
|
||||
- [Zero Tracker (By DrZZlo13)](https://github.com/DrZlo13/flipper-zero-music-tracker)
|
||||
|
||||
## Open PRs Checked Out & Not Merged In Main
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
name: Build dev
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build_dev:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Build
|
||||
uses: oleksiikutuzov/flipperzero-ufbt-action@v1
|
||||
with:
|
||||
channel: dev
|
||||
@@ -0,0 +1,4 @@
|
||||
# Flipper Zero music tracker
|
||||
-=-=- MVP Stage: minimum viable player -=-=-
|
||||
|
||||
[>Get latest build<](https://nightly.link/DrZlo13/flipper-zero-music-tracker/workflows/build_dev/master/zero_tracker.fap.zip)
|
||||
@@ -0,0 +1,14 @@
|
||||
App(
|
||||
appid="zero_tracker",
|
||||
name="Zero Tracker",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="zero_tracker_app",
|
||||
requires=[
|
||||
"gui",
|
||||
],
|
||||
stack_size=4 * 1024,
|
||||
order=20,
|
||||
fap_icon="zero_tracker.png",
|
||||
fap_category="Music",
|
||||
fap_icon_assets="icons",
|
||||
)
|
||||
@@ -0,0 +1,102 @@
|
||||
#include "speaker_hal.h"
|
||||
|
||||
#define FURI_HAL_SPEAKER_TIMER TIM16
|
||||
#define FURI_HAL_SPEAKER_CHANNEL LL_TIM_CHANNEL_CH1
|
||||
#define FURI_HAL_SPEAKER_PRESCALER 500
|
||||
|
||||
void tracker_speaker_play(float frequency, float pwm) {
|
||||
uint32_t autoreload = (SystemCoreClock / FURI_HAL_SPEAKER_PRESCALER / frequency) - 1;
|
||||
if(autoreload < 2) {
|
||||
autoreload = 2;
|
||||
} else if(autoreload > UINT16_MAX) {
|
||||
autoreload = UINT16_MAX;
|
||||
}
|
||||
|
||||
if(pwm < 0) pwm = 0;
|
||||
if(pwm > 1) pwm = 1;
|
||||
|
||||
uint32_t compare_value = pwm * autoreload;
|
||||
|
||||
if(compare_value == 0) {
|
||||
compare_value = 1;
|
||||
}
|
||||
|
||||
if(LL_TIM_OC_GetCompareCH1(FURI_HAL_SPEAKER_TIMER) != compare_value) {
|
||||
LL_TIM_OC_SetCompareCH1(FURI_HAL_SPEAKER_TIMER, compare_value);
|
||||
}
|
||||
|
||||
if(LL_TIM_GetAutoReload(FURI_HAL_SPEAKER_TIMER) != autoreload) {
|
||||
LL_TIM_SetAutoReload(FURI_HAL_SPEAKER_TIMER, autoreload);
|
||||
if(LL_TIM_GetCounter(FURI_HAL_SPEAKER_TIMER) > autoreload) {
|
||||
LL_TIM_SetCounter(FURI_HAL_SPEAKER_TIMER, 0);
|
||||
}
|
||||
}
|
||||
|
||||
LL_TIM_EnableAllOutputs(FURI_HAL_SPEAKER_TIMER);
|
||||
}
|
||||
|
||||
void tracker_speaker_stop() {
|
||||
LL_TIM_DisableAllOutputs(FURI_HAL_SPEAKER_TIMER);
|
||||
}
|
||||
|
||||
void tracker_speaker_init() {
|
||||
furi_hal_speaker_start(200.0f, 0.01f);
|
||||
tracker_speaker_stop();
|
||||
}
|
||||
|
||||
void tracker_speaker_deinit() {
|
||||
furi_hal_speaker_stop();
|
||||
}
|
||||
|
||||
static FuriHalInterruptISR tracker_isr;
|
||||
static void* tracker_isr_context;
|
||||
static void tracker_interrupt_cb(void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
if(LL_TIM_IsActiveFlag_UPDATE(TIM2)) {
|
||||
LL_TIM_ClearFlag_UPDATE(TIM2);
|
||||
|
||||
if(tracker_isr) {
|
||||
tracker_isr(tracker_isr_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tracker_interrupt_init(float freq, FuriHalInterruptISR isr, void* context) {
|
||||
tracker_isr = isr;
|
||||
tracker_isr_context = context;
|
||||
|
||||
furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, tracker_interrupt_cb, NULL);
|
||||
|
||||
LL_TIM_InitTypeDef TIM_InitStruct = {0};
|
||||
// Prescaler to get 1kHz clock
|
||||
TIM_InitStruct.Prescaler = SystemCoreClock / 1000000 - 1;
|
||||
TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
|
||||
// Auto reload to get freq Hz interrupt
|
||||
TIM_InitStruct.Autoreload = (1000000 / freq) - 1;
|
||||
TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
|
||||
LL_TIM_Init(TIM2, &TIM_InitStruct);
|
||||
LL_TIM_EnableIT_UPDATE(TIM2);
|
||||
LL_TIM_EnableAllOutputs(TIM2);
|
||||
LL_TIM_EnableCounter(TIM2);
|
||||
}
|
||||
|
||||
void tracker_interrupt_deinit() {
|
||||
FURI_CRITICAL_ENTER();
|
||||
LL_TIM_DeInit(TIM2);
|
||||
FURI_CRITICAL_EXIT();
|
||||
|
||||
furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL);
|
||||
}
|
||||
|
||||
void tracker_debug_init() {
|
||||
furi_hal_gpio_init(&gpio_ext_pc3, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
|
||||
}
|
||||
|
||||
void tracker_debug_set(bool value) {
|
||||
furi_hal_gpio_write(&gpio_ext_pc3, value);
|
||||
}
|
||||
|
||||
void tracker_debug_deinit() {
|
||||
furi_hal_gpio_init(&gpio_ext_pc3, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#include <furi_hal.h>
|
||||
|
||||
void tracker_speaker_init();
|
||||
|
||||
void tracker_speaker_deinit();
|
||||
|
||||
void tracker_speaker_play(float frequency, float pwm);
|
||||
|
||||
void tracker_speaker_stop();
|
||||
|
||||
void tracker_interrupt_init(float freq, FuriHalInterruptISR isr, void* context);
|
||||
|
||||
void tracker_interrupt_deinit();
|
||||
|
||||
void tracker_debug_init();
|
||||
|
||||
void tracker_debug_set(bool value);
|
||||
|
||||
void tracker_debug_deinit();
|
||||
@@ -0,0 +1,441 @@
|
||||
#include "tracker.h"
|
||||
#include <stdbool.h>
|
||||
#include "speaker_hal.h"
|
||||
|
||||
// SongState song_state = {
|
||||
// .tick = 0,
|
||||
// .tick_limit = 2,
|
||||
// .row = 0,
|
||||
// };
|
||||
|
||||
typedef struct {
|
||||
uint8_t speed;
|
||||
uint8_t depth;
|
||||
int8_t direction;
|
||||
int8_t value;
|
||||
} IntegerOscillator;
|
||||
|
||||
typedef struct {
|
||||
float frequency;
|
||||
float frequency_target;
|
||||
float pwm;
|
||||
bool play;
|
||||
IntegerOscillator vibrato;
|
||||
} ChannelState;
|
||||
|
||||
typedef struct {
|
||||
ChannelState* channels;
|
||||
uint8_t tick;
|
||||
uint8_t tick_limit;
|
||||
|
||||
uint8_t pattern_index;
|
||||
uint8_t row_index;
|
||||
uint8_t order_list_index;
|
||||
} SongState;
|
||||
|
||||
typedef struct {
|
||||
uint8_t note;
|
||||
uint8_t effect;
|
||||
uint8_t data;
|
||||
} UnpackedRow;
|
||||
|
||||
struct Tracker {
|
||||
Song* song;
|
||||
bool playing;
|
||||
TrackerMessageCallback callback;
|
||||
void* context;
|
||||
SongState song_state;
|
||||
};
|
||||
|
||||
static void channels_state_init(ChannelState* channel) {
|
||||
channel->frequency = 0;
|
||||
channel->frequency_target = FREQUENCY_UNSET;
|
||||
channel->pwm = PWM_DEFAULT;
|
||||
channel->play = false;
|
||||
channel->vibrato.speed = 0;
|
||||
channel->vibrato.depth = 0;
|
||||
channel->vibrato.direction = 0;
|
||||
channel->vibrato.value = 0;
|
||||
}
|
||||
|
||||
static void tracker_song_state_init(Tracker* tracker) {
|
||||
tracker->song_state.tick = 0;
|
||||
tracker->song_state.tick_limit = 2;
|
||||
tracker->song_state.row_index = 0;
|
||||
tracker->song_state.order_list_index = 0;
|
||||
tracker->song_state.pattern_index = tracker->song->order_list[0];
|
||||
|
||||
if(tracker->song_state.channels != NULL) {
|
||||
free(tracker->song_state.channels);
|
||||
}
|
||||
|
||||
tracker->song_state.channels = malloc(sizeof(ChannelState) * tracker->song->channels_count);
|
||||
for(uint8_t i = 0; i < tracker->song->channels_count; i++) {
|
||||
channels_state_init(&tracker->song_state.channels[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void tracker_song_state_clear(Tracker* tracker) {
|
||||
if(tracker->song_state.channels != NULL) {
|
||||
free(tracker->song_state.channels);
|
||||
tracker->song_state.channels = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t record_get_note(Row note) {
|
||||
return note & ROW_NOTE_MASK;
|
||||
}
|
||||
|
||||
static uint8_t record_get_effect(Row note) {
|
||||
return (note >> 6) & ROW_EFFECT_MASK;
|
||||
}
|
||||
|
||||
static uint8_t record_get_effect_data(Row note) {
|
||||
return (note >> 10) & ROW_EFFECT_DATA_MASK;
|
||||
}
|
||||
|
||||
#define NOTES_PER_OCT 12
|
||||
const float notes_oct[NOTES_PER_OCT] = {
|
||||
130.813f,
|
||||
138.591f,
|
||||
146.832f,
|
||||
155.563f,
|
||||
164.814f,
|
||||
174.614f,
|
||||
184.997f,
|
||||
195.998f,
|
||||
207.652f,
|
||||
220.00f,
|
||||
233.082f,
|
||||
246.942f,
|
||||
};
|
||||
|
||||
static float note_to_freq(uint8_t note) {
|
||||
if(note == NOTE_NONE) return 0.0f;
|
||||
note = note - NOTE_C2;
|
||||
uint8_t octave = note / NOTES_PER_OCT;
|
||||
uint8_t note_in_oct = note % NOTES_PER_OCT;
|
||||
return notes_oct[note_in_oct] * (1 << octave);
|
||||
}
|
||||
|
||||
static float frequency_offset_semitones(float frequency, uint8_t semitones) {
|
||||
return frequency * (1.0f + ((1.0f / 12.0f) * semitones));
|
||||
}
|
||||
|
||||
static float frequency_get_seventh_of_a_semitone(float frequency) {
|
||||
return frequency * ((1.0f / 12.0f) / 7.0f);
|
||||
}
|
||||
|
||||
static UnpackedRow get_current_row(Song* song, SongState* song_state, uint8_t channel) {
|
||||
Pattern* pattern = &song->patterns[song_state->pattern_index];
|
||||
Row row = pattern->channels[channel].rows[song_state->row_index];
|
||||
return (UnpackedRow){
|
||||
.note = record_get_note(row),
|
||||
.effect = record_get_effect(row),
|
||||
.data = record_get_effect_data(row),
|
||||
};
|
||||
}
|
||||
|
||||
static int16_t advance_order_and_get_next_pattern_index(Song* song, SongState* song_state) {
|
||||
song_state->order_list_index++;
|
||||
if(song_state->order_list_index >= song->order_list_size) {
|
||||
return -1;
|
||||
} else {
|
||||
return song->order_list[song_state->order_list_index];
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int16_t pattern;
|
||||
int16_t row;
|
||||
bool change_pattern;
|
||||
bool change_row;
|
||||
} Location;
|
||||
|
||||
static void tracker_send_position_message(Tracker* tracker) {
|
||||
if(tracker->callback != NULL) {
|
||||
tracker->callback(
|
||||
(TrackerMessage){
|
||||
.type = TrackerPositionChanged,
|
||||
.data =
|
||||
{
|
||||
.position =
|
||||
{
|
||||
.order_list_index = tracker->song_state.order_list_index,
|
||||
.row = tracker->song_state.row_index,
|
||||
},
|
||||
},
|
||||
},
|
||||
tracker->context);
|
||||
}
|
||||
}
|
||||
|
||||
static void tracker_send_end_message(Tracker* tracker) {
|
||||
if(tracker->callback != NULL) {
|
||||
tracker->callback((TrackerMessage){.type = TrackerEndOfSong}, tracker->context);
|
||||
}
|
||||
}
|
||||
|
||||
static void advance_to_pattern(Tracker* tracker, Location advance) {
|
||||
if(advance.change_pattern) {
|
||||
if(advance.pattern < 0 || advance.pattern >= tracker->song->patterns_count) {
|
||||
tracker->playing = false;
|
||||
tracker_send_end_message(tracker);
|
||||
} else {
|
||||
tracker->song_state.pattern_index = advance.pattern;
|
||||
tracker->song_state.row_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(advance.change_row) {
|
||||
if(advance.row < 0) advance.row = 0;
|
||||
if(advance.row >= PATTERN_SIZE) advance.row = PATTERN_SIZE - 1;
|
||||
tracker->song_state.row_index = advance.row;
|
||||
}
|
||||
|
||||
tracker_send_position_message(tracker);
|
||||
}
|
||||
|
||||
static void tracker_interrupt_body(Tracker* tracker) {
|
||||
if(!tracker->playing) {
|
||||
tracker_speaker_stop();
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t channel_index = 0;
|
||||
SongState* song_state = &tracker->song_state;
|
||||
ChannelState* channel_state = &song_state->channels[channel_index];
|
||||
Song* song = tracker->song;
|
||||
UnpackedRow row = get_current_row(song, song_state, channel_index);
|
||||
|
||||
// load frequency from note at tick 0
|
||||
if(song_state->tick == 0) {
|
||||
bool invalidate_row = false;
|
||||
// handle "on first tick" effects
|
||||
if(row.effect == EffectBreakPattern) {
|
||||
int16_t next_row_index = row.data;
|
||||
int16_t next_pattern_index =
|
||||
advance_order_and_get_next_pattern_index(song, song_state);
|
||||
advance_to_pattern(
|
||||
tracker,
|
||||
(Location){
|
||||
.pattern = next_pattern_index,
|
||||
.row = next_row_index,
|
||||
.change_pattern = true,
|
||||
.change_row = true,
|
||||
});
|
||||
|
||||
invalidate_row = true;
|
||||
}
|
||||
|
||||
if(row.effect == EffectJumpToOrder) {
|
||||
song_state->order_list_index = row.data;
|
||||
int16_t next_pattern_index = song->order_list[song_state->order_list_index];
|
||||
|
||||
advance_to_pattern(
|
||||
tracker,
|
||||
(Location){
|
||||
.pattern = next_pattern_index,
|
||||
.change_pattern = true,
|
||||
});
|
||||
|
||||
invalidate_row = true;
|
||||
}
|
||||
|
||||
// tracker state can be affected by effects
|
||||
if(!tracker->playing) {
|
||||
tracker_speaker_stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if(invalidate_row) {
|
||||
row = get_current_row(song, song_state, channel_index);
|
||||
|
||||
if(row.effect == EffectSetSpeed) {
|
||||
song_state->tick_limit = row.data;
|
||||
}
|
||||
}
|
||||
|
||||
// handle note effects
|
||||
if(row.note == NOTE_OFF) {
|
||||
channel_state->play = false;
|
||||
} else if((row.note > NOTE_NONE) && (row.note < NOTE_OFF)) {
|
||||
channel_state->play = true;
|
||||
|
||||
// reset vibrato
|
||||
channel_state->vibrato.speed = 0;
|
||||
channel_state->vibrato.depth = 0;
|
||||
channel_state->vibrato.value = 0;
|
||||
channel_state->vibrato.direction = 0;
|
||||
|
||||
// reset pwm
|
||||
channel_state->pwm = PWM_DEFAULT;
|
||||
|
||||
if(row.effect == EffectSlideToNote) {
|
||||
channel_state->frequency_target = note_to_freq(row.note);
|
||||
} else {
|
||||
channel_state->frequency = note_to_freq(row.note);
|
||||
channel_state->frequency_target = FREQUENCY_UNSET;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(channel_state->play) {
|
||||
float frequency, pwm;
|
||||
|
||||
if((row.effect == EffectSlideUp || row.effect == EffectSlideDown) &&
|
||||
row.data != EFFECT_DATA_NONE) {
|
||||
// apply slide effect
|
||||
channel_state->frequency += (row.effect == EffectSlideUp ? 1 : -1) * row.data;
|
||||
} else if(row.effect == EffectSlideToNote) {
|
||||
// apply slide to note effect, if target frequency is set
|
||||
if(channel_state->frequency_target > 0) {
|
||||
if(channel_state->frequency_target > channel_state->frequency) {
|
||||
channel_state->frequency += row.data;
|
||||
if(channel_state->frequency > channel_state->frequency_target) {
|
||||
channel_state->frequency = channel_state->frequency_target;
|
||||
channel_state->frequency_target = FREQUENCY_UNSET;
|
||||
}
|
||||
} else if(channel_state->frequency_target < channel_state->frequency) {
|
||||
channel_state->frequency -= row.data;
|
||||
if(channel_state->frequency < channel_state->frequency_target) {
|
||||
channel_state->frequency = channel_state->frequency_target;
|
||||
channel_state->frequency_target = FREQUENCY_UNSET;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frequency = channel_state->frequency;
|
||||
pwm = channel_state->pwm;
|
||||
|
||||
// apply arpeggio effect
|
||||
if(row.effect == EffectArpeggio) {
|
||||
if(row.data != EFFECT_DATA_NONE) {
|
||||
if((song_state->tick % 3) == 1) {
|
||||
uint8_t note_offset = EFFECT_DATA_GET_X(row.data);
|
||||
frequency = frequency_offset_semitones(frequency, note_offset);
|
||||
} else if((song_state->tick % 3) == 2) {
|
||||
uint8_t note_offset = EFFECT_DATA_GET_Y(row.data);
|
||||
frequency = frequency_offset_semitones(frequency, note_offset);
|
||||
}
|
||||
}
|
||||
} else if(row.effect == EffectVibrato) {
|
||||
// apply vibrato effect, data = speed, depth
|
||||
uint8_t vibrato_speed = EFFECT_DATA_GET_X(row.data);
|
||||
uint8_t vibrato_depth = EFFECT_DATA_GET_Y(row.data);
|
||||
|
||||
// update vibrato parameters if speed or depth is non-zero
|
||||
if(vibrato_speed != 0) channel_state->vibrato.speed = vibrato_speed;
|
||||
if(vibrato_depth != 0) channel_state->vibrato.depth = vibrato_depth;
|
||||
|
||||
// update vibrato value
|
||||
channel_state->vibrato.value +=
|
||||
channel_state->vibrato.direction * channel_state->vibrato.speed;
|
||||
|
||||
// change direction if value is at the limit
|
||||
if(channel_state->vibrato.value > channel_state->vibrato.depth) {
|
||||
channel_state->vibrato.direction = -1;
|
||||
} else if(channel_state->vibrato.value < -channel_state->vibrato.depth) {
|
||||
channel_state->vibrato.direction = 1;
|
||||
} else if(channel_state->vibrato.direction == 0) {
|
||||
// set initial direction, if it is not set
|
||||
channel_state->vibrato.direction = 1;
|
||||
}
|
||||
|
||||
frequency +=
|
||||
(frequency_get_seventh_of_a_semitone(frequency) * channel_state->vibrato.value);
|
||||
} else if(row.effect == EffectPWM) {
|
||||
pwm = (pwm - PWM_MIN) / EFFECT_DATA_1_MAX * row.data + PWM_MIN;
|
||||
}
|
||||
|
||||
tracker_speaker_play(frequency, pwm);
|
||||
} else {
|
||||
tracker_speaker_stop();
|
||||
}
|
||||
|
||||
song_state->tick++;
|
||||
if(song_state->tick >= song_state->tick_limit) {
|
||||
song_state->tick = 0;
|
||||
|
||||
// next note
|
||||
song_state->row_index = (song_state->row_index + 1);
|
||||
|
||||
if(song_state->row_index >= PATTERN_SIZE) {
|
||||
int16_t next_pattern_index =
|
||||
advance_order_and_get_next_pattern_index(song, song_state);
|
||||
advance_to_pattern(
|
||||
tracker,
|
||||
(Location){
|
||||
.pattern = next_pattern_index,
|
||||
.change_pattern = true,
|
||||
});
|
||||
} else {
|
||||
tracker_send_position_message(tracker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void tracker_interrupt_cb(void* context) {
|
||||
Tracker* tracker = (Tracker*)context;
|
||||
tracker_debug_set(true);
|
||||
tracker_interrupt_body(tracker);
|
||||
tracker_debug_set(false);
|
||||
}
|
||||
|
||||
/*********************************************************************
|
||||
* Tracker Interface
|
||||
*********************************************************************/
|
||||
|
||||
Tracker* tracker_alloc() {
|
||||
Tracker* tracker = malloc(sizeof(Tracker));
|
||||
return tracker;
|
||||
}
|
||||
|
||||
void tracker_free(Tracker* tracker) {
|
||||
tracker_song_state_clear(tracker);
|
||||
free(tracker);
|
||||
}
|
||||
|
||||
void tracker_set_message_callback(Tracker* tracker, TrackerMessageCallback callback, void* context) {
|
||||
furi_check(tracker->playing == false);
|
||||
tracker->callback = callback;
|
||||
tracker->context = context;
|
||||
}
|
||||
|
||||
void tracker_set_song(Tracker* tracker, Song* song) {
|
||||
furi_check(tracker->playing == false);
|
||||
tracker->song = song;
|
||||
tracker_song_state_init(tracker);
|
||||
}
|
||||
|
||||
void tracker_set_order_index(Tracker* tracker, uint8_t order_index) {
|
||||
furi_check(tracker->playing == false);
|
||||
furi_check(order_index < tracker->song->order_list_size);
|
||||
tracker->song_state.order_list_index = order_index;
|
||||
tracker->song_state.pattern_index = tracker->song->order_list[order_index];
|
||||
}
|
||||
|
||||
void tracker_set_row(Tracker* tracker, uint8_t row) {
|
||||
furi_check(tracker->playing == false);
|
||||
furi_check(row < PATTERN_SIZE);
|
||||
tracker->song_state.row_index = row;
|
||||
}
|
||||
|
||||
void tracker_start(Tracker* tracker) {
|
||||
furi_check(tracker->song != NULL);
|
||||
|
||||
tracker->playing = true;
|
||||
tracker_send_position_message(tracker);
|
||||
tracker_debug_init();
|
||||
tracker_speaker_init();
|
||||
tracker_interrupt_init(tracker->song->ticks_per_second, tracker_interrupt_cb, tracker);
|
||||
}
|
||||
|
||||
void tracker_stop(Tracker* tracker) {
|
||||
tracker_interrupt_deinit();
|
||||
tracker_speaker_deinit();
|
||||
tracker_debug_deinit();
|
||||
|
||||
tracker->playing = false;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
#include "tracker_notes.h"
|
||||
#include "tracker_song.h"
|
||||
|
||||
typedef enum {
|
||||
TrackerPositionChanged,
|
||||
TrackerEndOfSong,
|
||||
} TrackerMessageType;
|
||||
|
||||
typedef struct {
|
||||
TrackerMessageType type;
|
||||
union tracker_message_data {
|
||||
struct {
|
||||
uint8_t order_list_index;
|
||||
uint8_t row;
|
||||
} position;
|
||||
} data;
|
||||
} TrackerMessage;
|
||||
|
||||
typedef void (*TrackerMessageCallback)(TrackerMessage message, void* context);
|
||||
|
||||
typedef struct Tracker Tracker;
|
||||
|
||||
Tracker* tracker_alloc();
|
||||
|
||||
void tracker_free(Tracker* tracker);
|
||||
|
||||
void tracker_set_message_callback(Tracker* tracker, TrackerMessageCallback callback, void* context);
|
||||
|
||||
void tracker_set_song(Tracker* tracker, Song* song);
|
||||
|
||||
void tracker_set_order_index(Tracker* tracker, uint8_t order_index);
|
||||
|
||||
void tracker_set_row(Tracker* tracker, uint8_t row);
|
||||
|
||||
void tracker_start(Tracker* tracker);
|
||||
|
||||
void tracker_stop(Tracker* tracker);
|
||||
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#define NOTE_NONE 0
|
||||
#define NOTE_C2 1
|
||||
#define NOTE_Cs2 2
|
||||
#define NOTE_D2 3
|
||||
#define NOTE_Ds2 4
|
||||
#define NOTE_E2 5
|
||||
#define NOTE_F2 6
|
||||
#define NOTE_Fs2 7
|
||||
#define NOTE_G2 8
|
||||
#define NOTE_Gs2 9
|
||||
#define NOTE_A2 10
|
||||
#define NOTE_As2 11
|
||||
#define NOTE_B2 12
|
||||
#define NOTE_C3 13
|
||||
#define NOTE_Cs3 14
|
||||
#define NOTE_D3 15
|
||||
#define NOTE_Ds3 16
|
||||
#define NOTE_E3 17
|
||||
#define NOTE_F3 18
|
||||
#define NOTE_Fs3 19
|
||||
#define NOTE_G3 20
|
||||
#define NOTE_Gs3 21
|
||||
#define NOTE_A3 22
|
||||
#define NOTE_As3 23
|
||||
#define NOTE_B3 24
|
||||
#define NOTE_C4 25
|
||||
#define NOTE_Cs4 26
|
||||
#define NOTE_D4 27
|
||||
#define NOTE_Ds4 28
|
||||
#define NOTE_E4 29
|
||||
#define NOTE_F4 30
|
||||
#define NOTE_Fs4 31
|
||||
#define NOTE_G4 32
|
||||
#define NOTE_Gs4 33
|
||||
#define NOTE_A4 34
|
||||
#define NOTE_As4 35
|
||||
#define NOTE_B4 36
|
||||
#define NOTE_C5 37
|
||||
#define NOTE_Cs5 38
|
||||
#define NOTE_D5 39
|
||||
#define NOTE_Ds5 40
|
||||
#define NOTE_E5 41
|
||||
#define NOTE_F5 42
|
||||
#define NOTE_Fs5 43
|
||||
#define NOTE_G5 44
|
||||
#define NOTE_Gs5 45
|
||||
#define NOTE_A5 46
|
||||
#define NOTE_As5 47
|
||||
#define NOTE_B5 48
|
||||
#define NOTE_C6 49
|
||||
#define NOTE_Cs6 50
|
||||
#define NOTE_D6 51
|
||||
#define NOTE_Ds6 52
|
||||
#define NOTE_E6 53
|
||||
#define NOTE_F6 54
|
||||
#define NOTE_Fs6 55
|
||||
#define NOTE_G6 56
|
||||
#define NOTE_Gs6 57
|
||||
#define NOTE_A6 58
|
||||
#define NOTE_As6 59
|
||||
#define NOTE_B6 60
|
||||
#define NOTE_OFF 63
|
||||
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Row
|
||||
*
|
||||
* AH AL
|
||||
* FEDCBA98 76543210
|
||||
* nnnnnnee eedddddd
|
||||
* -------- --------
|
||||
* nnnnnn = [0] do nothing, [1..60] note number, [61] note off, [62..63] not used
|
||||
* ee ee = [0..F] effect
|
||||
* 111222 = [0..63] or [0..7, 0..7] effect data
|
||||
*/
|
||||
typedef uint16_t Row;
|
||||
|
||||
#define ROW_NOTE_MASK 0x3F
|
||||
#define ROW_EFFECT_MASK 0x0F
|
||||
#define ROW_EFFECT_DATA_MASK 0x3F
|
||||
|
||||
typedef enum {
|
||||
// 0xy, x - first semitones offset, y - second semitones offset. 0 - no offset .. 7 - +7 semitones...
|
||||
// Play the arpeggio chord with three notes. The first note is the base note, the second and third are offset by x and y.
|
||||
// Each note plays one tick.
|
||||
EffectArpeggio = 0x00,
|
||||
|
||||
// 1xx, xx - effect speed, 0 - no effect, 1 - slowest, 0x3F - fastest.
|
||||
// Slide the note pitch up by xx Hz every tick.
|
||||
EffectSlideUp = 0x01,
|
||||
|
||||
// 2xx, xx - effect speed, 0 - no effect, 1 - slowest, 0x3F - fastest.
|
||||
// Slide the note pitch down by xx Hz every tick.
|
||||
EffectSlideDown = 0x02,
|
||||
|
||||
// 3xx, xx - effect speed, 0 - no effect, 1 - slowest, 0x3F - fastest.
|
||||
// Slide the already playing note pitch towards another one by xx Hz every tick.
|
||||
// The note value is saved until the note is playing, so you don't have to repeat the note value to continue sliding.
|
||||
EffectSlideToNote = 0x03,
|
||||
|
||||
// 4xy, x - vibrato speed (0..7), y - vibrato depth (0..7).
|
||||
// Vibrato effect. The pitch of the note increases by x Hz each tick to a positive vibrato depth, then decreases to a negative depth.
|
||||
// Value 1 of depth means 1/7 of a semitone (about 14.28 ct), so value 7 means full semitone.
|
||||
// Note will play without vibrato on the first tick at the beginning of the effect.
|
||||
// Vibrato speed and depth are saved until the note is playing, and will be updated only if they are not zero, so you doesn't have to repeat them every tick.
|
||||
EffectVibrato = 0x04,
|
||||
|
||||
// Effect05 = 0x05,
|
||||
// Effect06 = 0x06,
|
||||
// Effect07 = 0x07,
|
||||
// Effect08 = 0x08,
|
||||
// Effect09 = 0x09,
|
||||
// Effect0A = 0x0A,
|
||||
|
||||
// Bxx, xx - pattern number
|
||||
// Jump to the order xx in the pattern order table at first tick of current row.
|
||||
// So if you want to jump to the pattern after note 4, you should put this effect on the 5th note.
|
||||
EffectJumpToOrder = 0x0B,
|
||||
|
||||
// Cxx, xx - pwm value
|
||||
// Set the PWM value to xx for current row.
|
||||
EffectPWM = 0x0C,
|
||||
|
||||
// Bxx, xx - row number
|
||||
// Jump to the row xx in next pattern at first tick of current row.
|
||||
// So if you want to jump to the pattern after note 4, you should put this effect on the 5th note.
|
||||
EffectBreakPattern = 0x0D,
|
||||
|
||||
// Effect0E = 0x0E,
|
||||
|
||||
// Fxx, xx - song speed, 0 - 1 tick per note, 1 - 2 ticks per note, 0x3F - 64 ticks per note.
|
||||
// Set the speed of the song in terms of ticks per note.
|
||||
// Will be applied at the first tick of current row.
|
||||
EffectSetSpeed = 0x0F,
|
||||
} Effect;
|
||||
|
||||
#define EFFECT_DATA_2(x, y) ((x) | ((y) << 3))
|
||||
#define EFFECT_DATA_GET_X(data) ((data)&0x07)
|
||||
#define EFFECT_DATA_GET_Y(data) (((data) >> 3) & 0x07)
|
||||
#define EFFECT_DATA_NONE 0
|
||||
#define EFFECT_DATA_1_MAX 0x3F
|
||||
#define EFFECT_DATA_2_MAX 0x07
|
||||
|
||||
#define FREQUENCY_UNSET -1.0f
|
||||
|
||||
#define PWM_MIN 0.01f
|
||||
#define PWM_MAX 0.5f
|
||||
#define PWM_DEFAULT PWM_MAX
|
||||
|
||||
#define PATTERN_SIZE 64
|
||||
|
||||
#define ROW_MAKE(note, effect, data) \
|
||||
((Row)(((note)&0x3F) | (((effect)&0xF) << 6) | (((data)&0x3F) << 10)))
|
||||
|
||||
typedef struct {
|
||||
Row rows[PATTERN_SIZE];
|
||||
} Channel;
|
||||
|
||||
typedef struct {
|
||||
Channel* channels;
|
||||
} Pattern;
|
||||
|
||||
typedef struct {
|
||||
uint8_t channels_count;
|
||||
uint8_t patterns_count;
|
||||
Pattern* patterns;
|
||||
uint8_t order_list_size;
|
||||
uint8_t* order_list;
|
||||
uint16_t ticks_per_second;
|
||||
} Song;
|
||||
@@ -0,0 +1,509 @@
|
||||
#include <furi.h>
|
||||
#include "zero_tracker.h"
|
||||
#include "tracker_engine/tracker.h"
|
||||
|
||||
// Channel p_0_channels[] = {
|
||||
// {
|
||||
// .rows =
|
||||
// {
|
||||
// // 1/4
|
||||
// ROW_MAKE(NOTE_C3, EffectArpeggio, EFFECT_DATA_2(4, 7)),
|
||||
// ROW_MAKE(0, EffectArpeggio, EFFECT_DATA_2(4, 7)),
|
||||
// ROW_MAKE(NOTE_C4, EffectSlideToNote, 0x20),
|
||||
// ROW_MAKE(0, EffectSlideToNote, 0x20),
|
||||
// //
|
||||
// ROW_MAKE(0, EffectSlideToNote, 0x20),
|
||||
// ROW_MAKE(0, EffectSlideToNote, 0x20),
|
||||
// ROW_MAKE(0, EffectSlideToNote, 0x20),
|
||||
// ROW_MAKE(0, EffectSlideToNote, 0x20),
|
||||
// //
|
||||
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
|
||||
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
|
||||
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
|
||||
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
|
||||
// //
|
||||
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
|
||||
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
|
||||
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
|
||||
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
|
||||
// // 2/4
|
||||
// ROW_MAKE(NOTE_C3, EffectSlideDown, 0x20),
|
||||
// ROW_MAKE(0, EffectSlideDown, 0x20),
|
||||
// ROW_MAKE(NOTE_C4, EffectVibrato, EFFECT_DATA_2(3, 3)),
|
||||
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
|
||||
// //
|
||||
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
|
||||
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
|
||||
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
|
||||
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
|
||||
// //
|
||||
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
|
||||
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
|
||||
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
|
||||
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
|
||||
// //
|
||||
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
|
||||
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
|
||||
// ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(3, 3)),
|
||||
// ROW_MAKE(NOTE_OFF, EffectVibrato, EFFECT_DATA_2(3, 3)),
|
||||
// // 3/4
|
||||
// ROW_MAKE(NOTE_C3, EffectArpeggio, EFFECT_DATA_2(4, 7)),
|
||||
// ROW_MAKE(0, EffectArpeggio, EFFECT_DATA_2(4, 7)),
|
||||
// ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
// ROW_MAKE(0, 0, 0),
|
||||
// //
|
||||
// ROW_MAKE(0, 0, 0),
|
||||
// ROW_MAKE(0, 0, 0),
|
||||
// ROW_MAKE(0, 0, 0),
|
||||
// ROW_MAKE(0, 0, 0),
|
||||
// //
|
||||
// ROW_MAKE(NOTE_C2, EffectPWM, 60),
|
||||
// ROW_MAKE(0, EffectPWM, 32),
|
||||
// ROW_MAKE(0, EffectPWM, 12),
|
||||
// ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
// //
|
||||
// ROW_MAKE(0, 0, 0),
|
||||
// ROW_MAKE(0, 0, 0),
|
||||
// ROW_MAKE(0, 0, 0),
|
||||
// ROW_MAKE(0, 0, 0),
|
||||
// // 4/4
|
||||
// ROW_MAKE(NOTE_C3, EffectSlideDown, 0x20),
|
||||
// ROW_MAKE(0, EffectSlideDown, 0x20),
|
||||
// ROW_MAKE(0, EffectSlideDown, 0x20),
|
||||
// ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
// //
|
||||
// ROW_MAKE(0, 0, 0),
|
||||
// ROW_MAKE(0, 0, 0),
|
||||
// ROW_MAKE(0, 0, 0),
|
||||
// ROW_MAKE(0, 0, 0),
|
||||
// //
|
||||
// ROW_MAKE(NOTE_C2, EffectPWM, 60),
|
||||
// ROW_MAKE(0, EffectPWM, 32),
|
||||
// ROW_MAKE(0, EffectPWM, 12),
|
||||
// ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
// //
|
||||
// ROW_MAKE(0, 0, 0),
|
||||
// ROW_MAKE(0, 0, 0),
|
||||
// ROW_MAKE(0, 0, 0),
|
||||
// ROW_MAKE(0, 0, 0),
|
||||
// },
|
||||
// },
|
||||
// };
|
||||
|
||||
Channel p_0_channels[] = {
|
||||
{
|
||||
.rows =
|
||||
{
|
||||
//
|
||||
ROW_MAKE(NOTE_A4, EffectArpeggio, EFFECT_DATA_2(4, 7)),
|
||||
ROW_MAKE(NOTE_C3, 0, 0),
|
||||
ROW_MAKE(NOTE_F2, 0, 0),
|
||||
ROW_MAKE(NOTE_C3, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_E4, 0, 0),
|
||||
ROW_MAKE(NOTE_C3, 0, 0),
|
||||
ROW_MAKE(NOTE_E4, EffectPWM, 50),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_A4, 0, 0),
|
||||
ROW_MAKE(0, EffectPWM, 55),
|
||||
ROW_MAKE(0, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_E5, 0, 0),
|
||||
ROW_MAKE(0, EffectPWM, 55),
|
||||
ROW_MAKE(0, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_D5, 0, 0),
|
||||
ROW_MAKE(NOTE_C3, EffectSlideDown, 0x30),
|
||||
ROW_MAKE(NOTE_F2, 0, 0),
|
||||
ROW_MAKE(NOTE_C3, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_C5, 0, 0),
|
||||
ROW_MAKE(NOTE_C3, 0, 0),
|
||||
ROW_MAKE(NOTE_C5, 0, 0),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_A4, 0, 0),
|
||||
ROW_MAKE(0, 0, 0),
|
||||
ROW_MAKE(0, 0, 0),
|
||||
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
|
||||
//
|
||||
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
|
||||
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
|
||||
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_B4, EffectArpeggio, EFFECT_DATA_2(4, 7)),
|
||||
ROW_MAKE(NOTE_D3, 0, 0),
|
||||
ROW_MAKE(NOTE_G2, 0, 0),
|
||||
ROW_MAKE(NOTE_D3, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_E4, 0, 0),
|
||||
ROW_MAKE(NOTE_D3, 0, 0),
|
||||
ROW_MAKE(NOTE_E4, EffectPWM, 50),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_A4, 0, 0),
|
||||
ROW_MAKE(0, EffectPWM, 55),
|
||||
ROW_MAKE(0, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_E5, 0, 0),
|
||||
ROW_MAKE(0, EffectPWM, 55),
|
||||
ROW_MAKE(0, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_D5, 0, 0),
|
||||
ROW_MAKE(NOTE_D3, EffectSlideDown, 0x3F),
|
||||
ROW_MAKE(NOTE_G2, 0, 0),
|
||||
ROW_MAKE(NOTE_D3, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_C5, 0, 0),
|
||||
ROW_MAKE(NOTE_D3, 0, 0),
|
||||
ROW_MAKE(NOTE_C5, 0, 0),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_A4, 0, 0),
|
||||
ROW_MAKE(0, 0, 0),
|
||||
ROW_MAKE(0, 0, 0),
|
||||
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
|
||||
//
|
||||
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
|
||||
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
|
||||
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Channel p_1_channels[] = {
|
||||
{
|
||||
.rows =
|
||||
{
|
||||
//
|
||||
ROW_MAKE(NOTE_C5, EffectArpeggio, EFFECT_DATA_2(4, 7)),
|
||||
ROW_MAKE(NOTE_E3, 0, 0),
|
||||
ROW_MAKE(NOTE_A2, 0, 0),
|
||||
ROW_MAKE(NOTE_E3, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_B4, 0, 0),
|
||||
ROW_MAKE(NOTE_E3, 0, 0),
|
||||
ROW_MAKE(NOTE_B4, EffectPWM, 50),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_G4, 0, 0),
|
||||
ROW_MAKE(0, EffectPWM, 55),
|
||||
ROW_MAKE(0, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_C5, 0, 0),
|
||||
ROW_MAKE(0, EffectPWM, 55),
|
||||
ROW_MAKE(0, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_C6, 0, 0),
|
||||
ROW_MAKE(NOTE_E3, EffectSlideDown, 0x30),
|
||||
ROW_MAKE(NOTE_A2, 0, 0),
|
||||
ROW_MAKE(NOTE_E3, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_B4, 0, 0),
|
||||
ROW_MAKE(NOTE_E3, 0, 0),
|
||||
ROW_MAKE(NOTE_B4, EffectPWM, 50),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_G4, 0, 0),
|
||||
ROW_MAKE(0, 0, 0),
|
||||
ROW_MAKE(0, 0, 0),
|
||||
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
|
||||
//
|
||||
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
|
||||
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
|
||||
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_C5, EffectArpeggio, EFFECT_DATA_2(4, 7)),
|
||||
ROW_MAKE(NOTE_E3, 0, 0),
|
||||
ROW_MAKE(NOTE_A2, 0, 0),
|
||||
ROW_MAKE(NOTE_E3, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_B4, 0, 0),
|
||||
ROW_MAKE(NOTE_E3, 0, 0),
|
||||
ROW_MAKE(NOTE_B4, EffectPWM, 50),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_G4, 0, 0),
|
||||
ROW_MAKE(0, EffectPWM, 55),
|
||||
ROW_MAKE(0, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_D5, 0, 0),
|
||||
ROW_MAKE(0, EffectPWM, 55),
|
||||
ROW_MAKE(0, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_C6, 0, 0),
|
||||
ROW_MAKE(NOTE_E3, EffectSlideDown, 0x30),
|
||||
ROW_MAKE(NOTE_A2, 0, 0),
|
||||
ROW_MAKE(NOTE_E3, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_B4, 0, 0),
|
||||
ROW_MAKE(NOTE_E3, 0, 0),
|
||||
ROW_MAKE(NOTE_B4, EffectPWM, 50),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_G4, 0, 0),
|
||||
ROW_MAKE(0, 0, 0),
|
||||
ROW_MAKE(0, 0, 0),
|
||||
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
|
||||
//
|
||||
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(1, 1)),
|
||||
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
|
||||
ROW_MAKE(0, EffectVibrato, EFFECT_DATA_2(2, 2)),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Channel p_2_channels[] = {
|
||||
{
|
||||
.rows =
|
||||
{
|
||||
//
|
||||
ROW_MAKE(NOTE_C5, EffectArpeggio, EFFECT_DATA_2(4, 7)),
|
||||
ROW_MAKE(NOTE_E3, 0, 0),
|
||||
ROW_MAKE(NOTE_A2, 0, 0),
|
||||
ROW_MAKE(NOTE_E3, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_C5, 0, 0),
|
||||
ROW_MAKE(NOTE_A4, 0, 0),
|
||||
ROW_MAKE(NOTE_C5, 0, 0),
|
||||
ROW_MAKE(NOTE_A4, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_C5, EffectPWM, 55),
|
||||
ROW_MAKE(NOTE_A4, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_C5, EffectPWM, 35),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_C5, 0, 0),
|
||||
ROW_MAKE(NOTE_A4, 0, 0),
|
||||
ROW_MAKE(NOTE_C5, EffectPWM, 55),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_D5, 0, 0),
|
||||
ROW_MAKE(NOTE_E3, EffectSlideDown, 0x30),
|
||||
ROW_MAKE(NOTE_A2, 0, 0),
|
||||
ROW_MAKE(NOTE_E3, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
ROW_MAKE(NOTE_E3, 0, 0),
|
||||
ROW_MAKE(NOTE_B4, EffectPWM, 55),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_D5, 0, 0),
|
||||
ROW_MAKE(NOTE_B4, 0, 0),
|
||||
ROW_MAKE(NOTE_D5, EffectPWM, 55),
|
||||
ROW_MAKE(NOTE_B4, EffectPWM, 55),
|
||||
//
|
||||
ROW_MAKE(NOTE_D5, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_B4, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_D5, EffectPWM, 35),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_D5, EffectArpeggio, EFFECT_DATA_2(4, 7)),
|
||||
ROW_MAKE(NOTE_E3, 0, 0),
|
||||
ROW_MAKE(NOTE_A2, 0, 0),
|
||||
ROW_MAKE(NOTE_E3, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_E5, 0, 0),
|
||||
ROW_MAKE(NOTE_C5, 0, 0),
|
||||
ROW_MAKE(NOTE_E5, 0, 0),
|
||||
ROW_MAKE(NOTE_C5, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_E5, EffectPWM, 55),
|
||||
ROW_MAKE(NOTE_C5, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_E5, EffectPWM, 35),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_E5, 0, 0),
|
||||
ROW_MAKE(NOTE_C5, 0, 0),
|
||||
ROW_MAKE(NOTE_E5, EffectPWM, 55),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_D5, 0, 0),
|
||||
ROW_MAKE(NOTE_E3, EffectSlideDown, 0x30),
|
||||
ROW_MAKE(NOTE_A2, 0, 0),
|
||||
ROW_MAKE(NOTE_E3, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
ROW_MAKE(NOTE_E3, 0, 0),
|
||||
ROW_MAKE(NOTE_B4, EffectPWM, 55),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_D5, 0, 0),
|
||||
ROW_MAKE(NOTE_B4, 0, 0),
|
||||
ROW_MAKE(NOTE_D5, EffectPWM, 55),
|
||||
ROW_MAKE(NOTE_B4, EffectPWM, 55),
|
||||
//
|
||||
ROW_MAKE(NOTE_D5, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_B4, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_D5, EffectPWM, 35),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Channel p_3_channels[] = {
|
||||
{
|
||||
.rows =
|
||||
{
|
||||
//
|
||||
ROW_MAKE(NOTE_Ds5, EffectArpeggio, EFFECT_DATA_2(4, 6)),
|
||||
ROW_MAKE(NOTE_C5, 0, 0),
|
||||
ROW_MAKE(NOTE_Ds5, 0, 0),
|
||||
ROW_MAKE(NOTE_C5, EffectPWM, 55),
|
||||
//
|
||||
ROW_MAKE(NOTE_Ds5, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_C5, EffectPWM, 35),
|
||||
ROW_MAKE(NOTE_Ds5, EffectPWM, 30),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_D5, 0, 0),
|
||||
ROW_MAKE(NOTE_B4, 0, 0),
|
||||
ROW_MAKE(NOTE_D5, 0, 0),
|
||||
ROW_MAKE(NOTE_B4, EffectPWM, 55),
|
||||
//
|
||||
ROW_MAKE(NOTE_D5, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_B4, EffectPWM, 35),
|
||||
ROW_MAKE(NOTE_D5, EffectPWM, 30),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_Cs5, EffectArpeggio, EFFECT_DATA_2(4, 6)),
|
||||
ROW_MAKE(NOTE_As4, 0, 0),
|
||||
ROW_MAKE(NOTE_Cs5, 0, 0),
|
||||
ROW_MAKE(NOTE_As4, EffectPWM, 55),
|
||||
//
|
||||
ROW_MAKE(NOTE_Cs5, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_As4, EffectPWM, 35),
|
||||
ROW_MAKE(NOTE_Cs5, EffectPWM, 30),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_C5, 0, 0),
|
||||
ROW_MAKE(NOTE_A4, 0, 0),
|
||||
ROW_MAKE(NOTE_C5, 0, 0),
|
||||
ROW_MAKE(NOTE_A4, EffectPWM, 55),
|
||||
//
|
||||
ROW_MAKE(NOTE_C5, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_A4, EffectPWM, 35),
|
||||
ROW_MAKE(NOTE_C5, EffectPWM, 30),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_B4, EffectArpeggio, EFFECT_DATA_2(4, 6)),
|
||||
ROW_MAKE(NOTE_Gs4, 0, 0),
|
||||
ROW_MAKE(NOTE_B4, 0, 0),
|
||||
ROW_MAKE(NOTE_Gs4, EffectPWM, 55),
|
||||
//
|
||||
ROW_MAKE(NOTE_B4, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_Gs4, EffectPWM, 35),
|
||||
ROW_MAKE(NOTE_B4, EffectPWM, 30),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_C5, 0, 0),
|
||||
ROW_MAKE(NOTE_A4, 0, 0),
|
||||
ROW_MAKE(NOTE_C5, 0, 0),
|
||||
ROW_MAKE(NOTE_A4, EffectPWM, 55),
|
||||
//
|
||||
ROW_MAKE(NOTE_C5, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_A4, EffectPWM, 35),
|
||||
ROW_MAKE(NOTE_C5, EffectPWM, 30),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_Cs5, EffectArpeggio, EFFECT_DATA_2(4, 6)),
|
||||
ROW_MAKE(NOTE_As4, 0, 0),
|
||||
ROW_MAKE(NOTE_Cs5, 0, 0),
|
||||
ROW_MAKE(NOTE_As4, EffectPWM, 55),
|
||||
//
|
||||
ROW_MAKE(NOTE_Cs5, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_As4, EffectPWM, 35),
|
||||
ROW_MAKE(NOTE_Cs5, EffectPWM, 30),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
//
|
||||
ROW_MAKE(NOTE_D5, 0, 0),
|
||||
ROW_MAKE(NOTE_B4, 0, 0),
|
||||
ROW_MAKE(NOTE_D5, 0, 0),
|
||||
ROW_MAKE(NOTE_B4, EffectPWM, 55),
|
||||
//
|
||||
ROW_MAKE(NOTE_D5, EffectPWM, 45),
|
||||
ROW_MAKE(NOTE_B4, EffectPWM, 35),
|
||||
ROW_MAKE(NOTE_D5, EffectPWM, 30),
|
||||
ROW_MAKE(NOTE_OFF, 0, 0),
|
||||
},
|
||||
},
|
||||
};
|
||||
Pattern patterns[] = {
|
||||
{.channels = p_0_channels},
|
||||
{.channels = p_1_channels},
|
||||
{.channels = p_2_channels},
|
||||
{.channels = p_3_channels},
|
||||
};
|
||||
|
||||
uint8_t order_list[] = {
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
3,
|
||||
};
|
||||
|
||||
Song song = {
|
||||
.channels_count = 1,
|
||||
.patterns_count = sizeof(patterns) / sizeof(patterns[0]),
|
||||
.patterns = patterns,
|
||||
|
||||
.order_list_size = sizeof(order_list) / sizeof(order_list[0]),
|
||||
.order_list = order_list,
|
||||
|
||||
.ticks_per_second = 60,
|
||||
};
|
||||
|
||||
void tracker_message(TrackerMessage message, void* context) {
|
||||
FuriMessageQueue* queue = context;
|
||||
furi_assert(queue);
|
||||
furi_message_queue_put(queue, &message, 0);
|
||||
}
|
||||
|
||||
int32_t zero_tracker_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
FuriMessageQueue* queue = furi_message_queue_alloc(8, sizeof(TrackerMessage));
|
||||
Tracker* tracker = tracker_alloc();
|
||||
tracker_set_message_callback(tracker, tracker_message, queue);
|
||||
tracker_set_song(tracker, &song);
|
||||
tracker_start(tracker);
|
||||
|
||||
while(1) {
|
||||
TrackerMessage message;
|
||||
FuriStatus status = furi_message_queue_get(queue, &message, portMAX_DELAY);
|
||||
if(status == FuriStatusOk) {
|
||||
if(message.type == TrackerPositionChanged) {
|
||||
uint8_t order_list_index = message.data.position.order_list_index;
|
||||
uint8_t row = message.data.position.row;
|
||||
uint8_t pattern = song.order_list[order_list_index];
|
||||
FURI_LOG_I("Tracker", "O:%d P:%d R:%d", order_list_index, pattern, row);
|
||||
} else if(message.type == TrackerEndOfSong) {
|
||||
FURI_LOG_I("Tracker", "End of song");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracker_stop(tracker);
|
||||
tracker_free(tracker);
|
||||
furi_message_queue_free(queue);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 136 B |
Reference in New Issue
Block a user