فهرست منبع

work on audio

Sanjay Govind 10 ماه پیش
والد
کامیت
24677e511c
6فایلهای تغییر یافته به همراه351 افزوده شده و 93 حذف شده
  1. 3 3
      helpers/pof_usb.c
  2. 85 83
      helpers/pof_usb_xbox360.c
  3. 113 7
      virtual_portal.c
  4. 13 0
      virtual_portal.h
  5. 112 0
      wav_player_hal.c
  6. 25 0
      wav_player_hal.h

+ 3 - 3
helpers/pof_usb.c

@@ -47,9 +47,8 @@ static int32_t pof_thread_worker(void* context) {
         uint32_t now = furi_get_tick();
         uint32_t flags = furi_thread_flags_wait(EventAll, FuriFlagWaitAny, timeout);
         if(flags & EventRx) { //fast flag
-            UNUSED(pof_usb_receive);
 
-            if(virtual_portal->speaker) {
+            // if(virtual_portal->speaker) {
                 uint8_t buf[POF_USB_RX_MAX_SIZE];
                 len_data = pof_usb_receive(dev, buf, POF_USB_RX_MAX_SIZE);
                 // https://github.com/xMasterX/all-the-plugins/blob/dev/base_pack/wav_player/wav_player_hal.c
@@ -61,8 +60,9 @@ static int32_t pof_thread_worker(void* context) {
                     }
                     FURI_LOG_RAW_I("\r\n");
                     */
+                   virtual_portal_process_audio(virtual_portal, buf, len_data);
                 }
-            }
+            // }
 
             if(pof_usb->dataAvailable > 0) {
                 memset(tx_data, 0, sizeof(tx_data));

+ 85 - 83
helpers/pof_usb_xbox360.c

@@ -9,13 +9,13 @@
 #define POF_USB_VID (0x1430)
 #define POF_USB_PID (0x1F17)
 
-#define POF_USB_EP_IN  (0x81)
+#define POF_USB_EP_IN (0x81)
 #define POF_USB_EP_OUT (0x02)
-#define POF_USB_X360_AUDIO_EP_IN1  (0x83)
-#define POF_USB_X360_AUDIO_EP_OUT1  (0x04)
-#define POF_USB_X360_AUDIO_EP_IN2  (0x85)
-#define POF_USB_X360_AUDIO_EP_OUT2  (0x06)
-#define POF_USB_X360_PLUGIN_MODULE_EP_IN  (0x87)
+#define POF_USB_X360_AUDIO_EP_IN1 (0x83)
+#define POF_USB_X360_AUDIO_EP_OUT1 (0x04)
+#define POF_USB_X360_AUDIO_EP_IN2 (0x85)
+#define POF_USB_X360_AUDIO_EP_OUT2 (0x06)
+#define POF_USB_X360_PLUGIN_MODULE_EP_IN (0x87)
 
 #define POF_USB_ACTUAL_OUTPUT_SIZE 0x20
 
@@ -27,18 +27,18 @@ static const struct usb_string_descriptor dev_manuf_desc =
 static const struct usb_string_descriptor dev_product_desc =
     USB_ARRAY_DESC(0x53, 0x70, 0x79, 0x72, 0x6f, 0x20, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x00);
 static const struct usb_string_descriptor dev_security_desc =
-    USB_ARRAY_DESC(0x58, 0x62, 0x6f, 0x78, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 
-                   0x79, 0x20, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x20, 0x33, 0x2c, 0x20, 
-                   0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x31, 0x2e, 0x30, 0x30, 
-                   0x2c, 0x20, 0xa9, 0x20, 0x32, 0x30, 0x30, 0x35, 0x20, 0x4d, 0x69, 0x63, 
-                   0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 
-                   0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x20, 0x41, 0x6c, 0x6c, 0x20, 
-                   0x72, 0x69, 0x67, 0x68, 0x74, 0x73, 0x20, 0x72, 0x65, 0x73, 0x65, 0x72, 
+    USB_ARRAY_DESC(0x58, 0x62, 0x6f, 0x78, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74,
+                   0x79, 0x20, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x20, 0x33, 0x2c, 0x20,
+                   0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x31, 0x2e, 0x30, 0x30,
+                   0x2c, 0x20, 0xa9, 0x20, 0x32, 0x30, 0x30, 0x35, 0x20, 0x4d, 0x69, 0x63,
+                   0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f,
+                   0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x20, 0x41, 0x6c, 0x6c, 0x20,
+                   0x72, 0x69, 0x67, 0x68, 0x74, 0x73, 0x20, 0x72, 0x65, 0x73, 0x65, 0x72,
                    0x76, 0x65, 0x64, 0x2e);
 
 static usbd_respond pof_usb_ep_config(usbd_device* dev, uint8_t cfg);
 static usbd_respond
-    pof_hid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback);
+pof_hid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback);
 static void pof_usb_send(usbd_device* dev, uint8_t* buf, uint16_t len);
 static int32_t pof_usb_receive(usbd_device* dev, uint8_t* buf, uint16_t max_len);
 
@@ -52,13 +52,13 @@ static int32_t pof_thread_worker(void* context) {
 
     uint32_t len_data = 0;
     uint8_t tx_data[POF_USB_TX_MAX_SIZE] = {0};
-    uint32_t timeout = TIMEOUT_NORMAL; // FuriWaitForever; //ms
+    uint32_t timeout = TIMEOUT_NORMAL;  // FuriWaitForever; //ms
     uint32_t last = 0;
 
-    while(true) {
+    while (true) {
         uint32_t now = furi_get_tick();
         uint32_t flags = furi_thread_flags_wait(EventAll, FuriFlagWaitAny, timeout);
-        if(flags & EventRx) { //fast flag
+        if (flags & EventRx) {  // fast flag
 
             uint8_t buf[POF_USB_RX_MAX_SIZE];
             len_data = pof_usb_receive(dev, buf, POF_USB_RX_MAX_SIZE);
@@ -68,7 +68,7 @@ static int32_t pof_thread_worker(void* context) {
                 // prepend packet with xinput header
                 int send_len =
                     virtual_portal_process_message(virtual_portal, buf + 2, tx_data + 2);
-                if(send_len > 0) {
+                if (send_len > 0) {
                     tx_data[0] = 0x0b;
                     tx_data[1] = 0x14;
                     pof_usb_send(dev, tx_data, POF_USB_ACTUAL_OUTPUT_SIZE);
@@ -84,13 +84,14 @@ static int32_t pof_thread_worker(void* context) {
                 }
                 FURI_LOG_RAW_I("\r\n");
                 */
+                virtual_portal_process_audio(virtual_portal, buf + 2, len_data - 2);
             }
 
             // Check next status time since the timeout based one might be starved by incoming packets.
-            if(now > last + timeout) {
+            if (now > last + timeout) {
                 memset(tx_data, 0, sizeof(tx_data));
                 len_data = virtual_portal_send_status(virtual_portal, tx_data + 2);
-                if(len_data > 0) {
+                if (len_data > 0) {
                     tx_data[0] = 0x0b;
                     tx_data[1] = 0x14;
                     pof_usb_send(dev, tx_data, POF_USB_ACTUAL_OUTPUT_SIZE);
@@ -99,38 +100,38 @@ static int32_t pof_thread_worker(void* context) {
                 timeout = TIMEOUT_NORMAL;
             }
 
-            flags &= ~EventRx; // clear flag
+            flags &= ~EventRx;  // clear flag
         }
 
-        if(flags) {
-            if(flags & EventResetSio) {
+        if (flags) {
+            if (flags & EventResetSio) {
             }
-            if(flags & EventTxComplete) {
+            if (flags & EventTxComplete) {
                 pof_usb->tx_complete = true;
             }
 
-            if(flags & EventTxImmediate) {
+            if (flags & EventTxImmediate) {
                 pof_usb->tx_immediate = true;
-                if(pof_usb->tx_complete) {
+                if (pof_usb->tx_complete) {
                     flags |= EventTx;
                 }
             }
 
-            if(flags & EventTx) {
+            if (flags & EventTx) {
                 pof_usb->tx_complete = false;
                 pof_usb->tx_immediate = false;
             }
 
-            if(flags & EventExit) {
+            if (flags & EventExit) {
                 FURI_LOG_I(TAG, "exit");
                 break;
             }
         }
 
-        if(flags == (uint32_t)FuriFlagErrorISR) { // timeout
+        if (flags == (uint32_t)FuriFlagErrorISR) {  // timeout
             memset(tx_data, 0, sizeof(tx_data));
             len_data = virtual_portal_send_status(virtual_portal, tx_data + 2);
-            if(len_data > 0) {
+            if (len_data > 0) {
                 tx_data[0] = 0x0b;
                 tx_data[1] = 0x14;
                 pof_usb_send(dev, tx_data, POF_USB_ACTUAL_OUTPUT_SIZE);
@@ -168,7 +169,7 @@ static void pof_usb_deinit(usbd_device* dev) {
     usbd_reg_control(dev, NULL);
 
     PoFUsb* pof_usb = pof_cur;
-    if(!pof_usb || pof_usb->dev != dev) {
+    if (!pof_usb || pof_usb->dev != dev) {
         return;
     }
     pof_cur = NULL;
@@ -211,7 +212,7 @@ static void pof_usb_wakeup(usbd_device* dev) {
 
 static void pof_usb_suspend(usbd_device* dev) {
     PoFUsb* pof_usb = pof_cur;
-    if(!pof_usb || pof_usb->dev != dev) return;
+    if (!pof_usb || pof_usb->dev != dev) return;
 }
 
 static void pof_usb_rx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) {
@@ -231,34 +232,34 @@ static void pof_usb_tx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep)
 }
 
 static usbd_respond pof_usb_ep_config(usbd_device* dev, uint8_t cfg) {
-    switch(cfg) {
-    case 0: // deconfig
-        usbd_ep_deconfig(dev, POF_USB_EP_OUT);
-        usbd_ep_deconfig(dev, POF_USB_EP_IN);
-        usbd_reg_endpoint(dev, POF_USB_EP_OUT, NULL);
-        usbd_reg_endpoint(dev, POF_USB_EP_IN, NULL);
-        usbd_reg_endpoint(dev, POF_USB_X360_AUDIO_EP_IN1, NULL);
-        usbd_reg_endpoint(dev, POF_USB_X360_AUDIO_EP_IN2, NULL);
-        usbd_reg_endpoint(dev, POF_USB_X360_AUDIO_EP_OUT1, NULL);
-        usbd_reg_endpoint(dev, POF_USB_X360_AUDIO_EP_OUT2, NULL);
-        usbd_reg_endpoint(dev, POF_USB_X360_PLUGIN_MODULE_EP_IN, NULL);
-        usbd_ep_deconfig(dev, POF_USB_X360_AUDIO_EP_IN1);
-        usbd_ep_deconfig(dev, POF_USB_X360_AUDIO_EP_IN2);
-        usbd_ep_deconfig(dev, POF_USB_X360_AUDIO_EP_OUT1);
-        usbd_ep_deconfig(dev, POF_USB_X360_AUDIO_EP_OUT2);
-        usbd_ep_deconfig(dev, POF_USB_X360_PLUGIN_MODULE_EP_IN);
-        return usbd_ack;
-    case 1: // config
-        usbd_ep_config(dev, POF_USB_EP_IN, USB_EPTYPE_INTERRUPT, POF_USB_EP_IN_SIZE);
-        usbd_ep_config(dev, POF_USB_EP_OUT, USB_EPTYPE_INTERRUPT, POF_USB_EP_OUT_SIZE);
-        usbd_reg_endpoint(dev, POF_USB_EP_IN, pof_usb_tx_ep_callback);
-        usbd_reg_endpoint(dev, POF_USB_EP_OUT, pof_usb_rx_ep_callback);
-        usbd_ep_config(dev, POF_USB_X360_AUDIO_EP_IN1, USB_EPTYPE_INTERRUPT, POF_USB_EP_IN_SIZE);
-        usbd_ep_config(dev, POF_USB_X360_AUDIO_EP_IN2, USB_EPTYPE_INTERRUPT, POF_USB_EP_OUT_SIZE);
-        usbd_ep_config(dev, POF_USB_X360_AUDIO_EP_OUT1, USB_EPTYPE_INTERRUPT, POF_USB_EP_IN_SIZE);
-        usbd_ep_config(dev, POF_USB_X360_AUDIO_EP_OUT2, USB_EPTYPE_INTERRUPT, POF_USB_EP_OUT_SIZE);
-        usbd_ep_config(dev, POF_USB_X360_PLUGIN_MODULE_EP_IN, USB_EPTYPE_INTERRUPT, POF_USB_EP_OUT_SIZE);
-        return usbd_ack;
+    switch (cfg) {
+        case 0:  // deconfig
+            usbd_ep_deconfig(dev, POF_USB_EP_OUT);
+            usbd_ep_deconfig(dev, POF_USB_EP_IN);
+            usbd_reg_endpoint(dev, POF_USB_EP_OUT, NULL);
+            usbd_reg_endpoint(dev, POF_USB_EP_IN, NULL);
+            usbd_reg_endpoint(dev, POF_USB_X360_AUDIO_EP_IN1, NULL);
+            usbd_reg_endpoint(dev, POF_USB_X360_AUDIO_EP_IN2, NULL);
+            usbd_reg_endpoint(dev, POF_USB_X360_AUDIO_EP_OUT1, NULL);
+            usbd_reg_endpoint(dev, POF_USB_X360_AUDIO_EP_OUT2, NULL);
+            usbd_reg_endpoint(dev, POF_USB_X360_PLUGIN_MODULE_EP_IN, NULL);
+            usbd_ep_deconfig(dev, POF_USB_X360_AUDIO_EP_IN1);
+            usbd_ep_deconfig(dev, POF_USB_X360_AUDIO_EP_IN2);
+            usbd_ep_deconfig(dev, POF_USB_X360_AUDIO_EP_OUT1);
+            usbd_ep_deconfig(dev, POF_USB_X360_AUDIO_EP_OUT2);
+            usbd_ep_deconfig(dev, POF_USB_X360_PLUGIN_MODULE_EP_IN);
+            return usbd_ack;
+        case 1:  // config
+            usbd_ep_config(dev, POF_USB_EP_IN, USB_EPTYPE_INTERRUPT, POF_USB_EP_IN_SIZE);
+            usbd_ep_config(dev, POF_USB_EP_OUT, USB_EPTYPE_INTERRUPT, POF_USB_EP_OUT_SIZE);
+            usbd_reg_endpoint(dev, POF_USB_EP_IN, pof_usb_tx_ep_callback);
+            usbd_reg_endpoint(dev, POF_USB_EP_OUT, pof_usb_rx_ep_callback);
+            usbd_ep_config(dev, POF_USB_X360_AUDIO_EP_IN1, USB_EPTYPE_INTERRUPT, POF_USB_EP_IN_SIZE);
+            usbd_ep_config(dev, POF_USB_X360_AUDIO_EP_IN2, USB_EPTYPE_INTERRUPT, POF_USB_EP_OUT_SIZE);
+            usbd_ep_config(dev, POF_USB_X360_AUDIO_EP_OUT1, USB_EPTYPE_INTERRUPT, POF_USB_EP_IN_SIZE);
+            usbd_ep_config(dev, POF_USB_X360_AUDIO_EP_OUT2, USB_EPTYPE_INTERRUPT, POF_USB_EP_OUT_SIZE);
+            usbd_ep_config(dev, POF_USB_X360_PLUGIN_MODULE_EP_IN, USB_EPTYPE_INTERRUPT, POF_USB_EP_OUT_SIZE);
+            return usbd_ack;
     }
     return usbd_fail;
 }
@@ -302,7 +303,7 @@ struct XInputVibrationCapabilities_t {
     uint8_t left_motor;
     uint8_t right_motor;
     uint8_t padding_2[3];
-} __attribute__((packed)) ;
+} __attribute__((packed));
 
 struct XInputInputCapabilities_t {
     uint8_t rid;
@@ -329,8 +330,8 @@ static const struct usb_device_descriptor usb_pof_dev_descr_xbox_360 = {
     .idVendor = POF_USB_VID,
     .idProduct = POF_USB_PID,
     .bcdDevice = VERSION_BCD(1, 0, 0),
-    .iManufacturer = 1, // UsbDevManuf
-    .iProduct = 2, // UsbDevProduct
+    .iManufacturer = 1,  // UsbDevManuf
+    .iProduct = 2,       // UsbDevProduct
     .iSerialNumber = 0,
     .bNumConfigurations = 1,
 };
@@ -429,8 +430,8 @@ static const struct PoFUsbDescriptorXbox360 usb_pof_cfg_descr_x360 = {
         },
     .audio_desc =
         {0x1B, 0x21, 0x00, 0x01, 0x01, 0x01, POF_USB_X360_AUDIO_EP_IN1, 0x40, 0x01, POF_USB_X360_AUDIO_EP_OUT1,
-            0x20, 0x16, POF_USB_X360_AUDIO_EP_IN2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16,
-            POF_USB_X360_AUDIO_EP_OUT2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+         0x20, 0x16, POF_USB_X360_AUDIO_EP_IN2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16,
+         POF_USB_X360_AUDIO_EP_OUT2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
     .ep_in_audio1 =
         {
             .bLength = sizeof(struct usb_endpoint_descriptor),
@@ -509,7 +510,7 @@ static const struct PoFUsbDescriptorXbox360 usb_pof_cfg_descr_x360 = {
 
 /* Control requests handler */
 static usbd_respond
-    pof_hid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback) {
+pof_hid_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback) {
     UNUSED(callback);
     uint8_t wValueH = req->wValue >> 8;
     uint8_t wValueL = req->wValue & 0xFF;
@@ -533,18 +534,19 @@ static usbd_respond
         return usbd_ack;
     }
 
-    if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) ==
-           (USB_REQ_DEVICE | USB_REQ_STANDARD) && req->bRequest == USB_STD_GET_DESCRIPTOR) {
-        switch(wValueH) {
-        case USB_DTYPE_STRING:
-            if (wValueL == 4) {
-                dev->status.data_ptr = (uint8_t*)&dev_security_desc;
-                dev->status.data_count = dev_security_desc.bLength;
-                return usbd_ack;
-            }
-            return usbd_fail;
-        default:
-            return usbd_fail;
+    if (((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) ==
+            (USB_REQ_DEVICE | USB_REQ_STANDARD) &&
+        req->bRequest == USB_STD_GET_DESCRIPTOR) {
+        switch (wValueH) {
+            case USB_DTYPE_STRING:
+                if (wValueL == 4) {
+                    dev->status.data_ptr = (uint8_t*)&dev_security_desc;
+                    dev->status.data_count = dev_security_desc.bLength;
+                    return usbd_ack;
+                }
+                return usbd_fail;
+            default:
+                return usbd_fail;
         }
     }
     return usbd_fail;
@@ -566,15 +568,15 @@ PoFUsb* pof_usb_start_xbox360(VirtualPortal* virtual_portal) {
     pof_usb->usb.str_manuf_descr = (void*)&dev_manuf_desc;
     pof_usb->usb.str_prod_descr = (void*)&dev_product_desc;
     pof_usb->usb.str_serial_descr = NULL;
-    if(!furi_hal_usb_set_config(&pof_usb->usb, pof_usb)) {
+    if (!furi_hal_usb_set_config(&pof_usb->usb, pof_usb)) {
         FURI_LOG_E(TAG, "USB locked, can not start");
-        if(pof_usb->usb.str_manuf_descr) {
+        if (pof_usb->usb.str_manuf_descr) {
             free(pof_usb->usb.str_manuf_descr);
         }
-        if(pof_usb->usb.str_prod_descr) {
+        if (pof_usb->usb.str_prod_descr) {
             free(pof_usb->usb.str_prod_descr);
         }
-        if(pof_usb->usb.str_serial_descr) {
+        if (pof_usb->usb.str_serial_descr) {
             free(pof_usb->usb.str_serial_descr);
         }
 
@@ -586,7 +588,7 @@ PoFUsb* pof_usb_start_xbox360(VirtualPortal* virtual_portal) {
 }
 
 void pof_usb_stop_xbox360(PoFUsb* pof_usb) {
-    if(pof_usb) {
+    if (pof_usb) {
         furi_hal_usb_set_config(pof_usb->usb_prev, NULL);
     }
 }

+ 113 - 7
virtual_portal.c

@@ -1,4 +1,9 @@
 #include "virtual_portal.h"
+#include "wav_player_hal.h"
+#include "string.h"
+
+#include <stm32wbxx_ll_dma.h>
+#include <furi_hal.h>
 
 #define TAG "VirtualPortal"
 
@@ -26,6 +31,52 @@ static float lerp(float start, float end, float t) {
     return start + (end - start) * t;
 }
 
+static void wav_player_dma_isr(void* ctx) {
+    VirtualPortal* virtual_portal = (VirtualPortal*)ctx;
+    // half of transfer
+    if(LL_DMA_IsActiveFlag_HT1(DMA1)) {
+        LL_DMA_ClearFlag_HT1(DMA1);
+        if (!virtual_portal->playing_audio) {
+            return;
+        }
+        if (virtual_portal->count < SAMPLES_COUNT / 2) {
+            virtual_portal->playing_audio = false;
+            wav_player_speaker_stop();
+            return;
+        }
+        // fill first half of buffer
+        for (int i = 0; i < SAMPLES_COUNT / 2; i++) {
+            virtual_portal->audio_buffer[i] = *virtual_portal->tail;
+            if (++virtual_portal->tail == virtual_portal->end) {
+                virtual_portal->tail = virtual_portal->current_audio_buffer;
+            }
+            virtual_portal->count--;
+        }
+    }
+
+    // transfer complete
+    if(LL_DMA_IsActiveFlag_TC1(DMA1)) {
+        LL_DMA_ClearFlag_TC1(DMA1);
+
+        if (!virtual_portal->playing_audio) {
+            return;
+        }
+        if (virtual_portal->count < SAMPLES_COUNT / 2) {
+            virtual_portal->playing_audio = false;
+            wav_player_speaker_stop();
+            return;
+        }
+        // fill second half of buffer
+        for (int i = SAMPLES_COUNT / 2; i < SAMPLES_COUNT; i++) {
+            virtual_portal->audio_buffer[i] = *virtual_portal->tail;
+            if (++virtual_portal->tail == virtual_portal->end) {
+                virtual_portal->tail = virtual_portal->current_audio_buffer;
+            }
+            virtual_portal->count--;
+        }
+    }
+}
+
 void virtual_portal_tick(void* ctx) {
     VirtualPortal* virtual_portal = (VirtualPortal*)ctx;
     (void)virtual_portal;
@@ -36,7 +87,7 @@ void virtual_portal_tick(void* ctx) {
     uint32_t elapsed = furi_get_tick() - led->start_time;
     if (elapsed < led->delay) {
         float t_phase = fminf((float)elapsed / (float)led->delay, 1);
-        
+
         if (led->two_phase) {
             if (led->current_phase == 0) {
                 // Phase 1: Increase channels that need to go up, hold others constant
@@ -67,7 +118,7 @@ void virtual_portal_tick(void* ctx) {
             led->g = lerp(led->last_g, led->target_g, t_phase);
             led->b = lerp(led->last_b, led->target_b, t_phase);
         }
-        
+
         furi_hal_light_set(LightRed, led->r);
         furi_hal_light_set(LightGreen, led->g);
         furi_hal_light_set(LightBlue, led->b);
@@ -103,23 +154,23 @@ void queue_led_command(VirtualPortal* virtual_portal, int side, uint8_t r, uint8
             led = &virtual_portal->left;
             break;
     }
-    
+
     // Store current values as last values
     led->last_r = led->r;
     led->last_g = led->g;
     led->last_b = led->b;
-    
+
     // Set target values
     led->target_r = r;
     led->target_g = g;
     led->target_b = b;
-    
+
     if (duration) {
         // Determine if we need a two-phase transition
         bool increasing = (r > led->last_r) || (g > led->last_g) || (b > led->last_b);
         bool decreasing = (r < led->last_r) || (g < led->last_g) || (b < led->last_b);
         led->two_phase = increasing && decreasing;
-        
+
         // Set up transition parameters
         led->start_time = furi_get_tick();
         if (led->two_phase) {
@@ -128,7 +179,7 @@ void queue_led_command(VirtualPortal* virtual_portal, int side, uint8_t r, uint8
         } else {
             led->delay = duration;
         }
-        
+
         // Start in phase 0
         led->current_phase = 0;
         led->running = true;
@@ -158,11 +209,24 @@ VirtualPortal* virtual_portal_alloc(NotificationApp* notifications) {
     }
     virtual_portal->sequence_number = 0;
     virtual_portal->active = false;
+    virtual_portal->volume = 10.0f;
 
     virtual_portal->led_timer = furi_timer_alloc(virtual_portal_tick,
                                                  FuriTimerTypePeriodic, virtual_portal);
+    virtual_portal->head = virtual_portal->current_audio_buffer;
+    virtual_portal->tail = virtual_portal->current_audio_buffer;
+    virtual_portal->end = &virtual_portal->current_audio_buffer[SAMPLES_COUNT_BUFFERED];
 
     furi_timer_start(virtual_portal->led_timer, 10);
+    if(furi_hal_speaker_acquire(1000)) {
+        virtual_portal->got_speaker = true;
+        wav_player_speaker_init(8000);
+        wav_player_dma_init((uint32_t)virtual_portal->audio_buffer, SAMPLES_COUNT);
+
+        furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, wav_player_dma_isr, virtual_portal);
+
+        wav_player_dma_start();
+    }
 
     return virtual_portal;
 }
@@ -179,6 +243,13 @@ void virtual_portal_free(VirtualPortal* virtual_portal) {
     }
     furi_timer_stop(virtual_portal->led_timer);
     furi_timer_free(virtual_portal->led_timer);
+    if (virtual_portal->got_speaker) {
+        furi_hal_speaker_release();
+        wav_player_speaker_stop();
+        wav_player_dma_stop();
+    }
+    wav_player_hal_deinit();
+    furi_hal_interrupt_set_isr(FuriHalInterruptIdDma1Ch1, NULL, NULL);
 
     free(virtual_portal);
 }
@@ -470,6 +541,41 @@ int virtual_portal_write(VirtualPortal* virtual_portal, uint8_t* message, uint8_
     return 3;
 }
 
+void virtual_portal_process_audio(
+    VirtualPortal* virtual_portal,
+    uint8_t* message,
+    uint8_t len) {
+    for (size_t i = 0; i < len; i += 2) {
+        int16_t int_16 =
+            (((int16_t)message[i + 1]) + ((int16_t)message[i] << 8));
+
+        float data = (((float)int_16 / INT16_MAX) * 2) - 1;
+
+        data *= virtual_portal->volume;  // volume
+        data = tanhf(data);   // hyperbolic tangent limiter
+
+        data *= UINT8_MAX / 2;  // scale -128..127
+        data += UINT8_MAX / 2;  // to unsigned
+
+        if (data < 0) {
+            data = 0;
+        }
+
+        if (data > 255) {
+            data = 255;
+        }
+        *virtual_portal->head = data;
+        virtual_portal->count++;
+        if (++virtual_portal->head == virtual_portal->current_audio_buffer + sizeof(virtual_portal->current_audio_buffer)) {
+            virtual_portal->head = virtual_portal->current_audio_buffer;
+        }
+    }
+    if (!virtual_portal->playing_audio && virtual_portal->count > SAMPLES_COUNT / 2) {
+        wav_player_speaker_start();
+        virtual_portal->playing_audio = true;
+    }
+}
+
 // 32 byte message, 32 byte response;
 int virtual_portal_process_message(
     VirtualPortal* virtual_portal,

+ 13 - 0
virtual_portal.h

@@ -6,6 +6,8 @@
 #include "pof_token.h"
 
 #define POF_TOKEN_LIMIT 16
+#define SAMPLES_COUNT 1024
+#define SAMPLES_COUNT_BUFFERED SAMPLES_COUNT * 4
 
 typedef enum {
     PoFHid,
@@ -45,8 +47,17 @@ typedef struct {
 typedef struct {
     PoFToken* tokens[POF_TOKEN_LIMIT];
     uint8_t sequence_number;
+    float volume;
+    bool playing_audio;
+    uint8_t audio_buffer[SAMPLES_COUNT];
+    uint8_t current_audio_buffer[SAMPLES_COUNT_BUFFERED];
+    uint8_t* head;
+    uint8_t* tail;
+    uint8_t* end;
+    uint16_t count;
     bool active;
     bool speaker;
+    bool got_speaker;
     NotificationApp* notifications;
     PoFType type;
     VirtualPortalLed left;
@@ -66,5 +77,7 @@ int virtual_portal_process_message(
     VirtualPortal* virtual_portal,
     uint8_t* message,
     uint8_t* response);
+void virtual_portal_process_audio(VirtualPortal* virtual_portal,
+    uint8_t* message, uint8_t len);
 
 int virtual_portal_send_status(VirtualPortal* virtual_portal, uint8_t* response);

+ 112 - 0
wav_player_hal.c

@@ -0,0 +1,112 @@
+#include "wav_player_hal.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>
+
+//#define FURI_HAL_SPEAKER_TIMER TIM16
+
+#define FURI_HAL_SPEAKER_TIMER TIM16
+
+#define SAMPLE_RATE_TIMER TIM2
+
+#define FURI_HAL_SPEAKER_CHANNEL LL_TIM_CHANNEL_CH1
+#define DMA_INSTANCE DMA1, LL_DMA_CHANNEL_1
+
+void wav_player_speaker_init(uint32_t sample_rate) {
+    // Enable bus
+    furi_hal_bus_enable(FuriHalBusTIM2);
+
+    LL_TIM_InitTypeDef TIM_InitStruct = {0};
+    //TIM_InitStruct.Prescaler = 4;
+    TIM_InitStruct.Prescaler = 1;
+    TIM_InitStruct.Autoreload =
+        255; //in this fork used purely as PWM timer, the DMA now is triggered by SAMPLE_RATE_TIMER
+    LL_TIM_Init(FURI_HAL_SPEAKER_TIMER, &TIM_InitStruct);
+
+    LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0};
+    TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1;
+    TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE;
+    TIM_OC_InitStruct.CompareValue = 127;
+    LL_TIM_OC_Init(FURI_HAL_SPEAKER_TIMER, FURI_HAL_SPEAKER_CHANNEL, &TIM_OC_InitStruct);
+
+    //======================================================
+
+    TIM_InitStruct.Prescaler = 0;
+    //TIM_InitStruct.Autoreload = 1451; //64 000 000 / 1451 ~= 44100 Hz
+
+    TIM_InitStruct.Autoreload = SystemCoreClock / sample_rate - 1; //to support various sample rates
+
+    LL_TIM_Init(SAMPLE_RATE_TIMER, &TIM_InitStruct);
+
+    //LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0};
+    TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1;
+    TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE;
+    TIM_OC_InitStruct.CompareValue = 127;
+    LL_TIM_OC_Init(SAMPLE_RATE_TIMER, FURI_HAL_SPEAKER_CHANNEL, &TIM_OC_InitStruct);
+
+    //=========================================================
+    //configuring PA6 pin as TIM16 output
+
+    furi_hal_gpio_init_ex(
+        &gpio_ext_pa6,
+        GpioModeAltFunctionPushPull,
+        GpioPullNo,
+        GpioSpeedVeryHigh,
+        GpioAltFn14TIM16);
+}
+
+void wav_player_hal_deinit() {
+    furi_hal_gpio_init(&gpio_ext_pa6, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+
+    // Disable bus
+    furi_hal_bus_disable(FuriHalBusTIM2);
+}
+
+void wav_player_speaker_start() {
+    LL_TIM_EnableAllOutputs(FURI_HAL_SPEAKER_TIMER);
+    LL_TIM_EnableCounter(FURI_HAL_SPEAKER_TIMER);
+
+    LL_TIM_EnableAllOutputs(SAMPLE_RATE_TIMER);
+    LL_TIM_EnableCounter(SAMPLE_RATE_TIMER);
+}
+
+void wav_player_speaker_stop() {
+    LL_TIM_DisableAllOutputs(FURI_HAL_SPEAKER_TIMER);
+    LL_TIM_DisableCounter(FURI_HAL_SPEAKER_TIMER);
+
+    LL_TIM_DisableAllOutputs(SAMPLE_RATE_TIMER);
+    LL_TIM_DisableCounter(SAMPLE_RATE_TIMER);
+}
+
+void wav_player_dma_init(uint32_t address, size_t size) {
+    uint32_t dma_dst = (uint32_t) & (FURI_HAL_SPEAKER_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_BYTE);
+
+    LL_DMA_EnableIT_TC(DMA_INSTANCE);
+    LL_DMA_EnableIT_HT(DMA_INSTANCE);
+}
+
+void wav_player_dma_start() {
+    LL_DMA_EnableChannel(DMA_INSTANCE);
+    LL_TIM_EnableDMAReq_UPDATE(SAMPLE_RATE_TIMER);
+}
+
+void wav_player_dma_stop() {
+    LL_DMA_DisableChannel(DMA_INSTANCE);
+}
+

+ 25 - 0
wav_player_hal.h

@@ -0,0 +1,25 @@
+#pragma once
+#include <stdint.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void wav_player_speaker_init(uint32_t sample_rate);
+
+void wav_player_speaker_start();
+
+void wav_player_speaker_stop();
+
+void wav_player_dma_init(uint32_t address, size_t size);
+
+void wav_player_dma_start();
+
+void wav_player_dma_stop();
+
+void wav_player_hal_deinit();
+
+#ifdef __cplusplus
+}
+#endif