Jelajahi dunia sintesis audio dan pemrosesan sinyal digital (DSP) menggunakan Python. Pelajari cara menghasilkan bentuk gelombang, menerapkan filter, dan menciptakan suara dari awal.
Melepaskan Suara: Menyelami Python untuk Sintesis Audio dan Pemrosesan Sinyal Digital
Mulai dari streaming musik di headphone Anda hingga lanskap suara imersif video game dan asisten suara di perangkat kita, audio digital adalah bagian tak terpisahkan dari kehidupan modern. Namun, pernahkah Anda bertanya-tanya bagaimana suara-suara ini diciptakan? Ini bukan sihir; ini adalah perpaduan menakjubkan antara matematika, fisika, dan ilmu komputer yang dikenal sebagai Pemrosesan Sinyal Digital (Digital Signal Processing - DSP). Hari ini, kita akan membuka tirai dan menunjukkan kepada Anda cara memanfaatkan kekuatan Python untuk menghasilkan, memanipulasi, dan menyintesis suara dari nol.
Panduan ini ditujukan untuk para pengembang, ilmuwan data, musisi, seniman, dan siapa saja yang ingin tahu tentang persimpangan antara kode dan kreativitas. Anda tidak perlu menjadi ahli DSP atau insinyur audio berpengalaman. Dengan pemahaman dasar tentang Python, Anda akan segera dapat merancang lanskap suara unik Anda sendiri. Kita akan menjelajahi blok bangunan fundamental audio digital, menghasilkan bentuk gelombang klasik, membentuknya dengan envelope dan filter, dan bahkan membangun sebuah mini-synthesizer. Mari kita mulai perjalanan kita ke dunia audio komputasional yang penuh warna.
Memahami Blok Bangunan Audio Digital
Sebelum kita dapat menulis satu baris kode pun, kita harus memahami bagaimana suara direpresentasikan di dalam komputer. Di dunia fisik, suara adalah gelombang tekanan analog yang kontinu. Komputer, yang bersifat digital, tidak dapat menyimpan gelombang kontinu. Sebaliknya, mereka mengambil ribuan cuplikan, atau sampel, dari gelombang setiap detiknya. Proses ini disebut sampling.
Sample Rate
Sample Rate (Tingkat Sampel) menentukan berapa banyak sampel yang diambil per detik. Ini diukur dalam Hertz (Hz). Sample rate yang lebih tinggi menghasilkan representasi gelombang suara asli yang lebih akurat, yang mengarah ke audio dengan fidelitas lebih tinggi. Sample rate yang umum meliputi:
- 44100 Hz (44.1 kHz): Standar untuk CD audio. Ini dipilih berdasarkan teorema sampling Nyquist-Shannon, yang menyatakan bahwa sample rate harus setidaknya dua kali frekuensi tertinggi yang ingin Anda tangkap. Karena rentang pendengaran manusia mencapai puncaknya sekitar 20.000 Hz, 44.1 kHz menyediakan buffer yang cukup.
- 48000 Hz (48 kHz): Standar untuk video profesional dan stasiun kerja audio digital (DAW).
- 96000 Hz (96 kHz): Digunakan dalam produksi audio resolusi tinggi untuk akurasi yang lebih besar lagi.
Untuk tujuan kita, kita akan menggunakan 44100 Hz, karena ini memberikan keseimbangan yang sangat baik antara kualitas dan efisiensi komputasi.
Bit Depth
Jika sample rate menentukan resolusi dalam waktu, Bit Depth (Kedalaman Bit) menentukan resolusi dalam amplitudo (kenyaringan). Setiap sampel adalah angka yang mewakili amplitudo gelombang pada saat tertentu. Bit depth adalah jumlah bit yang digunakan untuk menyimpan angka tersebut. Bit depth yang lebih tinggi memungkinkan lebih banyak nilai amplitudo yang mungkin, menghasilkan rentang dinamis yang lebih besar (perbedaan antara suara paling pelan dan paling keras yang mungkin) dan tingkat kebisingan (noise floor) yang lebih rendah.
- 16-bit: Standar untuk CD, menawarkan 65.536 tingkat amplitudo yang mungkin.
- 24-bit: Standar untuk produksi audio profesional, menawarkan lebih dari 16,7 juta tingkat.
Saat kita menghasilkan audio di Python menggunakan pustaka seperti NumPy, kita biasanya bekerja dengan angka floating-point (misalnya, antara -1.0 dan 1.0) untuk presisi maksimum. Angka-angka ini kemudian dikonversi ke bit depth tertentu (seperti integer 16-bit) saat menyimpan ke file atau memutarnya kembali melalui perangkat keras.
Channels
Ini hanya merujuk pada jumlah aliran audio. Audio Mono memiliki satu saluran, sedangkan audio Stereo memiliki dua (kiri dan kanan), menciptakan kesan ruang dan arah.
Menyiapkan Lingkungan Python Anda
Untuk memulai, kita memerlukan beberapa pustaka Python yang esensial. Mereka membentuk perangkat kita untuk komputasi numerik, pemrosesan sinyal, visualisasi, dan pemutaran audio.
Anda dapat menginstalnya menggunakan pip:
pip install numpy scipy matplotlib sounddevice
Mari kita ulas secara singkat peran masing-masing:
- NumPy: Batu penjuru komputasi ilmiah di Python. Kita akan menggunakannya untuk membuat dan memanipulasi array angka, yang akan merepresentasikan sinyal audio kita.
- SciPy: Dibangun di atas NumPy, pustaka ini menyediakan banyak sekali koleksi algoritma untuk pemrosesan sinyal, termasuk pembuatan bentuk gelombang dan pemfilteran.
- Matplotlib: Pustaka plotting utama di Python. Ini sangat berharga untuk memvisualisasikan bentuk gelombang kita dan memahami efek dari pemrosesan yang kita lakukan.
- SoundDevice: Pustaka yang praktis untuk memutar kembali array NumPy kita sebagai audio melalui speaker komputer Anda. Ini menyediakan antarmuka yang sederhana dan lintas platform.
Pembuatan Bentuk Gelombang: Jantung Sintesis
Semua suara, tidak peduli seberapa kompleksnya, dapat dipecah menjadi kombinasi bentuk gelombang dasar yang sederhana. Ini adalah warna-warna primer pada palet sonik kita. Mari kita pelajari cara menghasilkannya.
Gelombang Sinus: Nada Paling Murni
Gelombang sinus adalah blok bangunan absolut dari semua suara. Ini mewakili satu frekuensi tunggal tanpa nada tambahan (overtone) atau harmonik. Suaranya sangat halus, bersih, dan sering digambarkan 'seperti seruling'. Rumus matematisnya adalah:
y(t) = Amplitudo * sin(2 * π * frekuensi * t)
Di mana 't' adalah waktu. Mari kita terjemahkan ini ke dalam kode Python.
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()
Dalam kode ini, np.linspace membuat sebuah array yang merepresentasikan sumbu waktu. Kita kemudian menerapkan fungsi sinus ke array waktu ini, yang diskalakan dengan frekuensi yang diinginkan. Hasilnya adalah array NumPy di mana setiap elemen adalah sampel dari gelombang suara kita. Kita kemudian dapat memutarnya dengan sounddevice dan memvisualisasikannya dengan matplotlib.
Menjelajahi Bentuk Gelombang Fundamental Lainnya
Meskipun gelombang sinus itu murni, ia tidak selalu yang paling menarik. Bentuk gelombang dasar lainnya kaya akan harmonik, memberinya karakter (timbre) yang lebih kompleks dan cerah. Modul scipy.signal menyediakan fungsi-fungsi yang praktis untuk menghasilkannya.
Gelombang Kotak (Square Wave)
Gelombang kotak melompat secara instan antara amplitudo maksimum dan minimumnya. Gelombang ini hanya berisi harmonik bernomor ganjil. Suaranya cerah, sengau, dan agak 'kosong' atau 'digital', sering dikaitkan dengan musik video game awal.
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()
Gelombang Gigi Gergaji (Sawtooth Wave)
Gelombang gigi gergaji naik secara linear dan kemudian langsung jatuh ke nilai minimumnya (atau sebaliknya). Gelombang ini sangat kaya, mengandung semua harmonik integer (baik genap maupun ganjil). Hal ini membuatnya terdengar sangat cerah, berdengung, dan merupakan titik awal yang fantastis untuk sintesis subtraktif, yang akan kita bahas nanti.
# 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()
Gelombang Segitiga (Triangle Wave)
Gelombang segitiga naik dan turun secara linear. Seperti gelombang kotak, ia hanya mengandung harmonik ganjil, tetapi amplitudonya menurun jauh lebih cepat. Hal ini memberinya suara yang lebih lembut dan lebih halus daripada gelombang kotak, lebih dekat ke gelombang sinus tetapi dengan sedikit lebih banyak 'isi'.
# 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: Suara Keacakan
White noise adalah sinyal yang mengandung energi yang sama di setiap frekuensi. Kedengarannya seperti statis atau desis air terjun. Ini sangat berguna dalam desain suara untuk menciptakan suara perkusi (seperti hi-hat dan snare) dan efek atmosferik. Menghasilkannya sangatlah sederhana.
# 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()
Sintesis Aditif: Membangun Kompleksitas
Matematikawan Prancis Joseph Fourier menemukan bahwa setiap bentuk gelombang periodik yang kompleks dapat diuraikan menjadi penjumlahan gelombang sinus sederhana. Inilah dasar dari sintesis aditif. Dengan menambahkan gelombang sinus dari frekuensi (harmonik) dan amplitudo yang berbeda, kita dapat membangun timbre baru yang lebih kaya.
Mari kita ciptakan nada yang lebih kompleks dengan menambahkan beberapa harmonik pertama dari frekuensi fundamental.
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()
Dengan memilih secara cermat harmonik mana yang akan ditambahkan dan pada amplitudo berapa, Anda dapat mulai meniru suara instrumen di dunia nyata. Contoh sederhana ini sudah terdengar jauh lebih kaya dan lebih menarik daripada gelombang sinus biasa.
Membentuk Suara dengan Envelope (ADSR)
Sejauh ini, suara kita dimulai dan berhenti secara tiba-tiba. Suara tersebut memiliki volume yang konstan sepanjang durasinya, yang terdengar sangat tidak alami dan robotik. Di dunia nyata, suara berevolusi seiring waktu. Nada piano memiliki awal yang tajam dan keras yang cepat memudar, sementara nada yang dimainkan biola dapat membengkak volumenya secara bertahap. Kita mengontrol evolusi dinamis ini menggunakan amplitude envelope.
Model ADSR
Jenis envelope yang paling umum adalah envelope ADSR, yang memiliki empat tahap:
- Attack: Waktu yang dibutuhkan suara untuk beralih dari senyap ke amplitudo maksimumnya. Attack yang cepat menciptakan suara perkusi yang tajam (seperti pukulan drum). Attack yang lambat menciptakan suara yang lembut dan membengkak (seperti string pad).
- Decay: Waktu yang dibutuhkan suara untuk menurun dari level attack maksimum ke level sustain.
- Sustain: Tingkat amplitudo yang dipertahankan suara selama nada ditahan. Ini adalah level, bukan waktu.
- Release: Waktu yang dibutuhkan suara untuk memudar dari level sustain hingga senyap setelah nada dilepaskan. Release yang panjang membuat suara bertahan lebih lama, seperti nada piano dengan pedal sustain ditekan.
Mengimplementasikan Envelope ADSR dengan Python
Kita dapat mengimplementasikan sebuah fungsi untuk menghasilkan envelope ADSR sebagai array NumPy. Kita kemudian menerapkannya pada bentuk gelombang kita melalui perkalian element-wise sederhana.
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()
Perhatikan betapa dramatisnya bentuk gelombang dasar yang sama mengubah karakternya hanya dengan menerapkan envelope yang berbeda. Ini adalah teknik fundamental dalam desain suara.
Pengantar Pemfilteran Digital (Sintesis Subtraktif)
Sementara sintesis aditif membangun suara dengan menambahkan gelombang sinus, sintesis subtraktif bekerja sebaliknya. Kita mulai dengan sinyal yang kaya harmonik (seperti gelombang gigi gergaji atau white noise) dan kemudian membuang atau melemahkan frekuensi tertentu menggunakan filter. Ini analog dengan seorang pematung yang memulai dengan balok marmer dan memahatnya untuk menampakkan sebuah bentuk.
Jenis-jenis Filter Utama
- Low-Pass Filter: Ini adalah filter paling umum dalam sintesis. Filter ini memungkinkan frekuensi di bawah titik 'cutoff' tertentu untuk lewat sambil melemahkan frekuensi di atasnya. Ini membuat suara menjadi lebih gelap, lebih hangat, atau lebih teredam.
- High-Pass Filter: Kebalikan dari low-pass filter. Filter ini memungkinkan frekuensi di atas cutoff untuk lewat, menghilangkan frekuensi bass dan nada rendah. Ini membuat suara menjadi lebih tipis atau cempreng.
- Band-Pass Filter: Hanya mengizinkan pita frekuensi tertentu untuk lewat, memotong baik frekuensi tinggi maupun rendah. Ini dapat menciptakan efek 'telepon' atau 'radio'.
- Band-Stop (Notch) Filter: Kebalikan dari band-pass. Filter ini menghilangkan pita frekuensi tertentu.
Mengimplementasikan Filter dengan SciPy
Pustaka scipy.signal menyediakan alat yang kuat untuk merancang dan menerapkan filter digital. Kita akan menggunakan jenis umum yang disebut filter Butterworth, yang dikenal karena responsnya yang datar di passband.
Prosesnya melibatkan dua langkah: pertama, merancang filter untuk mendapatkan koefisiennya, dan kedua, menerapkan koefisien tersebut ke sinyal audio kita.
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()
Dengarkan perbedaan antara gelombang asli dan yang difilter. Yang asli cerah dan berdengung; versi yang difilter jauh lebih lembut dan lebih gelap karena harmonik frekuensi tinggi telah dihilangkan. Mengubah-ubah frekuensi cutoff dari low-pass filter adalah salah satu teknik paling ekspresif dan umum dalam musik elektronik.
Modulasi: Menambahkan Gerakan dan Kehidupan
Suara statis itu membosankan. Modulasi adalah kunci untuk menciptakan suara yang dinamis, berkembang, dan menarik. Prinsipnya sederhana: gunakan satu sinyal (modulator) untuk mengontrol parameter sinyal lain (carrier). Modulator yang umum adalah Low-Frequency Oscillator (LFO), yang hanyalah osilator dengan frekuensi di bawah rentang pendengaran manusia (misalnya, 0,1 Hz hingga 20 Hz).
Modulasi Amplitudo (AM) dan Tremolo
Ini terjadi saat kita menggunakan LFO untuk mengontrol amplitudo suara kita. Hasilnya adalah denyutan volume yang berirama, yang dikenal sebagai 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()
Modulasi Frekuensi (FM) dan Vibrato
Ini terjadi saat kita menggunakan LFO untuk mengontrol frekuensi suara kita. Modulasi frekuensi yang lambat dan halus menciptakan vibrato, yaitu getaran nada lembut yang digunakan penyanyi dan pemain biola untuk menambah ekspresi.
# 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()
Ini adalah versi sederhana dari sintesis FM. Ketika frekuensi LFO ditingkatkan ke rentang yang dapat didengar, ia menciptakan frekuensi sideband yang kompleks, menghasilkan nada yang kaya, seperti lonceng, dan metalik. Inilah dasar dari suara ikonik synthesizer seperti Yamaha DX7.
Menyatukan Semuanya: Proyek Mini Synthesizer
Mari kita gabungkan semua yang telah kita pelajari ke dalam sebuah kelas synthesizer yang sederhana dan fungsional. Ini akan membungkus osilator, envelope, dan filter kita menjadi satu objek tunggal yang dapat digunakan kembali.
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()
Kelas sederhana ini adalah demonstrasi yang kuat dari prinsip-prinsip yang telah kita bahas. Saya mendorong Anda untuk bereksperimen dengannya. Coba berbagai bentuk gelombang, ubah parameter ADSR, dan ganti cutoff filter untuk melihat seberapa radikal Anda dapat mengubah suara.
Melampaui Dasar: Ke Mana Selanjutnya?
Kita baru saja menyentuh permukaan dari bidang sintesis audio dan DSP yang dalam dan memuaskan. Jika ini telah memicu minat Anda, berikut adalah beberapa topik lanjutan untuk dijelajahi:
- Sintesis Wavetable: Alih-alih menggunakan bentuk matematis yang sempurna, teknik ini menggunakan bentuk gelombang satu siklus yang telah direkam sebelumnya sebagai sumber osilator, memungkinkan timbre yang sangat kompleks dan berkembang.
- Sintesis Granular: Menciptakan suara baru dengan mendekonstruksi sampel audio yang ada menjadi fragmen-fragmen kecil (grain) dan kemudian menyusun ulang, meregangkan, dan mengubah nadanya. Ini fantastis untuk menciptakan tekstur atmosfer dan pad.
- Sintesis Pemodelan Fisik: Pendekatan menarik yang mencoba menciptakan suara dengan memodelkan secara matematis sifat-sifat fisik suatu instrumen—senar gitar, tabung klarinet, membran drum.
- Pemrosesan Audio Real-time: Pustaka seperti PyAudio dan SoundCard memungkinkan Anda bekerja dengan aliran audio dari mikrofon atau input lain secara real-time, membuka pintu untuk efek langsung, instalasi interaktif, dan banyak lagi.
- Machine Learning dalam Audio: AI dan deep learning sedang merevolusi audio. Model dapat menghasilkan musik baru, menyintesis ucapan manusia yang realistis, atau bahkan memisahkan instrumen individu dari lagu yang sudah di-mix.
Kesimpulan
Kita telah melakukan perjalanan dari sifat fundamental suara digital hingga membangun synthesizer yang fungsional. Kita belajar cara menghasilkan bentuk gelombang murni dan kompleks menggunakan Python, NumPy, dan SciPy. Kita menemukan cara memberi kehidupan dan bentuk pada suara kita menggunakan envelope ADSR, memahat karakternya dengan filter digital, dan menambahkan gerakan dinamis dengan modulasi. Kode yang telah kita tulis bukan hanya latihan teknis; ini adalah alat kreatif.
Tumpukan ilmiah Python yang kuat menjadikannya platform yang luar biasa untuk belajar, bereksperimen, dan berkreasi di dunia audio. Apakah tujuan Anda adalah membuat efek suara kustom untuk sebuah proyek, membangun instrumen musik, atau sekadar memahami teknologi di balik suara yang Anda dengar setiap hari, prinsip-prinsip yang telah Anda pelajari di sini adalah titik awal Anda. Sekarang, giliran Anda untuk bereksperimen. Mulailah menggabungkan teknik-teknik ini, coba parameter baru, dan dengarkan baik-baik hasilnya. Alam semesta suara yang luas kini ada di ujung jari Anda—apa yang akan Anda ciptakan?