Arduino Library for Muse

Hi you all,

I’ve tried to compile various C++ examples for my muse proto but it seems those which interest me, like the bluetooth speaker one, do not compile any longer (last change to the repo: ~3 years ago). That or I’m just bad at it.

So I decided to start from scratch using the esp-idf library, using PlatformIO in vscode (using esp-wrover-kit as a board, because I’ve seen a bunch of projects doing that too).

So far all I’d like is to get some sound, whatever sound, out of that board.

Here is my source file, can you spot anything wrong?
There are no compile errors nor runtime errors… And everything seems to proceed smoothly: audio buffers are written one after the other, it’s quick… I’ve tried a bunch of different pins too, with no luck there either.

Your help would be immensely appreciated, thanks in advance for any tips you may be able to provide!

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "driver/uart.h"
#include "driver/i2s_std.h"
#include "driver/gpio.h"
#include <math.h>
#include <vector>
#include <sstream>

#define I2S_BCLK   GPIO_NUM_5
#define I2S_LRC    GPIO_NUM_25
#define I2S_DOUT   GPIO_NUM_26
#define I2S_DIN    GPIO_NUM_35
#define BUF_SIZE   64

/** This is necessary for the linker to find the firmware's entry point */
extern "C" {
    void app_main(void);
}

#define SAMPLE_RATE     (44100)
#define PI              (3.14159265)

i2s_chan_handle_t tx_handle;

typedef std::vector<int16_t> VectorBuffer;

VectorBuffer generate_sine_wave(
    float sample_rate_Hz,
    float duration_s = 1.0f,
    float frequency = 440.0f,
    float amplitude = 1.0f
) {
    const auto samples_count = (int)(sample_rate_Hz * duration_s);
    VectorBuffer buffer(samples_count);

    for (int i = 0; i < samples_count - 1; i++) {
        const auto sample = (int16_t)(amplitude * INT16_MAX * sin((2 * PI * i * frequency) / sample_rate_Hz));
        buffer.at(i) = sample;
    }

    return buffer;
}

// Should play a F note for 0.1 second every second
void i2s_task(void *pvParameter) {
    while (1) {
        auto buffer = generate_sine_wave(
            SAMPLE_RATE,
            0.1f,
            440.0f,
            1.0f
        );

        ESP_LOGI("I2S_TASK", "Playing sine wave of %d samples", buffer.size());

        size_t bytes_written;

        for (;;) {
            if (buffer.empty()) {
                ESP_LOGD("I2S_TASK", "Buffer is empty, good.");
                break;
            }

            esp_err_t e = i2s_channel_write(tx_handle, buffer.data(), buffer.size() * sizeof(int16_t), &bytes_written, portMAX_DELAY);
            if (e != ESP_OK) {
                ESP_LOGE("I2S_TASK", "Error writing to I2S: %d", e);
                break;
            }

            if (bytes_written == 0) {
                // this should not happen
                ESP_LOGE("I2S_TASK", "No bytes written");
                break;
            }

            ESP_LOGI("I2S_TASK", "Wrote %d bytes", bytes_written);

            buffer.erase(buffer.begin(), buffer.begin() + bytes_written / sizeof(int16_t));
        };

        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void hello_task(void *pvParameter) {
    while (1) {
        ESP_LOGI("HELLO_TASK", "hello");
        vTaskDelay(500 / portTICK_PERIOD_MS);
    }
}

void app_main() {
    // UART configuration
    const uart_config_t uart_config = {
        .baud_rate = 115200,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        // rx_flow_ctrl_thresh is irrelevant in our case because we have
        // flow_ctrl set to UART_HW_FLOWCTRL_DISABLE, whatever that means
        .rx_flow_ctrl_thresh = 122,
        .source_clk = UART_SCLK_APB,
        .flags = UART_MODE_UART,
    };

    uart_param_config(UART_NUM_0, &uart_config);
    uart_driver_install(UART_NUM_0, 1024 * 2, 0, 0, NULL, 0);

    // I2S configuration
    i2s_std_config_t std_config = {
        .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
        .slot_cfg = I2S_STD_PCM_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
        .gpio_cfg = {
            .mclk = I2S_GPIO_UNUSED,
            .bclk = I2S_BCLK,
            .ws = I2S_LRC,
            .dout = I2S_DOUT,
            .din = I2S_DIN,
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv = false,
            }
        },
    };

    i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);

    i2s_new_channel(&chan_cfg, &tx_handle, NULL);

    i2s_channel_init_std_mode(tx_handle, &std_config);

    i2s_channel_enable(tx_handle);

    // Create the hello_task
    xTaskCreate(&hello_task, "hello_task", 2048, NULL, 5, NULL);

    // Create the i2s_task
    xTaskCreate(&i2s_task, "i2s_task", 2048, NULL, 5, NULL);
}

