In [22]:
from __future__ import print_function, division

%matplotlib inline
from context import *

Visualizing Files

First let's look at the full file (meaning both channels)

In [84]:
# Load it
f1sps, f1 = wavfile.read('data/kick1.wav')
In [85]:
# Plot it
plt.xlabel('Samples')
plt.ylabel('Amplitude')
plt.title('kick1.wav')
plt.plot(f1)

# load an interactive player
ipd.Audio('data/kick1.wav')
Out[85]:

... and now we'll just stick with one from here on out

In [86]:
# Plotting one channel
plt.plot(f1)
Out[86]:
[<matplotlib.lines.Line2D at 0x7fa55cb122e8>,
 <matplotlib.lines.Line2D at 0x7fa55cb12b00>]

Loading a second file...

In [87]:
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')
Out[87]:

Combining

Our first step towards programatically building songs!

In [88]:
# Concatenate them end to end
combined = np.vstack((f1, f2))

plt.plot(combined)

wavfile.write('./combined.wav', f2sps, combined)
ipd.Audio('combined.wav')
Out[88]:

Taking a closer look at the files

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

In [89]:
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']
)
Out[89]:
file samples_per_econd number_of_samples
0 kick21.wav 44100 9108
1 kick2.wav 44100 8604
2 combined.wav 44100 17712

Hey 16 bit audio!

Makes sense...

In [90]:
f1.dtype
Out[90]:
dtype('int16')

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.

In [91]:
f1
Out[91]:
array([[-381, -132],
       [-390, -146],
       [-378, -129],
       ...,
       [  11,  108],
       [  10,  115],
       [  13,  164]], dtype=int16)
In [92]:
# Line up the sounds again but with 
# some zeros between them.
arr_w_silence = np.vstack((f1, np.zeros((len(f1), 2)), f2))
In [93]:
# 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)
Out[93]:

Let's make loops!

In [94]:
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')
2
Out[94]:

Need more looping

One loop around is nice but we need it to repeat several times to make a beat.

In [95]:
loop = break_beat
num_loops = 4

for i in range(num_loops):
    loop = np.vstack((loop, break_beat)) 
In [96]:
pd.DataFrame(
    (
        ('Origional Break', len(break_beat), break_beat.ndim),
        ('After Looping', len(loop), loop.ndim)
    ),
    columns=['Array Name', 'numbers_of_samples', 'channels']
)
Out[96]:
Array Name numbers_of_samples channels
0 Origional Break 106104 2
1 After Looping 530520 2

Voilà!

In [97]:
fname = 'viynl_loop_looped.wav'
wavfile.write(fname, bb_sps, loop)

plt.figure(figsize=(17, 2))
plt.plot(loop)
ipd.Audio(fname)
Out[97]:

Now we could load in some audio samples I took from a record and chopped up.

In [98]:
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')
]
In [99]:
plt.figure(figsize=(15, 6))
for i, x in enumerate(SPEC_DEL_samples):
    plt.subplot(4, 4, i+1)
    plt.plot(x)
In [100]:
plt.figure(figsize=(15, 6))
for i, x in enumerate(AMAPOLA_samples):
    plt.subplot(4, 4, i+1)
    plt.plot(x)

Layering

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.

In [121]:
# Ignore the need to convert type here, long story
ama_sample = AMAPOLA_samples[0]
#layered_ama.ndim
In [122]:
layered_diff = len(loop) - len(ama_sample)
In [125]:
empty_space = np.zeros((layered_diff))
In [126]:
padded_ama = np.int16(
    np.hstack((ama_sample, empty_space))
)
In [77]:
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))
In [127]:
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)
Out[127]:
[<matplotlib.lines.Line2D at 0x7fa55ca8c2b0>,
 <matplotlib.lines.Line2D at 0x7fa55ca8c400>]

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

In [128]:
loop
Out[128]:
array([[ -126, -1398],
       [  802,   -22],
       [ 1439,  1025],
       ...,
       [ 1467,   898],
       [ 1090,   576],
       [  686,   131]], dtype=int16)
In [132]:
# 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
In [133]:
plt.plot(layered)
wavfile.write('layered.wav', bb_sps, layered)
ipd.Audio('layered.wav')
1
Out[133]:

