# Mic Visualizer

## Lab 4: Microphone Visualizer

### Part 3 - Oscilloscope & Signal Processing

***

### 1. โครงสร้างภาพรวมของ Lab

#### Why? - ทำไมต้องเรียนรู้เรื่องนี้

* **Audio Level Monitoring**: VU Meter เป็นเครื่องมือพื้นฐานในทุกระบบที่เกี่ยวกับเสียง ตั้งแต่สตูดิโอบันทึกเสียงจนถึงระบบ PA ในอาคาร
* **Peak Detection**: การตรวจจับค่า Peak เป็นเทคนิคสำคัญในระบบ Power Systems (surge detection), Audio (clipping prevention) และ Vibration Monitoring
* **Threshold-based Alarming**: ระบบเตือนภัยในโรงงาน (noise level > 85 dBA), ระบบ Power Quality (voltage sag/swell) ใช้หลักการ threshold เดียวกัน
* **Real-time Data Streaming**: Pattern การรับข้อมูลต่อเนื่องจาก sensor แล้วแสดงผลแบบ real-time ใช้ได้กับทุก sensor ไม่เฉพาะ microphone

#### What? - จะได้เรียนรู้อะไร

1. **Simulated Mic Input**: จำลองสัญญาณ PDM Microphone (เพราะ PC Sim ไม่มี mic จริง)
2. **VU Meter Display**: สร้าง Level Meter ด้วย `lv_bar` พร้อมสี gradient
3. **Peak Detection & Hold**: ตรวจจับค่า Peak แล้วค้างไว้ก่อนลดลง
4. **Color Threshold**: Green (safe) / Yellow (warning) / Red (critical)
5. **Clipping Detection**: ตรวจจับเมื่อสัญญาณเกิน threshold

#### How? - จะทำอย่างไร

1. จำลองข้อมูล Microphone ด้วย Sine + Noise (amplitude แปรผัน)
2. สร้าง Waveform chart แสดง signal
3. สร้าง VU Meter Bar แนวตั้ง พร้อมสี zone
4. คำนวณ Peak-to-Peak, RMS, Level % แบบ real-time
5. เพิ่ม Peak Hold indicator และ Clipping warning

<figure><img src="/files/CG1Go1B8spq9uN8u8kAT" alt=""><figcaption></figcaption></figure>

***

### 2. หลักการทำงานและ Flowchart

#### 2.1 PDM Microphone

```
┌─────────────────────────────────────────────────────────────┐
│                   PDM MICROPHONE                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   PDM = Pulse Density Modulation                            │
│                                                             │
│   Sound → PDM Mic → 1-bit stream → Decimation → 16-bit PCM  │
│                                                             │
│   ┌─────┐     ┌──────────┐     ┌───────────┐                │
│   │ Mic │────▶│PDM Data  │────▶│ Decimator │────▶ Samples   │
│   └─────┘     │(1-bit)   │     │ Filter    │     (int16_t)  │
│               └──────────┘     └───────────┘                │
│                                                             │
│   Output: 16-bit signed samples at configured sample rate   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

#### 2.2 VU Meter Concept

```
┌─────────────────────────────────────────────────────────────┐
│                     VU METER                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Peak-to-Peak → Level (%)                                  │
│                                                             │
│   Silent:    Vpp ≈ 0        Level ≈ 0%                      │
│   Quiet:     Vpp ≈ 5000     Level ≈ 8%                      │
│   Normal:    Vpp ≈ 20000    Level ≈ 30%                     │
│   Loud:      Vpp ≈ 50000    Level ≈ 75%                     │
│   Clipping:  Vpp = 65535    Level = 100%                    │
│                                                             │
│   ┌───────────────────────────────────────┐                 │
│   │████████████████░░░░░░░░░░░░░░░░░░░░░░ │  Level: 45%     │
│   └───────────────────────────────────────┘                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

***

### 3. ฟังก์ชันสำคัญ

#### 3.1 Simulated Mic API

