1
0
mirror of https://gitlab.com/80486DX2-66/gists synced 2025-04-12 02:29:06 +05:30

python: add fm_harmonics_music_generator.py

This commit is contained in:
パチュリー・ノーレッジ 2025-02-19 23:16:54 +03:00
parent 0c8f34b6fe
commit ba627444b2
Signed by: 80486DX2-66
GPG Key ID: 83631EF27054609B

@ -0,0 +1,170 @@
# fm_harmonics_music_generator.py
#
# Created at 2025-01-01
#
# Most of the code is AI generated, but the script works just fine.
#
# Unfortunately, I don't have enough time to add more typing now.
#
# Author: Intel A80486DX2-66
# License: Creative Commons Zero 1.0 Universal or Unlicense
# SPDX-License-Identifier: CC0-1.0 OR Unlicense
import numpy as np
from scipy.io.wavfile import write
from random import randint, seed, getrandbits, uniform
def extend(l: list, n: int) -> list:
extended_list = [] # Create a new list to hold the extended values
for value in l:
extended_list.extend([value] * n) # Append the value n times
return extended_list
def generate_fm_wave(carrier_freq, modulator_freq, duration, sample_rate=44100, modulation_index=1.0):
"""Generate a sine wave using FM synthesis."""
t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
modulating_signal = np.sin(2 * np.pi * modulator_freq * t) # Modulator signal
wave = 0.5 * np.sin(2 * np.pi * carrier_freq * t + modulation_index * modulating_signal) # FM synthesis
return wave
def apply_envelope(wave, attack_time, decay_time, sustain_level, release_time, sample_rate=44100):
"""Apply an ADSR envelope to the wave."""
total_samples = len(wave)
attack_samples = int(attack_time * sample_rate)
decay_samples = int(decay_time * sample_rate)
release_samples = int(release_time * sample_rate)
# Create the envelope
envelope = np.zeros(total_samples)
# Attack
envelope[:attack_samples] = np.linspace(0, 1, attack_samples)
# Decay
if attack_samples + decay_samples < total_samples:
envelope[attack_samples:attack_samples + decay_samples] = np.linspace(1, sustain_level, decay_samples)
# Sustain
if attack_samples + decay_samples < total_samples:
envelope[attack_samples + decay_samples:total_samples - release_samples] = sustain_level
# Release
if total_samples - release_samples > 0:
envelope[total_samples - release_samples:] = np.linspace(sustain_level, 0, release_samples)
return wave * envelope
def generate_sine_wave(frequencies, duration, sample_rate=44100):
"""Generate a sine wave for a list of frequencies."""
t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
wave = np.zeros_like(t)
for freq in frequencies:
wave += np.sin(2 * np.pi * freq * t)
return wave
def create_sound_file(patterns, repetitions, tempo, output_file, carrier, modulator, modulation_index):
"""
Create a sound file with specified frequency patterns, repetitions, and tempo.
:param patterns: List of lists containing frequencies (in Hz) for each pattern.
:param repetitions: Number of times to repeat each pattern.
:param tempo: Tempo in beats per minute (BPM).
:param output_file: Name of the output WAV file.
"""
sample_rate = 44100 # Standard sample rate
beat_duration = 60 / tempo # Duration of one beat in seconds
sound_wave = np.array([]) # Initialize an empty array for the sound wave
pattern_extension = 10
extended_patterns = extend(patterns, pattern_extension)
t = 0
for i in range(repetitions): # Repeat the entire pattern
for pattern in extended_patterns:
actual_beat_duration = beat_duration / pattern_extension
# Generate a sine wave for the current pattern
sine_wave = generate_sine_wave(pattern, actual_beat_duration)
## carrier=int(frequency / randint(1, 2))
## modulator=randint(int(frequency / 4), int(frequency / 2)) # uniform(frequency / 4, frequency / 2)
fm_wave = generate_fm_wave(carrier, modulator, actual_beat_duration, modulation_index=modulation_index)
# Combine the sine wave and FM wave
combined_wave = sine_wave + fm_wave
# Apply envelope to the combined wave
## combined_wave = apply_envelope(combined_wave, attack_time=0.01, decay_time=8, sustain_level=0.7, release_time=0.2, sample_rate=sample_rate)
# Append the combined wave to the sound wave
sound_wave = np.concatenate((sound_wave, combined_wave))
t += 1
# Normalize the sound wave to the range of int16
sound_wave = np.int16(sound_wave / np.max(np.abs(sound_wave)) * 32767)
# Write the sound wave to a WAV file
write(output_file, sample_rate, sound_wave)
def harmonics(frequency, count: int, step: int = 1) -> list:
return [frequency * i for i in range(1, count, step)]
if __name__ == "__main__":
## seed(0)
tempo_multiplier = 4
randbool = lambda: getrandbits(1) == 1
min_frequency = 35
max_frequency = 120
frequency_limit_diff = max_frequency - min_frequency
frequency = randint(min_frequency, max_frequency)
n = 2**randint(1, 5) # 4/4 beat
frequency_subtract = frequency // frequency_limit_diff
negative_frequency_drift = randbool()
precision = 2
precision_machine = 10**precision
frequency_drift_toggle = randbool()
frequency_drift_max = randint(0, 1) if frequency_drift_toggle else 0
frequency_drift_value = lambda: randint(-frequency_drift_max * precision_machine if negative_frequency_drift else 0, frequency_drift_max * precision_machine) / precision_machine
frequency_drift = lambda: frequency_drift_value() if randbool() else 0
carrier = uniform(frequency / 4, frequency * 2)
modulator = randint(0, frequency)
modulation_index = randint(5, 50)
patterns = [
harmonics(
frequency + frequency_drift(),
randint(
1,
16),
randint(1, 2))
for i in range(n)
]
repetitions = randint(2, 8) # 4/4 beat
tempo = randint(60, 160)
output_file = "output.wav"
print(f"Base: {frequency} Hz, frequency drift: {'on' if frequency_drift_toggle else 'off'}")
print(f"Carrier: {carrier} Hz")
print(f"Modulator: {modulator} Hz")
print(f"Modulation index: {modulation_index}")
print(f"Tempo: {tempo} BPM")
create_sound_file(patterns, repetitions, tempo * tempo_multiplier, output_file, carrier, modulator, modulation_index)
print("Done!")