You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
121 lines
3.7 KiB
121 lines
3.7 KiB
# Copyright (C) 2021 harrysentonbury
|
|
# GNU General Public License v3.0
|
|
|
|
from audio2numpy import open_audio
|
|
import numpy as np
|
|
import sounddevice as sd
|
|
import scipy.io.wavfile as wf
|
|
import scipy.signal as sig
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
|
class ThyEnveloper():
|
|
"""
|
|
Get the envelope of a sound.
|
|
data - 1d numpy array.
|
|
window_size - int, optional n samples. default=700.
|
|
invert - boolean, optional invert envelope. default=False.
|
|
"""
|
|
def __init__(self, data, window_size=700, invert=False):
|
|
self.data = data
|
|
self.window_size = window_size
|
|
self.invert = invert
|
|
|
|
def get_envelope(self):
|
|
"Returns the envelope"
|
|
self.data_size = np.size(self.data)
|
|
self.data = np.abs(self.data)
|
|
self.result = np.zeros(self.data_size)
|
|
self.iterations = int(self.data_size // self.window_size)
|
|
self.extra = self.data_size % self.window_size
|
|
|
|
for i in range(self.iterations):
|
|
self.ave = np.max(self.data[i * self.window_size:i * self.window_size + self.window_size])
|
|
self.slice = np.linspace(self.result[i * self.window_size-1] if i > 0 else np.max(self.data[i:self.window_size]),
|
|
self.ave, self.window_size)
|
|
self.result[i * self.window_size:i * self.window_size + self.window_size] = self.slice
|
|
|
|
self.slice = np.linspace(self.result[-self.extra - 1], np.max(self.data[-self.extra:]), self.extra)
|
|
if self.invert:
|
|
self.result = 1 - self.result
|
|
if self.extra == 0:
|
|
return self.result
|
|
else:
|
|
self.result[-self.extra:] = self.slice
|
|
return self.result
|
|
|
|
|
|
# Read input wav or mp3 file.
|
|
path_to_file = "audio/go.wav"
|
|
|
|
if path_to_file.endswith(".mp3"):
|
|
data, sample_rate = open_audio(path_to_file)
|
|
data = np.float64(data)
|
|
|
|
else:
|
|
sample_rate, data = wf.read(path_to_file)
|
|
data = np.float64(data)
|
|
|
|
# If stereo convert to mono
|
|
try:
|
|
data = data[:, 0] + data[:, 1]
|
|
print("stereo to MONO")
|
|
except IndexError:
|
|
print("MONO")
|
|
|
|
# Normalise (also converts to float64)
|
|
data = data / np.max(np.abs(data))
|
|
|
|
# Get envelope
|
|
envelope_obj = ThyEnveloper(data)
|
|
# envelope_obj.invert = True
|
|
envelope = envelope_obj.get_envelope()
|
|
|
|
# make a frequency modulated sound
|
|
def triangles(freq, form=0.9, detune=0):
|
|
y = sig.sawtooth(x * (freq + detune), form)
|
|
return y + 0.1
|
|
|
|
|
|
def make_em(detune_f=0):
|
|
y = (triangles(freq, detune=detune_f) + triangles(freq + 1, detune=detune_f) +
|
|
triangles(freq + 2, detune=detune_f) + triangles(freq - 1, detune=detune_f) +
|
|
triangles(freq/2, detune=detune_f)) * 0.3
|
|
return y
|
|
|
|
|
|
freq = 55 * (2**(3 / 12))
|
|
data_size = np.size(data)
|
|
x = np.linspace(0, 2 * np.pi * data_size / sample_rate, data_size)
|
|
|
|
sound = make_em()
|
|
sound = (sound / np.max(np.abs(sound)))
|
|
|
|
# Multiply sound by envelope, convert to stereo with delay on right channel
|
|
sound *= envelope
|
|
sound_r = np.roll(sound, 1000)
|
|
sound_stereo = np.vstack((sound, sound_r)).T
|
|
sound_int16 = np.int16(sound_stereo * 32767)
|
|
|
|
# Listening
|
|
sd.play(sound_stereo, sample_rate)
|
|
sd.wait()
|
|
|
|
# Plotting
|
|
if data_size >= int(sample_rate // 2):
|
|
plot_len = int(sample_rate // 2)
|
|
else:
|
|
plot_len = data_size
|
|
plt.figure(figsize=(10, 4), facecolor="#465467")
|
|
plt.title("ThyEnveloper Plot", fontsize=15)
|
|
plt.ylabel("Amplitude")
|
|
plt.xlabel("Samples t")
|
|
plt.plot(sound[:plot_len], label="Returned Audio", color="#004df5")
|
|
plt.plot(envelope[:plot_len], label="Envelope", linewidth=2, color="#d00000")
|
|
plt.plot(data[:plot_len], label="Voice Input", color=(0.42, 0.31, 0.45, 0.73), linewidth=0.7)
|
|
plt.legend(fontsize=10)
|
|
plt.show()
|
|
|
|
## uncomment to write to wav file
|
|
#wf.write("audio/woteva.wav", sample_rate, sound_int16)
|