<table><thead><tr><th width="251.47161865234375">Function</th><th>Description</th></tr></thead><tbody><tr><td><code>sim_mic_generate()</code></td><td>สร้างสัญญาณจำลอง microphone</td></tr><tr><td><code>calc_peak_to_peak()</code></td><td>คำนวณ Peak-to-Peak</td></tr><tr><td><code>calc_rms()</code></td><td>คำนวณ RMS</td></tr></tbody></table>

#### 3.2 VU Meter Functions

<table><thead><tr><th width="250.9176025390625">Function</th><th>Description</th></tr></thead><tbody><tr><td><code>update_vu_meter()</code></td><td>อัพเดทค่าและสี VU bar</td></tr><tr><td><code>update_peak_hold()</code></td><td>อัพเดท Peak Hold indicator</td></tr><tr><td><code>check_clipping()</code></td><td>ตรวจจับ clipping</td></tr></tbody></table>

***

### 4. Code เต็ม

#### 4.1 Constants and Global Variables

```c
#include "lvgl.h"
#include <math.h>
#include <stdlib.h>

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

#define MIC_BUFFER_SIZE     512
#define DISPLAY_POINTS      200
#define MIC_SAMPLE_RATE     22050
#define PEAK_HOLD_TIME_MS   2000
#define PEAK_DECAY_RATE     1
#define CLIP_THRESHOLD      90   /* % level ที่ถือว่า clipping */
#define MAX_AMPLITUDE       32767

/* Global variables */
static int16_t mic_buffer[MIC_BUFFER_SIZE];
static lv_obj_t *mic_chart = NULL;
static lv_chart_series_t *mic_series = NULL;
static lv_obj_t *vu_bar = NULL;
static lv_obj_t *level_label = NULL;
static lv_obj_t *peak_label = NULL;
static lv_obj_t *rms_label = NULL;
static lv_obj_t *clip_warning = NULL;
static lv_timer_t *mic_timer = NULL;

static uint8_t current_level = 0;
static uint8_t peak_level = 0;
static uint32_t peak_timestamp = 0;
static int16_t sim_volume = 15000;  /* Simulated volume */
static uint32_t sim_phase = 0;      /* Phase counter */
```

#### 4.2 Simulated Microphone

```c
/* จำลองสัญญาณ Microphone
 * ใช้ sine wave หลายความถี่ + noise + amplitude variation
 * เพื่อให้ดูเหมือนเสียงจริง
 */
static void sim_mic_generate(int16_t *buf, uint16_t count, int16_t volume)
{
    for (uint16_t i = 0; i < count; i++) {
        double t = (double)(sim_phase + i) / MIC_SAMPLE_RATE;

        /* สร้างสัญญาณจำลองเสียง (หลายความถี่) */
        double val = 0;
        val += 0.5 * sin(2.0 * M_PI * 330 * t);   /* E4 */
        val += 0.3 * sin(2.0 * M_PI * 440 * t);   /* A4 */
        val += 0.2 * sin(2.0 * M_PI * 660 * t);   /* E5 */

        /* Amplitude variation (เหมือนเสียงพูด - ดังเบาสลับ) */
        double envelope = 0.5 + 0.5 * sin(2.0 * M_PI * 3.0 * t);
        val *= envelope;

        /* เพิ่ม random noise */
        int16_t noise = (rand() % 2001) - 1000;  /* +/- 1000 */
        buf[i] = (int16_t)(val * volume) + (noise * volume / 32000);
    }
    sim_phase += count;
}

/* คำนวณ Peak-to-Peak */
static int16_t calc_peak_to_peak(const int16_t *buf, uint16_t count)
{
    int16_t min_val = buf[0];
    int16_t max_val = buf[0];
    for (uint16_t i = 1; i < count; i++) {
        if (buf[i] < min_val) min_val = buf[i];
        if (buf[i] > max_val) max_val = buf[i];
    }
    return max_val - min_val;
}

/* คำนวณ RMS */
static float calc_rms(const int16_t *buf, uint16_t count)
{
    double sum_sq = 0;
    for (uint16_t i = 0; i < count; i++) {
        sum_sq += (double)buf[i] * buf[i];
    }
    return (float)sqrt(sum_sq / count);
}
```

#### 4.3 VU Meter Update Functions

