Skip to content

Embed Presentation

Do What

music box สำหรับ gen เพลงโดยมีการอ้างอิง pattern มาจากnoteของuser

Do Why

คิง: ชอบเกี่ยวกับเพลง โอ: อยากได้something ที่เกี่ยวข้อกับAI (อยากทำเกี่ยวกับAI) +ทั้งคิงและโอรู้สึกว่ามันดูcreativeดี
Hardware devices
devices
Quantity
esp32 node32 lite
1
MAX98357
1
Speaker 3W
1
Keypad matrix 4*4
1
There are no rows in this table

Schematic

Screenshot 2025-11-04 at 11.19.13 PM.png
Pin
devices
Pin
Duty
Keypad matrix4*4
IO14, IO27, IO19, IO18 (rowPin1-4) IO4, IO13, IO32, IO33 (colPin5-8)
I/O Pin
MAX98357
IO25 (LRCLK), IO26 (BCLK), IO16 (DIN), GND, VCC(5V)
I/O Pin
Speaker 3W
VCC(+), GND(-)
-
There are no rows in this table

Architect Overview

Script.png

Component

Esp32 (Client)
HW Interface: Keypad, I2S Audio
Processing: Multi-threading
Core 0: Request API
Core 1: Play sound (High Priority)
Communication: HTTPS (nissorn.codes) → Wifi with TLS/SSL
Safety: Watchdog, Mutex lock
Raspberry PI (Server)
AI Model: Markov Chain 4th-order (with PyTorch for ARM)
API: FastAPI RESTful web service
Deployment: Systemd service start auto
Cloud
CDN: Cloudflare global edge nw
Security: SSL/ TLS encryption


Firmware


4*4 Matrix

const byte ROWS = 4;
const byte COLS = 4;

char keys[ROWS][COLS] = {
{ '1', '2', '3', 'A' },
{ '4', '5', '6', 'B' },
{ '7', '8', '9', 'C' },
{ '*', '0', '#', 'D' }
};

byte rowPins[ROWS] = { 14, 27, 19, 18 };
byte colPins[COLS] = { 4, 13, 32, 33 };

I2S Configuration

i2s_config_t i2s_config = {
//ESP32 เป็น master (สร้าง clock), TX = ส่งข้อมูล
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
//ส่ง sample 16000 ตัวต่อวินาที
.sample_rate = 16000,
//แต่ละ sample ใช้ 16 bits
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
//สลับส่ง Left แล้ว Right
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
//ใช้ standard I2S protocol
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
//Interrupt priority level 1
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
//ใช้ 8 DMA buffers
.dma_buf_count = 8,

//แต่ละ buffer เก็บได้ 128 samples
.dma_buf_len = 128,
// ไม่ใช้ APLL (Audio PLL) ใช้ clock ปกติ
.use_apll = false,
// ล้าง descriptor อัตโนมัติหลังส่งเสร็จ
.tx_desc_auto_clear = true
};



Audio Generate

Sine Wave

image.png
void generateAndPlayTone() {
unsigned long elapsed = millis() - noteStartTime;
float envelope = 1.0;

// ADSR Envelope - Fade out
int fadeTime = min(80, currentNoteDuration / 4);
if (elapsed > currentNoteDuration - fadeTime) {
envelope = (float)(currentNoteDuration - elapsed) / fadeTime;
envelope = max(0.0f, min(1.0f, envelope));
}

float volumeMultiplier = getVolumeMultiplier();

// Generate samples
for (int i = 0; i < BUFFER_SIZE; i++) {
float angle = 2.0 * PI * currentFrequency * sampleCounter / SAMPLE_RATE;
int16_t sample = (int16_t)(sin(angle) * 20000 * envelope * volumeMultiplier);
audioBuffer[i] = sample;
sampleCounter++;
}

// Write to I2S
size_t bytesWritten = 0;
i2s_write(I2S_NUM_0, audioBuffer, BUFFER_SIZE * sizeof(int16_t), &bytesWritten, portMAX_DELAY);
}

