Spaces:
Sleeping
Sleeping
| import wave | |
| import numpy as np | |
| # 參數設定 | |
| sample_rate = 44100 # 取樣率 (Hz) | |
| duration = 0.5 # 音效長度 (秒) | |
| num_impacts = 1 # 撞擊次數,可自行調整 | |
| # 建立空的音訊 buffer | |
| num_samples = int(sample_rate * duration) | |
| audio = np.zeros(num_samples, dtype=np.float32) | |
| # 產生單一撞擊聲的函式 | |
| def generate_impact(sample_rate, max_length_ms=5): | |
| """ | |
| 回傳一個短促的撞擊聲波形 (numpy 1D array)。 | |
| 使用白噪音 * 指數衰減包絡,模擬鋼珠撞擊的高頻 'click'。 | |
| """ | |
| length_ms = np.random.uniform(2, max_length_ms) # 2~5ms 的撞擊長度 | |
| length_samples = int(sample_rate * length_ms / 1000.0) | |
| t = np.arange(length_samples) / sample_rate | |
| # 指數衰減包絡,衰減越快越「硬」 | |
| decay_rate = np.random.uniform(600, 1200) # 衰減速率隨機 | |
| envelope = np.exp(-decay_rate * t) | |
| # 高頻噪音,模擬金屬撞擊的尖銳感 | |
| noise = np.random.randn(length_samples) | |
| impact = noise * envelope | |
| # 控制單一撞擊的音量 | |
| impact *= np.random.uniform(0.4, 0.9) | |
| return impact | |
| # 產生多個撞擊,時間點分佈在 0.5 秒內 | |
| # 為了模擬鋼珠逐漸減速,可以讓後面時間的撞擊較密集 | |
| times = np.linspace(0.0, duration, num_impacts + 2)[1:-1] # 避開完全 0 和完全結尾 | |
| # 稍微往後擠,形成「一開始快、後面密」的感覺 | |
| times = times**1.4 * (duration / (times[-1] ** 1.4)) | |
| for impact_time in times: | |
| impact = generate_impact(sample_rate) | |
| start_idx = int(impact_time * sample_rate) | |
| end_idx = start_idx + len(impact) | |
| if start_idx >= num_samples: | |
| continue | |
| if end_idx > num_samples: | |
| impact = impact[: num_samples - start_idx] | |
| end_idx = num_samples | |
| audio[start_idx:end_idx] += impact | |
| # 簡單做一下整體衰減包絡,避免尾端太突兀 | |
| t_all = np.linspace(0, duration, num_samples, endpoint=False) | |
| global_env = np.exp(-t_all * 4.0) # 4 可調大一點讓衰減更快 | |
| audio *= global_env | |
| # 避免削波:正規化到 -1.0 ~ 1.0 區間內,並留一點安全裕度 | |
| max_val = np.max(np.abs(audio)) | |
| if max_val > 0: | |
| audio = audio / max_val * 0.9 | |
| # 轉成 16-bit PCM 並輸出為 WAV 檔 | |
| output_name = "roulette_ball.wav" | |
| with wave.open(output_name, "w") as wf: | |
| wf.setnchannels(1) # 單聲道 | |
| wf.setsampwidth(2) # 16-bit | |
| wf.setframerate(sample_rate) | |
| audio_int16 = (audio * 32767).astype(np.int16) | |
| wf.writeframes(audio_int16.tobytes()) | |
| print(f"已輸出音效檔:{output_name}") | |