Chop it!!!

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.

In [135]:
# Thanks for the convenient method, numpy
chops = np.array_split(break_beat, 16)
In [137]:
plt.figure(figsize=(15, 24))
for i, x in enumerate(chops):
    plt.subplot(8, 2, i+1)
    plt.plot(x)

Write to sections to individual files!

In [141]:
for i, x in enumerate(chops):
    'data/.wav'
    fname = ('viynl_loop-' + str(i + 1) + '.wav')
    wavfile.write(fname, bb_sps, x)

That's all for now

I would like to save some cool stuff for showing off in the future.

Some things that are on the horizons:

  • Applying effects
  • Experimenting with surround
  • Using ML for classification, synthesis and other goodies
  • Full Songs
In [1]:
 
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-1-397d543883c5> in <module>
----> 1 i

NameError: name 'i' is not defined
In [2]:
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)))
In [63]:
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
In [64]:
df = pd.DataFrame(convert_to_series('./'))
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-64-daadca6fa1ba> in <module>
----> 1 df = pd.DataFrame(convert_to_series('./'))

~/.local/lib/python3.6/site-packages/pandas/core/frame.py in __init__(self, data, index, columns, dtype, copy)
    442         elif isinstance(data, abc.Iterable) and not isinstance(data, (str, bytes)):
    443             if not isinstance(data, abc.Sequence):
--> 444                 data = list(data)
    445             if len(data) > 0:
    446                 if is_list_like(data[0]) and getattr(data[0], "ndim", 1) == 1:

<ipython-input-63-6a7e7efed07f> in convert_to_series(root)
      6                 "data_type",
      7                 "samples"]