```c
/* อัพเดทสี VU bar ตามระดับ */
static void update_vu_color(uint8_t level)
{
    lv_color_t color;
    if (level <= 60) {
        color = lv_color_hex(0x00cc44);  /* Green */
    } else if (level <= 80) {
        color = lv_color_hex(0xffcc00);  /* Yellow */
    } else {
        color = lv_color_hex(0xff2222);  /* Red */
    }
    lv_obj_set_style_bg_color(vu_bar, color, LV_PART_INDICATOR);
}

/* อัพเดท Peak Hold */
static void update_peak_hold(uint8_t level)
{
    uint32_t now = lv_tick_get();

    if (level > peak_level) {
        peak_level = level;
        peak_timestamp = now;
    }

    /* Decay หลังจาก hold time */
    if (lv_tick_elaps(peak_timestamp) > PEAK_HOLD_TIME_MS) {
        if (peak_level > level + PEAK_DECAY_RATE) {
            peak_level -= PEAK_DECAY_RATE;
        } else {
            peak_level = level;
        }
    }

    if (peak_label) {
        lv_label_set_text_fmt(peak_label, "Peak: %d%%",
                              (int)peak_level);
    }
}

/* ตรวจจับ Clipping */
static void check_clipping(uint8_t level)
{
    if (!clip_warning) return;

    if (level >= CLIP_THRESHOLD) {
        lv_obj_remove_flag(clip_warning, LV_OBJ_FLAG_HIDDEN);
        /* กระพริบสีแดง */
        static bool blink = false;
        blink = !blink;
        lv_obj_set_style_text_color(clip_warning,
            blink ? lv_color_hex(0xff0000) : lv_color_hex(0xff8888), 0);
    } else {
        lv_obj_add_flag(clip_warning, LV_OBJ_FLAG_HIDDEN);
    }
}
```

#### 4.4 Timer Callback

```c
static void mic_timer_cb(lv_timer_t *timer)
{
    (void)timer;

    /* จำลองข้อมูลจาก Microphone */
    sim_mic_generate(mic_buffer, MIC_BUFFER_SIZE, sim_volume);

    /* อัพเดท Waveform chart */
    if (mic_chart && mic_series) {
        for (int i = 0; i < DISPLAY_POINTS; i++) {
            int idx = i * MIC_BUFFER_SIZE / DISPLAY_POINTS;
            /* Map int16_t (-32768..+32767) to 0..100 */
            int32_t val = ((int32_t)mic_buffer[idx] + MAX_AMPLITUDE)
                          * 100 / (2 * MAX_AMPLITUDE);
            if (val < 0) val = 0;
            if (val > 100) val = 100;
            lv_chart_set_value_by_id(mic_chart, mic_series, i, val);
        }
        lv_chart_refresh(mic_chart);
    }

    /* คำนวณ Level */
    int16_t vpp = calc_peak_to_peak(mic_buffer, MIC_BUFFER_SIZE);
    current_level = (uint8_t)((uint32_t)vpp * 100 / (2 * MAX_AMPLITUDE));
    if (current_level > 100) current_level = 100;

    /* คำนวณ RMS */
    float rms = calc_rms(mic_buffer, MIC_BUFFER_SIZE);

    /* อัพเดท VU Meter */
    lv_bar_set_value(vu_bar, current_level, LV_ANIM_ON);
    update_vu_color(current_level);
    update_peak_hold(current_level);
    check_clipping(current_level);

    /* อัพเดท Labels */
    if (level_label) {
        lv_label_set_text_fmt(level_label, "Level: %d%%",
                              (int)current_level);
    }
    if (rms_label) {
        lv_label_set_text_fmt(rms_label, "RMS: %d",
                              (int)rms);
    }
}
```

#### 4.5 Main Function

