LTVA1 3 лет назад
Родитель
Сommit
0722fac264
7 измененных файлов с 532 добавлено и 84 удалено
  1. 161 84
      flizzer_tracker.c
  2. 113 0
      flizzer_tracker_hal.c
  3. 20 0
      flizzer_tracker_hal.h
  4. 38 0
      sound_engine/freqs.c
  5. 10 0
      sound_engine/freqs.h
  6. 137 0
      sound_engine/sound_engine.c
  7. 53 0
      sound_engine/sound_engine.h

+ 161 - 84
flizzer_tracker.c

@@ -1,11 +1,14 @@
 #include <stdio.h>
 #include <furi.h>
 #include <gui/gui.h>
+#include <notification/notification_messages.h>
 #include <input/input.h>
 #include <furi_hal.h>
 #include <u8g2_glue.h>
 #include <stm32wbxx_ll_tim.h>
 
+#include "flizzer_tracker_hal.h"
+
 /*
 Fontname: -Raccoon-Fixed4x6-Medium-R-Normal--6-60-75-75-P-40-ISO10646-1
 Copyright: 
@@ -45,14 +48,21 @@ typedef enum {
 typedef struct {
 	EventType type;
 	InputEvent input;
-} HelloWorldEvent;
+} FlizzerTrackerEvent;
 
 typedef struct 
 {
 	bool stop;
 	uint32_t counter;
 	uint32_t counter_2;
-} DirectDraw;
+	NotificationApp* notification;
+
+	SoundEngine sound_engine;
+
+	uint32_t frequency;
+	uint8_t current_waveform_index;
+	uint16_t pw;
+} FlizzerTrackerApp;
 
 #define FURI_HAL_SPEAKER_TIMER TIM2
 #define FURI_HAL_SPEAKER_CHANNEL LL_TIM_CHANNEL_CH1
@@ -64,9 +74,9 @@ void timer_draw_callback(void* ctx)
 		LL_TIM_ClearFlag_UPDATE(TIM2);
 	}
 
-	DirectDraw* instance = (DirectDraw*)ctx;
+	FlizzerTrackerApp* tracker = (FlizzerTrackerApp*)ctx;
 
-	instance->counter++;
+	tracker->counter++;
 	//return;
 }
 
@@ -77,25 +87,67 @@ void timer_draw_callback_2(void* ctx)
 		LL_TIM_ClearFlag_UPDATE(TIM1);
 	}
 
-	DirectDraw* instance = (DirectDraw*)ctx;
+	FlizzerTrackerApp* tracker = (FlizzerTrackerApp*)ctx;
 
-	instance->counter_2++;
+	tracker->counter_2++;
 	//return;
 }
 
-static void draw_callback(Canvas* canvas, void* ctx) 
+static void sound_engine_dma_isr(void* ctx)
 {
-	DirectDraw* instance = (DirectDraw*)ctx;
+    FlizzerTrackerApp* tracker = (FlizzerTrackerApp*)ctx;
+
+    // half of transfer
+    if(LL_DMA_IsActiveFlag_HT1(DMA1))
+	{
+        LL_DMA_ClearFlag_HT1(DMA1);
+        // fill first half of buffer
+        sound_engine_fill_buffer(&tracker->sound_engine, tracker->sound_engine.audio_buffer, tracker->sound_engine.audio_buffer_size / 2);
 
-	char buffer[20] = {0};
+		tracker->counter++;
+    }
+
+    // transfer complete
+    if(LL_DMA_IsActiveFlag_TC1(DMA1))
+	{
+        LL_DMA_ClearFlag_TC1(DMA1);
+        // fill second half of buffer
+        sound_engine_fill_buffer(&tracker->sound_engine, &tracker->sound_engine.audio_buffer[tracker->sound_engine.audio_buffer_size / 2], tracker->sound_engine.audio_buffer_size / 2); //&app->sample_buffer[index]
+
+		tracker->counter++;
+    }
+}
+
+const char* wave_names[5] = 
+{
+	"NONE",
+	"NOISE",
+	"PULSE",
+	"TRIANGE",
+	"SAWTOOTH",
+};
 
-	snprintf(buffer, 20, "FRAMES: %ld", instance->counter);
+static void draw_callback(Canvas* canvas, void* ctx) 
+{
+	FlizzerTrackerApp* tracker = (FlizzerTrackerApp*)ctx;
 
 	canvas_clear(canvas);
 
 	canvas_set_custom_font(canvas, u8g2_font_tom_thumb_4x6_tr);
+
+	char buffer[30] = {0};
+
+	snprintf(buffer, 20, "FREQUENCY: %ld Hz", tracker->frequency);
 	
 	canvas_draw_str(canvas, 0, 10, buffer);
+
+	snprintf(buffer, 20, "WAVEFORM: %s", wave_names[tracker->current_waveform_index]);
+	
+	canvas_draw_str(canvas, 0, 20, buffer);
+
+	snprintf(buffer, 20, "PULSE WIDTH: $%03X", tracker->pw);
+	
+	canvas_draw_str(canvas, 0, 30, buffer);
 }
 
 static void input_callback(InputEvent* input_event, void* ctx) 
@@ -104,87 +156,36 @@ static void input_callback(InputEvent* input_event, void* ctx)
 	furi_assert(ctx);
 	FuriMessageQueue* event_queue = ctx;
 
-	HelloWorldEvent event = {.type = EventTypeInput, .input = *input_event};
+	FlizzerTrackerEvent event = {.type = EventTypeInput, .input = *input_event};
 	furi_message_queue_put(event_queue, &event, FuriWaitForever);
 }
 
-static void direct_draw_run(DirectDraw* instance) 
+const uint8_t waveforms[5] = 
 {
-	vTaskPrioritySet(furi_thread_get_current_id(), FuriThreadPriorityIdle);
-
-	furi_hal_interrupt_set_isr_ex(FuriHalInterruptIdTim1UpTim16, 14, timer_draw_callback_2, (void*)instance);
-
-	LL_TIM_InitTypeDef TIM_InitStruct = {0};
-	// Prescaler to get 1kHz clock
-	TIM_InitStruct.Prescaler = 32768;
-	TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
-	// Auto reload to get freq Hz interrupt
-	TIM_InitStruct.Autoreload = 2500;
-
-	//TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
-	LL_TIM_Init(TIM1, &TIM_InitStruct);
-
-	LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0};
-	TIM_OC_InitStruct.CompareValue = 127;
-	TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1;
-	LL_TIM_OC_Init(TIM1, FURI_HAL_SPEAKER_CHANNEL, &TIM_OC_InitStruct);
-
-	LL_TIM_EnableIT_UPDATE(TIM1);
-	LL_TIM_EnableAllOutputs(TIM1);
-	LL_TIM_EnableCounter(TIM1);
-
-	furi_hal_interrupt_set_isr_ex(FuriHalInterruptIdTIM2, 15, timer_draw_callback, (void*)instance);
-
-	LL_TIM_InitTypeDef TIM_InitStruct2 = {0};
-	// Prescaler to get 1kHz clock
-	TIM_InitStruct2.Prescaler = 32768;
-	TIM_InitStruct2.CounterMode = LL_TIM_COUNTERMODE_UP;
-	// Auto reload to get freq Hz interrupt
-	TIM_InitStruct2.Autoreload = 200;
-	TIM_InitStruct2.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
-	LL_TIM_Init(TIM2, &TIM_InitStruct2);
-	LL_TIM_EnableIT_UPDATE(TIM2);
-	LL_TIM_EnableAllOutputs(TIM2);
-	LL_TIM_EnableCounter(TIM2);
-
-	///
-
-	bool unu = furi_hal_speaker_acquire(1000);
-	UNUSED(unu);
-
-	LL_TIM_InitTypeDef TIM_InitStruct3 = {0};
-	//TIM_InitStruct.Prescaler = 4;
-	TIM_InitStruct3.Prescaler = 200;
-	TIM_InitStruct3.Autoreload =
-		255; //in this fork used purely as PWM timer, the DMA now is triggered by SAMPLE_RATE_TIMER
-	LL_TIM_Init(TIM16, &TIM_InitStruct3);
-
-	LL_TIM_OC_InitTypeDef TIM_OC_InitStruct2 = {0};
-	TIM_OC_InitStruct2.OCMode = LL_TIM_OCMODE_PWM1;
-	TIM_OC_InitStruct2.OCState = LL_TIM_OCSTATE_ENABLE;
-	TIM_OC_InitStruct2.CompareValue = 127;
-	LL_TIM_OC_Init(TIM16, FURI_HAL_SPEAKER_CHANNEL, &TIM_OC_InitStruct2);
-	LL_TIM_EnableAllOutputs(TIM16);
-	LL_TIM_EnableCounter(TIM16);
-}
+	SE_WAVEFORM_NONE,
+	SE_WAVEFORM_NOISE,
+	SE_WAVEFORM_PULSE,
+	SE_WAVEFORM_TRIANGLE,
+	SE_WAVEFORM_SAW,
+};
 
 int32_t flizzer_tracker_app(void* p) 
 {
 	UNUSED(p);
 
-	// Текущее событие типа кастомного типа HelloWorldEvent
-	HelloWorldEvent event;
-	// Очередь событий на 8 элементов размера HelloWorldEvent
-	FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(HelloWorldEvent));
+	// Текущее событие типа кастомного типа FlizzerTrackerEvent
+	FlizzerTrackerEvent event;
+	// Очередь событий на 8 элементов размера FlizzerTrackerEvent
+	FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(FlizzerTrackerEvent));
 
-	DirectDraw* instance = malloc(sizeof(DirectDraw));
+	FlizzerTrackerApp* tracker = malloc(sizeof(FlizzerTrackerApp));
 
-	direct_draw_run(instance);
+	//direct_draw_run(tracker);
 
 	// Создаем новый view port
 	ViewPort* view_port = view_port_alloc();
 	// Создаем callback отрисовки, без контекста
-	view_port_draw_callback_set(view_port, draw_callback, instance);
+	view_port_draw_callback_set(view_port, draw_callback, tracker);
 	// Создаем callback нажатий на клавиши, в качестве контекста передаем
 	// нашу очередь сообщений, чтоб запихивать в неё эти события
 	view_port_input_callback_set(view_port, input_callback, event_queue);
@@ -194,7 +195,23 @@ int32_t flizzer_tracker_app(void* p)
 	// Подключаем view port к GUI в полноэкранном режиме
 	gui_add_view_port(gui, view_port, GuiLayerFullscreen);
 
-	
+	tracker->notification = furi_record_open(RECORD_NOTIFICATION);
+    notification_message(tracker->notification, &sequence_display_backlight_enforce_on);
+
+	furi_hal_interrupt_set_isr_ex(FuriHalInterruptIdDma1Ch1, 13, sound_engine_dma_isr, tracker);
+
+	//sound_engine_init(&tracker->sound_engine, 44100, false, 4096);
+	sound_engine_init(&tracker->sound_engine, 44100, true, 4096);
+
+	tracker->sound_engine.channel[0].waveform = SE_WAVEFORM_NOISE;
+	tracker->sound_engine.channel[0].pw = 0x200;
+
+	tracker->frequency = 440;
+	tracker->current_waveform_index = 1;
+
+	sound_engine_set_channel_frequency(&tracker->sound_engine, &tracker->sound_engine.channel[0], 440 * 256);
+
+	sound_engine_start();
 
 	// Бесконечный цикл обработки очереди событий
 	while(1) 
@@ -207,26 +224,84 @@ int32_t flizzer_tracker_app(void* p)
 		if(event.type == EventTypeInput) 
 		{
 			// Если нажата кнопка "назад", то выходим из цикла, а следовательно и из приложения
-			if(event.input.key == InputKeyBack) 
+			if(event.input.key == InputKeyBack && event.input.type == InputTypeShort) 
 			{
 				break;
 			}
+
+			if(event.input.key == InputKeyUp && event.input.type == InputTypeShort) 
+			{
+				tracker->frequency += 50;
+				sound_engine_set_channel_frequency(&tracker->sound_engine, &tracker->sound_engine.channel[0], tracker->frequency * 1024);
+				//break;
+			}
+
+			if(event.input.key == InputKeyDown && event.input.type == InputTypeShort) 
+			{
+				if(tracker->frequency > 50)
+				{
+					tracker->frequency -= 50;
+				}
+				sound_engine_set_channel_frequency(&tracker->sound_engine, &tracker->sound_engine.channel[0], tracker->frequency * 1024);
+				//break;
+			}
+
+			if(event.input.key == InputKeyRight && event.input.type == InputTypeShort) 
+			{
+				if(tracker->current_waveform_index < 4)
+				{
+					tracker->current_waveform_index++;
+				}
+				
+				tracker->sound_engine.channel[0].waveform = waveforms[tracker->current_waveform_index];
+				//break;
+			}
+
+			if(event.input.key == InputKeyLeft && event.input.type == InputTypeShort) 
+			{
+				if(tracker->current_waveform_index > 0)
+				{
+					tracker->current_waveform_index--;
+				}
+				
+				tracker->sound_engine.channel[0].waveform = waveforms[tracker->current_waveform_index];
+				//break;
+			}
+
+			if(event.input.key == InputKeyOk && event.input.type == InputTypeShort) 
+			{
+				if(tracker->pw + 0x100 < 0xFFF)
+				{
+					tracker->pw += 0x100;
+				}
+
+				else
+				{
+					tracker->pw = 0x100;
+				}
+				
+				tracker->sound_engine.channel[0].waveform = waveforms[tracker->current_waveform_index];
+				tracker->sound_engine.channel[0].pw = tracker->pw;
+				//break;
+			}
 		}
 	}
 
-	furi_hal_interrupt_set_isr_ex(FuriHalInterruptIdTIM2, 14, NULL, NULL);
-	furi_hal_interrupt_set_isr_ex(FuriHalInterruptIdTim1UpTim16, 15, NULL, NULL);
+	//furi_hal_interrupt_set_isr_ex(FuriHalInterruptIdTIM2, 14, NULL, NULL);
+	//furi_hal_interrupt_set_isr_ex(FuriHalInterruptIdTim1UpTim16, 15, NULL, NULL);
+
+	sound_engine_stop();
+	sound_engine_deinit(&tracker->sound_engine);
 
 	FURI_CRITICAL_ENTER();
 	LL_TIM_DeInit(TIM1);
 	LL_TIM_DeInit(TIM2);
 	LL_TIM_DeInit(TIM16);
 	FURI_CRITICAL_EXIT();
-	
-	furi_hal_speaker_release();
 
-	free(instance);
-	
+	notification_message(tracker->notification, &sequence_display_backlight_enforce_auto);
+    furi_record_close(RECORD_NOTIFICATION);
+
 	// Специальная очистка памяти, занимаемой очередью
 	furi_message_queue_free(event_queue);
 
@@ -235,5 +310,7 @@ int32_t flizzer_tracker_app(void* p)
 	view_port_free(view_port);
 	furi_record_close(RECORD_GUI);
 
+	free(tracker);
+
 	return 0;
 }

+ 113 - 0
flizzer_tracker_hal.c

@@ -0,0 +1,113 @@
+#include "flizzer_tracker_hal.h"
+
+#define SPEAKER_PWM_TIMER TIM16
+#define SAMPLE_RATE_TIMER TIM2
+#define TRACKER_ENGINE_TIMER TIM1
+
+#define SPEAKER_PWM_TIMER_CHANNEL LL_TIM_CHANNEL_CH1
+
+#define TIMER_BASE_CLOCK 64000000 /* CPU frequency, 64 MHz */
+
+#define DMA_INSTANCE DMA1, LL_DMA_CHANNEL_1
+
+void sound_engine_PWM_timer_init(bool external_audio_output) //external audio on pin PA6
+{
+	LL_TIM_InitTypeDef TIM_InitStruct = {0};
+	LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0};
+
+	TIM_InitStruct.Prescaler = 0;
+	TIM_InitStruct.Autoreload = 1023; //10-bit PWM resolution at around 60 kHz PWM rate
+	TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
+	LL_TIM_Init(SPEAKER_PWM_TIMER, &TIM_InitStruct);
+
+	TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1;
+	TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE;
+	TIM_OC_InitStruct.CompareValue = 0;
+	LL_TIM_OC_Init(SPEAKER_PWM_TIMER, SPEAKER_PWM_TIMER_CHANNEL, &TIM_OC_InitStruct);
+
+	if(external_audio_output)
+	{
+		furi_hal_gpio_init_ex(&gpio_ext_pa6, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedVeryHigh, GpioAltFn14TIM16);
+	}
+
+	else
+	{
+		bool unu = furi_hal_speaker_acquire(1000);
+		UNUSED(unu);
+	}
+}
+
+void sound_engine_timer_init(uint32_t sample_rate) //external audio on pin PA6
+{
+	LL_TIM_InitTypeDef TIM_InitStruct = {0};
+	LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0};
+
+	TIM_InitStruct.Prescaler = 0;
+	TIM_InitStruct.Autoreload = TIMER_BASE_CLOCK / sample_rate; //to support various sample rates
+	TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
+	LL_TIM_Init(SAMPLE_RATE_TIMER, &TIM_InitStruct);
+
+	TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1;
+	TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE;
+	LL_TIM_OC_Init(SAMPLE_RATE_TIMER, SPEAKER_PWM_TIMER_CHANNEL, &TIM_OC_InitStruct);
+}
+
+void sound_engine_dma_init(uint32_t address, uint32_t size)
+{
+	uint32_t dma_dst = (uint32_t) & (SPEAKER_PWM_TIMER->CCR1);
+
+	LL_DMA_ConfigAddresses(DMA_INSTANCE, address, dma_dst, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
+	LL_DMA_SetDataLength(DMA_INSTANCE, size);
+
+	LL_DMA_SetPeriphRequest(DMA_INSTANCE, LL_DMAMUX_REQ_TIM2_UP);
+	LL_DMA_SetDataTransferDirection(DMA_INSTANCE, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
+	LL_DMA_SetChannelPriorityLevel(DMA_INSTANCE, LL_DMA_PRIORITY_VERYHIGH);
+	LL_DMA_SetMode(DMA_INSTANCE, LL_DMA_MODE_CIRCULAR);
+	LL_DMA_SetPeriphIncMode(DMA_INSTANCE, LL_DMA_PERIPH_NOINCREMENT);
+	LL_DMA_SetMemoryIncMode(DMA_INSTANCE, LL_DMA_MEMORY_INCREMENT);
+	LL_DMA_SetPeriphSize(DMA_INSTANCE, LL_DMA_PDATAALIGN_HALFWORD);
+	LL_DMA_SetMemorySize(DMA_INSTANCE, LL_DMA_MDATAALIGN_HALFWORD);
+
+	LL_DMA_EnableIT_TC(DMA_INSTANCE);
+	LL_DMA_EnableIT_HT(DMA_INSTANCE);
+}
+
+void sound_engine_init_hardware(uint32_t sample_rate, bool external_audio_output, uint16_t* audio_buffer, uint32_t audio_buffer_size)
+{
+	sound_engine_dma_init((uint32_t)audio_buffer, audio_buffer_size);
+	sound_engine_timer_init(sample_rate);
+	sound_engine_PWM_timer_init(external_audio_output);
+}
+
+void sound_engine_dma_start()
+{
+	LL_DMA_EnableChannel(DMA_INSTANCE);
+	LL_TIM_EnableDMAReq_UPDATE(SAMPLE_RATE_TIMER);
+}
+
+void sound_engine_dma_stop()
+{
+	LL_DMA_DisableChannel(DMA_INSTANCE);
+}
+
+void sound_engine_start()
+{
+	LL_TIM_EnableAllOutputs(SAMPLE_RATE_TIMER);
+	LL_TIM_EnableCounter(SAMPLE_RATE_TIMER);
+
+	LL_TIM_EnableAllOutputs(SPEAKER_PWM_TIMER);
+	LL_TIM_EnableCounter(SPEAKER_PWM_TIMER);
+
+	sound_engine_dma_start();
+}
+
+void sound_engine_stop()
+{
+	LL_TIM_DisableAllOutputs(SAMPLE_RATE_TIMER);
+	LL_TIM_DisableCounter(SAMPLE_RATE_TIMER);
+
+	LL_TIM_DisableAllOutputs(SPEAKER_PWM_TIMER);
+	LL_TIM_DisableCounter(SPEAKER_PWM_TIMER);
+
+	sound_engine_dma_stop();
+}

+ 20 - 0
flizzer_tracker_hal.h

@@ -0,0 +1,20 @@
+#pragma once
+
+#include "sound_engine/sound_engine.h"
+
+#include <stm32wbxx_ll_tim.h>
+#include <stm32wbxx_ll_dma.h>
+#include <stm32wbxx_ll_gpio.h>
+
+#include <furi_hal.h>
+#include <furi_hal_gpio.h>
+#include <furi_hal_resources.h>
+
+void sound_engine_PWM_timer_init(bool external_audio_output);
+void sound_engine_timer_init(uint32_t sample_rate);
+void sound_engine_dma_init(uint32_t address, uint32_t size);
+void sound_engine_init_hardware(uint32_t sample_rate, bool external_audio_output, uint16_t* audio_buffer, uint32_t audio_buffer_size);
+void sound_engine_dma_start();
+void sound_engine_dma_stop();
+void sound_engine_start();
+void sound_engine_stop();

+ 38 - 0
sound_engine/freqs.c

@@ -0,0 +1,38 @@
+#include "freqs.h"
+
+const uint32_t frequency_table[FREQ_TAB_SIZE] = 
+{
+    (uint32_t)(2093.00 * 1024), //7th octave, the highest in this tracker
+	(uint32_t)(2217.46 * 1024), //frequency precision is 1 / 1024th of Hz
+	(uint32_t)(2349.32 * 1024),
+	(uint32_t)(2489.02 * 1024),
+	(uint32_t)(2637.02 * 1024),
+	(uint32_t)(2793.83 * 1024),
+	(uint32_t)(2959.96 * 1024),
+	(uint32_t)(3135.96 * 1024),
+	(uint32_t)(3322.44 * 1024),
+	(uint32_t)(3520.00 * 1024),
+	(uint32_t)(3729.31 * 1024),
+	(uint32_t)(3951.07 * 1024),
+};
+
+uint32_t get_freq(uint16_t note)
+{
+    if (note >= ((FREQ_TAB_SIZE * 8) << 8))
+	{
+		return frequency_table[FREQ_TAB_SIZE - 1];
+	}
+
+    if ((note & 0xff) == 0)
+	{
+		return frequency_table[((note >> 8) % 12)] / ((NUM_OCTAVES - 1) - ((note >> 8) / 12)); //wrap to one octave
+	}
+
+    else
+    {
+        uint64_t f1 = frequency_table[((note >> 8) % 12)] / ((NUM_OCTAVES - 1) - ((note >> 8) / 12));
+        uint64_t f2 = frequency_table[(((note >> 8) + 1) % 12)] / ((NUM_OCTAVES - 1) - (((note >> 8) + 1) / 12));
+
+        return f1 + (uint64_t)((f2 - f1) * (note & 0xff)) / 256;
+    }
+}

+ 10 - 0
sound_engine/freqs.h

@@ -0,0 +1,10 @@
+#pragma once
+
+#include <stdio.h>
+
+#define FREQ_TAB_SIZE 12 /* one octave */
+#define NUM_OCTAVES 8 /* 0-7th octaves */
+
+extern const uint32_t frequency_table[FREQ_TAB_SIZE];
+
+uint32_t get_freq(uint16_t note);

+ 137 - 0
sound_engine/sound_engine.c

@@ -0,0 +1,137 @@
+#include "sound_engine.h"
+#include "../flizzer_tracker_hal.h"
+
+#include <furi_hal.h>
+
+void sound_engine_init(SoundEngine* sound_engine, uint32_t sample_rate, bool external_audio_output, uint32_t audio_buffer_size)
+{
+	sound_engine->audio_buffer = malloc(audio_buffer_size * sizeof(sound_engine->audio_buffer[0]));
+	sound_engine->audio_buffer_size = audio_buffer_size;
+	sound_engine->sample_rate = sample_rate;
+	sound_engine->external_audio_output = external_audio_output;
+
+	for(int i = 0; i < NUM_CHANNELS; ++i)
+	{
+		sound_engine->channel[i].lfsr = RANDOM_SEED;
+	}
+
+	sound_engine_init_hardware(sample_rate, external_audio_output, sound_engine->audio_buffer, audio_buffer_size);
+}
+
+void sound_engine_deinit(SoundEngine* sound_engine)
+{
+	free(sound_engine->audio_buffer);
+
+	if(!(sound_engine->external_audio_output))
+	{
+		furi_hal_speaker_release();
+	}
+}
+
+void sound_engine_set_channel_frequency(SoundEngine* sound_engine, SoundEngineChannel* channel, uint32_t frequency)
+{
+	if(frequency != 0)
+	{
+		channel->frequency = (uint64_t)(ACC_LENGTH) / (uint64_t)1024 * (uint64_t)(frequency) / (uint64_t)sound_engine->sample_rate;
+	}
+
+	else
+	{
+		channel->frequency = 0;
+	}
+}
+
+static inline uint16_t sound_engine_pulse(uint32_t acc, uint32_t pw) //0-FFF pulse width range
+{
+	return (((acc >> (((uint32_t)ACC_BITS - 17))) >= ((pw == 0xfff ? pw + 1 : pw) << 4) ? (WAVE_AMP - 1) : 0));
+}
+
+
+static inline uint16_t sound_engine_saw(uint32_t acc) 
+{
+	return (acc >> (ACC_BITS - OUTPUT_BITS - 1)) & (WAVE_AMP - 1);
+}
+
+
+static inline uint16_t sound_engine_triangle(uint32_t acc)
+{
+	return ((((acc & (ACC_LENGTH / 2)) ? ~acc : acc) >> (ACC_BITS - OUTPUT_BITS - 2)) & (WAVE_AMP * 2 - 1));
+}
+
+inline static void shift_lfsr(uint32_t* v, uint32_t tap_0, uint32_t tap_1)
+{
+	typedef uint32_t T;
+	const T zero = (T)(0);
+	const T lsb = zero + (T)(1);
+	const T feedback = (
+		(lsb << (tap_0)) ^
+		(lsb << (tap_1))
+	);
+
+	*v = (*v >> 1) ^ ((zero - (*v & lsb)) & feedback);
+}
+
+uint16_t sound_engine_osc(SoundEngineChannel* channel, uint32_t prev_acc)
+{
+	switch(channel->waveform)
+	{
+		case SE_WAVEFORM_NOISE:
+		{
+			//return sound_engine_noise(channel->accumulator, prev_acc, &channel->lfsr);
+			if((prev_acc & (ACC_LENGTH / 32)) != (channel->accumulator & (ACC_LENGTH / 32)))
+			{
+				shift_lfsr(&channel->lfsr, 22, 17);
+				channel->lfsr &= (1 << (22 + 1)) - 1;
+			}
+
+			return (channel->lfsr) & (WAVE_AMP - 1);
+			
+			break;
+		}
+
+		case SE_WAVEFORM_PULSE:
+		{
+			return sound_engine_pulse(channel->accumulator, channel->pw);
+			break;
+		}
+
+		case SE_WAVEFORM_TRIANGLE:
+		{
+			return sound_engine_triangle(channel->accumulator);
+			break;
+		}
+		
+		case SE_WAVEFORM_SAW:
+		{
+			return sound_engine_saw(channel->accumulator);
+			break;
+		}
+	}
+
+	return 0;
+}
+
+void sound_engine_fill_buffer(SoundEngine* sound_engine, uint16_t* audio_buffer, uint32_t audio_buffer_size)
+{
+	for(uint32_t i = 0; i < audio_buffer_size; ++i)
+	{
+		uint16_t output = 0;
+
+		for(uint32_t chan = 0; chan < NUM_CHANNELS; ++chan)
+		{
+			SoundEngineChannel* channel = &sound_engine->channel[chan];
+
+			if(channel->frequency > 0)
+			{
+				uint32_t prev_acc = channel->accumulator;
+
+				channel->accumulator += channel->frequency;
+				channel->accumulator &= ACC_LENGTH - 1;
+
+				output += sound_engine_osc(channel, prev_acc);
+			}
+		}
+
+		audio_buffer[i] = (output >> (6 + 2)); //2 more bits so all channels fit
+	}
+}

+ 53 - 0
sound_engine/sound_engine.h

@@ -0,0 +1,53 @@
+#pragma once
+
+#include <stdio.h>
+#include <stdbool.h>
+
+#define NUM_CHANNELS 4
+
+#define RANDOM_SEED 0xf31782ce
+
+#define ACC_BITS 23
+#define ACC_LENGTH (1 << (ACC_BITS - 1))
+
+#define OUTPUT_BITS 16
+#define WAVE_AMP (1 << OUTPUT_BITS)
+
+typedef enum
+{
+	SE_WAVEFORM_NONE = 0,
+	SE_WAVEFORM_NOISE = 1,
+	SE_WAVEFORM_PULSE = 2,
+	SE_WAVEFORM_TRIANGLE = 4,
+	SE_WAVEFORM_SAW = 8,
+} SoudEngineWaveformType;
+
+typedef struct
+{
+	uint8_t a, d, s, r, volume;
+	uint32_t envelope, envelope_speed;
+} SoundEngineADSR;
+
+typedef struct
+{
+	uint32_t accumulator;
+	uint32_t frequency;
+	uint8_t waveform;
+	uint16_t pw;
+	uint32_t lfsr;
+	SoundEngineADSR adsr;
+} SoundEngineChannel;
+
+typedef struct
+{
+	SoundEngineChannel channel[NUM_CHANNELS];
+	uint32_t sample_rate;
+	uint16_t* audio_buffer;
+	uint32_t audio_buffer_size;
+	bool external_audio_output;
+} SoundEngine;
+
+void sound_engine_init(SoundEngine* sound_engine, uint32_t sample_rate, bool external_audio_output, uint32_t audio_buffer_size);
+void sound_engine_deinit(SoundEngine* sound_engine);
+void sound_engine_set_channel_frequency(SoundEngine* sound_engine, SoundEngineChannel* channel, uint32_t frequency);
+void sound_engine_fill_buffer(SoundEngine* sound_engine, uint16_t* audio_buffer, uint32_t audio_buffer_size);