134 lines
4.4 KiB
Python
134 lines
4.4 KiB
Python
# Copyright (C) 2020 harrysentonbury
|
|
# GNU General Public License v3.0
|
|
|
|
import numpy as np
|
|
import sounddevice as sd
|
|
|
|
|
|
class ThyBeatifier():
|
|
"""
|
|
data - 1d numpy array
|
|
bar_size - int, number of samples per bar Eg:
|
|
for 1 second of 48kH audio bar_size=48000
|
|
beats_per_bar - int, number of beats per bar
|
|
attack - int, attack size in samples default=100
|
|
flip - boolean, reverse beat shape, default=False
|
|
decay_log_base - float, log base of decay
|
|
decay_min - float, sets decay_log_base of minimum decay value, default=-1
|
|
"""
|
|
def __init__(self, data, bar_size, beats_per_bar, attack=100, flip=False,
|
|
decay_log_base=10, decay_min=-1):
|
|
self.data = data
|
|
self.bar_size = bar_size
|
|
self.beats_per_bar = beats_per_bar
|
|
self.flip = flip
|
|
self.attack = attack
|
|
if decay_min > 1:
|
|
raise ValueError("Must be <= 1")
|
|
self.decay_min = decay_min
|
|
self.decay_log_base = decay_log_base
|
|
|
|
def beater(self):
|
|
"""stick sound array into drumming like envelope thingy"""
|
|
self.data = self.data / np.max(np.absolute(self.data))
|
|
self.duration = np.size(self.data) / self.bar_size
|
|
self.beats_duration = int(self.duration * self.beats_per_bar)
|
|
self.extra = np.zeros(np.size(self.data) % self.beats_duration)
|
|
self.ramp_decay = np.logspace(1, self.decay_min, int(np.size(
|
|
self.data) // self.beats_duration),
|
|
base=self.decay_log_base) / self.decay_log_base
|
|
self.ramp_attack = np.linspace(self.ramp_decay[-1], 1, self.attack)
|
|
self.ramp_decay[:self.attack] *= self.ramp_attack
|
|
self.beats = np.array([])
|
|
for i in range(self.beats_duration):
|
|
self.beats = np.concatenate((self.beats, self.ramp_decay))
|
|
if self.flip:
|
|
self.beats = np.flip(self.beats)
|
|
self.beats = np.concatenate((self.beats, self.extra))
|
|
return self.data * self.beats
|
|
|
|
|
|
def lfo(frequency=0.5):
|
|
y = (np.sin(x * frequency))
|
|
return y
|
|
|
|
def ranlfo(frequency=0.5):
|
|
y = (np.sin(xx * frequency))
|
|
return y
|
|
|
|
def bass_waveform2(bass_f):
|
|
""" fm """
|
|
y = (np.sin(bass_f * x + ((lfo(1) * 2) + 2) * np.sin((bass_f / 2) * x))) * 0.6
|
|
return y
|
|
|
|
def bass_waveform(bass_f):
|
|
y = (np.sin(bass_f * x + ((lfo(1) * 2) + 2) * np.sin((bass_f / 2) * x))) * 0.4
|
|
y = np.concatenate((y, bass_waveform2(bass_freq2)))
|
|
return y
|
|
|
|
|
|
def thorny_waveform():
|
|
def thorn(freq):
|
|
"""thorn wave"""
|
|
y = 2 / np.pi * np.arcsin(np.sin(freq * x))
|
|
y = np.exp(y) * 0.2
|
|
return y
|
|
thorns_0 = (thorn((bass_freq * 4) - 2) + thorn((bass_freq * 4) - 1) + \
|
|
thorn(bass_freq * 4) + thorn(bass_freq * 4)) * 0.1
|
|
thorns_1 = (thorn((bass_freq2 * 4) - 2) + thorn((bass_freq2 * 4) - 1) + \
|
|
thorn(bass_freq2 * 4) + thorn(bass_freq2 * 4)) * 0.1
|
|
ramp_2 = np.linspace(0, 1, np.size(thorns_0))
|
|
thorns_0[:] *= ramp_2
|
|
thorns_1[:] *= ramp_2
|
|
thorns = np.concatenate((thorns_0, thorns_1))
|
|
return thorns
|
|
|
|
|
|
def mordant_evolution_wave():
|
|
y = np.sin(((freq_0 * 4) + ranlfo()) * x) + np.sin(((freq_0 * 4) + ranlfo()) * x
|
|
) + (np.sin(freq_0 * x + ramp_1 * np.sin(freq_0 * 0.5 * x)))
|
|
y[:fade_size] *= fade_in
|
|
y[-fade_size:] *= fade_out
|
|
y = np.concatenate((y, np.flip(y)))
|
|
return y * 0.08
|
|
|
|
|
|
sample_rate = 48000
|
|
duration = 8
|
|
freq_0 = 110.0
|
|
beats_per_bar = 4
|
|
bass_freq = (2**(6 / 12)) * freq_0
|
|
bass_freq2 = (2**(5 / 12)) * freq_0
|
|
fade_size = 150
|
|
|
|
x = np.linspace(0, duration * 2 * np.pi, int(duration * sample_rate))
|
|
xx = np.arange(np.size(x))
|
|
|
|
fade_in = np.linspace(0, 1, fade_size)
|
|
fade_out = np.flip(fade_in)
|
|
|
|
ramp_0 = np.logspace(1, -1, np.size(x)) * 0.6
|
|
ramp_1 = (np.flip(ramp_0) * 5) - 0.3
|
|
waveform = mordant_evolution_wave()
|
|
bass_wave = bass_waveform(bass_freq)
|
|
|
|
# turns waveforms into purcussion
|
|
bass = ThyBeatifier(data=bass_wave, bar_size=sample_rate, beats_per_bar=0.5, flip=False)
|
|
sound = ThyBeatifier(data=waveform, bar_size=sample_rate, beats_per_bar=beats_per_bar)
|
|
|
|
a_result_0 = (sound.beater() * 0.13) + (bass.beater() * 0.25) + thorny_waveform()
|
|
|
|
# split into two channels and delay one channel a bit
|
|
roll_size = 600
|
|
a_result_1 = np.roll(a_result_0, roll_size)
|
|
a_result_1[:roll_size] = 0
|
|
a_result_stereo = np.vstack((a_result_0, a_result_1)).T
|
|
|
|
# repeat a couple of times
|
|
for i in range(2):
|
|
a_result_stereo = np.concatenate((a_result_stereo, a_result_stereo), axis=0)
|
|
|
|
|
|
sd.play(a_result_stereo, sample_rate)
|
|
sd.wait()
|