from __future__ import print_function, division
%matplotlib inline
from context import *
First let's look at the full file (meaning both channels)
# Load it
f1sps, f1 = wavfile.read('data/kick1.wav')
# Plot it
plt.xlabel('Samples')
plt.ylabel('Amplitude')
plt.title('kick1.wav')
plt.plot(f1)
# load an interactive player
ipd.Audio('data/kick1.wav')
... and now we'll just stick with one from here on out
# Plotting one channel
plt.plot(f1)
Loading a second file...
f2sps, f2 = wavfile.read('data/kick2.wav')
plt.xlabel('Samples')
plt.ylabel('Amplitude')
plt.title('kick2.wav')
plt.plot(f2)
ipd.Audio('data/kick1.wav')
Our first step towards programatically building songs!
# Concatenate them end to end
combined = np.vstack((f1, f2))
plt.plot(combined)
wavfile.write('./combined.wav', f2sps, combined)
ipd.Audio('combined.wav')
I think it's pretty cool that I can use numpy to load an array of the individual samples in the audio file.
Take a look! We can se that every sample is accounted for
csps, cf = wavfile.read('combined.wav')
# Just as expected
assert len(cf) == len(f1) + len(f2)
pd.DataFrame(
[['kick21.wav', f1sps, len(f1)],
['kick2.wav', f2sps, len(f2)],
['combined.wav', csps, len(cf)]],
columns=[
'file',
'samples_per_econd',
'number_of_samples']
)
Hey 16 bit audio!
Makes sense...
f1.dtype
Obviously we can do more than just adding two together. With the right calculations, we can create rythm by adding empty space in between the drum samples (sample in this case meaning small snippet of audio).
Here's a demo.
f1
# Line up the sounds again but with
# some zeros between them.
arr_w_silence = np.vstack((f1, np.zeros((len(f1), 2)), f2))
# For som reason putting zeros
# in there converts type ... convert back
arr_w_silence = np.int16(arr_w_silence)
# Give it a name
fname = 'combined_with_silence.wav'
wavfile.write(fname, csps, arr_w_silence)
plt.plot(arr_w_silence)
ipd.Audio(fname)
bb_sps, break_beat = wavfile.read('data/viynl_loop.wav')
print(break_beat.ndim)
plt.figure(figsize=(17, 2))
plt.plot(break_beat)
ipd.Audio('data/viynl_loop.wav')
One loop around is nice but we need it to repeat several times to make a beat.
loop = break_beat
num_loops = 4
for i in range(num_loops):
loop = np.vstack((loop, break_beat))
pd.DataFrame(
(
('Origional Break', len(break_beat), break_beat.ndim),
('After Looping', len(loop), loop.ndim)
),
columns=['Array Name', 'numbers_of_samples', 'channels']
)
fname = 'viynl_loop_looped.wav'
wavfile.write(fname, bb_sps, loop)
plt.figure(figsize=(17, 2))
plt.plot(loop)
ipd.Audio(fname)
Now we could load in some audio samples I took from a record and chopped up.
AMAPOLA_samples = [
sf.read(p, dtype='int16')[0] for p in data_dir.glob('AMAPOLA/*.WAV')
]
SPEC_DEL_samples = [
sf.read(p, dtype='int16')[0] for p in data_dir.glob('SPEC_DEL/*.WAV')
]
plt.figure(figsize=(15, 6))
for i, x in enumerate(SPEC_DEL_samples):
plt.subplot(4, 4, i+1)
plt.plot(x)
plt.figure(figsize=(15, 6))
for i, x in enumerate(AMAPOLA_samples):
plt.subplot(4, 4, i+1)
plt.plot(x)
Next I can show that layering is no problem either.
The only thing to consider is that you can't add numpy arrays together if they are not of even length so we will have to pad the shorter arrays with zeros equal to the difference between them and the longer ones.
# Ignore the need to convert type here, long story
ama_sample = AMAPOLA_samples[0]
#layered_ama.ndim
layered_diff = len(loop) - len(ama_sample)
empty_space = np.zeros((layered_diff))
padded_ama = np.int16(
np.hstack((ama_sample, empty_space))
)
print("Length of the shorter sample: ", len(layered_ama))
print("Length of the Amapola sample: ", len(loop))
print("The difference is: ", layered_diff)
print("Length of smaller one after padding: ", len(padded_ama))
plt.figure(figsize=(15, 6))
plt.subplot(1, 2, 1)
plt.title('Short Sample with Padding')
plt.plot(padded_ama)
plt.subplot(1, 2, 2)
plt.title('Drum Loop')
plt.plot(loop)
Now combining is as straightforward as it gets.
Or at least it would have ben if I didn't have to fiddle with the volumes to get them relativly close to eachother
loop
# Seems to want to turn into a float64 type though
# I'll figure that out later
layered = np.int16(loop[:,0] * 0.3 + padded_ama * 1.5) * 2
plt.plot(layered)
wavfile.write('layered.wav', bb_sps, layered)
ipd.Audio('layered.wav')
The last thing (for now) is to show how you can chop up your samples.
I'm only going to demonstrate splitting into 16 (almost) perfectly even sections but that's often what I do anyway when I have a drum loop like this ready and it's going on the MPC.
# Thanks for the convenient method, numpy
chops = np.array_split(break_beat, 16)
plt.figure(figsize=(15, 24))
for i, x in enumerate(chops):
plt.subplot(8, 2, i+1)
plt.plot(x)
for i, x in enumerate(chops):
'data/.wav'
fname = ('viynl_loop-' + str(i + 1) + '.wav')
wavfile.write(fname, bb_sps, x)
I would like to save some cool stuff for showing off in the future.
Some things that are on the horizons:
import pathlib
import re
from scipy.io import wavfile
def get_snares(root):
pattern = re.compile(r'.*[Ss]nare.*\.(wav|WAV)')
root = pathlib.Path(root)
return (i for i in root.rglob('*')
if pattern.match(str(i)))
def get_kicks(root):
pattern = re.compile(r'.*[Kk]ick.*\.(wav|WAV)')
root = pathlib.Path(root)
return (i for i in root.rglob('*')
if pattern.match(str(i)))
def convert_to_series(root):
for i in get_snares(root):
index = [
"path",
"samples_per_second",
"data_type",
"samples"]
sound_file = wavfile.read(str(i))
ser = pd.Series([
str(i),
sound_file[0],
sound_file[1].dtype,
sound_file[1]],
index = index)
yield ser
df = pd.DataFrame(convert_to_series('./'))
df
print(len(df))
print(len(df[df['data_type'] == np.dtype('int16')]))
print(len(df[df['data_type'] == np.dtype('int32')]))
not_int16 = df[df['data_type'] != np.dtype('int16')]
not_int16
df.memory_usage()
df.iloc[0, 'numpy_array']