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:
parent
0c8f34b6fe
commit
ba627444b2
170
python-programming/fm_harmonics_music_generator.py
Normal file
170
python-programming/fm_harmonics_music_generator.py
Normal file
@ -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!")
|
Loading…
x
Reference in New Issue
Block a user