renderer.py
4.1 kB · python · 103 lines
1import numpy as np2import os3import wave4from typing import Callable, List, Tuple5from .config import Config6from .enums import WaveType78# RENDERER910class Renderer:1112 def __init__(self, config: Config, wave_type: WaveType = WaveType.SINE,13 num_harmonics: int = 1, harmonic_wave: WaveType = WaveType.TRIANGLE):14 self.config = config15 self.wave_type = wave_type16 self.num_harmonics = num_harmonics17 self.harmonic_wave = harmonic_wave1819 # RENDER2021 def render(self, track: List[Tuple[List[int], float]],22 voice_fn: Callable = None) -> np.ndarray:23 amplitude = np.iinfo(np.int16).max24 audio_segments = []25 for chord, duration in track:26 frame_duration_samples = int(duration * self.config.sample_rate)27 frame_audio = np.zeros(frame_duration_samples, dtype=np.float32)28 for midi_note in chord:29 if midi_note is not None:30 if voice_fn is not None:31 wt, nh, hw = voice_fn()32 else:33 wt = self.wave_type34 nh = self.num_harmonics35 hw = self.harmonic_wave36 note_freq = _midi_to_freq(midi_note)37 note_wave = _generate_wave(38 config=self.config,39 wave_type=wt,40 frequency=note_freq,41 duration=duration,42 num_harmonics=nh,43 harmonic_wave=hw,44 )45 frame_audio += note_wave46 frame_audio = _fade(frame_audio, self.config.fade_duration, self.config.sample_rate)47 audio_segments.append(frame_audio)48 audio_data = np.concatenate(audio_segments)49 peak_amplitude = np.max(np.abs(audio_data))50 if peak_amplitude > 0:51 audio_data /= peak_amplitude52 return (audio_data * amplitude).astype(np.int16)5354 # SAVE5556 def save(self, filepath: str, audio: np.ndarray):57 os.makedirs(os.path.dirname(filepath), exist_ok=True)58 with wave.open(filepath, "w") as wf:59 wf.setnchannels(1)60 wf.setsampwidth(2)61 wf.setframerate(self.config.sample_rate)62 wf.writeframes(audio.tobytes())6364# MIDI6566def _midi_to_freq(midi_note: int) -> float:67 return 440 * 2**((midi_note - 69) / 12)6869# WAVES7071def _wave(t_array: np.ndarray, freq: float, wave_type: WaveType) -> np.ndarray:72 t = t_array * freq73 wave_factory = {74 WaveType.SINE: np.sin(2. * np.pi * t),75 WaveType.TRIANGLE: 2 * np.abs(2 * (t - np.floor(t + 0.5))) - 1,76 WaveType.SQUARE: np.sign(np.sin(2. * np.pi * t)),77 WaveType.SAWTOOTH: 2 * (t - np.floor(0.5 + t)),78 }79 return wave_factory[wave_type]8081def _generate_wave(config: Config, wave_type: WaveType, frequency: float, duration: float, num_harmonics: int = None, harmonic_wave: WaveType = None) -> np.ndarray:82 time_array = np.linspace(0.0, duration, int(config.sample_rate * duration), endpoint=False)83 output_wave = np.zeros_like(time_array)84 harmonic_factory = {85 WaveType.SQUARE: {i: 1.0 / i for i in range(1, num_harmonics + 1, 2)},86 WaveType.TRIANGLE: {i: 1.0 / (i * i) for i in range(1, num_harmonics + 1, 2)},87 WaveType.SAWTOOTH: {i: 1.0 / i for i in range(1, num_harmonics + 1)},88 }89 for harmonic, amplitude_factor in harmonic_factory[harmonic_wave].items():90 output_wave += amplitude_factor * _wave(time_array, frequency * harmonic, wave_type)91 if np.max(np.abs(output_wave)) > 0:92 output_wave /= np.max(np.abs(output_wave))93 return output_wave9495def _fade(audio_segment: np.ndarray, fade_duration: float, sample_rate: int) -> np.ndarray:96 fade_length = int(sample_rate * fade_duration)97 actual_fade_length = min(fade_length, len(audio_segment) // 2)98 if actual_fade_length > 0:99 fade_in = np.linspace(0.0, 1.0, actual_fade_length)100 fade_out = np.linspace(1.0, 0.0, actual_fade_length)101 audio_segment[:actual_fade_length] *= fade_in102 audio_segment[-actual_fade_length:] *= fade_out103 return audio_segment