"""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)