1. Sine Wave Formula:
y(t) = A × sin(2πft)

Where:
- A = Amplitude (volume)
- f = Frequency (pitch in Hz)
- t = Time (in seconds)

2. Discrete Sampling:
Sample[n] = A × sin(2π × f × n / Fs)

Where:
- n = Sample number (0, 1, 2, ...)
- Fs = Sample rate (16000 Hz)

3. Example: 440Hz (A4) note
Sample[0] = 20000 × sin(2π × 440 × 0 / 16000) = 0
Sample[1] = 20000 × sin(2π × 440 × 1 / 16000) = 3457
Sample[2] = 20000 × sin(2π × 440 × 2 / 16000) = 6845
...

4. Volume Control:
Logarithmic scale (human hearing):
Volume 0 = -∞ dB (silence)
Volume 5 = -10 dB (half loudness)
Volume 10 = 0 dB (full)

Multiplier = 10^((volume-10)/10)

Examples:
Vol 0: 10^(-10/10) = 0.0 (mute)
Vol 5: 10^(-5/10) = 0.316 (-10dB)
Vol 10: 10^(0/10) = 1.0 (full)

Multitask

// ESP32 FreeRTOS Configuration
#define configTICK_RATE_HZ 1000 // 1000 ticks per second = 1ms per tick
#define configUSE_PREEMPTION 1 // Enable preemptive scheduling
#define configUSE_TIME_SLICING 1 // Enable round-robin for equal priority
Task States:
┌─────────┐ vTaskDelay() ┌─────────┐
│ Running │───────────────→│ Blocked │
└────┬────┘ └────┬────┘
│ │
│ vTaskResume() │ Timeout
│ │
↓ ↓
┌─────────┐ ┌─────────┐
│ Ready │←───────────────│Suspended│
└─────────┘ vTaskSuspend()└─────────┘

- 1: Background tasks (HTTP request)
- 3: Real-time tasks (Audio playback)

Request (Core 0)

void requestTask(void* parameter) {
Serial.printf("[Core %d] 🧵 Request Task started\\n", xPortGetCoreID()); // ตรวจสอบว่ารันบน Core 0
// Phase 1: Initial buffer fill (เติมบัฟเฟอร์ให้เต็ม 90% ก่อนเริ่มเล่น)
while (getBufferCount() < AMBIENT_BUFFER_SIZE * 0.9 && requestTaskRunning) {
if (!isRequestingNewChunk && WiFi.status() == WL_CONNECTED) {
requestAmbientChunk(false);
vTaskDelay(500 / portTICK_PERIOD_MS); // หน่วง 500ms หลังร้องขอ
}
vTaskDelay(100 / portTICK_PERIOD_MS); // หน่วง 100ms ตรวจสอบสถานะ
}
Serial.println("✅ Initial buffer filled!");
// Phase 2: Maintenance loop (วนรักษาระดับบัฟเฟอร์)
while (requestTaskRunning) {
if (currentMode == MODE_AMBIENT || currentMode == MODE_PLAYING_SEED) {
if (shouldRequestNewChunk()) {
requestAmbientChunk(false);
}
vTaskDelay(200 / portTICK_PERIOD_MS); // หน่วง 200ms ในโหมดปกติ
} else {
vTaskDelay(1000 / portTICK_PERIOD_MS); // หน่วงนานขึ้นเมื่อไม่ได้ใช้งาน
}
}
vTaskDelete(NULL); // ลบตัวเองเมื่อจบการทำงาน
}

Playback (Core1)

void playbackTask(void* parameter) {
Serial.printf("[Core %d] 🧵 Playback Task started\\n", xPortGetCoreID()); // ตรวจสอบว่ารันบน Core 1
while (playbackTaskRunning) {
if (currentMode == MODE_AMBIENT || currentMode == MODE_PLAYING_SEED) {
if (isPlaying) {
// Continue current note
if (millis() - noteStartTime >= currentNoteDuration) {
Want to print your doc?
This is not the way.
Try clicking the ··· in the right corner or using a keyboard shortcut (
CtrlP
) instead.