```c
void part3_ex4_mic_visualizer(void)
{
    lv_obj_t *scr = lv_screen_active();
    lv_obj_set_style_bg_color(scr, lv_color_hex(0x0a0a1e), 0);

    /* ===== Title ===== */
    lv_obj_t *title = lv_label_create(scr);
    lv_label_set_text(title, "Part 3 Ex4: Mic Visualizer");
    lv_obj_set_style_text_color(title, lv_color_hex(0x00ff88), 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 5);

    /* ===== Waveform Chart ===== */
    mic_chart = lv_chart_create(scr);
    lv_obj_set_size(mic_chart, 340, 140);
    lv_obj_align(mic_chart, LV_ALIGN_TOP_LEFT, 10, 28);
    lv_chart_set_type(mic_chart, LV_CHART_TYPE_LINE);
    lv_chart_set_point_count(mic_chart, DISPLAY_POINTS);
    lv_chart_set_range(mic_chart, LV_CHART_AXIS_PRIMARY_Y, 0, 100);
    lv_chart_set_div_line_count(mic_chart, 4, 5);

    lv_obj_set_style_bg_color(mic_chart, lv_color_hex(0x001a00), 0);
    lv_obj_set_style_line_color(mic_chart, lv_color_hex(0x003300),
                                LV_PART_MAIN);
    lv_obj_set_style_border_color(mic_chart, lv_color_hex(0x006600), 0);
    lv_obj_set_style_border_width(mic_chart, 1, 0);
    lv_obj_set_style_size(mic_chart, 0, 0, LV_PART_INDICATOR);

    mic_series = lv_chart_add_series(mic_chart,
                     lv_color_hex(0x00ff00), LV_CHART_AXIS_PRIMARY_Y);

    /* ===== VU Meter Bar (แนวตั้ง) ===== */
    vu_bar = lv_bar_create(scr);
    lv_obj_set_size(vu_bar, 35, 140);
    lv_bar_set_range(vu_bar, 0, 100);
    lv_bar_set_value(vu_bar, 0, LV_ANIM_OFF);
    lv_obj_align(vu_bar, LV_ALIGN_TOP_RIGHT, -15, 28);

    /* VU Bar styling */
    lv_obj_set_style_bg_color(vu_bar, lv_color_hex(0x1a1a2e), LV_PART_MAIN);
    lv_obj_set_style_bg_color(vu_bar, lv_color_hex(0x00cc44),
                              LV_PART_INDICATOR);
    lv_obj_set_style_border_color(vu_bar, lv_color_hex(0x444466), 0);
    lv_obj_set_style_border_width(vu_bar, 1, 0);
    lv_obj_set_style_radius(vu_bar, 4, 0);

    /* VU zone labels */
    lv_obj_t *vu_red = lv_label_create(scr);
    lv_label_set_text(vu_red, "R");
    lv_obj_set_style_text_color(vu_red, lv_color_hex(0xff4444), 0);
    lv_obj_align_to(vu_red, vu_bar, LV_ALIGN_OUT_RIGHT_TOP, 5, 10);

    lv_obj_t *vu_yel = lv_label_create(scr);
    lv_label_set_text(vu_yel, "Y");
    lv_obj_set_style_text_color(vu_yel, lv_color_hex(0xffcc00), 0);
    lv_obj_align_to(vu_yel, vu_bar, LV_ALIGN_OUT_RIGHT_MID, 5, -15);

    lv_obj_t *vu_grn = lv_label_create(scr);
    lv_label_set_text(vu_grn, "G");
    lv_obj_set_style_text_color(vu_grn, lv_color_hex(0x00cc44), 0);
    lv_obj_align_to(vu_grn, vu_bar, LV_ALIGN_OUT_RIGHT_BOTTOM, 5, -10);

    /* ===== Info Panel ===== */
    level_label = lv_label_create(scr);
    lv_label_set_text(level_label, "Level: 0%");
    lv_obj_set_style_text_color(level_label, lv_color_hex(0x00ff00), 0);
    lv_obj_align(level_label, LV_ALIGN_BOTTOM_LEFT, 10, -45);

    peak_label = lv_label_create(scr);
    lv_label_set_text(peak_label, "Peak: 0%");
    lv_obj_set_style_text_color(peak_label, lv_color_hex(0xffcc00), 0);
    lv_obj_align(peak_label, LV_ALIGN_BOTTOM_MID, 0, -45);

    rms_label = lv_label_create(scr);
    lv_label_set_text(rms_label, "RMS: 0");
    lv_obj_set_style_text_color(rms_label, lv_color_hex(0x888888), 0);
    lv_obj_align(rms_label, LV_ALIGN_BOTTOM_RIGHT, -10, -45);

    /* ===== Volume Control ===== */
    lv_obj_t *vol_lbl = lv_label_create(scr);
    lv_label_set_text(vol_lbl, "Volume:");
    lv_obj_set_style_text_color(vol_lbl, lv_color_hex(0xaaaaaa), 0);
    lv_obj_align(vol_lbl, LV_ALIGN_BOTTOM_LEFT, 10, -15);

    lv_obj_t *vol_slider = lv_slider_create(scr);
    lv_slider_set_range(vol_slider, 1000, 32000);
    lv_slider_set_value(vol_slider, sim_volume, LV_ANIM_OFF);
    lv_obj_set_width(vol_slider, 180);
    lv_obj_align(vol_slider, LV_ALIGN_BOTTOM_LEFT, 80, -15);
    lv_obj_add_event_cb(vol_slider, volume_slider_cb,
                        LV_EVENT_VALUE_CHANGED, NULL);

    /* ===== Clipping Warning ===== */
    clip_warning = lv_label_create(scr);
    lv_label_set_text(clip_warning, "! CLIP !");
    lv_obj_set_style_text_color(clip_warning, lv_color_hex(0xff0000), 0);
    lv_obj_set_style_text_font(clip_warning, &lv_font_montserrat_16, 0);
    lv_obj_align(clip_warning, LV_ALIGN_BOTTOM_RIGHT, -10, -15);
    lv_obj_add_flag(clip_warning, LV_OBJ_FLAG_HIDDEN);

    /* ===== Timer (50ms for smooth display) ===== */
    mic_timer = lv_timer_create(mic_timer_cb, 50, NULL);
}

/* Volume slider callback */
static void volume_slider_cb(lv_event_t *e)
{
    if (lv_event_get_code(e) != LV_EVENT_VALUE_CHANGED) return;
    lv_obj_t *slider = lv_event_get_target(e);
    sim_volume = (int16_t)lv_slider_get_value(slider);
}
```