EDIT: grammar, code excerpt cleanup

Hi!

Try a simple example like this one :

Also in your code you don’t enable the amp GPIO_NUM_21

Feed this example in any AI it will make the code you need. We are in the process of making a library to ease Arduino/IDF devlopment.

Thanks a lot, I’ll try to get this to compile.

Do you have any examples using the latest version of esp-idf by any chance? (v5.3.1)

The one in the examples is rather outdated if I’m not mistaken.

Ok so now I have another issue :slight_smile:

The sketch you pointed me to works fine on the muse proto.

But since yesterday I’ve received my muse Luxe, and I can’t get this sketch to work… Here is is my updated code (maybe a bit hard to read because of the #ifdef’s, but I wanted to ensure that the base version still works on the proto so that I can know I haven’t damaged anything too much).

Any ideas? Did the pin change? Do I need to set the volume somehow? I’ve disabled the gain stuff because it crashed the Luxe.

Here’s the updated code:

#include "Arduino.h"
#include "SPIFFS.h"
#include "driver/i2s.h"
#include "driver/gpio.h"
#include <vector>

#define LUXE

#define I2SR (i2s_port_t)0
#define PW GPIO_NUM_21  // Amp power ON
#define GAIN GPIO_NUM_23
#define BLOCK_SIZE 128
#define TOUCH_PIN T9  // GPIO32

File wavFile;

const i2s_config_t i2s_configR = {
  .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX),
  .sample_rate = 44100,
  .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
  #ifdef LUXE
    .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
  #else
    .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
  #endif
  .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S),
  .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
  .dma_buf_count = 4,
  .dma_buf_len = BLOCK_SIZE
};

#ifdef LUXE
  i2s_pin_config_t pin_configR = {
    .bck_io_num = GPIO_NUM_5,
    .ws_io_num = GPIO_NUM_25,
    .data_out_num = GPIO_NUM_26,
    .data_in_num = GPIO_NUM_35
  };
#else
  // yes, actually they seem to be the same?
  i2s_pin_config_t pin_configR = {
    .bck_io_num = GPIO_NUM_5,
    .ws_io_num = GPIO_NUM_25,
    .data_out_num = GPIO_NUM_26,
    .data_in_num = GPIO_NUM_35
  };
#endif

void playWAV() {
  if (!wavFile) {
    Serial.println("No WAV file is open");
    return;
  }

  gpio_set_level(PW, 1);

  uint8_t buffer[BLOCK_SIZE];
  size_t bytesRead, bytesWritten;

  // Skip the WAV header
  wavFile.seek(44);

  while (wavFile.available()) {
    bytesRead = wavFile.read(buffer, BLOCK_SIZE);

    #ifdef LUXE
    std::vector<uint8_t> stereo_buffer(bytesRead * 2);
    for (size_t i = 0; i < bytesRead; i++) {
      stereo_buffer[i * 2] = buffer[i];
      stereo_buffer[i * 2 + 1] = buffer[i];
    }

    const auto err = i2s_write(
      I2SR, stereo_buffer.data(),
      stereo_buffer.size(),
      &bytesWritten,
      portMAX_DELAY
    );

    if (err != ESP_OK) {
      Serial.printf("i2s_write error: %d\n", err);
      abort();
    }
    #else
      const auto err = i2s_write(
        I2SR, buffer,
        bytesRead,
        &bytesWritten,
        portMAX_DELAY
      );

      if (err != ESP_OK) {
        Serial.printf("i2s_write error: %d\n", err);
        abort();
      }
    #endif
  }

  gpio_set_level(PW, 0);
}


