Files
Halloween/tools/add_dark_groan_v2.py
T

100 lines
3.9 KiB
Python

"""Replace both old bass growls with a softly entering, breathy vocal moan."""
import array
import math
import random
import wave
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / 'preview' / 'v4'
rate = 24000
random.seed(72)
ambience = []
lp = 0.0
for i in range(rate * 20):
t = i / rate
lp = .90 * lp + .10 * random.uniform(-1, 1)
env = (.5 + .5 * math.sin(t * 2.1)) ** 3
value = .07 * lp * env if t < 10 or t > 17 else .006 * lp
if 7.1 < t < 8.3:
value += .13 * lp * (.5 + .5 * math.sin(t * 90))
# The old 62 Hz modulated growl and rapid onset scratching are omitted.
ambience.append(value)
random.seed(149)
moan = [0.0] * len(ambience)
phase = breath_low = breath_high = 0.0
start, length = 13.04, 3.45
for i in range(int(length * rate)):
t = i / rate
progress = t / length
pitch = 108 - 26 * progress + 1.4 * math.sin(t * 8)
phase += math.tau * pitch / rate
voice = 0.0
for harmonic in range(2, 23):
frequency = harmonic * pitch
resonance = (.8 * math.exp(-((frequency - 460) / 150) ** 2)
+ .4 * math.exp(-((frequency - 950) / 220) ** 2)
+ .18 * math.exp(-((frequency - 1450) / 260) ** 2))
voice += resonance * math.sin(harmonic * phase + harmonic * .17) / math.sqrt(harmonic)
noise = random.uniform(-1, 1)
breath_low += .12 * (noise - breath_low)
breath_high += .015 * (noise - breath_high)
attack = min(1.0, t / .65)
attack = attack * attack * (3 - 2 * attack)
release = min(1.0, (length - t) / .9)
release = release * release * (3 - 2 * release)
envelope = attack * release * (.85 + .15 * math.sin(math.pi * progress))
moan[round(start * rate) + i] = envelope * (.37 * voice + .13 * (breath_low - breath_high))
mixed = []
peak = 0.0
for i, ambient in enumerate(ambience):
value = moan[i]
for delay, gain in [(.17, .16), (.33, .08)]:
index = i - round(delay * rate)
if index >= 0:
value += gain * moan[index]
value += ambient
peak = max(peak, abs(value))
mixed.append(round(value * 32767))
assert peak < .98, peak
path = OUT / 'effects_with_groan_v2.wav'
with wave.open(str(path), 'wb') as handle:
handle.setparams((1, 2, rate, len(mixed), 'NONE', 'not compressed'))
handle.writeframes(array.array('h', mixed).tobytes())
print(f'Soft vocal onset at {start}s; audio peak {peak:.3f}, no clipping')
import bpy
bpy.ops.wm.open_mainfile(filepath=str(OUT / 'halloween_cone_demo.blend'))
scene = bpy.context.scene
for strip in list(scene.sequence_editor.sequences):
if strip.type == 'SOUND':
scene.sequence_editor.sequences.remove(strip)
scene.sequence_editor.sequences.new_sound('Soft breathy moan and ambience', str(path), channel=2, frame_start=1)
bpy.ops.file.pack_all()
bpy.ops.wm.save_as_mainfile(filepath=str(OUT / 'halloween_cone_demo_audio_v2.blend'))
# Reuse the complete latest picture, including the cone and fictional IDs.
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.render.resolution_x = 1280
scene.render.resolution_y = 720
scene.render.resolution_percentage = 100
scene.render.fps = 24
scene.frame_start = 1
scene.frame_end = 480
scene.view_settings.view_transform = 'Standard'
scene.view_settings.look = 'None'
scene.sequence_editor_create()
scene.sequence_editor.sequences.new_movie('Latest complete picture', str(OUT / 'halloween_cone_demo.mp4'), channel=1, frame_start=1)
scene.sequence_editor.sequences.new_sound('Revised audio', str(path), channel=2, frame_start=1)
scene.render.image_settings.file_format = 'FFMPEG'
scene.render.ffmpeg.format = 'MPEG4'
scene.render.ffmpeg.codec = 'H264'
scene.render.ffmpeg.constant_rate_factor = 'HIGH'
scene.render.ffmpeg.audio_codec = 'AAC'
scene.render.ffmpeg.audio_bitrate = 192
scene.render.filepath = str(OUT / 'halloween_cone_demo_audio_v2.mp4')
bpy.ops.render.render(animation=True)