สำรวจโลกแห่งการสังเคราะห์เสียงและการประมวลผลสัญญาณดิจิทัล (DSP) โดยใช้ Python เรียนรู้การสร้างรูปคลื่น ใช้ฟิลเตอร์ และสร้างเสียงจากศูนย์
ปลดปล่อยพลังเสียง: เจาะลึกการสังเคราะห์เสียงและการประมวลผลสัญญาณดิจิทัลด้วย Python
ตั้งแต่มิวสิกสตรีมมิ่งในหูฟังของคุณไปจนถึงภูมิทัศน์เสียงที่สมจริงของวิดีโอเกมและผู้ช่วยเสียงบนอุปกรณ์ของเรา เสียงดิจิทัลเป็นส่วนสำคัญของชีวิตยุคใหม่ แต่คุณเคยสงสัยหรือไม่ว่าเสียงเหล่านี้ถูกสร้างขึ้นได้อย่างไร? มันไม่ใช่เวทมนตร์ มันเป็นการผสมผสานที่น่าทึ่งของคณิตศาสตร์ ฟิสิกส์ และวิทยาการคอมพิวเตอร์ที่เรียกว่าการประมวลผลสัญญาณดิจิทัล (DSP) วันนี้ เราจะเปิดม่านและแสดงให้คุณเห็นวิธีควบคุมพลังของ Python เพื่อสร้าง จัดการ และสังเคราะห์เสียงจากพื้นฐาน
คู่มือนี้มีไว้สำหรับนักพัฒนา นักวิทยาศาสตร์ข้อมูล นักดนตรี ศิลปิน และทุกคนที่อยากรู้เกี่ยวกับจุดตัดของโค้ดและความคิดสร้างสรรค์ คุณไม่จำเป็นต้องเป็นผู้เชี่ยวชาญด้าน DSP หรือวิศวกรเสียงที่มีประสบการณ์ ด้วยความเข้าใจพื้นฐานเกี่ยวกับ Python คุณจะสร้างสรรค์ภูมิทัศน์เสียงที่เป็นเอกลักษณ์ของคุณเองได้ในไม่ช้า เราจะสำรวจส่วนประกอบพื้นฐานของเสียงดิจิทัล สร้างรูปคลื่นคลาสสิก กำหนดรูปร่างด้วย envelopes และ filters และสร้างมินิซินธิไซเซอร์ มาเริ่มการเดินทางสู่โลกแห่งเสียงเชิงคำนวณที่สดใสกันเถอะ
ทำความเข้าใจส่วนประกอบพื้นฐานของเสียงดิจิทัล
ก่อนที่เราจะเขียนโค้ดแม้แต่บรรทัดเดียว เราต้องเข้าใจว่าเสียงถูกแสดงในคอมพิวเตอร์อย่างไร ในโลกทางกายภาพ เสียงเป็นคลื่นอนาล็อกต่อเนื่องของความดัน คอมพิวเตอร์ซึ่งเป็นดิจิทัล ไม่สามารถจัดเก็บคลื่นต่อเนื่องได้ แต่จะทำการสแนปชอตหรือ samples ของคลื่นหลายพันครั้งต่อวินาที กระบวนการนี้เรียกว่า sampling
Sample Rate
Sample Rate กำหนดจำนวน samples ที่ถูกนำต่อวินาที วัดเป็น Hertz (Hz) sample rate ที่สูงขึ้นส่งผลให้การแสดงคลื่นเสียงต้นฉบับมีความแม่นยำมากขึ้น นำไปสู่เสียงที่มีความเที่ยงตรงสูงขึ้น sample rate ทั่วไป ได้แก่:
- 44100 Hz (44.1 kHz): มาตรฐานสำหรับ audio CDs เลือกตามทฤษฎีบท Nyquist-Shannon sampling ซึ่งระบุว่า sample rate ต้องเป็นอย่างน้อยสองเท่าของความถี่สูงสุดที่คุณต้องการบันทึก เนื่องจากช่วงการได้ยินของมนุษย์อยู่ที่ประมาณ 20,000 Hz ดังนั้น 44.1 kHz จึงมี buffer เพียงพอ
- 48000 Hz (48 kHz): มาตรฐานสำหรับวิดีโอระดับมืออาชีพและ digital audio workstations (DAWs)
- 96000 Hz (96 kHz): ใช้ในการผลิตเสียงที่มีความละเอียดสูงเพื่อให้ได้ความแม่นยำมากยิ่งขึ้น
สำหรับจุดประสงค์ของเรา เราจะใช้ 44100 Hz เป็นหลัก เนื่องจากให้ความสมดุลที่ยอดเยี่ยมระหว่างคุณภาพและประสิทธิภาพเชิงคำนวณ
Bit Depth
หาก sample rate กำหนดความละเอียดในเวลา Bit Depth จะกำหนดความละเอียดใน amplitude (ความดัง) แต่ละ sample คือตัวเลขที่แสดงถึง amplitude ของคลื่น ณ ช่วงเวลานั้นๆ bit depth คือจำนวนบิตที่ใช้ในการจัดเก็บตัวเลขนั้น bit depth ที่สูงขึ้นช่วยให้มีค่า amplitude ที่เป็นไปได้มากขึ้น ส่งผลให้ dynamic range กว้างขึ้น (ความแตกต่างระหว่างเสียงที่เงียบที่สุดและดังที่สุดที่เป็นไปได้) และ noise floor ที่ต่ำลง
- 16-bit: มาตรฐานสำหรับ CDs ให้ระดับ amplitude ที่เป็นไปได้ 65,536 ระดับ
- 24-bit: มาตรฐานสำหรับการผลิตเสียงระดับมืออาชีพ ให้มากกว่า 16.7 ล้านระดับ
เมื่อเราสร้างเสียงใน Python โดยใช้ไลบรารีเช่น NumPy เรามักจะทำงานกับ floating-point numbers (เช่น ระหว่าง -1.0 ถึง 1.0) เพื่อความแม่นยำสูงสุด จากนั้นจะถูกแปลงเป็น bit depth ที่เฉพาะเจาะจง (เช่น 16-bit integers) เมื่อบันทึกลงในไฟล์หรือเล่นผ่านฮาร์ดแวร์
Channels
สิ่งนี้หมายถึงจำนวน audio streams Mono audio มีหนึ่ง channel ในขณะที่ Stereo audio มีสอง (ซ้ายและขวา) สร้างความรู้สึกของพื้นที่และทิศทาง
การตั้งค่าสภาพแวดล้อม Python ของคุณ
ในการเริ่มต้น เราต้องมี Python libraries ที่จำเป็นสองสามตัว พวกเขาเป็นเครื่องมือของเราสำหรับการคำนวณเชิงตัวเลข การประมวลผลสัญญาณ การสร้างภาพ และการเล่นเสียง
คุณสามารถติดตั้งได้โดยใช้ pip:
pip install numpy scipy matplotlib sounddevice
มาทบทวนบทบาทของพวกเขาโดยย่อ:
- NumPy: หัวใจสำคัญของการคำนวณทางวิทยาศาสตร์ใน Python เราจะใช้มันเพื่อสร้างและจัดการ arrays ของตัวเลข ซึ่งจะแสดงถึงสัญญาณเสียงของเรา
- SciPy: สร้างขึ้นบน NumPy ให้ชุดของ algorithms มากมายสำหรับการประมวลผลสัญญาณ รวมถึงการสร้างรูปคลื่นและการ filtering
- Matplotlib: ไลบรารีการ plotting หลักใน Python มีค่าอย่างยิ่งสำหรับการสร้างภาพรูปคลื่นของเราและทำความเข้าใจผลกระทบของการประมวลผลของเรา
- SoundDevice: ไลบรารีที่สะดวกสำหรับการเล่น NumPy arrays ของเราเป็นเสียงผ่านลำโพงคอมพิวเตอร์ของคุณ ให้ interface ที่เรียบง่ายและข้ามแพลตฟอร์ม
การสร้างรูปคลื่น: หัวใจของการสังเคราะห์
เสียงทั้งหมด ไม่ว่าจะซับซ้อนเพียงใด สามารถแบ่งออกเป็นการรวมกันของรูปคลื่นพื้นฐานที่เรียบง่ายได้ สิ่งเหล่านี้คือสีหลักบน palette เสียงของเรา มาเรียนรู้วิธีสร้างพวกมันกัน
The Sine Wave: The Purest Tone
คลื่นไซน์คือ building block ที่สมบูรณ์ของเสียงทั้งหมด แสดงถึงความถี่เดียวโดยไม่มี overtones หรือ harmonics ให้เสียงที่ราบรื่น สะอาด และมักจะอธิบายว่าเป็น 'เหมือนขลุ่ย' สูตรทางคณิตศาสตร์คือ:
y(t) = Amplitude * sin(2 * π * frequency * t)
โดยที่ 't' คือเวลา มาแปลสิ่งนี้เป็น Python code กัน
import numpy as np
import sounddevice as sd
import matplotlib.pyplot as plt
# --- Global Parameters ---
SAMPLE_RATE = 44100 # samples per second
DURATION = 3.0 # seconds
# --- Waveform Generation ---
def generate_sine_wave(frequency, duration, sample_rate, amplitude=0.5):
"""Generate a sine wave.
Args:
frequency (float): The frequency of the sine wave in Hz.
duration (float): The duration of the wave in seconds.
sample_rate (int): The sample rate in Hz.
amplitude (float): The amplitude of the wave (0.0 to 1.0).
Returns:
np.ndarray: The generated sine wave as a NumPy array.
"""
# Create an array of time points
t = np.linspace(0, duration, int(sample_rate * duration), False)
# Generate the sine wave
# 2 * pi * frequency is the angular frequency
wave = amplitude * np.sin(2 * np.pi * frequency * t)
return wave
# --- Example Usage ---
if __name__ == "__main__":
# Generate a 440 Hz (A4 note) sine wave
frequency_a4 = 440.0
sine_wave = generate_sine_wave(frequency_a4, DURATION, SAMPLE_RATE)
print("Playing 440 Hz sine wave...")
# Play the sound
sd.play(sine_wave, SAMPLE_RATE)
sd.wait() # Wait for the sound to finish playing
print("Playback finished.")
# --- Visualization ---
# Plot a small portion of the wave to see its shape
plt.figure(figsize=(12, 4))
plt.plot(sine_wave[:500])
plt.title("Sine Wave (440 Hz)")
plt.xlabel("Sample")
plt.ylabel("Amplitude")
plt.grid(True)
plt.show()
ใน code นี้ np.linspace สร้าง array ที่แสดงถึงแกนเวลา จากนั้นเราจะใช้ sine function กับ time array นี้ โดยปรับขนาดตามความถี่ที่ต้องการ ผลลัพธ์คือ NumPy array ที่แต่ละองค์ประกอบคือ sample ของคลื่นเสียงของเรา จากนั้นเราสามารถเล่นได้ด้วย sounddevice และสร้างภาพด้วย matplotlib
Exploring Other Fundamental Waveforms
ในขณะที่ sine wave บริสุทธิ์ แต่ก็ไม่ได้น่าสนใจที่สุดเสมอไป รูปคลื่นพื้นฐานอื่นๆ อุดมไปด้วย harmonics ทำให้มี character (timbre) ที่ซับซ้อนและสดใสกว่า module scipy.signal ให้ functions ที่สะดวกสำหรับการสร้างพวกมัน
Square Wave
คลื่นสี่เหลี่ยมจะกระโดดทันทีระหว่าง amplitudes สูงสุดและต่ำสุด ประกอบด้วยเฉพาะ odd-numbered harmonics มีเสียงที่สดใส reed และ 'hollow' หรือ 'digital' เล็กน้อย ซึ่งมักเกี่ยวข้องกับเพลงวิดีโอเกมในยุคแรกๆ
from scipy import signal
# Generate a square wave
square_wave = 0.5 * signal.square(2 * np.pi * 440 * np.linspace(0, DURATION, int(SAMPLE_RATE * DURATION), False))
# sd.play(square_wave, SAMPLE_RATE)
# sd.wait()
Sawtooth Wave
คลื่น sawtooth จะ ramp ขึ้นเป็นเส้นตรงแล้วลดลงเป็นค่าต่ำสุดทันที (หรือในทางกลับกัน) อุดมไปด้วยอย่างไม่น่าเชื่อ ประกอบด้วย integer harmonics ทั้งหมด (ทั้งคู่และคี่) ทำให้เสียงสดใส buzzy มาก และเป็นจุดเริ่มต้นที่ยอดเยี่ยมสำหรับการ subtractive synthesis ซึ่งเราจะกล่าวถึงในภายหลัง
# Generate a sawtooth wave
sawtooth_wave = 0.5 * signal.sawtooth(2 * np.pi * 440 * np.linspace(0, DURATION, int(SAMPLE_RATE * DURATION), False))
# sd.play(sawtooth_wave, SAMPLE_RATE)
# sd.wait()
Triangle Wave
คลื่น triangle จะ ramp ขึ้นและลงเป็นเส้นตรง เช่นเดียวกับคลื่นสี่เหลี่ยม ประกอบด้วยเฉพาะ odd harmonics แต่ amplitude ของพวกมันจะลดลงเร็วกว่ามาก ทำให้ได้เสียงที่นุ่มนวลและ mellow กว่าคลื่นสี่เหลี่ยม ใกล้เคียงกับคลื่นไซน์ แต่มี 'body' มากกว่าเล็กน้อย
# Generate a triangle wave (a sawtooth with 0.5 width)
triangle_wave = 0.5 * signal.sawtooth(2 * np.pi * 440 * np.linspace(0, DURATION, int(SAMPLE_RATE * DURATION), False), width=0.5)
# sd.play(triangle_wave, SAMPLE_RATE)
# sd.wait()
White Noise: The Sound of Randomness
White noise เป็นสัญญาณที่มีพลังงานเท่ากันในทุกความถี่ ให้เสียงเหมือน static หรือ 'shhh' ของน้ำตก มีประโยชน์อย่างเหลือเชื่อในการออกแบบเสียงสำหรับการสร้างเสียง percussive (เช่น hi-hats และ snares) และ atmospheric effects การสร้างมันนั้นง่ายอย่างน่าทึ่ง
# Generate white noise
num_samples = int(SAMPLE_RATE * DURATION)
white_noise = np.random.uniform(-1, 1, num_samples)
# sd.play(white_noise, SAMPLE_RATE)
# sd.wait()
Additive Synthesis: Building Complexity
นักคณิตศาสตร์ชาวฝรั่งเศส Joseph Fourier ค้นพบว่า complex, periodic waveform ใดๆ สามารถ deconstructed เป็นผลรวมของ sine waves ที่เรียบง่ายได้ นี่คือพื้นฐานของ additive synthesis โดยการเพิ่ม sine waves ของความถี่ (harmonics) และ amplitudes ที่แตกต่างกัน เราสามารถสร้าง timbres ใหม่ที่สมบูรณ์ยิ่งขึ้น
มาสร้าง tone ที่ซับซ้อนยิ่งขึ้นโดยการเพิ่ม harmonics สองสามตัวแรกของ fundamental frequency
def generate_complex_tone(fundamental_freq, duration, sample_rate):
t = np.linspace(0, duration, int(sample_rate * duration), False)
# Start with the fundamental frequency
tone = 0.5 * np.sin(2 * np.pi * fundamental_freq * t)
# Add harmonics (overtones)
# 2nd harmonic (octave higher), lower amplitude
tone += 0.25 * np.sin(2 * np.pi * (2 * fundamental_freq) * t)
# 3rd harmonic, even lower amplitude
tone += 0.12 * np.sin(2 * np.pi * (3 * fundamental_freq) * t)
# 5th harmonic
tone += 0.08 * np.sin(2 * np.pi * (5 * fundamental_freq) * t)
# Normalize the waveform to be between -1 and 1
tone = tone / np.max(np.abs(tone))
return tone
# --- Example Usage ---
complex_tone = generate_complex_tone(220, DURATION, SAMPLE_RATE)
sd.play(complex_tone, SAMPLE_RATE)
sd.wait()
โดยการเลือกอย่างระมัดระวังว่า harmonics ใดที่จะเพิ่มและที่ amplitudes ใด คุณสามารถเริ่มเลียนแบบเสียงของ instruments ในโลกแห่งความเป็นจริงได้ ตัวอย่างง่ายๆ นี้ให้เสียงที่สมบูรณ์และน่าสนใจกว่า sine wave ธรรมดา
Shaping Sound with Envelopes (ADSR)
จนถึงตอนนี้ เสียงของเราเริ่มและหยุดอย่างกะทันหัน พวกเขามี volume คงที่ตลอดระยะเวลาของพวกเขา ซึ่งให้เสียงที่ไม่เป็นธรรมชาติและเป็นหุ่นยนต์ ในโลกแห่งความเป็นจริง เสียงมีการพัฒนาไปตามกาลเวลา โน้ตเปียโนมีการเริ่มต้นที่คมชัดและดังซึ่งจางหายไปอย่างรวดเร็ว ในขณะที่โน้ตที่เล่นบนไวโอลินสามารถ swell ใน volume ได้อย่างค่อยเป็นค่อยไป เราควบคุมวิวัฒนาการ dynamic นี้โดยใช้ amplitude envelope
The ADSR Model
envelope ที่พบมากที่สุดคือ ADSR envelope ซึ่งมีสี่ stages:
- Attack: เวลาที่ใช้เพื่อให้เสียงเปลี่ยนจากเงียบเป็น amplitude สูงสุด attack ที่รวดเร็วสร้างเสียง percussive ที่คมชัด (เช่น drum hit) attack ที่ช้าสร้างเสียง swell ที่นุ่มนวล (เช่น string pad)
- Decay: เวลาที่ใช้เพื่อให้เสียงลดลงจากระดับ attack สูงสุดไปเป็นระดับ sustain
- Sustain: ระดับ amplitude ที่เสียงคงไว้ตราบเท่าที่โน้ตถูกกด นี่คือระดับ ไม่ใช่เวลา
- Release: เวลาที่ใช้เพื่อให้เสียงจางลงจากระดับ sustain ไปเป็นความเงียบหลังจากที่โน้ตถูกปล่อย release ที่ยาวทำให้เสียง linger เหมือนโน้ตเปียโนที่กด sustain pedal ค้างไว้
Implementing an ADSR Envelope in Python
เราสามารถ implement function เพื่อสร้าง ADSR envelope เป็น NumPy array จากนั้นเราจะนำไปใช้กับ waveform ของเราผ่านการคูณแบบ element-wise ที่เรียบง่าย
def adsr_envelope(duration, sample_rate, attack_time, decay_time, sustain_level, release_time):
num_samples = int(duration * sample_rate)
attack_samples = int(attack_time * sample_rate)
decay_samples = int(decay_time * sample_rate)
release_samples = int(release_time * sample_rate)
sustain_samples = num_samples - attack_samples - decay_samples - release_samples
if sustain_samples < 0:
# If times are too long, adjust them proportionally
total_time = attack_time + decay_time + release_time
attack_time, decay_time, release_time = \
attack_time/total_time*duration, decay_time/total_time*duration, release_time/total_time*duration
attack_samples = int(attack_time * sample_rate)
decay_samples = int(decay_time * sample_rate)
release_samples = int(release_time * sample_rate)
sustain_samples = num_samples - attack_samples - decay_samples - release_samples
# Generate each part of the envelope
attack = np.linspace(0, 1, attack_samples)
decay = np.linspace(1, sustain_level, decay_samples)
sustain = np.full(sustain_samples, sustain_level)
release = np.linspace(sustain_level, 0, release_samples)
return np.concatenate([attack, decay, sustain, release])
# --- Example Usage: Plucky vs. Pad Sound ---
# Pluck sound (fast attack, quick decay, no sustain)
pluck_envelope = adsr_envelope(DURATION, SAMPLE_RATE, 0.01, 0.2, 0.0, 0.5)
# Pad sound (slow attack, long release)
pad_envelope = adsr_envelope(DURATION, SAMPLE_RATE, 0.5, 0.2, 0.7, 1.0)
# Generate a harmonically rich sawtooth wave to apply envelopes to
saw_wave_for_env = generate_complex_tone(220, DURATION, SAMPLE_RATE)
# Apply envelopes
plucky_sound = saw_wave_for_env * pluck_envelope
pad_sound = saw_wave_for_env * pad_envelope
print("Playing plucky sound...")
sd.play(plucky_sound, SAMPLE_RATE)
sd.wait()
print("Playing pad sound...")
sd.play(pad_sound, SAMPLE_RATE)
sd.wait()
# Visualize the envelopes
plt.figure(figsize=(12, 6))
plt.subplot(2, 1, 1)
plt.plot(pluck_envelope)
plt.title("Pluck ADSR Envelope")
plt.subplot(2, 1, 2)
plt.plot(pad_envelope)
plt.title("Pad ADSR Envelope")
plt.tight_layout()
plt.show()
สังเกตว่า waveform พื้นฐานเดียวกันเปลี่ยน character อย่างมากเพียงใดโดยการใช้ envelope ที่แตกต่างกัน นี่เป็นเทคนิคพื้นฐานในการออกแบบเสียง
Introduction to Digital Filtering (Subtractive Synthesis)
ในขณะที่ additive synthesis สร้างเสียงโดยการเพิ่ม sine waves subtractive synthesis ทำงานในทางตรงกันข้าม เราเริ่มต้นด้วยสัญญาณที่ harmonically rich (เช่น sawtooth wave หรือ white noise) แล้ว carve ออกหรือ attenuate ความถี่ที่เฉพาะเจาะจงโดยใช้ filters นี่คือ analog กับ sculptor ที่เริ่มต้นด้วย block ของหินอ่อนและ chipping ออกเพื่อเผยให้เห็น form
Key Filter Types
- Low-Pass Filter: นี่คือ filter ที่พบมากที่สุดในการสังเคราะห์ อนุญาตให้ความถี่ below จุด 'cutoff' ที่แน่นอนผ่านเข้ามาในขณะที่ attenuate ความถี่ above มัน ทำให้เสียง dark, warm หรือ muffled มากขึ้น
- High-Pass Filter: ตรงข้ามกับ low-pass filter อนุญาตให้ความถี่ above cutoff ผ่านเข้ามา โดยลบเบสและความถี่ต่ำ ทำให้เสียง thin หรือ tinny มากขึ้น
- Band-Pass Filter: อนุญาตให้เฉพาะ band ที่เฉพาะเจาะจงของความถี่ผ่านเข้ามา โดยตัดทั้ง highs และ lows สิ่งนี้สามารถสร้างเอฟเฟกต์ 'telephone' หรือ 'radio' ได้
- Band-Stop (Notch) Filter: ตรงข้ามกับ band-pass ลบ band ที่เฉพาะเจาะจงของความถี่
Implementing Filters with SciPy
library scipy.signal ให้เครื่องมือที่มีประสิทธิภาพสำหรับการออกแบบและใช้ digital filters เราจะใช้ type ทั่วไปที่เรียกว่า Butterworth filter ซึ่งเป็นที่รู้จักสำหรับ flat response ใน passband
กระบวนการนี้เกี่ยวข้องกับสอง steps: ขั้นแรก การออกแบบ filter เพื่อรับ coefficients และขั้นที่สอง การใช้ coefficients เหล่านั้นกับสัญญาณเสียงของเรา
from scipy.signal import butter, lfilter, freqz
def butter_lowpass_filter(data, cutoff, fs, order=5):
"""Apply a low-pass Butterworth filter to a signal."""
nyquist = 0.5 * fs
normal_cutoff = cutoff / nyquist
# Get the filter coefficients
b, a = butter(order, normal_cutoff, btype='low', analog=False)
y = lfilter(b, a, data)
return y
# --- Example Usage ---
# Start with a rich signal: sawtooth wave
saw_wave_rich = 0.5 * signal.sawtooth(2 * np.pi * 220 * np.linspace(0, DURATION, int(SAMPLE_RATE * DURATION), False))
print("Playing original sawtooth wave...")
sd.play(saw_wave_rich, SAMPLE_RATE)
sd.wait()
# Apply a low-pass filter with a cutoff of 800 Hz
filtered_saw = butter_lowpass_filter(saw_wave_rich, cutoff=800, fs=SAMPLE_RATE, order=6)
print("Playing filtered sawtooth wave...")
sd.play(filtered_saw, SAMPLE_RATE)
sd.wait()
# --- Visualization of the filter's frequency response ---
cutoff_freq = 800
order = 6
b, a = butter(order, cutoff_freq / (0.5 * SAMPLE_RATE), btype='low')
w, h = freqz(b, a, worN=8000)
plt.figure(figsize=(10, 5))
plt.plot(0.5 * SAMPLE_RATE * w / np.pi, np.abs(h), 'b')
plt.plot(cutoff_freq, 0.5 * np.sqrt(2), 'ko')
plt.axvline(cutoff_freq, color='k', linestyle='--')
plt.xlim(0, 5000)
plt.title("Low-pass Filter Frequency Response")
plt.xlabel('Frequency [Hz]')
plt.grid()
plt.show()
ฟังความแตกต่างระหว่าง original และ filtered waves original สดใสและ buzzy filtered version นุ่มนวลและ dark กว่ามากเนื่องจาก high-frequency harmonics ถูกลบออก การ sweeping cutoff frequency ของ low-pass filter เป็นหนึ่งในเทคนิคที่ expressive และพบมากที่สุดใน electronic music
Modulation: Adding Movement and Life
Static sounds น่าเบื่อ Modulation เป็นกุญแจสำคัญในการสร้าง dynamic, evolving และน่าสนใจ principles ง่าย: ใช้สัญญาณหนึ่ง (modulator) เพื่อควบคุม parameter ของสัญญาณอื่น (carrier) modulator ทั่วไปคือ Low-Frequency Oscillator (LFO) ซึ่งเป็นเพียง oscillator ที่มีความถี่ต่ำกว่าช่วงการได้ยินของมนุษย์ (เช่น 0.1 Hz ถึง 20 Hz)
Amplitude Modulation (AM) and Tremolo
นี่คือเมื่อเราใช้ LFO เพื่อควบคุม amplitude ของเสียงของเรา ผลลัพธ์คือ rhythmic pulsing ใน volume ที่เรียกว่า tremolo
# Carrier wave (the sound we hear)
carrier_freq = 300
carrier = generate_sine_wave(carrier_freq, DURATION, SAMPLE_RATE)
# Modulator LFO (controls the volume)
lfo_freq = 5 # 5 Hz LFO
modulator = generate_sine_wave(lfo_freq, DURATION, SAMPLE_RATE, amplitude=1.0)
# Create tremolo effect
# We scale the modulator to be from 0 to 1
tremolo_modulator = (modulator + 1) / 2
tremolo_sound = carrier * tremolo_modulator
print("Playing tremolo effect...")
sd.play(tremolo_sound, SAMPLE_RATE)
sd.wait()
Frequency Modulation (FM) and Vibrato
นี่คือเมื่อเราใช้ LFO เพื่อควบคุมความถี่ของเสียงของเรา modulation ที่ช้าและ subtle ของความถี่สร้าง vibrato การ wavering ของ pitch ที่นุ่มนวลที่นักร้องและนักไวโอลินใช้เพื่อเพิ่ม expression
# Create vibrato effect
t = np.linspace(0, DURATION, int(SAMPLE_RATE * DURATION), False)
carrier_freq = 300
lfo_freq = 7
modulation_depth = 10 # How much the frequency will vary
# The LFO will be added to the carrier frequency
modulator_vibrato = modulation_depth * np.sin(2 * np.pi * lfo_freq * t)
# The instantaneous frequency changes over time
instantaneous_freq = carrier_freq + modulator_vibrato
# We need to integrate the frequency to get the phase
phase = np.cumsum(2 * np.pi * instantaneous_freq / SAMPLE_RATE)
vibrato_sound = 0.5 * np.sin(phase)
print("Playing vibrato effect...")
sd.play(vibrato_sound, SAMPLE_RATE)
sd.wait()
นี่คือ version ที่ง่ายกว่าของ FM synthesis เมื่อ LFO frequency เพิ่มขึ้นใน audible range จะสร้าง complex sideband frequencies ส่งผลให้ rich, bell-like และ metallic tones นี่คือพื้นฐานของเสียง iconic ของ synthesizers เช่น Yamaha DX7
Putting It All Together: A Mini Synthesizer Project
มา combine ทุกสิ่งที่เราได้เรียนรู้เข้าสู่ simple, functional synthesizer class สิ่งนี้จะ encapsulate oscillator, envelope และ filter ของเราเข้าสู่ object ที่ reusable เดียว
class MiniSynth:
def __init__(self, sample_rate=44100):
self.sample_rate = sample_rate
def generate_note(self, frequency, duration, waveform='sine',
adsr_params=(0.05, 0.2, 0.5, 0.3),
filter_params=None):
"""Generate a single synthesized note."""
num_samples = int(duration * self.sample_rate)
t = np.linspace(0, duration, num_samples, False)
# 1. Oscillator
if waveform == 'sine':
wave = np.sin(2 * np.pi * frequency * t)
elif waveform == 'square':
wave = signal.square(2 * np.pi * frequency * t)
elif waveform == 'sawtooth':
wave = signal.sawtooth(2 * np.pi * frequency * t)
elif waveform == 'triangle':
wave = signal.sawtooth(2 * np.pi * frequency * t, width=0.5)
else:
raise ValueError("Unsupported waveform")
# 2. Envelope
attack, decay, sustain, release = adsr_params
envelope = adsr_envelope(duration, self.sample_rate, attack, decay, sustain, release)
# Ensure envelope and wave are the same length
min_len = min(len(wave), len(envelope))
wave = wave[:min_len] * envelope[:min_len]
# 3. Filter (optional)
if filter_params:
cutoff = filter_params.get('cutoff', 1000)
order = filter_params.get('order', 5)
filter_type = filter_params.get('type', 'low')
if filter_type == 'low':
wave = butter_lowpass_filter(wave, cutoff, self.sample_rate, order)
# ... could add high-pass etc. here
# Normalize to 0.5 amplitude
return wave * 0.5
# --- Example Usage of the Synth ---
synth = MiniSynth()
# A bright, plucky bass sound
bass_note = synth.generate_note(
frequency=110, # A2 note
duration=1.5,
waveform='sawtooth',
adsr_params=(0.01, 0.3, 0.0, 0.2),
filter_params={'cutoff': 600, 'order': 6}
)
print("Playing synth bass note...")
sd.play(bass_note, SAMPLE_RATE)
sd.wait()
# A soft, atmospheric pad sound
pad_note = synth.generate_note(
frequency=440, # A4 note
duration=5.0,
waveform='triangle',
adsr_params=(1.0, 0.5, 0.7, 1.5)
)
print("Playing synth pad note...")
sd.play(pad_note, SAMPLE_RATE)
sd.wait()
# A simple melody
melody = [
('C4', 261.63, 0.4),
('D4', 293.66, 0.4),
('E4', 329.63, 0.4),
('C4', 261.63, 0.8)
]
final_melody = []
for note, freq, dur in melody:
sound = synth.generate_note(freq, dur, 'square', adsr_params=(0.01, 0.1, 0.2, 0.1), filter_params={'cutoff': 1500})
final_melody.append(sound)
full_melody_wave = np.concatenate(final_melody)
print("Playing a short melody...")
sd.play(full_melody_wave, SAMPLE_RATE)
sd.wait()
simple class นี้เป็นการสาธิตที่มีประสิทธิภาพของ principles ที่เราได้กล่าวถึง ฉันขอแนะนำให้คุณทดลองกับมัน ลอง waveforms ที่แตกต่างกัน ปรับแต่ง ADSR parameters และเปลี่ยน filter cutoff เพื่อดูว่าคุณสามารถเปลี่ยนเสียงได้อย่างสิ้นเชิง
Beyond the Basics: Where to Go Next?
เราได้ scratched เฉพาะ surface ของ deep และ rewarding field ของ audio synthesis และ DSP หากสิ่งนี้ spark ความสนใจของคุณ นี่คือ advanced topics ที่จะสำรวจ:
- Wavetable Synthesis: แทนที่จะใช้ shapes ที่สมบูรณ์ทางคณิตศาสตร์ เทคนิคนี้ใช้ pre-recorded, single-cycle waveforms เป็น oscillator source ทำให้สามารถใช้ timbres ที่ complex และ evolving อย่างไม่น่าเชื่อ
- Granular Synthesis: สร้างเสียงใหม่โดยการ deconstructing audio sample ที่มีอยู่เป็น fragments ขนาดเล็ก (grains) แล้ว re-arranging, stretching และ pitching พวกเขา เหมาะอย่างยิ่งสำหรับการสร้าง atmospheric textures และ pads
- Physical Modeling Synthesis: แนวทางที่น่าสนใจที่พยายามสร้างเสียงโดยการ mathematical modeling คุณสมบัติทางกายภาพของ instrument—the string of a guitar, the tube of a clarinet, the membrane of a drum
- Real-time Audio Processing: Libraries เช่น PyAudio และ SoundCard ช่วยให้คุณทำงานกับ audio streams จาก microphones หรือ inputs อื่นๆ ใน real time เปิดประตูสู่ live effects, interactive installations และอื่นๆ
- Machine Learning in Audio: AI และ deep learning กำลังปฏิวัติ audio Models สามารถสร้าง novel music สังเคราะห์ realistic human speech หรือแม้กระทั่งแยก individual instruments จากเพลง mixed
Conclusion
เราได้ journeyed จาก nature พื้นฐานของเสียงดิจิทัลไปจนถึงการสร้าง functional synthesizer เราได้เรียนรู้วิธีสร้าง pure และ complex waveforms โดยใช้ Python, NumPy และ SciPy เราค้นพบวิธีให้เสียงของเรามี life และ shape โดยใช้ ADSR envelopes sculpt character ของพวกเขาด้วย digital filters และเพิ่ม dynamic movement ด้วย modulation code ที่เราได้เขียนไม่ใช่แค่ technical exercise เป็น creative tool
Python's powerful scientific stack ทำให้เป็น platform ที่โดดเด่นสำหรับการเรียนรู้ การทดลอง และการสร้างในโลกของ audio ไม่ว่าเป้าหมายของคุณคือการสร้าง custom sound effect สำหรับ project สร้าง musical instrument หรือเพียงแค่เข้าใจเทคโนโลยีเบื้องหลังเสียงที่คุณได้ยินทุกวัน principles ที่คุณได้เรียนรู้ที่นี่คือ starting point ของคุณ ตอนนี้ ถึง turn ของคุณที่จะทดลอง เริ่ม combine เทคนิคเหล่านี้ ลอง parameters ใหม่ และฟังผลลัพธ์อย่างใกล้ชิด จักรวาลอันกว้างใหญ่ของเสียงอยู่ที่ปลายนิ้วของคุณแล้ว—สิ่งที่คุณจะสร้าง