Last active
April 7, 2023 18:31
-
-
Save TrebledJ/f42f9030d1bee0ece8af7fc0db5d0151 to your computer and use it in GitHub Desktop.
Revisions
-
TrebledJ revised this gist
Apr 7, 2023 . No changes.There are no files selected for viewing
-
TrebledJ revised this gist
Apr 7, 2023 . No changes.There are no files selected for viewing
-
TrebledJ revised this gist
Apr 7, 2023 . No changes.There are no files selected for viewing
-
TrebledJ revised this gist
Mar 8, 2023 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -141,6 +141,6 @@ def update(frame): ani = FuncAnimation(fig, update, gen, interval=interval) if save_animation: ani.save('wavetable-synthesis.gif', writer='imagemagick', fps=1000 // interval) else: plt.show() -
TrebledJ created this gist
Mar 8, 2023 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,146 @@ # Wavetable Synthesis Demo # by @TrebledJ from matplotlib.patches import ConnectionPatch import matplotlib.ticker as mticker import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation import numpy as np # Wavetable Parameters table_size = 32 # Size of the wavetable (lookup table). # Generation Parameters freq = 8 # Frequency of the sine wave to generate (in Hz). sample_rate = 100 # Sampling rate of the generated signal. nsamples = 25 # Number of samples to generate. # Animation Parameters # Save the animation (True) or play the animation using Matplotlib's rendering (False). save_animation = True # Number of milliseconds between frames. interval = 500 def generate_samples(wavetable, freq, sample_rate, *, once=False, nsamples=20): """ Generate samples from a wavetable. Note that `sample_rate` should be at least `2 * freq`, and the ### Parameters 1. wavetable : np.array[float] - The wavetable containing all the pregenerated samples. 2. freq : float - The frequency of the waveform to generate. 3. sample_rate : float - The sampling rate of the waveform to generate. 4. once : bool - True to make at most one pass through the wavetable. - False to run for `nsamples` times. 5. nsamples : int - Number of samples to generate. ### Returns - A generator yielding (phases, samples), i.e. (x, y) values along the wavetable. """ phases = [] # Stores phases (x). samples = [] # Stores samples (y). # The phase is a float with two components: the integer part and the decimal part. # The integer part indicates the sample along the wavetable which we are at, modulus # the wavetable's length. The decimal part indicates the fraction between the left and # right samples. phase = 0 for _ in range(nsamples): if once and phase > table_size: break idx = int(phase) # Integer part. frac = phase - idx # Decimal part. samp0 = wavetable[idx] samp1 = wavetable[(idx + 1) % table_size] samp = samp0 + (samp1 - samp0) * frac # Interpolate! phases.append(phase) samples.append(samp) yield phases, samples phase += table_size * freq / sample_rate phase %= table_size yield None t = np.linspace(0, 1, table_size + 1) wavetable = np.sin(2 * np.pi * t) fig, ax = plt.subplots(2, 1) ax[0].plot(t * table_size, wavetable, 'bo-') ax[0].set_title("Wavetable (Lookup Table)") ax[0].axis('off') ax[1].set_title('Samples') ax[1].set_xlim(-0.5, nsamples + 0.5) ax[1].set_ylim(-1.1, 1.1) ax[1].xaxis.set_major_locator(mticker.MultipleLocator(5)) # We'll store drawn points and connections (to be removed on subsequent iterations). points = [] connections = [] def update(frame): """ Update the animation frame with the next sample. """ if frame is None: # Finished! try: points[-1].remove() connections[-1].remove() except: pass # Freeze! ani.pause() return fig, ph, samples = frame wt_x = ph[-1] % table_size wt_y = samples[-1] p = ax[0].plot([wt_x], [wt_y], 'ro', ms=4) ax[1].plot(np.arange(len(samples)), samples, 'ro') # Magic line between axs. con = ConnectionPatch(xyA=(wt_x, wt_y), xyB=(len(samples) - 1, samples[-1]), coordsA=ax[0].transData, coordsB=ax[1].transData, color="red") fig.add_artist(con) try: points[-1].remove() connections[-1].remove() except: pass points.append(p[0]) connections.append(con) return fig, gen = generate_samples(wavetable, freq, sample_rate, nsamples=nsamples) ani = FuncAnimation(fig, update, gen, interval=interval) if save_animation: ani.save('wavetable_synthesis.gif', writer='imagemagick', fps=1000 // interval) else: plt.show()