491 lines
15 KiB
Python
491 lines
15 KiB
Python
import math
|
|
import random
|
|
import struct
|
|
import wave
|
|
import os
|
|
|
|
os.makedirs('assets/sounds', exist_ok=True)
|
|
|
|
def write_wav(filename, samples, sample_rate=44100):
|
|
with wave.open(filename, 'w') as wav_file:
|
|
wav_file.setnchannels(1)
|
|
wav_file.setsampwidth(2)
|
|
wav_file.setframerate(sample_rate)
|
|
for s in samples:
|
|
# Clamp and convert to 16-bit
|
|
val = int(max(-1.0, min(1.0, s)) * 32767)
|
|
data = struct.pack('<h', val)
|
|
wav_file.writeframesraw(data)
|
|
|
|
# 1. Shotgun Fire Sound (Bang + Boom + Decay)
|
|
sample_rate = 44100
|
|
duration = 0.8
|
|
num_samples = int(sample_rate * duration)
|
|
fire_samples = []
|
|
|
|
# Low-pass filter states
|
|
prev_val1 = 0.0
|
|
prev_val2 = 0.0
|
|
|
|
for i in range(num_samples):
|
|
t = i / sample_rate
|
|
|
|
# Base broadband noise
|
|
noise = random.uniform(-1.0, 1.0)
|
|
|
|
# 1. The Crack (Initial explosion, very sharp envelope)
|
|
crack_env = math.exp(-t * 50.0)
|
|
crack = noise * crack_env * 1.5
|
|
|
|
# 2. The Body (Deep explosive boom, slower decay)
|
|
body_env = math.exp(-t * 6.0)
|
|
|
|
# Dynamic low-pass filter (closes rapidly to muffle the tail into a boom)
|
|
alpha = max(0.01, 0.2 * math.exp(-t * 15.0))
|
|
filtered1 = prev_val1 + alpha * (noise - prev_val1)
|
|
prev_val1 = filtered1
|
|
|
|
# Second pass for deeper muffle
|
|
filtered2 = prev_val2 + alpha * (filtered1 - prev_val2)
|
|
prev_val2 = filtered2
|
|
|
|
body = filtered2 * body_env * 10.0 # Boost the bass significantly
|
|
|
|
# Mix crack and body
|
|
raw_sample = crack + body
|
|
|
|
# Soft clipping (tanh) to simulate a microphone peaking from a loud blast
|
|
sample = math.tanh(raw_sample * 2.5)
|
|
|
|
fire_samples.append(sample)
|
|
|
|
write_wav('assets/sounds/shotgun_fire.wav', fire_samples)
|
|
|
|
|
|
# 2. Shotgun Reload Sound (Break, Insert, Snap)
|
|
duration = 1.0
|
|
num_samples = int(sample_rate * duration)
|
|
reload_samples = []
|
|
|
|
for i in range(num_samples):
|
|
t = i / sample_rate
|
|
sample = 0.0
|
|
noise = random.uniform(-1.0, 1.0)
|
|
|
|
# Phase 1: Break open (t=0.0)
|
|
if t < 0.2:
|
|
env_click = math.exp(-t * 60.0)
|
|
click = noise * env_click * 0.5
|
|
clunk_freq = 300.0 * math.exp(-t * 20.0) + 100.0
|
|
clunk = math.sin(t * clunk_freq * 2 * math.pi) * math.exp(-t * 15.0) * 0.4
|
|
sample += click + clunk
|
|
|
|
# Phase 2: Insert shells (t=0.3 to 0.6)
|
|
if 0.3 <= t < 0.6:
|
|
t2 = t - 0.3
|
|
slide_env = math.sin(t2 * math.pi / 0.3) * 0.15
|
|
slide = noise * slide_env
|
|
if t2 > 0.15:
|
|
seat_env = math.exp(-(t2 - 0.15) * 40.0)
|
|
seat_click = noise * seat_env * 0.4
|
|
sample += seat_click
|
|
sample += slide
|
|
|
|
# Phase 3: Snap shut (t=0.7)
|
|
if t >= 0.7:
|
|
t3 = t - 0.7
|
|
snap_env = math.exp(-t3 * 40.0)
|
|
snap = noise * snap_env * 0.8
|
|
ring_env = math.exp(-t3 * 10.0)
|
|
ring1 = math.sin(t3 * 3500.0 * 2 * math.pi)
|
|
ring2 = math.sin(t3 * 4200.0 * 2 * math.pi)
|
|
ring = (ring1 + ring2) * ring_env * 0.1
|
|
thud = math.sin(t3 * 80.0 * 2 * math.pi) * math.exp(-t3 * 15.0) * 0.5
|
|
sample += snap + ring + thud
|
|
|
|
reload_samples.append(sample)
|
|
|
|
# Global low-pass to smooth out the harsh digital noise
|
|
prev = 0.0
|
|
for i in range(len(reload_samples)):
|
|
prev = prev + 0.5 * (reload_samples[i] - prev)
|
|
reload_samples[i] = max(-1.0, min(1.0, prev * 1.5))
|
|
|
|
write_wav('assets/sounds/shotgun_reload.wav', reload_samples)
|
|
|
|
# 3. Footstep Sound (Quiet Thud + Scrape)
|
|
footstep_duration = 0.15
|
|
footstep_samples = []
|
|
for i in range(int(sample_rate * footstep_duration)):
|
|
t = i / sample_rate
|
|
noise = random.uniform(-1.0, 1.0)
|
|
# Dull Thud (lower frequency, less metallic)
|
|
thud_env = math.exp(-t * 25.0)
|
|
thud = math.sin(t * 50.0 * 2 * math.pi) * thud_env * 0.15
|
|
# Scrape (very quiet, low-passed)
|
|
scrape_env = math.exp(-t * 50.0)
|
|
scrape = noise * scrape_env * 0.03
|
|
footstep_samples.append(thud + scrape)
|
|
|
|
# Filter footstep to ensure no tin-can high frequencies
|
|
prev = 0.0
|
|
for i in range(len(footstep_samples)):
|
|
prev = prev + 0.3 * (footstep_samples[i] - prev)
|
|
footstep_samples[i] = prev
|
|
|
|
write_wav('assets/sounds/footstep.wav', footstep_samples)
|
|
|
|
# 4. Dash Sound (Whoosh)
|
|
dash_duration = 0.5
|
|
dash_samples = []
|
|
prev_dash = 0.0
|
|
for i in range(int(sample_rate * dash_duration)):
|
|
t = i / sample_rate
|
|
noise = random.uniform(-1.0, 1.0)
|
|
env = math.sin(t * math.pi / dash_duration) ** 2 # Smooth bell curve
|
|
|
|
alpha = 0.1
|
|
filtered = prev_dash + alpha * (noise - prev_dash)
|
|
prev_dash = filtered
|
|
|
|
dash_samples.append(filtered * env * 0.8)
|
|
write_wav('assets/sounds/dash.wav', dash_samples)
|
|
|
|
# 5. Hit Confirm (Tink/Chirp)
|
|
hit_duration = 0.2
|
|
hit_samples = []
|
|
for i in range(int(sample_rate * hit_duration)):
|
|
t = i / sample_rate
|
|
env = math.exp(-t * 30.0)
|
|
# High pitch chirp sweeping slightly
|
|
freq = 2000.0 + math.exp(-t * 20.0) * 1000.0
|
|
tink = math.sin(t * freq * 2 * math.pi) * env * 0.3
|
|
hit_samples.append(tink)
|
|
write_wav('assets/sounds/hit_confirm.wav', hit_samples)
|
|
|
|
# 6. Wallrun (Continuous scrape)
|
|
# We want this to seamlessly loop
|
|
wallrun_duration = 1.0
|
|
wallrun_samples = []
|
|
for i in range(int(sample_rate * wallrun_duration)):
|
|
t = i / sample_rate
|
|
noise = random.uniform(-1.0, 1.0)
|
|
# Give it a slight 4/4 rhythm
|
|
rhythm = 0.8 + 0.2 * math.sin(t * 8.0 * math.pi)
|
|
|
|
# Low pass to keep it as a dull scrape
|
|
alpha = 0.05
|
|
sample = noise * alpha * rhythm * 0.4
|
|
wallrun_samples.append(sample)
|
|
write_wav('assets/sounds/wallrun.wav', wallrun_samples)
|
|
|
|
# 7. Slide (Continuous deep friction)
|
|
slide_duration = 1.0
|
|
slide_samples = []
|
|
for i in range(int(sample_rate * slide_duration)):
|
|
noise = random.uniform(-1.0, 1.0)
|
|
# Deeper, steadier scrape than wallrun
|
|
alpha = 0.02
|
|
sample = noise * alpha * 0.5
|
|
slide_samples.append(sample)
|
|
write_wav('assets/sounds/slide.wav', slide_samples)
|
|
|
|
# 8. Wind (Subtle rushing air)
|
|
wind_duration = 2.0 # Generate 2 seconds
|
|
sample_count = int(sample_rate * wind_duration)
|
|
raw_wind = []
|
|
prev_wind = 0.0
|
|
|
|
# Generate extra long to stabilize filter
|
|
for i in range(sample_count + int(sample_rate * 0.5)):
|
|
noise = random.uniform(-1.0, 1.0)
|
|
alpha = 0.015
|
|
filtered = prev_wind + alpha * (noise - prev_wind)
|
|
prev_wind = filtered
|
|
if i >= int(sample_rate * 0.5):
|
|
raw_wind.append(filtered * 0.5)
|
|
|
|
# Crossfade the last 0.1s into the first 0.1s to make it perfectly loop
|
|
crossfade_samples = int(sample_rate * 0.1)
|
|
loop_wind = raw_wind[:-crossfade_samples] # remove the tail
|
|
|
|
for i in range(crossfade_samples):
|
|
# Blend the tail into the start
|
|
tail_sample = raw_wind[-(crossfade_samples - i)]
|
|
start_sample = loop_wind[i]
|
|
t = i / float(crossfade_samples)
|
|
loop_wind[i] = start_sample * t + tail_sample * (1.0 - t)
|
|
|
|
write_wav('assets/sounds/wind.wav', loop_wind)
|
|
|
|
# 9. Jump Sound (Quick exertion / push)
|
|
jump_duration = 0.2
|
|
jump_samples = []
|
|
for i in range(int(sample_rate * jump_duration)):
|
|
t = i / sample_rate
|
|
noise = random.uniform(-1.0, 1.0)
|
|
# Lower frequency push (less tin can)
|
|
push_env = math.exp(-t * 25.0)
|
|
push = math.sin(t * 60.0 * 2 * math.pi) * push_env * 0.3
|
|
# High frequency clothing ruffle (quieter)
|
|
ruffle_env = math.exp(-t * 30.0)
|
|
ruffle = noise * ruffle_env * 0.05
|
|
jump_samples.append(push + ruffle)
|
|
|
|
# Filter jump to ensure no tin-can high frequencies
|
|
prev = 0.0
|
|
for i in range(len(jump_samples)):
|
|
prev = prev + 0.4 * (jump_samples[i] - prev)
|
|
jump_samples[i] = prev
|
|
|
|
write_wav('assets/sounds/jump.wav', jump_samples)
|
|
|
|
# 10. Double Jump Sound (Airy, cloudy whoosh)
|
|
dj_duration = 0.4
|
|
dj_samples = []
|
|
prev_dj = 0.0
|
|
for i in range(int(sample_rate * dj_duration)):
|
|
t = i / sample_rate
|
|
noise = random.uniform(-1.0, 1.0)
|
|
# Airy filter
|
|
alpha = 0.05
|
|
filtered = prev_dj + alpha * (noise - prev_dj)
|
|
prev_dj = filtered
|
|
|
|
# Envelope: soft attack, long decay
|
|
env = (math.exp(-t * 8.0) - math.exp(-t * 40.0)) * 1.5
|
|
|
|
# Slight pitch movement to give it a "lifting" feel
|
|
lift = math.sin(t * 150.0 * 2 * math.pi * (1.0 + t)) * env * 0.1
|
|
|
|
dj_samples.append((filtered * 0.4 + lift) * env)
|
|
write_wav('assets/sounds/double_jump.wav', dj_samples)
|
|
|
|
|
|
# ==========================================
|
|
# NEW WEAPONS (AK47, M4, MP7, DMR, Plasma, Nail)
|
|
# ==========================================
|
|
|
|
# 11. AK47 (Heavy punch, low thud, sharp crack)
|
|
ak_duration = 0.2
|
|
ak_samples = []
|
|
for i in range(int(sample_rate * ak_duration)):
|
|
t = i / sample_rate
|
|
noise = random.uniform(-1.0, 1.0)
|
|
|
|
# Sharp metallic crack
|
|
crack_env = math.exp(-t * 100.0)
|
|
crack = noise * crack_env * 0.4
|
|
|
|
# Deep heavy punch (70Hz sweep down to 40Hz)
|
|
punch_freq = max(40.0, 70.0 - t * 300.0)
|
|
punch_env = math.exp(-t * 20.0)
|
|
punch = math.sin(t * punch_freq * 2 * math.pi) * punch_env * 0.6
|
|
|
|
# Mechanism ring
|
|
ring_env = math.exp(-t * 15.0)
|
|
ring = math.sin(t * 800.0 * 2 * math.pi) * ring_env * 0.05
|
|
|
|
ak_samples.append(crack + punch + ring)
|
|
write_wav('assets/sounds/ak47_fire.wav', ak_samples)
|
|
|
|
|
|
# 12. M4 (Moderate punch, sharper crack, tighter burst)
|
|
m4_duration = 0.15
|
|
m4_samples = []
|
|
for i in range(int(sample_rate * m4_duration)):
|
|
t = i / sample_rate
|
|
noise = random.uniform(-1.0, 1.0)
|
|
|
|
crack_env = math.exp(-t * 120.0)
|
|
crack = noise * crack_env * 0.5
|
|
|
|
punch_freq = 90.0
|
|
punch_env = math.exp(-t * 25.0)
|
|
punch = math.sin(t * punch_freq * 2 * math.pi) * punch_env * 0.4
|
|
|
|
m4_samples.append(crack + punch)
|
|
write_wav('assets/sounds/m4_fire.wav', m4_samples)
|
|
|
|
|
|
# 13. MP7 (Very fast, light pop, high pitched)
|
|
mp7_duration = 0.1
|
|
mp7_samples = []
|
|
for i in range(int(sample_rate * mp7_duration)):
|
|
t = i / sample_rate
|
|
noise = random.uniform(-1.0, 1.0)
|
|
|
|
pop_env = math.exp(-t * 80.0)
|
|
pop = noise * pop_env * 0.3
|
|
|
|
punch_freq = 150.0
|
|
punch_env = math.exp(-t * 35.0)
|
|
punch = math.sin(t * punch_freq * 2 * math.pi) * punch_env * 0.2
|
|
|
|
mp7_samples.append(pop + punch)
|
|
write_wav('assets/sounds/mp7_fire.wav', mp7_samples)
|
|
|
|
|
|
# 14. DMR (Extremely loud, echoing boom, very sharp)
|
|
dmr_duration = 0.4
|
|
dmr_samples = []
|
|
for i in range(int(sample_rate * dmr_duration)):
|
|
t = i / sample_rate
|
|
noise = random.uniform(-1.0, 1.0)
|
|
|
|
crack_env = math.exp(-t * 50.0)
|
|
crack = noise * crack_env * 0.6
|
|
|
|
boom_freq = max(30.0, 60.0 - t * 100.0)
|
|
boom_env = math.exp(-t * 8.0)
|
|
boom = math.sin(t * boom_freq * 2 * math.pi) * boom_env * 0.7
|
|
|
|
tail_env = math.exp(-t * 5.0)
|
|
tail = noise * tail_env * 0.1 # outdoor echo reflection
|
|
|
|
dmr_samples.append(crack + boom + tail)
|
|
write_wav('assets/sounds/dmr_fire.wav', dmr_samples)
|
|
|
|
|
|
# 15. Plasma Gun (Sci-Fi pew, tonal sweep)
|
|
plasma_duration = 0.2
|
|
plasma_samples = []
|
|
for i in range(int(sample_rate * plasma_duration)):
|
|
t = i / sample_rate
|
|
|
|
# Sweep from 1200 Hz down to 300 Hz rapidly
|
|
freq = max(300.0, 1200.0 * math.exp(-t * 30.0))
|
|
pew_env = math.exp(-t * 15.0)
|
|
pew = math.sin(t * freq * 2 * math.pi) * pew_env * 0.6
|
|
|
|
plasma_samples.append(pew)
|
|
write_wav('assets/sounds/plasma_fire.wav', plasma_samples)
|
|
|
|
|
|
# 16. Nail Gun (Clack clack clack, metallic hit)
|
|
nail_duration = 0.1
|
|
nail_samples = []
|
|
for i in range(int(sample_rate * nail_duration)):
|
|
t = i / sample_rate
|
|
noise = random.uniform(-1.0, 1.0)
|
|
|
|
# Sharp metallic impact
|
|
hit_env = math.exp(-t * 150.0)
|
|
hit = noise * hit_env * 0.4
|
|
|
|
# Ringing metal (two tones)
|
|
ring_env = math.exp(-t * 40.0)
|
|
ring = (math.sin(t * 3500.0 * 2 * math.pi) + math.sin(t * 4100.0 * 2 * math.pi)) * ring_env * 0.15
|
|
|
|
# Mechanism chunk
|
|
chunk_env = math.exp(-t * 30.0)
|
|
chunk = math.sin(t * 200.0 * 2 * math.pi) * chunk_env * 0.2
|
|
|
|
nail_samples.append(hit + ring + chunk)
|
|
write_wav('assets/sounds/nailgun_fire.wav', nail_samples)
|
|
|
|
# 17. AWP (Thunderous crack, massive bass, slow echo)
|
|
awp_duration = 0.6
|
|
awp_samples = []
|
|
for i in range(int(sample_rate * awp_duration)):
|
|
t = i / sample_rate
|
|
noise = random.uniform(-1.0, 1.0)
|
|
crack = noise * math.exp(-t * 80.0) * 0.8
|
|
boom = math.sin(t * max(30.0, 80.0 - t * 150.0) * 2 * math.pi) * math.exp(-t * 10.0) * 0.9
|
|
awp_samples.append(crack + boom)
|
|
write_wav('assets/sounds/awp_fire.wav', awp_samples)
|
|
|
|
# 18. Rocket Launcher (Deep thud, whoosh)
|
|
rocket_duration = 0.4
|
|
rocket_samples = []
|
|
prev_r = 0.0
|
|
for i in range(int(sample_rate * rocket_duration)):
|
|
t = i / sample_rate
|
|
noise = random.uniform(-1.0, 1.0)
|
|
thud = math.sin(t * 50.0 * 2 * math.pi) * math.exp(-t * 20.0) * 0.7
|
|
alpha = 0.05
|
|
filtered = prev_r + alpha * (noise - prev_r)
|
|
prev_r = filtered
|
|
whoosh = filtered * math.exp(-t * 15.0) * 0.5
|
|
rocket_samples.append(thud + whoosh)
|
|
write_wav('assets/sounds/rocket_fire.wav', rocket_samples)
|
|
|
|
# 19. Swarm Launcher (High-pitched whistling zip)
|
|
swarm_duration = 0.2
|
|
swarm_samples = []
|
|
for i in range(int(sample_rate * swarm_duration)):
|
|
t = i / sample_rate
|
|
noise = random.uniform(-1.0, 1.0)
|
|
zip_freq = 600.0 + t * 2000.0
|
|
pew = math.sin(t * zip_freq * 2 * math.pi) * math.exp(-t * 25.0) * 0.4
|
|
swarm_samples.append(pew + noise * math.exp(-t * 50.0) * 0.2)
|
|
write_wav('assets/sounds/swarm_fire.wav', swarm_samples)
|
|
|
|
# 20. Mortar (Heavy hollow thunk)
|
|
mortar_duration = 0.3
|
|
mortar_samples = []
|
|
for i in range(int(sample_rate * mortar_duration)):
|
|
t = i / sample_rate
|
|
noise = random.uniform(-1.0, 1.0)
|
|
thunk_freq = 60.0
|
|
thunk = math.sin(t * thunk_freq * 2 * math.pi) * math.exp(-t * 15.0) * 0.7
|
|
ring = math.sin(t * 200.0 * 2 * math.pi) * math.exp(-t * 30.0) * 0.2
|
|
mortar_samples.append(thunk + ring)
|
|
write_wav('assets/sounds/mortar_fire.wav', mortar_samples)
|
|
|
|
# 21. Rocket Flight (Deep continuous looping whoosh/rumble)
|
|
flight_duration = 1.0 # 1 second loop
|
|
flight_samples = []
|
|
prev_f = 0.0
|
|
for i in range(int(sample_rate * flight_duration)):
|
|
t = i / sample_rate
|
|
noise = random.uniform(-1.0, 1.0)
|
|
alpha = 0.15 # Higher cutoff for more whoosh
|
|
filtered = prev_f + alpha * (noise - prev_f)
|
|
prev_f = filtered
|
|
rumble = math.sin(t * 70.0 * 2 * math.pi) * 1.5
|
|
raw = (filtered * 2.5 + rumble)
|
|
# Heavy soft clipping to make it sound intensely loud
|
|
flight_samples.append(math.tanh(raw * 2.0))
|
|
write_wav('assets/sounds/rocket_flight.wav', flight_samples)
|
|
|
|
# 22. Swarm Flight (Higher pitched continuous hiss)
|
|
swarm_flight_samples = []
|
|
prev_sf = 0.0
|
|
for i in range(int(sample_rate * flight_duration)):
|
|
t = i / sample_rate
|
|
noise = random.uniform(-1.0, 1.0)
|
|
alpha = 0.4 # Higher cutoff for hiss
|
|
filtered = prev_sf + alpha * (noise - prev_sf)
|
|
prev_sf = filtered
|
|
# Aggressive screaming tone
|
|
whine = math.sin(t * 800.0 * 2 * math.pi) * 1.5
|
|
raw = (filtered * 2.0 + whine)
|
|
# Heavy soft clipping
|
|
swarm_flight_samples.append(math.tanh(raw * 2.0))
|
|
write_wav('assets/sounds/swarm_flight.wav', swarm_flight_samples)
|
|
|
|
# 23. Vault Sound (Quick airy whoosh + clothing ruffle)
|
|
vault_duration = 0.3
|
|
vault_samples = []
|
|
prev_v = 0.0
|
|
for i in range(int(sample_rate * vault_duration)):
|
|
t = i / sample_rate
|
|
noise = random.uniform(-1.0, 1.0)
|
|
|
|
# Airy whoosh
|
|
alpha = 0.08
|
|
filtered = prev_v + alpha * (noise - prev_v)
|
|
prev_v = filtered
|
|
whoosh_env = math.sin(t * math.pi / vault_duration)
|
|
whoosh = filtered * whoosh_env * 0.6
|
|
|
|
# Ruffle/grab sound
|
|
ruffle_env = math.exp(-t * 30.0)
|
|
ruffle = noise * ruffle_env * 0.2
|
|
|
|
vault_samples.append(whoosh + ruffle)
|
|
write_wav('assets/sounds/vault.wav', vault_samples)
|
|
|
|
print("Generated all sounds!")
|