experimentations/brown.py

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)