void setup() {
  Serial.begin(115200);
  esp_err_t err;

  // Initialize SPIFFS
  if (!SPIFFS.begin(true)) {
    Serial.println("SPIFFS initialization failed");
    return;
  }
  Serial.println("SPIFFS initialized");


  // Find the first WAV file in the root directory
  File root = SPIFFS.open("/");
  wavFile = root.openNextFile();
  while (wavFile) {
    if (!wavFile.isDirectory() && String(wavFile.name()).endsWith(".wav")) {
      Serial.print("Found WAV file: ");
      Serial.println(wavFile.name());
      break;
    }
    wavFile = root.openNextFile();
  }

  if (!wavFile) {
    Serial.println("No WAV files found in spiff");
    return;
  }

  // Amp power enable
  gpio_reset_pin(PW);
  gpio_set_direction(PW, GPIO_MODE_OUTPUT);
  
  #ifndef LUXE
  gpio_reset_pin(GAIN);
  gpio_set_direction(GAIN, GPIO_MODE_OUTPUT);
  gpio_set_pull_mode(GAIN, GPIO_PULLDOWN_ONLY);
  #endif

  // I2S port0 init: TX, RX, mono, 16bits, 44100hz
  i2s_driver_install(I2SR, &i2s_configR, 0, NULL);
  i2s_set_pin(I2SR, &pin_configR);
  i2s_start(I2SR);


  #ifndef LUXE
  //Enable amp
  gpio_set_level(PW, 1);
  playWAV();
  gpio_set_level(PW, 0);
  #endif
}

void loop() {
  // touchDetachInterrupt(TOUCH_PIN);

  #ifndef LUXE
  //Enable amp
  gpio_set_level(PW, 1);
  #endif

  Serial.println("Playing WAV file...");
  playWAV();
  
  #ifndef LUXE
  gpio_set_level(PW, 0);
  #endif

  // touchAttachInterrupt(TOUCH_PIN, gotTouch, threshold);

  delay(1000); // Debounce delay
}

Thanks in advance!

check example such as:

Thanks, yes I’ve been looking at such examples, they usually do not compile and require significant work just to get running. Some of them were even too big to upload to my muse proto but maybe it’s more doable for the Luxe…

I’d love to be able to identify what’s really different between the muse proto and the muse luxe boards, it seems weird to me that I need to include a ton of libraries for a program which works fine on the proto to also work on the muse.

I see that many repositories include a muse_lib folder but there doesn’t seem to be a repository for it (or did I miss it?). Where should I take the muse_lib from to have the latest version?

Well if anyone is also interested in getting sound out of their speaker, I finally found two libraries which work, and they seem to be pretty elegantly written (I say two libraries because both of them are needed, it’s not a choice between the two):

Just use the pre-defined board AudioKitEs8388V2 board as an argument when an AudioBoard is called for, for instance in the sine wave playing example. It has the correct pins pre-defined and all.

If you’re using PlatformIO I’d suggest adding both libs to your lib directory as git submodules and to tell PlatformIO to look for them at this to your platformio.ini file:

lib_extra_dirs = lib

Thanks for sharing, we are all interested in new and easier ways to make code work for the Muse or Proto.

Sorry about the sarcastic tone, it had been a long and frustrating day of trial and error…

Hi,
I am working on an Arduino-type library for the Muse, starting with the Luxe speaker. It’s a work in progress, and I hope it will help ease development for it with the included examples:

I plan to add all the Muse family soon as well as forking the ES8388 lib to add recording examples.