***

### 5. องค์ความรู้และเทคนิค

#### 5.1 VU Meter vs Peak Meter

```c
/* VU Meter (Volume Unit):
 * - แสดงค่า average level (ballistic response ~300ms)
 * - ตอบสนองช้ากว่า peak
 * - ใช้ในการตั้งระดับเสียง (mixing console)
 *
 * Peak Meter:
 * - แสดงค่า peak สูงสุดทันที
 * - ตอบสนองเร็ว (< 1ms)
 * - ใช้ป้องกัน clipping (digital audio)
 *
 * ในระบบ Power Systems:
 * - เหมือน RMS Voltmeter vs Peak Voltmeter
 * - RMS ใช้คำนวณพลังงาน
 * - Peak ใช้ตรวจจับ transient/surge
 */
```

#### 5.2 Peak Hold Algorithm

```c
/* Peak Hold Pattern:
 *
 * 1. เก็บค่า peak สูงสุดที่เคยเกิด
 * 2. ค้างค่า peak ไว้ HOLD_TIME (เช่น 2 วินาที)
 * 3. หลังจาก hold time ให้ค่า peak ค่อยๆ ลดลง (decay)
 * 4. ถ้ามีค่าใหม่สูงกว่า ให้อัพเดท peak
 *
 * ใช้ได้กับ: audio level, voltage peak, current peak,
 * vibration peak, temperature peak
 */

/* Decay types: */
/* Linear decay: peak -= constant */
peak_level -= 1;

/* Exponential decay: peak *= factor */
peak_level = peak_level * 95 / 100;  /* 5% decay per update */
```

#### 5.3 Color Gradient for Level

