In [164]:
%pylab inline
from scipy import signal
from functools import partial
import math
import numpy as np
pylab.rcParams['figure.figsize'] = (16, 6)
pylab.rcParams["font.size"] = "14"
import IPython.display as ipd
from ipywidgets import interact, fixed
import ipywidgets as widgets

# settings
SRATE = 22050

# note names to freq for widgets
notes = 'C-,C#,D-,D#-,E-,F-,F#,G-,G#,A-,A#,B-'.split(',')
notes = [n + str(o) for o in range(1,5) for n in notes]
freqs = 55. * 2**(np.arange(3, 3 + len(notes)) / 12.)
notes = list(zip(notes, freqs))
#print(notes)

waveforms = [('saw', 0),('tri', 1), ('sqr', 2)]


Populating the interactive namespace from numpy and matplotlib

In [165]:
def display_wave(a, t, srat=SRATE):
    figure(1)
    # waveform
    plot(t, a)
    xlabel('Time (s)')
    grid(True)
    margins(0.0)

    plt.show()

    # audio player
    ipd.display(ipd.Audio(a, rate=srat))

In [168]:
def drones(frq, ticks, bpm, tpb, voices, waveform, srat=SRATE):
    # TODO: make stereo and spread voices
    # TODO: other combine modes (AM?)
    # TODO: center voice frequencies around the selected tone  (right now we only go up)
    #
    # Things we've tried:
    # - using 'sin' as a wave is not interesting (already 'tri' is relative quiet)
    # - incrementing num_cycles by 1 is enough, if we do e.g. 2, the pattern will repeat twice
    
    # from ticks and samplerate we calculate the number of samples for desired resync.   
    # ticks per second
    tps = (bpm * tpb) / 60.0
    # samples per tick
    spt = srat / tps
    dur = ticks * spt
    
    print("Duration samples: %f" %  dur)
    
    # from the tone frequency we can check what the phase would be at the point of resync.
    # samples_per_cycle = srat / note_frq
    spc = srat / frq
    # we round this to end with phase=0 at the end of the loop
    num_cycles = round(dur / spc)
    frqs=[]
    for v in range(voices):
        frqs.append(srat / (dur / num_cycles))
        num_cycles += 1
    
    # TODO: do a table with the error for the whole tone range and various bpms
    print("Frq error: ", abs(frq - frqs[0]))
    print("Frqs: ", *frqs)
    
    osc = None
    if waveform == 0:
        osc = partial(signal.sawtooth)
    elif waveform == 1:
        osc = partial(signal.sawtooth, width=0.5)
    elif waveform == 2:
        osc = partial(signal.square)

    twopi = 2.0 * np.pi
    t = np.linspace(0, dur/srat, int(dur))
    a = np.zeros(len(t))
    for v in range(voices):
        #a += np.sin(t * twopi * frqs[v])
        a += osc(t * twopi * frqs[v])
    
    display_wave(a, t)

In [167]:
interact(drones, 
         frq=notes, waveform=waveforms,
         ticks=widgets.IntSlider(min=1, max=256, step=1, value=16, continuous_update=False), 
         bpm=widgets.IntSlider(min=60, max=300, step=1, value=125, continuous_update=False), 
         tpb=widgets.IntSlider(min=1, max=16, step=1, value=4, continuous_update=False),
         voices=widgets.IntSlider(min=1, max=10, step=1, value=3),
         srat=fixed(SRATE))


Out[167]:
<function __main__.drones>

In [ ]: