94 lines
3.3 KiB
Python
94 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
|
|
# Copyright (C) 2021 harrysentonbury
|
|
# GNU General Public License v3.0
|
|
|
|
|
|
import numpy as np
|
|
# import scipy.io.wavfile as wf
|
|
import scipy.signal as sig
|
|
import simpleaudio as sa
|
|
|
|
|
|
class ThyRythmer():
|
|
"""
|
|
apply a percussion envelope to audio numpy array
|
|
data - 1d numpy array
|
|
bar_size - int, number of samples per bar Eg:
|
|
for 1 second of 48kH audio bar_size=48000
|
|
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, <= 1, sets decay_log_base of minimum decay value, default=-1
|
|
patern - python list of integers, 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
|
|
|
|
|
|
def sqwer(f):
|
|
y = sig.square(x * f, 0.5)
|
|
return y * 0.2
|
|
|
|
|
|
def filtering_butter(data, cru):
|
|
sos1 = sig.butter(10, cru, btype='highpass', fs=sample_rate, output='sos')
|
|
filt = sig.sosfilt(sos1, data)
|
|
return filt
|
|
|
|
|
|
sample_rate = 44100
|
|
duration = 10
|
|
|
|
x = np.linspace(0, 2 * np.pi * duration, int(sample_rate * duration))
|
|
sound = sqwer(330) + sqwer(664) + sqwer(333)
|
|
sound = filtering_butter(sound, 3000)
|
|
|
|
sound_beat = ThyRythmer(data=sound, bar_size=sample_rate, decay_min=-1, flip=True,
|
|
decay_log_base=5, pattern=[1, 1, 0])
|
|
|
|
sound_1 = sound_beat.beater()
|
|
sound_1 = np.int16(sound_1 * 32767)
|
|
|
|
play_object = sa.play_buffer(sound_1, 1, 2, sample_rate)
|
|
play_object.wait_done()
|
|
|
|
# wf.write("woteva.wav", sample_rate, sound_1)
|