```c
/* เทคนิคเปลี่ยนสีตามระดับ:
 *
 * Discrete zones (ง่าย):
 * Green < 60% < Yellow < 80% < Red
 *
 * Continuous gradient (สวยกว่า):
 * Interpolate RGB ตามค่า level
 */
lv_color_t level_to_color(uint8_t level)
{
    if (level < 60) {
        /* Green to Yellow (interpolate) */
        uint8_t r = (level * 255) / 60;
        return lv_color_make(r, 200, 0);
    } else if (level < 80) {
        /* Yellow to Red */
        uint8_t g = 200 - ((level - 60) * 200 / 20);
        return lv_color_make(255, g, 0);
    } else {
        return lv_color_make(255, 0, 0);  /* Red */
    }
}
```

#### 5.4 Timer Rate vs Responsiveness

```c
/* Timer rate กำหนด responsiveness ของ display:
 *
 * 200ms: อัพเดทช้า, CPU load ต่ำ (เพียงพอสำหรับ temperature)
 * 100ms: พอดี (10 Hz) สำหรับ level meter ทั่วไป
 *  50ms: smooth (20 Hz) สำหรับ audio visualization
 *  33ms: 30 fps สำหรับ waveform display
 *  16ms: 60 fps (max framerate ของ LVGL)
 *
 * Trade-off: ยิ่ง timer เร็ว ยิ่ง smooth แต่ CPU load สูง
 */
#define TIMER_PERIOD_MS  50  /* 20 Hz - ดีสำหรับ audio viz */
```

***

### 6. แบบฝึกหัด

#### Exercise 1: Audio Level Meter with Peak Hold Indicator

**โจทย์:** สร้าง VU Meter แบบมืออาชีพ:

* เพิ่ม Peak Hold indicator เป็นเส้นขีดบาง ๆ บน VU bar (ไม่ใช่แค่ text)
* สร้างเป็น object ขนาดเล็ก (เส้นสีขาว) ที่เลื่อนตำแหน่ง Y ตาม peak\_level
* เพิ่ม dB scale ข้าง VU bar (-40dB, -20dB, -10dB, -6dB, -3dB, 0dB)
* แปลง level% เป็น dB: `dB = 20 * log10(level/100)`

**คำใบ้:**

```c
/* Peak indicator object */
static lv_obj_t *peak_indicator = NULL;
peak_indicator = lv_obj_create(scr);
lv_obj_set_size(peak_indicator, 35, 3);  /* เส้นบาง */
lv_obj_set_style_bg_color(peak_indicator, lv_color_hex(0xffffff), 0);

/* อัพเดทตำแหน่ง Y ตาม peak_level */
int16_t y_offset = 140 - (peak_level * 140 / 100);
lv_obj_align_to(peak_indicator, vu_bar, LV_ALIGN_TOP_LEFT, 0, y_offset);
```

#### Exercise 2: Clipping Detector with History

**โจทย์:** สร้างระบบตรวจจับ Clipping พร้อมประวัติ:

* นับจำนวนครั้งที่เกิด clipping (clip count)
* แสดง clip count บนจอ
* เมื่อ clipping เกิด 3 ครั้งใน 5 วินาที ให้แสดง warning ใหญ่กลางจอ
* เพิ่มปุ่ม Reset เพื่อ clear clip count
* เปลี่ยน border สี chart เป็นสีแดงเมื่อ clipping

**คำใบ้:**

```c
static uint16_t clip_count = 0;
static uint32_t clip_times[10] = {0};  /* Circular buffer */
static uint8_t clip_idx = 0;

void record_clip(void) {
    clip_times[clip_idx] = lv_tick_get();
    clip_idx = (clip_idx + 1) % 10;
    clip_count++;
}

/* นับ clips ใน 5 วินาทีล่าสุด */
int count_recent_clips(uint32_t window_ms) {
    uint32_t now = lv_tick_get();
    int count = 0;
    for (int i = 0; i < 10; i++) {
        if (clip_times[i] > 0 && (now - clip_times[i]) < window_ms) {
            count++;
        }
    }
    return count;
}
```

***

### 7. References

* [PDM Microphones](https://www.analog.com/en/technical-articles/pdm-microphones.html)
* [VU Meter Standards](https://en.wikipedia.org/wiki/VU_meter)

***


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.aic-eec.com/interfacing-with-infineon-psoc-tm-edge/hmi-development/spectrum-analyzer/ux-ui-design/mic-visualizer.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