----> 8         sound_file = wavfile.read(str(i))
      9         ser = pd.Series([
     10             str(i),

~/.local/lib/python3.6/site-packages/scipy/io/wavfile.py in read(filename, mmap)
    234 
    235     try:
--> 236         file_size, is_big_endian = _read_riff_chunk(fid)
    237         fmt_chunk_received = False
    238         channels = 1

~/.local/lib/python3.6/site-packages/scipy/io/wavfile.py in _read_riff_chunk(fid)
    166         # There are also .wav files with "FFIR" or "XFIR" signatures?
    167         raise ValueError("File format {}... not "
--> 168                          "understood.".format(repr(str1)))
    169 
    170     # Size of entire file

ValueError: File format b'RPKN'... not understood.
In [65]:
df
Out[65]:
path samples_per_second data_type numpy_array
0 data/sonic_pi_beat_material/classic_viynl/samp... 30500 int16 [-1253, -911, -618, -340, -161, 66, 367, 259, ...
1 data/sonic_pi_beat_material/classic_viynl/samp... 30500 int16 [-3347, -3273, -3166, -3101, -3071, -3066, -30...
2 data/sonic_pi_beat_material/classic_viynl/samp... 18000 int16 [190, 378, 426, 352, 392, 340, -871, -2810, -2...
3 data/sonic_pi_beat_material/classic_viynl/samp... 44100 int16 [[-87, -87], [-87, -87], [-79, -79], [-75, -75...
4 data/sonic_pi_beat_material/classic_viynl/samp... 44100 int16 [[665, 346], [684, 359], [705, 353], [719, 347...
5 data/sonic_pi_beat_material/classic_viynl/samp... 44100 int16 [[4950, 2037], [6800, 5415], [3278, 6672], [-2...
6 data/sonic_pi_beat_material/classic_viynl/samp... 44100 int16 [[4950, 2037], [6800, 5415], [3278, 6672], [-2...
7 data/sonic_pi_beat_material/classic_viynl/samp... 44100 int16 [[103, 103], [137, 137], [200, 200], [194, 194...
8 data/sonic_pi_beat_material/classic_viynl/samp... 44100 int16 [[423, 423], [412, 412], [396, 396], [385, 385...
9 data/sonic_pi_beat_material/classic_viynl/samp... 30500 int16 [-1253, -911, -618, -340, -161, 66, 367, 259, ...
10 data/sonic_pi_beat_material/classic_viynl/samp... 30500 int16 [-3347, -3273, -3166, -3101, -3071, -3066, -30...
11 data/sonic_pi_beat_material/classic_viynl/samp... 18000 int16 [190, 378, 426, 352, 392, 340, -871, -2810, -2...
12 data/sonic_pi_beat_material/classic_viynl/samp... 44100 int16 [[-87, -87], [-87, -87], [-79, -79], [-75, -75...
13 data/sonic_pi_beat_material/classic_viynl/samp... 44100 int16 [[665, 346], [684, 359], [705, 353], [719, 347...
14 data/sonic_pi_beat_material/classic_viynl/samp... 44100 int16 [[4950, 2037], [6800, 5415], [3278, 6672], [-2...
15 data/sonic_pi_beat_material/classic_viynl/samp... 44100 int16 [[4950, 2037], [6800, 5415], [3278, 6672], [-2...
16 data/sonic_pi_beat_material/classic_viynl/samp... 44100 int16 [[103, 103], [137, 137], [200, 200], [194, 194...
17 data/sonic_pi_beat_material/classic_viynl/samp... 44100 int16 [[423, 423], [412, 412], [396, 396], [385, 385...
18 data/sonic_pi_beat_material/classic_viynl/samp... 30500 int16 [-1253, -911, -618, -340, -161, 66, 367, 259, ...
19 data/sonic_pi_beat_material/classic_viynl/samp... 30500 int16 [-3347, -3273, -3166, -3101, -3071, -3066, -30...
20 data/sonic_pi_beat_material/classic_viynl/samp... 18000 int16 [190, 378, 426, 352, 392, 340, -871, -2810, -2...
21 data/sonic_pi_beat_material/classic_viynl/samp... 44100 int16 [[-87, -87], [-87, -87], [-79, -79], [-75, -75...
22 data/sonic_pi_beat_material/classic_viynl/samp... 44100 int16 [[665, 346], [684, 359], [705, 353], [719, 347...
23 data/sonic_pi_beat_material/classic_viynl/samp... 44100 int16 [[4950, 2037], [6800, 5415], [3278, 6672], [-2...
24 data/sonic_pi_beat_material/classic_viynl/samp... 44100 int16 [[4950, 2037], [6800, 5415], [3278, 6672], [-2...
25 data/sonic_pi_beat_material/classic_viynl/samp... 44100 int16 [[103, 103], [137, 137], [200, 200], [194, 194...
26 data/sonic_pi_beat_material/classic_viynl/samp... 44100 int16 [[423, 423], [412, 412], [396, 396], [385, 385...
27 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[162, -280], [234, -195], [325, -79], [412, 5...
28 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[-185, -185], [-315, -318], [-566, -567], [-7...
29 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[30, 30], [123, 123], [172, 172], [215, 215],...
... ... ... ... ...
168 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[-79, -115], [71, 23], [163, 174], [133, 90],...
169 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[1612, 895], [1598, 883], [1571, 877], [1549,...
170 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[-419, -470], [-457, -460], [-467, -454], [-4...
171 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[132, -44], [66, -16], [67, 22], [60, 6], [65...
172 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[-805, -804], [-558, -558], [-66, -65], [383,...
173 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[-236, 180], [-140, 262], [-68, 327], [-51, 3...
174 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[916, 919], [874, 875], [867, 870], [968, 968...
175 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[-90, -89], [2, 3], [94, 94], [157, 159], [28...
176 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[-3825, -19], [-3941, -46], [-4022, -104], [-...
177 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[616, 618], [684, 686], [730, 733], [777, 779...
178 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[-22, 65], [-156, -37], [-315, -179], [-543, ...
179 data/sonic_pi_beat_material/classic_viynl/indi... 44100 float32 [[-1.5968283e-08, -7.3652155e-09], [-9.630464e...
180 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[96, 96], [130, 130], [69, 69], [25, 25], [52...
181 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[194, -320], [252, -248], [688, 62], [1593, 8...
182 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[172, 448], [171, 437], [194, 430], [246, 422...
183 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[-290, -154], [-468, -311], [-646, -501], [-5...
184 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[68, 21], [222, 88], [123, -19], [-35, -95], ...
185 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[-233, -316], [-286, -228], [-217, -294], [-2...
186 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[-359, -323], [-749, -680], [-315, -280], [-7...
187 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[24, 17], [-34, -24], [-68, -72], [-185, -181...
188 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[105, 105], [91, 91], [54, 54], [7, 7], [-26,...
189 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[-499, -508], [-540, -535], [-588, -591], [-6...
190 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[-274, -337], [-285, -335], [-291, -331], [-2...
191 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0...
192 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[-237, -663], [-129, -574], [-183, -540], [-4...
193 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[742, 92], [714, 174], [675, 218], [639, 188]...
194 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[364, 54], [302, 21], [277, -28], [310, -66],...
195 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[-424, -405], [-570, -440], [-511, -575], [-4...
196 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[-578, 200], [-649, 203], [-612, 275], [-481,...
197 data/sonic_pi_beat_material/classic_viynl/indi... 44100 int16 [[956, -449], [949, -465], [885, -543], [848, ...

198 rows × 4 columns

In [52]:
print(len(df))
print(len(df[df['data_type'] == np.dtype('int16')]))
print(len(df[df['data_type'] == np.dtype('int32')]))
198
192
0
In [56]:
not_int16 = df[df['data_type'] != np.dtype('int16')]
In [57]:
not_int16
Out[57]:
path samples_per_second data_type numpy_array
31 data/sonic_pi_beat_material/classic_viynl/indi... 44100 float32 [[-1.5968283e-08, -7.3652155e-09], [-9.630464e...
55 data/sonic_pi_beat_material/classic_viynl/indi... 44100 float32 [[-1.5968283e-08, -7.3652155e-09], [-9.630464e...
108 data/sonic_pi_beat_material/classic_viynl/indi... 44100 float32 [[-1.5968283e-08, -7.3652155e-09], [-9.630464e...
132 data/sonic_pi_beat_material/classic_viynl/indi... 44100 float32 [[-1.5968283e-08, -7.3652155e-09], [-9.630464e...
155 data/sonic_pi_beat_material/classic_viynl/indi... 44100 float32 [[-1.5968283e-08, -7.3652155e-09], [-9.630464e...
179 data/sonic_pi_beat_material/classic_viynl/indi... 44100 float32 [[-1.5968283e-08, -7.3652155e-09], [-9.630464e...
In [59]:
df.memory_usage()
Out[59]:
Index                  128
path                  1584
samples_per_second    1584
data_type             1584
numpy_array           1584
dtype: int64
In [68]:
df.iloc[0, 'numpy_array']
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~/.local/lib/python3.6/site-packages/pandas/core/indexing.py in _has_valid_tuple(self, key)
    231             try:
--> 232                 self._validate_key(k, i)
    233             except ValueError:

~/.local/lib/python3.6/site-packages/pandas/core/indexing.py in _validate_key(self, key, axis)
   2009                 "Can only index by location with "
-> 2010                 "a [{types}]".format(types=self._valid_types)
   2011             )

ValueError: Can only index by location with a [integer, integer slice (START point is INCLUDED, END point is EXCLUDED), listlike of integers, boolean array]

During handling of the above exception, another exception occurred:

ValueError                                Traceback (most recent call last)
<ipython-input-68-b680ce653fa1> in <module>
----> 1 df.iloc[0, 'numpy_array']

~/.local/lib/python3.6/site-packages/pandas/core/indexing.py in __getitem__(self, key)
   1402                 except (KeyError, IndexError, AttributeError):
   1403                     pass
-> 1404             return self._getitem_tuple(key)
   1405         else:
   1406             # we by definition only have the 0th axis

~/.local/lib/python3.6/site-packages/pandas/core/indexing.py in _getitem_tuple(self, tup)
   2065     def _getitem_tuple(self, tup):
   2066 
-> 2067         self._has_valid_tuple(tup)
   2068         try:
   2069             return self._getitem_lowerdim(tup)

~/.local/lib/python3.6/site-packages/pandas/core/indexing.py in _has_valid_tuple(self, key)
    234                 raise ValueError(
    235                     "Location based indexing can only have "
--> 236                     "[{types}] types".format(types=self._valid_types)
    237                 )
    238 

ValueError: Location based indexing can only have [integer, integer slice (START point is INCLUDED, END point is EXCLUDED), listlike of integers, boolean array] types
In [ ]: