1146 lines
44 KiB
Markdown
1146 lines
44 KiB
Markdown
|
|
Trò chuyện với Gemini
|
|
Hãy chuyển nội dung sau sang dạng markdown:
|
|
|
|
Kế Hoạch & Tài Liệu Kỹ Thuật: Porting DSP Engine Từ Frontend (JS) Sang Python (Docker)
|
|
|
|
Tài liệu này đóng vai trò là cẩm nang kỹ thuật hướng dẫn chi tiết cách dịch chuyển (port) các thuật toán tương tác âm thanh, bộ xử lý tín hiệu số (DSP) và cấu trúc quản lý từ mã nguồn Frontend (Web Audio API) sang hệ thống xử lý tập trung phía Server bằng Python, chạy trong container Docker.
|
|
|
|
1. Bản Đồ Ánh Xạ Công Nghệ (Technology Mapping)
|
|
|
|
Để đảm bảo hiệu năng xử lý tệp tin âm thanh lớn và triệt tiêu sai số thời gian thực, các tác vụ tính toán tương đương trên trình duyệt sẽ được ánh xạ sang các thư viện Python chuyên dụng như sau:
|
|
|
|
Chức năng ở Frontend (JS)
|
|
|
|
Cơ chế hoạt động của Web Audio
|
|
|
|
Thư viện & Module Python thay thế
|
|
|
|
Giải mã âm thanh
|
|
|
|
AudioContext.decodeAudioData()
|
|
|
|
librosa.load() hoặc soundfile.read()
|
|
|
|
Tìm Zero-Crossing
|
|
|
|
Duyệt mảng Float32Array thủ công
|
|
|
|
Phép toán mảng numpy.diff() & numpy.sign()
|
|
|
|
Cắt & Tạo Loop
|
|
|
|
AudioBuffer.slice() & AudioBufferSourceNode.loop
|
|
|
|
pydub.AudioSegment hoặc Slicing trên numpy.ndarray
|
|
|
|
Fade-in / Fade-out
|
|
|
|
GainNode.gain.linearRampToValueAtTime()
|
|
|
|
pydub.fade() hoặc Nhân mảng với numpy.linspace()
|
|
|
|
Thay đổi Volume (Gain)
|
|
|
|
GainNode.gain.value
|
|
|
|
Phép nhân số thực trên mảng numpy hoặc pydub + dB
|
|
|
|
Trộn âm đa kênh
|
|
|
|
OfflineAudioContext (Mixdown)
|
|
|
|
pydub.AudioSegment.overlay() hoặc Phép cộng mảng Numpy
|
|
|
|
Xuất tệp WAV/MP3/OGG
|
|
|
|
Ghi dữ liệu nhị phân RIFF thủ công
|
|
|
|
soundfile.write() (WAV) và pydub/FFmpeg (MP3, OGG)
|
|
|
|
2. Porting Thuật Toán DSP Cốt Lõi (Core DSP Algorithms)
|
|
|
|
2.1. Tìm kiếm điểm Zero-Crossing (Triệt tiêu tiếng Click/Pop)
|
|
|
|
Trong JavaScript, ta quét một cửa sổ $\pm 40\text{ms}$ quanh vị trí đích. Trong Python, ta tối ưu hóa tốc độ quét bằng các phép toán vectơ hóa trên Numpy để tìm ra chỉ số mảng có biên độ đổi dấu từ dương sang âm (hoặc ngược lại) gần nhất.
|
|
|
|
Mã nguồn Python nguyên mẫu (dsp_utils.py):
|
|
|
|
import numpy as np
|
|
|
|
|
|
|
|
def find_zero_crossing(y: np.ndarray, sr: int, target_time: float, window_seconds: float = 0.04) -> float:
|
|
|
|
"""
|
|
|
|
Tìm điểm zero-crossing gần nhất với mốc thời gian đích (giây) để tránh click/pop.
|
|
|
|
|
|
y: Mảng biên độ âm thanh (1D numpy array, Mono)
|
|
|
|
sr: Tần số lấy mẫu (Sample Rate)
|
|
|
|
target_time: Vị trí mong muốn cắt (giây)
|
|
|
|
window_seconds: Cửa sổ quét (mặc định 40ms)
|
|
|
|
"""
|
|
|
|
target_sample = int(target_time * sr)
|
|
|
|
window_samples = int(window_seconds * sr)
|
|
|
|
|
|
# Xác định giới hạn vùng quét an toàn
|
|
|
|
start_idx = max(0, target_sample - window_samples)
|
|
|
|
end_idx = min(len(y) - 2, target_sample + window_samples)
|
|
|
|
|
|
# Lấy phân khúc sóng âm trong cửa sổ quét
|
|
|
|
y_window = y[start_idx:end_idx]
|
|
|
|
|
|
# Tìm các điểm đổi dấu: y[i] * y[i+1] <= 0
|
|
|
|
# Sử dụng np.sign và np.diff để tìm điểm đổi dấu nhanh chóng
|
|
|
|
signs = np.sign(y_window)
|
|
|
|
# Bất kỳ vị trí nào diff != 0 nghĩa là có sự đổi dấu (đi qua điểm 0)
|
|
|
|
zero_crossings = np.where(np.diff(signs) != 0)[0]
|
|
|
|
|
|
if len(zero_crossings) == 0:
|
|
|
|
return target_time # Không tìm thấy, trả về vị trí gốc
|
|
|
|
|
|
# Chuyển chỉ số vùng quét về chỉ số mảng tuyệt đối
|
|
|
|
absolute_crossings = zero_crossings + start_idx
|
|
|
|
|
|
# Tìm điểm gần với target_sample nhất
|
|
|
|
distances = np.abs(absolute_crossings - target_sample)
|
|
|
|
closest_sample_idx = absolute_crossings[np.argmin(distances)]
|
|
|
|
|
|
# Trả về thời gian tương ứng (giây)
|
|
|
|
return float(closest_sample_idx / sr)
|
|
|
|
2.2. Biên Tập Cắt, Ghép & Tạo Fade (Slicing, Fading & Looping)
|
|
|
|
Khi thực hiện cắt một phân đoạn từ start_time đến end_time, áp dụng dải chuyển tiếp (Micro-fades) ở hai đầu phân đoạn là bắt buộc để triệt tiêu vĩnh viễn nhiễu biên độ đột ngột.
|
|
|
|
Mã nguồn Python nguyên mẫu (audio_editor.py):
|
|
|
|
from pydub import AudioSegment
|
|
|
|
import os
|
|
|
|
|
|
|
|
def cut_and_loop_segment(
|
|
|
|
file_path: str,
|
|
|
|
start_sec: float,
|
|
|
|
end_sec: float,
|
|
|
|
loop_count: int = 1,
|
|
|
|
fade_in_ms: int = 50,
|
|
|
|
fade_out_ms: int = 50,
|
|
|
|
volume_db_change: float = 0.0
|
|
|
|
) -> AudioSegment:
|
|
|
|
"""
|
|
|
|
Cắt đoạn âm thanh, áp dụng gain, fade-in/out và lặp lại đoạn đó.
|
|
|
|
pydub sử dụng mili-giây (ms) làm đơn vị chuẩn.
|
|
|
|
"""
|
|
|
|
# Tải tệp tin âm thanh gốc
|
|
|
|
sound = AudioSegment.from_file(file_path)
|
|
|
|
|
|
# Chuyển đổi giây sang mili-giây
|
|
|
|
start_ms = int(start_sec * 1000)
|
|
|
|
end_ms = int(end_sec * 1000)
|
|
|
|
|
|
# Trích xuất phân đoạn (Slicing)
|
|
|
|
clip = sound[start_ms:end_ms]
|
|
|
|
|
|
# Thay đổi âm lượng nếu có yêu cầu
|
|
|
|
if volume_db_change != 0.0:
|
|
|
|
clip = clip + volume_db_change
|
|
|
|
|
|
# Áp dụng hiệu ứng mờ đầu và mờ cuối (Fading)
|
|
|
|
if fade_in_ms > 0:
|
|
|
|
clip = clip.fade_in(fade_in_ms)
|
|
|
|
if fade_out_ms > 0:
|
|
|
|
clip = clip.fade_out(fade_out_ms)
|
|
|
|
|
|
# Tạo chuỗi lặp (Looping)
|
|
|
|
looped_clip = clip * loop_count
|
|
|
|
|
|
return looped_clip
|
|
|
|
2.3. Hòa Âm Đa Kênh Thống Nhất (Multitrack Mixdown)
|
|
|
|
Tương tự như OfflineAudioContext gộp nhiều luồng âm thanh ở Frontend, máy chủ Python sẽ duyệt qua danh sách các Track hoạt động, điều chỉnh Gain độc lập của từng track, và gộp chúng vào một tệp âm thanh Master duy nhất.
|
|
|
|
def mix_multitrack_session(tracks_meta: list, output_path: str, sample_rate: int = 44100, bit_depth: int = 16):
|
|
|
|
"""
|
|
|
|
tracks_meta: Danh sách cấu hình của từng track:
|
|
|
|
[{"file_path": "...", "volume": 0.8, "muted": False}, ...]
|
|
|
|
"""
|
|
|
|
master_mix = None
|
|
|
|
|
|
for track in tracks_meta:
|
|
|
|
if track.get("muted", False):
|
|
|
|
continue
|
|
|
|
|
|
# Đọc tệp âm thanh của track
|
|
|
|
sound = AudioSegment.from_file(track["file_path"])
|
|
|
|
|
|
# Áp dụng Gain (chuyển đổi từ tỷ lệ 0.0 -> 1.0 sang decibels dB)
|
|
|
|
gain_raw = track.get("volume", 1.0)
|
|
|
|
if gain_raw <= 0.001:
|
|
|
|
continue # Xem như tắt tiếng hoàn toàn
|
|
|
|
gain_db = 20 * np.log10(gain_raw)
|
|
|
|
sound = sound + gain_db
|
|
|
|
|
|
# Gộp vào Master Mix
|
|
|
|
if master_mix is None:
|
|
|
|
master_mix = sound
|
|
|
|
else:
|
|
|
|
# overlay tự động đồng bộ thời điểm bắt đầu tại mốc 0ms
|
|
|
|
master_mix = master_mix.overlay(sound, position=0)
|
|
|
|
|
|
if master_mix is not None:
|
|
|
|
# Xuất tệp tin WAV chất lượng cao thông qua Soundfile
|
|
|
|
# Chuyển đổi định dạng xuất bản theo yêu cầu
|
|
|
|
temp_wav = "temp_mix.wav"
|
|
|
|
master_mix.export(temp_wav, format="wav")
|
|
|
|
|
|
import soundfile as sf
|
|
|
|
y, sr = sf.read(temp_wav)
|
|
|
|
|
|
# Xác định subtype mã hóa bit-depth
|
|
|
|
subtype_map = {8: "PCM_S8", 16: "PCM_16", 24: "PCM_24"}
|
|
|
|
selected_subtype = subtype_map.get(bit_depth, "PCM_16")
|
|
|
|
|
|
# Ghi tệp nén/không nén hoàn chỉnh
|
|
|
|
sf.write(output_path, y, sample_rate, subtype=selected_subtype)
|
|
|
|
|
|
# Dọn dẹp tệp tạm
|
|
|
|
if os.path.exists(temp_wav):
|
|
|
|
os.remove(temp_wav)
|
|
|
|
3. Thiết Kế Luồng Dữ Liệu & Giao Tiếp API (FastAPI)
|
|
|
|
Khi người dùng thao tác trên giao diện SonicForge Studio và thực hiện một lệnh xử lý (như Cắt, Phân tích AI, Xuất bản), Frontend sẽ gửi cấu hình JSON lên FastAPI Gateway.
|
|
|
|
3.1. Thiết kế JSON Payload gửi từ Client lên Server
|
|
|
|
{
|
|
|
|
"session_id": "session_pro_2026_07_18",
|
|
|
|
"export_settings": {
|
|
|
|
"sample_rate": 44100,
|
|
|
|
"bit_depth": 24,
|
|
|
|
"format": "wav"
|
|
|
|
},
|
|
|
|
"tracks": [
|
|
|
|
{
|
|
|
|
"track_id": "1",
|
|
|
|
"file_id": "source_audio_drum_loop.wav",
|
|
|
|
"volume": 0.85,
|
|
|
|
"muted": false,
|
|
|
|
"clips": [
|
|
|
|
{
|
|
|
|
"clip_id": "clip_01",
|
|
|
|
"start_time_seconds": 2.450,
|
|
|
|
"end_time_seconds": 12.800,
|
|
|
|
"loop_count": 4,
|
|
|
|
"apply_zero_crossing": true,
|
|
|
|
"fade_in_ms": 150,
|
|
|
|
"fade_out_ms": 150
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
4. Kế Hoạch Triển Khai Phát Triển (5-Week Porting Roadmap)
|
|
|
|
Để quá trình tích hợp mã nguồn diễn ra liền mạch không phát sinh lỗi lệch pha âm thanh (Phase cancellation) hoặc lệch nhịp, lộ trình 5 tuần được phân chia cụ thể như sau:
|
|
|
|
[ TUẦN 1 ] ──► Thiết lập môi trường Docker, cài đặt FFmpeg & Cấu trúc FastAPI Gateway.
|
|
|
|
[ TUẦN 2 ] ──► Chuyển giao thuật toán Zero-Crossing & vẽ Peak Waveform đồng bộ sang Python.
|
|
|
|
[ TUẦN 3 ] ──► Viết bộ xử lý biên dịch đa kênh (Multitrack Overlay Engine) trên Celery Worker.
|
|
|
|
[ TUẦN 4 ] ──► Tích hợp OpenAI Compatible API kết nối DeepSeek/Ollama để phân tích cấu trúc khuôn nhạc.
|
|
|
|
[ TUẦN 5 ] ──► Kiểm định chất lượng âm thanh (Phát hiện Pop/Click, lệch pha) & Đóng gói hoàn tất.
|
|
|
|
Chi tiết các bước thực thi:
|
|
|
|
Tuần 1: Khởi tạo Hạ tầng Docker & API Gateway
|
|
|
|
Cài đặt Docker image tích hợp python:3.11-slim, biên dịch bổ sung gói phần mềm ffmpeg và libsndfile1.
|
|
|
|
Xây dựng các Endpoint cơ bản phục vụ tải lên (/upload) và tải xuống (/download) tệp tin âm nhạc.
|
|
|
|
Tuần 2: Porting & Kiểm nghiệm Thuật toán Zero-Crossing
|
|
|
|
Viết kịch bản kiểm thử (Unit tests) so sánh vị trí Zero-Crossing tìm được bằng JavaScript (trên Client) và bằng Numpy (trên Server).
|
|
|
|
Đảm bảo sai số định vị mẫu âm học giữa hai môi trường tiến về mức $0$ (tuyệt đối đồng bộ).
|
|
|
|
Tuần 3: Biên tập và Hòa âm Đa kênh
|
|
|
|
Triển khai bộ xử lý hàng đợi Celery kết nối Redis để thực thi các tác vụ xử lý âm thanh nặng dưới nền.
|
|
|
|
Đảm bảo tính năng tăng giảm âm lượng tuyến tính và logarit không xảy ra hiện tượng méo tiếng (Clipping distortion).
|
|
|
|
Tuần 4: Phân tích Âm học và Tích hợp Trí tuệ Nhân tạo (AI)
|
|
|
|
Gọi API OpenAI tương thích để phân tích, phân đoạn bố cục cấu trúc tệp nhạc (vào các phân khúc Intro, Verse, Chorus, Outro) dựa trên siêu dữ liệu (metadata) và sóng âm.
|
|
|
|
Sử dụng librosa.beat.beat_track để tự động xác định nhịp độ (Tempo - BPM) và vị trí phách của khuôn nhạc (bars).
|
|
|
|
Tuần 5: Kiểm định Chất lượng & Triển khai Hệ thống
|
|
|
|
Kiểm tra hiệu năng khi có nhiều người dùng đồng thời tải lên và thao tác cắt ghép trên dòng thời gian.
|
|
|
|
Tối ưu hóa dung lượng bộ nhớ tạm (Temporary storage) trong Docker, tự động dọn dẹp các tệp kết xuất hết hạn sau 24 giờ.
|
|
|
|
Python
|
|
import os
|
|
|
|
markdown_content = """# Kế Hoạch & Tài Liệu Kỹ Thuật: Porting DSP Engine Từ Frontend (JS) Sang Python (Docker)
|
|
|
|
Tài liệu này đóng vai trò là cẩm nang kỹ thuật hướng dẫn chi tiết cách dịch chuyển (port) các thuật toán tương tác âm thanh, bộ xử lý tín hiệu số (DSP) và cấu trúc quản lý từ mã nguồn Frontend (Web Audio API) sang hệ thống xử lý tập trung phía Server bằng Python, chạy trong container Docker.
|
|
|
|
---
|
|
|
|
## 1. Bản Đồ Ánh Xạ Công Nghệ (Technology Mapping)
|
|
|
|
Để đảm bảo hiệu năng xử lý tệp tin âm thanh lớn và triệt tiêu sai số thời gian thực, các tác vụ tính toán tương đương trên trình duyệt sẽ được ánh xạ sang các thư viện Python chuyên dụng như sau:
|
|
|
|
| Chức năng ở Frontend (JS) | Cơ chế hoạt động của Web Audio | Thư viện & Module Python thay thế |
|
|
| :--- | :--- | :--- |
|
|
| **Giải mã âm thanh** | `AudioContext.decodeAudioData()` | `librosa.load()` hoặc `soundfile.read()` |
|
|
| **Tìm Zero-Crossing** | Duyệt mảng `Float32Array` thủ công | Phép toán mảng `numpy.diff()` & `numpy.sign()` |
|
|
| **Cắt & Tạo Loop** | `AudioBuffer.slice()` & `AudioBufferSourceNode.loop` | `pydub.AudioSegment` hoặc Slicing trên `numpy.ndarray` |
|
|
| **Fade-in / Fade-out** | `GainNode.gain.linearRampToValueAtTime()` | `pydub.fade()` hoặc Nhân mảng với `numpy.linspace()` |
|
|
| **Thay đổi Volume (Gain)** | `GainNode.gain.value` | Phép nhân số thực trên mảng `numpy` hoặc `pydub` + dB |
|
|
| **Trộn âm đa kênh** | `OfflineAudioContext` (Mixdown) | `pydub.AudioSegment.overlay()` hoặc Phép cộng mảng `Numpy` |
|
|
| **Xuất tệp WAV/MP3/OGG** | Ghi dữ liệu nhị phân RIFF thủ công | `soundfile.write()` (WAV) và `pydub`/`FFmpeg` (MP3, OGG) |
|
|
|
|
---
|
|
|
|
## 2. Porting Thuật Toán DSP Cốt Lõi (Core DSP Algorithms)
|
|
|
|
### 2.1. Tìm kiếm điểm Zero-Crossing (Triệt tiêu tiếng Click/Pop)
|
|
|
|
Trong JavaScript, ta quét một cửa sổ $\pm 40\text{ms}$ quanh vị trí đích. Trong Python, ta tối ưu hóa tốc độ quét bằng các phép toán vectơ hóa trên Numpy để tìm ra chỉ số mảng có biên độ đổi dấu từ dương sang âm (hoặc ngược lại) gần nhất.
|
|
|
|
**Mã nguồn Python nguyên mẫu (`dsp_utils.py`):**
|
|
|
|
```python
|
|
import numpy as np
|
|
|
|
def find_zero_crossing(y: np.ndarray, sr: int, target_time: float, window_seconds: float = 0.04) -> float:
|
|
\"\"\"
|
|
Tìm điểm zero-crossing gần nhất với mốc thời gian đích (giây) để tránh click/pop.
|
|
|
|
y: Mảng biên độ âm thanh (1D numpy array, Mono)
|
|
sr: Tần số lấy mẫu (Sample Rate)
|
|
target_time: Vị trí mong muốn cắt (giây)
|
|
window_seconds: Cửa sổ quét (mặc định 40ms)
|
|
\"\"\"
|
|
target_sample = int(target_time * sr)
|
|
window_samples = int(window_seconds * sr)
|
|
|
|
# Xác định giới hạn vùng quét an toàn
|
|
start_idx = max(0, target_sample - window_samples)
|
|
end_idx = min(len(y) - 2, target_sample + window_samples)
|
|
|
|
# Lấy phân khúc sóng âm trong cửa sổ quét
|
|
y_window = y[start_idx:end_idx]
|
|
|
|
# Tìm các điểm đổi dấu: y[i] * y[i+1] <= 0
|
|
# Sử dụng np.sign và np.diff để tìm điểm đổi dấu nhanh chóng
|
|
signs = np.sign(y_window)
|
|
# Bất kỳ vị trí nào diff != 0 nghĩa là có sự đổi dấu (đi qua điểm 0)
|
|
zero_crossings = np.where(np.diff(signs) != 0)[0]
|
|
|
|
if len(zero_crossings) == 0:
|
|
return target_time # Không tìm thấy, trả về vị trí gốc
|
|
|
|
# Chuyển chỉ số vùng quét về chỉ số mảng tuyệt đối
|
|
absolute_crossings = zero_crossings + start_idx
|
|
|
|
# Tìm điểm gần với target_sample nhất
|
|
distances = np.abs(absolute_crossings - target_sample)
|
|
closest_sample_idx = absolute_crossings[np.argmin(distances)]
|
|
|
|
# Trả về thời gian tương ứng (giây)
|
|
return float(closest_sample_idx / sr)
|
|
2.2. Biên Tập Cắt, Ghép & Tạo Fade (Slicing, Fading & Looping)
|
|
Khi thực hiện cắt một phân đoạn từ start_time đến end_time, áp dụng dải chuyển tiếp (Micro-fades) ở hai đầu phân đoạn là bắt buộc để triệt tiêu vĩnh viễn nhiễu biên độ đột ngột.
|
|
|
|
Mã nguồn Python nguyên mẫu (audio_editor.py):
|
|
|
|
Python
|
|
from pydub import AudioSegment
|
|
import os
|
|
|
|
def cut_and_loop_segment(
|
|
file_path: str,
|
|
start_sec: float,
|
|
end_sec: float,
|
|
loop_count: int = 1,
|
|
fade_in_ms: int = 50,
|
|
fade_out_ms: int = 50,
|
|
volume_db_change: float = 0.0
|
|
) -> AudioSegment:
|
|
\"\"\"
|
|
Cắt đoạn âm thanh, áp dụng gain, fade-in/out và lặp lại đoạn đó.
|
|
pydub sử dụng mili-giây (ms) làm đơn vị chuẩn.
|
|
\"\"\"
|
|
# Tải tệp tin âm thanh gốc
|
|
sound = AudioSegment.from_file(file_path)
|
|
|
|
# Chuyển đổi giây sang mili-giây
|
|
start_ms = int(start_sec * 1000)
|
|
end_ms = int(end_sec * 1000)
|
|
|
|
# Trích xuất phân đoạn (Slicing)
|
|
clip = sound[start_ms:end_ms]
|
|
|
|
# Thay đổi âm lượng nếu có yêu cầu
|
|
if volume_db_change != 0.0:
|
|
clip = clip + volume_db_change
|
|
|
|
# Áp dụng hiệu ứng mờ đầu và mờ cuối (Fading)
|
|
if fade_in_ms > 0:
|
|
clip = clip.fade_in(fade_in_ms)
|
|
if fade_out_ms > 0:
|
|
clip = clip.fade_out(fade_out_ms)
|
|
|
|
# Tạo chuỗi lặp (Looping)
|
|
looped_clip = clip * loop_count
|
|
|
|
return looped_clip
|
|
2.3. Hòa Âm Đa Kênh Thống Nhất (Multitrack Mixdown)
|
|
Tương tự như OfflineAudioContext gộp nhiều luồng âm thanh ở Frontend, máy chủ Python sẽ duyệt qua danh sách các Track hoạt động, điều chỉnh Gain độc lập của từng track, và gộp chúng vào một tệp âm thanh Master duy nhất.
|
|
|
|
Python
|
|
def mix_multitrack_session(tracks_meta: list, output_path: str, sample_rate: int = 44100, bit_depth: int = 16):
|
|
\"\"\"
|
|
tracks_meta: Danh sách cấu hình của từng track:
|
|
[{"file_path": "...", "volume": 0.8, "muted": False}, ...]
|
|
\"\"\"
|
|
master_mix = None
|
|
|
|
for track in tracks_meta:
|
|
if track.get("muted", False):
|
|
continue
|
|
|
|
# Đọc tệp âm thanh của track
|
|
sound = AudioSegment.from_file(track["file_path"])
|
|
|
|
# Áp dụng Gain (chuyển đổi từ tỷ lệ 0.0 -> 1.0 sang decibels dB)
|
|
gain_raw = track.get("volume", 1.0)
|
|
if gain_raw <= 0.001:
|
|
continue # Xem như tắt tiếng hoàn toàn
|
|
gain_db = 20 * np.log10(gain_raw)
|
|
sound = sound + gain_db
|
|
|
|
# Gộp vào Master Mix
|
|
if master_mix is None:
|
|
master_mix = sound
|
|
else:
|
|
# overlay tự động đồng bộ thời điểm bắt đầu tại mốc 0ms
|
|
master_mix = master_mix.overlay(sound, position=0)
|
|
|
|
if master_mix is not None:
|
|
# Xuất tệp tin WAV chất lượng cao thông qua Soundfile
|
|
# Chuyển đổi định dạng xuất bản theo yêu cầu
|
|
temp_wav = "temp_mix.wav"
|
|
master_mix.export(temp_wav, format="wav")
|
|
|
|
import soundfile as sf
|
|
y, sr = sf.read(temp_wav)
|
|
|
|
# Xác định subtype mã hóa bit-depth
|
|
subtype_map = {8: "PCM_S8", 16: "PCM_16", 24: "PCM_24"}
|
|
selected_subtype = subtype_map.get(bit_depth, "PCM_16")
|
|
|
|
# Ghi tệp nén/không nén hoàn chỉnh
|
|
sf.write(output_path, y, sample_rate, subtype=selected_subtype)
|
|
|
|
# Dọn dẹp tệp tạm
|
|
if os.path.exists(temp_wav):
|
|
os.remove(temp_wav)
|
|
3. Thiết Kế Luồng Dữ Liệu & Giao Tiếp API (FastAPI)
|
|
Khi người dùng thao tác trên giao diện SonicForge Studio và thực hiện một lệnh xử lý (như Cắt, Phân tích AI, Xuất bản), Frontend sẽ gửi cấu hình JSON lên FastAPI Gateway.
|
|
|
|
3.1. Thiết kế JSON Payload gửi từ Client lên Server
|
|
JSON
|
|
{
|
|
"session_id": "session_pro_2026_07_18",
|
|
"export_settings": {
|
|
"sample_rate": 44100,
|
|
"bit_depth": 24,
|
|
"format": "wav"
|
|
},
|
|
"tracks": [
|
|
{
|
|
"track_id": "1",
|
|
"file_id": "source_audio_drum_loop.wav",
|
|
"volume": 0.85,
|
|
"muted": false,
|
|
"clips": [
|
|
{
|
|
"clip_id": "clip_01",
|
|
"start_time_seconds": 2.450,
|
|
"end_time_seconds": 12.800,
|
|
"loop_count": 4,
|
|
"apply_zero_crossing": true,
|
|
"fade_in_ms": 150,
|
|
"fade_out_ms": 150
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
4. Kế Hoạch Triển Khai Phát Triển (5-Week Porting Roadmap)
|
|
Để quá trình tích hợp mã nguồn diễn ra liền mạch không phát sinh lỗi lệch pha âm thanh (Phase cancellation) hoặc lệch nhịp, lộ trình 5 tuần được phân chia cụ thể như sau:
|
|
|
|
Plaintext
|
|
[ TUẦN 1 ] ──► Thiết lập môi trường Docker, cài đặt FFmpeg & Cấu trúc FastAPI Gateway.
|
|
[ TUẦN 2 ] ──► Chuyển giao thuật toán Zero-Crossing & vẽ Peak Waveform đồng bộ sang Python.
|
|
[ TUẦN 3 ] ──► Viết bộ xử lý biên dịch đa kênh (Multitrack Overlay Engine) trên Celery Worker.
|
|
[ TUẦN 4 ] ──► Tích hợp OpenAI Compatible API kết nối DeepSeek/Ollama để phân tích cấu trúc khuôn nhạc.
|
|
[ TUẦN 5 ] ──► Kiểm định chất lượng âm thanh (Phát hiện Pop/Click, lệch pha) & Đóng gói hoàn tất.
|
|
Chi tiết các bước thực thi:
|
|
Tuần 1: Khởi tạo Hạ tầng Docker & API Gateway
|
|
Cài đặt Docker image tích hợp python:3.11-slim, biên dịch bổ sung gói phần mềm ffmpeg và libsndfile1.
|
|
|
|
Xây dựng các Endpoint cơ bản phục vụ tải lên (/upload) và tải xuống (/download) tệp tin âm nhạc.
|
|
|
|
Tuần 2: Porting & Kiểm nghiệm Thuật toán Zero-Crossing
|
|
Viết kịch bản kiểm thử (Unit tests) so sánh vị trí Zero-Crossing tìm được bằng JavaScript (trên Client) và bằng Numpy (trên Server).
|
|
|
|
Đảm bảo sai số định vị mẫu âm học giữa hai môi trường tiến về mức 0 (tuyệt đối đồng bộ).
|
|
|
|
Tuần 3: Biên tập và Hòa âm Đa kênh
|
|
Triển khai bộ xử lý hàng đợi Celery kết nối Redis để thực thi các tác vụ xử lý âm thanh nặng dưới nền.
|
|
|
|
Đảm bảo tính năng tăng giảm âm lượng tuyến tính và logarit không xảy ra hiện tượng méo tiếng (Clipping distortion).
|
|
|
|
Tuần 4: Phân tích Âm học và Tích hợp Trí tuệ Nhân tạo (AI)
|
|
Gọi API OpenAI tương thích để phân tích, phân đoạn bố cục cấu trúc tệp nhạc (vào các phân khúc Intro, Verse, Chorus, Outro) dựa trên siêu dữ liệu (metadata) và sóng âm.
|
|
|
|
Sử dụng librosa.beat.beat_track để tự động xác định nhịp độ (Tempo - BPM) và vị trí phách của khuôn nhạc (bars).
|
|
|
|
Tuần 5: Kiểm định Chất lượng & Triển khai Hệ thống
|
|
Kiểm tra hiệu năng khi có nhiều người dùng đồng thời tải lên và thao tác cắt ghép trên dòng thời gian.
|
|
|
|
Tối ưu hóa dung lượng bộ nhớ tạm (Temporary storage) trong Docker, tự động dọn dẹp các tệp kết xuất hết hạn sau 24 giờ.
|
|
"""
|
|
|
|
with open("Porting_DSP_Engine_Plan.md", "w", encoding="utf-8") as f:
|
|
f.write(markdown_content)
|
|
print("File written successfully.")
|
|
|
|
|
|
```text?code_stdout&code_event_index=1
|
|
File written successfully.
|
|
|
|
Your Markdown file is ready
|
|
Biểu tượng MD
|
|
Porting_DSP_Engine_Plan
|
|
MD
|
|
Đang mở
|
|
|
|
Dưới đây là toàn bộ nội dung đã được chuyển đổi sang định dạng Markdown chuẩn, căn chỉnh bảng biểu, tối ưu hóa code block syntax highlighting (python, json, text), cũng như giữ nguyên các công thức dạng toán học LaTeX:
|
|
|
|
Kế Hoạch & Tài Liệu Kỹ Thuật: Porting DSP Engine Từ Frontend (JS) Sang Python (Docker)
|
|
Tài liệu này đóng vai trò là cẩm nang kỹ thuật hướng dẫn chi tiết cách dịch chuyển (port) các thuật toán tương tác âm thanh, bộ xử lý tín hiệu số (DSP) và cấu trúc quản lý từ mã nguồn Frontend (Web Audio API) sang hệ thống xử lý tập trung phía Server bằng Python, chạy trong container Docker.
|
|
|
|
1. Bản Đồ Ánh Xạ Công Nghệ (Technology Mapping)
|
|
Để đảm bảo hiệu năng xử lý tệp tin âm thanh lớn và triệt tiêu sai số thời gian thực, các tác vụ tính toán tương đương trên trình duyệt sẽ được ánh xạ sang các thư viện Python chuyên dụng như sau:
|
|
|
|
Chức năng ở Frontend (JS) Cơ chế hoạt động của Web Audio Thư viện & Module Python thay thế
|
|
Giải mã âm thanh AudioContext.decodeAudioData() librosa.load() hoặc soundfile.read()
|
|
Tìm Zero-Crossing Duyệt mảng Float32Array thủ công Phép toán mảng numpy.diff() & numpy.sign()
|
|
Cắt & Tạo Loop AudioBuffer.slice() & AudioBufferSourceNode.loop pydub.AudioSegment hoặc Slicing trên numpy.ndarray
|
|
Fade-in / Fade-out GainNode.gain.linearRampToValueAtTime() pydub.fade() hoặc Nhân mảng với numpy.linspace()
|
|
Thay đổi Volume (Gain) GainNode.gain.value Phép nhân số thực trên mảng numpy hoặc pydub + dB
|
|
Trộn âm đa kênh OfflineAudioContext (Mixdown) pydub.AudioSegment.overlay() hoặc Phép cộng mảng Numpy
|
|
Xuất tệp WAV/MP3/OGG Ghi dữ liệu nhị phân RIFF thủ công soundfile.write() (WAV) và pydub/FFmpeg (MP3, OGG)
|
|
2. Porting Thuật Toán DSP Cốt Lõi (Core DSP Algorithms)
|
|
2.1. Tìm kiếm điểm Zero-Crossing (Triệt tiêu tiếng Click/Pop)
|
|
Trong JavaScript, ta quét một cửa sổ ±40ms quanh vị trí đích. Trong Python, ta tối ưu hóa tốc độ quét bằng các phép toán vectơ hóa trên Numpy để tìm ra chỉ số mảng có biên độ đổi dấu từ dương sang âm (hoặc ngược lại) gần nhất.
|
|
|
|
Mã nguồn Python nguyên mẫu (dsp_utils.py):
|
|
|
|
Python
|
|
import numpy as np
|
|
|
|
def find_zero_crossing(y: np.ndarray, sr: int, target_time: float, window_seconds: float = 0.04) -> float:
|
|
"""
|
|
Tìm điểm zero-crossing gần nhất với mốc thời gian đích (giây) để tránh click/pop.
|
|
|
|
y: Mảng biên độ âm thanh (1D numpy array, Mono)
|
|
sr: Tần số lấy mẫu (Sample Rate)
|
|
target_time: Vị trí mong muốn cắt (giây)
|
|
window_seconds: Cửa sổ quét (mặc định 40ms)
|
|
"""
|
|
target_sample = int(target_time * sr)
|
|
window_samples = int(window_seconds * sr)
|
|
|
|
# Xác định giới hạn vùng quét an toàn
|
|
start_idx = max(0, target_sample - window_samples)
|
|
end_idx = min(len(y) - 2, target_sample + window_samples)
|
|
|
|
# Lấy phân khúc sóng âm trong cửa sổ quét
|
|
y_window = y[start_idx:end_idx]
|
|
|
|
# Tìm các điểm đổi dấu: y[i] * y[i+1] <= 0
|
|
# Sử dụng np.sign và np.diff để tìm điểm đổi dấu nhanh chóng
|
|
signs = np.sign(y_window)
|
|
# Bất kỳ vị trí nào diff != 0 nghĩa là có sự đổi dấu (đi qua điểm 0)
|
|
zero_crossings = np.where(np.diff(signs) != 0)[0]
|
|
|
|
if len(zero_crossings) == 0:
|
|
return target_time # Không tìm thấy, trả về vị trí gốc
|
|
|
|
# Chuyển chỉ số vùng quét về chỉ số mảng tuyệt đối
|
|
absolute_crossings = zero_crossings + start_idx
|
|
|
|
# Tìm điểm gần với target_sample nhất
|
|
distances = np.abs(absolute_crossings - target_sample)
|
|
closest_sample_idx = absolute_crossings[np.argmin(distances)]
|
|
|
|
# Trả về thời gian tương ứng (giây)
|
|
return float(closest_sample_idx / sr)
|
|
2.2. Biên Tập Cắt, Ghép & Tạo Fade (Slicing, Fading & Looping)
|
|
Khi thực hiện cắt một phân đoạn từ start_time đến end_time, áp dụng dải chuyển tiếp (Micro-fades) ở hai đầu phân đoạn là bắt buộc để triệt tiêu vĩnh viễn nhiễu biên độ đột ngột.
|
|
|
|
Mã nguồn Python nguyên mẫu (audio_editor.py):
|
|
|
|
Python
|
|
from pydub import AudioSegment
|
|
import os
|
|
|
|
def cut_and_loop_segment(
|
|
file_path: str,
|
|
start_sec: float,
|
|
end_sec: float,
|
|
loop_count: int = 1,
|
|
fade_in_ms: int = 50,
|
|
fade_out_ms: int = 50,
|
|
volume_db_change: float = 0.0
|
|
) -> AudioSegment:
|
|
"""
|
|
Cắt đoạn âm thanh, áp dụng gain, fade-in/out và lặp lại đoạn đó.
|
|
pydub sử dụng mili-giây (ms) làm đơn vị chuẩn.
|
|
"""
|
|
# Tải tệp tin âm thanh gốc
|
|
sound = AudioSegment.from_file(file_path)
|
|
|
|
# Chuyển đổi giây sang mili-giây
|
|
start_ms = int(start_sec * 1000)
|
|
end_ms = int(end_sec * 1000)
|
|
|
|
# Trích xuất phân đoạn (Slicing)
|
|
clip = sound[start_ms:end_ms]
|
|
|
|
# Thay đổi âm lượng nếu có yêu cầu
|
|
if volume_db_change != 0.0:
|
|
clip = clip + volume_db_change
|
|
|
|
# Áp dụng hiệu ứng mờ đầu và mờ cuối (Fading)
|
|
if fade_in_ms > 0:
|
|
clip = clip.fade_in(fade_in_ms)
|
|
if fade_out_ms > 0:
|
|
clip = clip.fade_out(fade_out_ms)
|
|
|
|
# Tạo chuỗi lặp (Looping)
|
|
looped_clip = clip * loop_count
|
|
|
|
return looped_clip
|
|
2.3. Hòa Âm Đa Kênh Thống Nhất (Multitrack Mixdown)
|
|
Tương tự như OfflineAudioContext gộp nhiều luồng âm thanh ở Frontend, máy chủ Python sẽ duyệt qua danh sách các Track hoạt động, điều chỉnh Gain độc lập của từng track, và gộp chúng vào một tệp âm thanh Master duy nhất.
|
|
|
|
Python
|
|
def mix_multitrack_session(tracks_meta: list, output_path: str, sample_rate: int = 44100, bit_depth: int = 16):
|
|
"""
|
|
tracks_meta: Danh sách cấu hình của từng track:
|
|
[{"file_path": "...", "volume": 0.8, "muted": False}, ...]
|
|
"""
|
|
master_mix = None
|
|
|
|
for track in tracks_meta:
|
|
if track.get("muted", False):
|
|
continue
|
|
|
|
# Đọc tệp âm thanh của track
|
|
sound = AudioSegment.from_file(track["file_path"])
|
|
|
|
# Áp dụng Gain (chuyển đổi từ tỷ lệ 0.0 -> 1.0 sang decibels dB)
|
|
gain_raw = track.get("volume", 1.0)
|
|
if gain_raw <= 0.001:
|
|
continue # Xem như tắt tiếng hoàn toàn
|
|
gain_db = 20 * np.log10(gain_raw)
|
|
sound = sound + gain_db
|
|
|
|
# Gộp vào Master Mix
|
|
if master_mix is None:
|
|
master_mix = sound
|
|
else:
|
|
# overlay tự động đồng bộ thời điểm bắt đầu tại mốc 0ms
|
|
master_mix = master_mix.overlay(sound, position=0)
|
|
|
|
if master_mix is not None:
|
|
# Xuất tệp tin WAV chất lượng cao thông qua Soundfile
|
|
# Chuyển đổi định dạng xuất bản theo yêu cầu
|
|
temp_wav = "temp_mix.wav"
|
|
master_mix.export(temp_wav, format="wav")
|
|
|
|
import soundfile as sf
|
|
y, sr = sf.read(temp_wav)
|
|
|
|
# Xác định subtype mã hóa bit-depth
|
|
subtype_map = {8: "PCM_S8", 16: "PCM_16", 24: "PCM_24"}
|
|
selected_subtype = subtype_map.get(bit_depth, "PCM_16")
|
|
|
|
# Ghi tệp nén/không nén hoàn chỉnh
|
|
sf.write(output_path, y, sample_rate, subtype=selected_subtype)
|
|
|
|
# Dọn dẹp tệp tạm
|
|
if os.path.exists(temp_wav):
|
|
os.remove(temp_wav)
|
|
3. Thiết Kế Luồng Dữ Liệu & Giao Tiếp API (FastAPI)
|
|
Khi người dùng thao tác trên giao diện SonicForge Studio và thực hiện một lệnh xử lý (như Cắt, Phân tích AI, Xuất bản), Frontend sẽ gửi cấu hình JSON lên FastAPI Gateway.
|
|
|
|
3.1. Thiết kế JSON Payload gửi từ Client lên Server
|
|
JSON
|
|
{
|
|
"session_id": "session_pro_2026_07_18",
|
|
"export_settings": {
|
|
"sample_rate": 44100,
|
|
"bit_depth": 24,
|
|
"format": "wav"
|
|
},
|
|
"tracks": [
|
|
{
|
|
"track_id": "1",
|
|
"file_id": "source_audio_drum_loop.wav",
|
|
"volume": 0.85,
|
|
"muted": false,
|
|
"clips": [
|
|
{
|
|
"clip_id": "clip_01",
|
|
"start_time_seconds": 2.450,
|
|
"end_time_seconds": 12.800,
|
|
"loop_count": 4,
|
|
"apply_zero_crossing": true,
|
|
"fade_in_ms": 150,
|
|
"fade_out_ms": 150
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
4. Kế Hoạch Triển Khai Phát Triển (5-Week Porting Roadmap)
|
|
Để quá trình tích hợp mã nguồn diễn ra liền mạch không phát sinh lỗi lệch pha âm thanh (Phase cancellation) hoặc lệch nhịp, lộ trình 5 tuần được phân chia cụ thể như sau:
|
|
|
|
Plaintext
|
|
[ TUẦN 1 ] ──► Thiết lập môi trường Docker, cài đặt FFmpeg & Cấu trúc FastAPI Gateway.
|
|
[ TUẦN 2 ] ──► Chuyển giao thuật toán Zero-Crossing & vẽ Peak Waveform đồng bộ sang Python.
|
|
[ TUẦN 3 ] ──► Viết bộ xử lý biên dịch đa kênh (Multitrack Overlay Engine) trên Celery Worker.
|
|
[ TUẦN 4 ] ──► Tích hợp OpenAI Compatible API kết nối DeepSeek/Ollama để phân tích cấu trúc khuôn nhạc.
|
|
[ TUẦN 5 ] ──► Kiểm định chất lượng âm thanh (Phát hiện Pop/Click, lệch pha) & Đóng gói hoàn tất.
|
|
Chi tiết các bước thực thi:
|
|
Tuần 1: Khởi tạo Hạ tầng Docker & API Gateway
|
|
Cài đặt Docker image tích hợp python:3.11-slim, biên dịch bổ sung gói phần mềm ffmpeg và libsndfile1.
|
|
|
|
Xây dựng các Endpoint cơ bản phục vụ tải lên (/upload) và tải xuống (/download) tệp tin âm nhạc.
|
|
|
|
Tuần 2: Porting & Kiểm nghiệm Thuật toán Zero-Crossing
|
|
Viết kịch bản kiểm thử (Unit tests) so sánh vị trí Zero-Crossing tìm được bằng JavaScript (trên Client) và bằng Numpy (trên Server).
|
|
|
|
Đảm bảo sai số định vị mẫu âm học giữa hai môi trường tiến về mức 0 (tuyệt đối đồng bộ).
|
|
|
|
Tuần 3: Biên tập và Hòa âm Đa kênh
|
|
Triển khai bộ xử lý hàng đợi Celery kết nối Redis để thực thi các tác vụ xử lý âm thanh nặng dưới nền.
|
|
|
|
Đảm bảo tính năng tăng giảm âm lượng tuyến tính và logarit không xảy ra hiện tượng méo tiếng (Clipping distortion).
|
|
|
|
Tuần 4: Phân tích Âm học và Tích hợp Trí tuệ Nhân tạo (AI)
|
|
Gọi API OpenAI tương thích để phân tích, phân đoạn bố cục cấu trúc tệp nhạc (vào các phân khúc Intro, Verse, Chorus, Outro) dựa trên siêu dữ liệu (metadata) và sóng âm.
|
|
|
|
Sử dụng librosa.beat.beat_track để tự động xác định nhịp độ (Tempo - BPM) và vị trí phách của khuôn nhạc (bars).
|
|
|
|
Tuần 5: Kiểm định Chất lượng & Triển khai Hệ thống
|
|
Kiểm tra hiệu năng khi có nhiều người dùng đồng thời tải lên và thao tác cắt ghép trên dòng thời gian.
|
|
|
|
Tối ưu hóa dung lượng bộ nhớ tạm (Temporary storage) trong Docker, tự động dọn dẹp các tệp kết xuất hết hạn sau 24 giờ.
|
|
|
|
|
|
|
|
|
|
Gemini là AI và có thể mắc sai sót.
|
|
|
|
# Kế Hoạch & Tài Liệu Kỹ Thuật: Porting DSP Engine Từ Frontend (JS) Sang Python (Docker)
|
|
|
|
Tài liệu này đóng vai trò là cẩm nang kỹ thuật hướng dẫn chi tiết cách dịch chuyển (port) các thuật toán tương tác âm thanh, bộ xử lý tín hiệu số (DSP) và cấu trúc quản lý từ mã nguồn Frontend (Web Audio API) sang hệ thống xử lý tập trung phía Server bằng Python, chạy trong container Docker.
|
|
|
|
---
|
|
|
|
## 1. Bản Đồ Ánh Xạ Công Nghệ (Technology Mapping)
|
|
|
|
Để đảm bảo hiệu năng xử lý tệp tin âm thanh lớn và triệt tiêu sai số thời gian thực, các tác vụ tính toán tương đương trên trình duyệt sẽ được ánh xạ sang các thư viện Python chuyên dụng như sau:
|
|
|
|
| Chức năng ở Frontend (JS) | Cơ chế hoạt động của Web Audio | Thư viện & Module Python thay thế |
|
|
| :--- | :--- | :--- |
|
|
| **Giải mã âm thanh** | `AudioContext.decodeAudioData()` | `librosa.load()` hoặc `soundfile.read()` |
|
|
| **Tìm Zero-Crossing** | Duyệt mảng `Float32Array` thủ công | Phép toán mảng `numpy.diff()` & `numpy.sign()` |
|
|
| **Cắt & Tạo Loop** | `AudioBuffer.slice()` & `AudioBufferSourceNode.loop` | `pydub.AudioSegment` hoặc Slicing trên `numpy.ndarray` |
|
|
| **Fade-in / Fade-out** | `GainNode.gain.linearRampToValueAtTime()` | `pydub.fade()` hoặc Nhân mảng với `numpy.linspace()` |
|
|
| **Thay đổi Volume (Gain)** | `GainNode.gain.value` | Phép nhân số thực trên mảng `numpy` hoặc `pydub` + dB |
|
|
| **Trộn âm đa kênh** | `OfflineAudioContext` (Mixdown) | `pydub.AudioSegment.overlay()` hoặc Phép cộng mảng `Numpy` |
|
|
| **Xuất tệp WAV/MP3/OGG** | Ghi dữ liệu nhị phân RIFF thủ công | `soundfile.write()` (WAV) và `pydub`/`FFmpeg` (MP3, OGG) |
|
|
|
|
---
|
|
|
|
## 2. Porting Thuật Toán DSP Cốt Lõi (Core DSP Algorithms)
|
|
|
|
### 2.1. Tìm kiếm điểm Zero-Crossing (Triệt tiêu tiếng Click/Pop)
|
|
|
|
Trong JavaScript, ta quét một cửa sổ $\pm 40 ext{ms}$ quanh vị trí đích. Trong Python, ta tối ưu hóa tốc độ quét bằng các phép toán vectơ hóa trên Numpy để tìm ra chỉ số mảng có biên độ đổi dấu từ dương sang âm (hoặc ngược lại) gần nhất.
|
|
|
|
**Mã nguồn Python nguyên mẫu (`dsp_utils.py`):**
|
|
|
|
```python
|
|
import numpy as np
|
|
|
|
def find_zero_crossing(y: np.ndarray, sr: int, target_time: float, window_seconds: float = 0.04) -> float:
|
|
"""
|
|
Tìm điểm zero-crossing gần nhất với mốc thời gian đích (giây) để tránh click/pop.
|
|
|
|
y: Mảng biên độ âm thanh (1D numpy array, Mono)
|
|
sr: Tần số lấy mẫu (Sample Rate)
|
|
target_time: Vị trí mong muốn cắt (giây)
|
|
window_seconds: Cửa sổ quét (mặc định 40ms)
|
|
"""
|
|
target_sample = int(target_time * sr)
|
|
window_samples = int(window_seconds * sr)
|
|
|
|
# Xác định giới hạn vùng quét an toàn
|
|
start_idx = max(0, target_sample - window_samples)
|
|
end_idx = min(len(y) - 2, target_sample + window_samples)
|
|
|
|
# Lấy phân khúc sóng âm trong cửa sổ quét
|
|
y_window = y[start_idx:end_idx]
|
|
|
|
# Tìm các điểm đổi dấu: y[i] * y[i+1] <= 0
|
|
# Sử dụng np.sign và np.diff để tìm điểm đổi dấu nhanh chóng
|
|
signs = np.sign(y_window)
|
|
# Bất kỳ vị trí nào diff != 0 nghĩa là có sự đổi dấu (đi qua điểm 0)
|
|
zero_crossings = np.where(np.diff(signs) != 0)[0]
|
|
|
|
if len(zero_crossings) == 0:
|
|
return target_time # Không tìm thấy, trả về vị trí gốc
|
|
|
|
# Chuyển chỉ số vùng quét về chỉ số mảng tuyệt đối
|
|
absolute_crossings = zero_crossings + start_idx
|
|
|
|
# Tìm điểm gần với target_sample nhất
|
|
distances = np.abs(absolute_crossings - target_sample)
|
|
closest_sample_idx = absolute_crossings[np.argmin(distances)]
|
|
|
|
# Trả về thời gian tương ứng (giây)
|
|
return float(closest_sample_idx / sr)
|
|
```
|
|
|
|
### 2.2. Biên Tập Cắt, Ghép & Tạo Fade (Slicing, Fading & Looping)
|
|
|
|
Khi thực hiện cắt một phân đoạn từ `start_time` đến `end_time`, áp dụng dải chuyển tiếp (Micro-fades) ở hai đầu phân đoạn là bắt buộc để triệt tiêu vĩnh viễn nhiễu biên độ đột ngột.
|
|
|
|
**Mã nguồn Python nguyên mẫu (`audio_editor.py`):**
|
|
|
|
```python
|
|
from pydub import AudioSegment
|
|
import os
|
|
|
|
def cut_and_loop_segment(
|
|
file_path: str,
|
|
start_sec: float,
|
|
end_sec: float,
|
|
loop_count: int = 1,
|
|
fade_in_ms: int = 50,
|
|
fade_out_ms: int = 50,
|
|
volume_db_change: float = 0.0
|
|
) -> AudioSegment:
|
|
"""
|
|
Cắt đoạn âm thanh, áp dụng gain, fade-in/out và lặp lại đoạn đó.
|
|
pydub sử dụng mili-giây (ms) làm đơn vị chuẩn.
|
|
"""
|
|
# Tải tệp tin âm thanh gốc
|
|
sound = AudioSegment.from_file(file_path)
|
|
|
|
# Chuyển đổi giây sang mili-giây
|
|
start_ms = int(start_sec * 1000)
|
|
end_ms = int(end_sec * 1000)
|
|
|
|
# Trích xuất phân đoạn (Slicing)
|
|
clip = sound[start_ms:end_ms]
|
|
|
|
# Thay đổi âm lượng nếu có yêu cầu
|
|
if volume_db_change != 0.0:
|
|
clip = clip + volume_db_change
|
|
|
|
# Áp dụng hiệu ứng mờ đầu và mờ cuối (Fading)
|
|
if fade_in_ms > 0:
|
|
clip = clip.fade_in(fade_in_ms)
|
|
if fade_out_ms > 0:
|
|
clip = clip.fade_out(fade_out_ms)
|
|
|
|
# Tạo chuỗi lặp (Looping)
|
|
looped_clip = clip * loop_count
|
|
|
|
return looped_clip
|
|
```
|
|
|
|
### 2.3. Hòa Âm Đa Kênh Thống Nhất (Multitrack Mixdown)
|
|
|
|
Tương tự như `OfflineAudioContext` gộp nhiều luồng âm thanh ở Frontend, máy chủ Python sẽ duyệt qua danh sách các Track hoạt động, điều chỉnh Gain độc lập của từng track, và gộp chúng vào một tệp âm thanh Master duy nhất.
|
|
|
|
```python
|
|
def mix_multitrack_session(tracks_meta: list, output_path: str, sample_rate: int = 44100, bit_depth: int = 16):
|
|
"""
|
|
tracks_meta: Danh sách cấu hình của từng track:
|
|
[{"file_path": "...", "volume": 0.8, "muted": False}, ...]
|
|
"""
|
|
master_mix = None
|
|
|
|
for track in tracks_meta:
|
|
if track.get("muted", False):
|
|
continue
|
|
|
|
# Đọc tệp âm thanh của track
|
|
sound = AudioSegment.from_file(track["file_path"])
|
|
|
|
# Áp dụng Gain (chuyển đổi từ tỷ lệ 0.0 -> 1.0 sang decibels dB)
|
|
gain_raw = track.get("volume", 1.0)
|
|
if gain_raw <= 0.001:
|
|
continue # Xem như tắt tiếng hoàn toàn
|
|
gain_db = 20 * np.log10(gain_raw)
|
|
sound = sound + gain_db
|
|
|
|
# Gộp vào Master Mix
|
|
if master_mix is None:
|
|
master_mix = sound
|
|
else:
|
|
# overlay tự động đồng bộ thời điểm bắt đầu tại mốc 0ms
|
|
master_mix = master_mix.overlay(sound, position=0)
|
|
|
|
if master_mix is not None:
|
|
# Xuất tệp tin WAV chất lượng cao thông qua Soundfile
|
|
# Chuyển đổi định dạng xuất bản theo yêu cầu
|
|
temp_wav = "temp_mix.wav"
|
|
master_mix.export(temp_wav, format="wav")
|
|
|
|
import soundfile as sf
|
|
y, sr = sf.read(temp_wav)
|
|
|
|
# Xác định subtype mã hóa bit-depth
|
|
subtype_map = {8: "PCM_S8", 16: "PCM_16", 24: "PCM_24"}
|
|
selected_subtype = subtype_map.get(bit_depth, "PCM_16")
|
|
|
|
# Ghi tệp nén/không nén hoàn chỉnh
|
|
sf.write(output_path, y, sample_rate, subtype=selected_subtype)
|
|
|
|
# Dọn dẹp tệp tạm
|
|
if os.path.exists(temp_wav):
|
|
os.remove(temp_wav)
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Thiết Kế Luồng Dữ Liệu & Giao Tiếp API (FastAPI)
|
|
|
|
Khi người dùng thao tác trên giao diện *SonicForge Studio* và thực hiện một lệnh xử lý (như Cắt, Phân tích AI, Xuất bản), Frontend sẽ gửi cấu hình JSON lên FastAPI Gateway.
|
|
|
|
### 3.1. Thiết kế JSON Payload gửi từ Client lên Server
|
|
|
|
```json
|
|
{
|
|
"session_id": "session_pro_2026_07_18",
|
|
"export_settings": {
|
|
"sample_rate": 44100,
|
|
"bit_depth": 24,
|
|
"format": "wav"
|
|
},
|
|
"tracks": [
|
|
{
|
|
"track_id": "1",
|
|
"file_id": "source_audio_drum_loop.wav",
|
|
"volume": 0.85,
|
|
"muted": false,
|
|
"clips": [
|
|
{
|
|
"clip_id": "clip_01",
|
|
"start_time_seconds": 2.450,
|
|
"end_time_seconds": 12.800,
|
|
"loop_count": 4,
|
|
"apply_zero_crossing": true,
|
|
"fade_in_ms": 150,
|
|
"fade_out_ms": 150
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Kế Hoạch Triển Khai Phát Triển (5-Week Porting Roadmap)
|
|
|
|
Để quá trình tích hợp mã nguồn diễn ra liền mạch không phát sinh lỗi lệch pha âm thanh (*Phase cancellation*) hoặc lệch nhịp, lộ trình 5 tuần được phân chia cụ thể như sau:
|
|
|
|
```text
|
|
[ TUẦN 1 ] ──► Thiết lập môi trường Docker, cài đặt FFmpeg & Cấu trúc FastAPI Gateway.
|
|
[ TUẦN 2 ] ──► Chuyển giao thuật toán Zero-Crossing & vẽ Peak Waveform đồng bộ sang Python.
|
|
[ TUẦN 3 ] ──► Viết bộ xử lý biên dịch đa kênh (Multitrack Overlay Engine) trên Celery Worker.
|
|
[ TUẦN 4 ] ──► Tích hợp OpenAI Compatible API kết nối DeepSeek/Ollama để phân tích cấu trúc khuôn nhạc.
|
|
[ TUẦN 5 ] ──► Kiểm định chất lượng âm thanh (Phát hiện Pop/Click, lệch pha) & Đóng gói hoàn tất.
|
|
```
|
|
|
|
### Chi tiết các bước thực thi:
|
|
|
|
#### **Tuần 1: Khởi tạo Hạ tầng Docker & API Gateway**
|
|
* Cài đặt Docker image tích hợp `python:3.11-slim`, biên dịch bổ sung gói phần mềm `ffmpeg` và `libsndfile1`.
|
|
* Xây dựng các Endpoint cơ bản phục vụ tải lên (`/upload`) và tải xuống (`/download`) tệp tin âm nhạc.
|
|
|
|
#### **Tuần 2: Porting & Kiểm nghiệm Thuật toán Zero-Crossing**
|
|
* Viết kịch bản kiểm thử (Unit tests) so sánh vị trí Zero-Crossing tìm được bằng JavaScript (trên Client) và bằng Numpy (trên Server).
|
|
* Đảm bảo sai số định vị mẫu âm học giữa hai môi trường tiến về mức $0$ (tuyệt đối đồng bộ).
|
|
|
|
#### **Tuần 3: Biên tập và Hòa âm Đa kênh**
|
|
* Triển khai bộ xử lý hàng đợi Celery kết nối Redis để thực thi các tác vụ xử lý âm thanh nặng dưới nền.
|
|
* Đảm bảo tính năng tăng giảm âm lượng tuyến tính và logarit không xảy ra hiện tượng méo tiếng (*Clipping distortion*).
|
|
|
|
#### **Tuần 4: Phân tích Âm học và Tích hợp Trí tuệ Nhân tạo (AI)**
|
|
* Gọi API OpenAI tương thích để phân tích, phân đoạn bố cục cấu trúc tệp nhạc (vào các phân khúc Intro, Verse, Chorus, Outro) dựa trên siêu dữ liệu (*metadata*) và sóng âm.
|
|
* Sử dụng `librosa.beat.beat_track` để tự động xác định nhịp độ (Tempo - BPM) và vị trí phách của khuôn nhạc (*bars*).
|
|
|
|
#### **Tuần 5: Kiểm định Chất lượng & Triển khai Hệ thống**
|
|
* Kiểm tra hiệu năng khi có nhiều người dùng đồng thời tải lên và thao tác cắt ghép trên dòng thời gian.
|
|
* Tối ưu hóa dung lượng bộ nhớ tạm (*Temporary storage*) trong Docker, tự động dọn dẹp các tệp kết xuất hết hạn sau 24 giờ.
|
|
Porting_DSP_Engine_Plan.md
|
|
Đang hiển thị Porting_DSP_Engine_Plan.md. |