mirror of
https://gitlab.com/80486DX2-66/gists
synced 2025-05-31 08:31:41 +05:30
171 lines
6.1 KiB
Python
171 lines
6.1 KiB
Python
# 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!")
|