Skip to content

Instantly share code, notes, and snippets.

@brunifrancesco
Last active November 22, 2020 12:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brunifrancesco/2a2d14016b8c064f4135d181ad29fbc9 to your computer and use it in GitHub Desktop.
Save brunifrancesco/2a2d14016b8c064f4135d181ad29fbc9 to your computer and use it in GitHub Desktop.
Signal and wave modelling
from abc import abstractmethod
from signals_wave import WaveM
class Modulation:
@abstractmethod
def modulate(self, signal_wave: WaveM, carrier_wave: WaveM) -> WaveM:
raise NotImplementedError("You need to implement this method before calling it!")
@abstractmethod
def demodulate(self, signal_wave: WaveM, carrier_wave: WaveM) -> WaveM:
raise NotImplementedError("You need to implement this method before calling it!")
class AmplitudeModulation(Modulation):
def modulate(self, signal_wave: WaveM, carrier_wave: WaveM) -> WaveM:
return signal_wave * carrier_wave
def demodulate(self, modulated_wave: WaveM, carrier_wave: WaveM) -> WaveM:
return carrier_wave * modulated_wave
import numpy as np
import matplotlib.pyplot as plt
from IPython.display import Audio
import math
PI2 = math.pi * 2
class Wave:
def __init__(self, ys, ts, framerate):
self.ys = ys
self.ts = ts
self.framerate = framerate
def play_wave(self):
return Audio(data=self.ys, rate=self.framerate)
def plot(self):
fig = plt.figure()
plt.plot(self.ts[0:1000], self.ys[0:1000], '-')
plt.title('Wave sample in domain time')
plt.xlabel('time')
plt.ylabel('values')
class Signal:
def __init__(self, freq=400, amp=1.0, offset=0, function=np.sin):
self.freq = freq
self.amp = amp
self.offset = offset
self.periodic_function = function
def print_period(self):
print("Period (s): %f" %(1.0/self.freq))
def compute_phases(self, ts):
return PI2 * self.freq * ts + self.offset
def evaluate(self, ts):
ts = np.asarray(ts)
phases = self.compute_phases(ts)
ys = self.amp * self.periodic_function(phases)
return ys
def create_wave(self, duration=2, start=0, framerate=3000):
n = round(duration * framerate)
ts = start + np.arange(n) / framerate
ys = self.evaluate(ts)
return Wave(ys, ts, framerate=framerate)
class WaveI(Wave):
def __init__(self, ys, ts, framerate):
super().__init__(ys, ts, framerate)
if ts is None:
self.ts = np.arange(len(ys)) / self.framerate
else:
self.ts = np.asanyarray(ts)
def plot_fft(self):
n = len(self.ys)
d = 1 / self.framerate
hs = np.fft.fft(self.ys)
fs = np.fft.fftfreq(n, d)
hs = np.asanyarray(hs)
fs = np.asanyarray(fs)
hs = np.fft.fftshift(hs)
amps = np.abs(hs)
fs = np.fft.fftshift(fs)
plt.plot(fs, amps ** 2)
plt.title('Wave in the frequency domain (real part)')
plt.xlabel('Frequency')
plt.ylabel('Power')
class FunctionBasedSignal(Signal):
def __init__(self, freq, function):
super().__init__(freq=freq)
self.periodic_function = function
def create_wave(self, duration=2, start=0, framerate=25000):
n = round(duration * framerate)
ts = start + np.arange(n) / framerate
ys = self.evaluate(ts)
return WaveI(ys, ts, framerate=framerate)
class HalfScalingFactor:
SCALE_FACTOR = np.float64(0.5)
@classmethod
def get_scale_factor(cls):
return cls.SCALE_FACTOR
@classmethod
def print_factor_scale(cls):
print(cls.SCALE_FACTOR)
class WaveModOps:
def __len__(self):
"""
Lenght of the wave
"""
return len(self.ys)
def __mul__(self, other):
"""
Multiplies two waves element-wise
"""
assert self.framerate == other.framerate
assert len(self) == len(other)
ys = self.ys * other.ys
return WaveM(ys, self.ts, self.framerate)
@property
def duration(self):
"""Duration (property).
returns: float duration in seconds
"""
return len(self.ys) / self.framerate
class Spectrum:
def __init__(self, hs, fs, framerate):
self.hs = np.asanyarray(hs)
self.fs = np.asanyarray(fs)
self.framerate = framerate
def render_full(self, high=None):
"""Extracts amps and fs from a full spectrum.
high: cutoff frequency
returns: fs, amps
"""
hs = np.fft.fftshift(self.hs)
amps = np.abs(hs)
fs = np.fft.fftshift(self.fs)
i = 0 if high is None else find_index(-high, fs)
j = None if high is None else find_index(high, fs) + 1
return fs[i:j], amps[i:j]
def plot(self, high=None, **options):
"""Plots amplitude vs frequency.
Note: if this is a full spectrum, it ignores low and high
high: frequency to cut off at
"""
fs, amps = self.render_full(high)
plt.plot(fs, amps, **options)
def low_pass(self, cutoff, factor=0):
"""Attenuate frequencies above the cutoff.
cutoff: frequency in Hz
factor: what to multiply the magnitude by
"""
self.hs[abs(self.fs) > cutoff] *= factor
def make_wave(self, duration=1, start=0, framerate=11025):
"""Makes a Wave object.
duration: float seconds
start: float seconds
framerate: int frames per second
returns: Wave
"""
ys = np.fft.ifft(self.hs)
# NOTE: whatever the start time was, we lose it when
# we transform back; we could fix that by saving start
# time in the Spectrum
# ts = self.start + np.arange(len(ys)) / self.framerate
return WaveM(ys, ts=None, framerate=self.framerate)
class WaveM(WaveI, HalfScalingFactor, WaveModOps):
def scale(self):
self.ys = np.float64(self.ys)
self.ys *= self.get_scale_factor()
def make_spectrum(self):
n = len(self.ys)
d = 1 / self.framerate
hs = np.fft.fft(self.ys)
fs = np.fft.fftfreq(n, d)
return Spectrum(hs, fs, self.framerate)
@staticmethod
def convert( wave):
return WaveM(wave.ys, wave.ts, wave.framerate)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment