134 lines
4.5 KiB
Python
134 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
|
|
# Copyright (C) 2021 harrysentonbury
|
|
# GNU General Public License v3.0
|
|
|
|
|
|
from audio2numpy import open_audio
|
|
import numpy as np
|
|
import scipy.io.wavfile as wf
|
|
import sounddevice as sd
|
|
import time
|
|
|
|
|
|
class ThyRythmer():
|
|
"""
|
|
apply a percussion envelope to audio numpy array
|
|
data - 1d numpy array
|
|
bar_size - number of samples per bar Eg: for 1 second of 48kH audio
|
|
bar_size=48000
|
|
attack - int, optional attack size in samples default=100
|
|
flip - boolean, reverse beat shape, default=False
|
|
decay_log_base - float, optional log base of decay, default=10
|
|
decay_min - <= 1, int or float, optional sets decay_log_base of minimum
|
|
decay value, default=-1
|
|
pattern - python list, optional. 1=beat 0=miss a beat, default=[1, 1, 1, 1]
|
|
"""
|
|
def __init__(self, data, bar_size, attack=100, flip=False,
|
|
decay_min=-1, decay_log_base=10, pattern=[1, 1, 1, 1]):
|
|
self.data = data
|
|
self.bar_size = bar_size
|
|
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
|
|
self.pattern = pattern
|
|
if type(self.pattern) is not list:
|
|
raise TypeError(f"pattern must be a list of integers {type(self.pattern)}")
|
|
self.beats_per_bar = np.size(self.pattern)
|
|
|
|
def beater(self):
|
|
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, np.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([])
|
|
self.decay_end_val = np.zeros(np.size(self.ramp_decay))
|
|
self.decay_end_val[:] = self.ramp_decay[-1]
|
|
for i in range(int(self.beats_duration / self.beats_per_bar)):
|
|
self.pattern.extend(self.pattern)
|
|
for i in range(self.beats_duration):
|
|
if self.pattern[i] >= 1:
|
|
self.beats = np.concatenate((self.beats, self.ramp_decay))
|
|
else:
|
|
self.beats = np.concatenate((self.beats, self.decay_end_val))
|
|
if self.flip:
|
|
self.beats = np.flip(self.beats)
|
|
self.beats = np.concatenate((self.beats, self.extra))
|
|
return self.data * self.beats
|
|
|
|
|
|
path_to_file = "audio/brown_8.mp3"
|
|
## uncomment for mic input.
|
|
#path_to_file = "mic"
|
|
|
|
if path_to_file.endswith(".mp3"):
|
|
afile, sample_rate = open_audio(path_to_file)
|
|
afile = np.float64(afile)
|
|
|
|
elif path_to_file.endswith(".wav"):
|
|
sample_rate, afile = wf.read(path_to_file)
|
|
afile = np.float64(afile)
|
|
else:
|
|
duration = 2.0
|
|
sample_rate = 44100
|
|
print("3")
|
|
time.sleep(0.5)
|
|
print("2")
|
|
time.sleep(0.5)
|
|
print("1")
|
|
time.sleep(0.5)
|
|
|
|
print("NOW")
|
|
afile = sd.rec(int(duration * sample_rate), samplerate=sample_rate, channels=1,
|
|
dtype='float64')
|
|
sd.wait()
|
|
print("DONE")
|
|
afile = afile.reshape((np.size(afile),))
|
|
|
|
# if stereo convert to mono
|
|
try:
|
|
afile[:, 0] = afile[:, 0] + afile[:, 1]
|
|
afile = afile[:, 0]
|
|
except IndexError:
|
|
pass
|
|
|
|
print(f"file data type: {afile.dtype}")
|
|
print(f"file shape: {afile.shape}")
|
|
|
|
# make a frequency modulated sound
|
|
data_size = np.size(afile)
|
|
x = np.linspace(0, 2 * np.pi * data_size / sample_rate, data_size)
|
|
ramp_0 = np.logspace(1, 0, data_size, base=3)
|
|
|
|
sound_beat = ThyRythmer(data=afile, bar_size=sample_rate, decay_min=-0.5, flip=True,
|
|
decay_log_base=2, pattern=[1, 1, 0, 1, 1, 0])
|
|
|
|
# do things :¬o
|
|
i_factor = 5
|
|
sound_0 = np.array([])
|
|
gap = np.zeros(22050)
|
|
for i in range(1, 9):
|
|
sound = np.sin(x * 220 + ramp_0 * i * np.sin(x * 330))
|
|
sound = (sound / np.max(np.abs(sound))) * 0.05
|
|
sound_beat.flip = not sound_beat.flip
|
|
sound_beat.decay_log_base = i + 1
|
|
sound_1 = sound_beat.beater()
|
|
sound_2 = np.where(np.abs(sound_1) > 0.01 * i * i_factor, sound_1, sound + sound_1)
|
|
sound_3 = np.where(np.abs(sound_1) < 0.005 * i * i_factor, sound_1, sound * sound_1 * 5)
|
|
sound_0 = np.concatenate((sound_0, sound_2, sound_3, gap))
|
|
|
|
|
|
sd.play(sound_0, sample_rate)
|
|
sd.wait()
|
|
|
|
## uncomment to write to wav file
|
|
#wf.write("audio/woteva.wav", sample_rate, sound_0)
|