Skip to content

Instantly share code, notes, and snippets.

@cool-RR
Created December 14, 2018 19:23
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 cool-RR/282c52106213406449eba69cd1b06181 to your computer and use it in GitHub Desktop.
Save cool-RR/282c52106213406449eba69cd1b06181 to your computer and use it in GitHub Desktop.
import numbers
import struct
import pathlib
import math
import abc
import wave as wave_module
import itertools
import re
import sortedcontainers
CUTOFF_FACTOR = 8
letter_map = {'c': 0, 'd': 2, 'e': 4, 'f': 5, 'g': 7, 'a': 9, 'b': 11}
for letter, number in dict(letter_map).items():
letter_map[f'{letter}#'] = (number + 1) % 12
letter_map[f'{letter}b'] = (number - 1) % 12
def get_frequency_from_letter(letter):
match = re.match('^([abcdefg][b#]?)([0-9]?)', letter.lower())
letter_, octave = match.groups()
octave = int(octave) if octave else 4
return 16.352 * 2 ** (octave + (letter_map[letter_] / 12))
class Timbre:
def __init__(self, overtones: dict):
self.overtones = dict(overtones)
class Instrument:
def __init__(self, timbre: Timbre, half_life: numbers.Rational = 0.2):
self.timbre = timbre
self.half_life = half_life
piano = Instrument(Timbre({i: 2 / i for i in range(1, 8)}), half_life=0.2)
class Listenable(metaclass=abc.ABCMeta):
@abc.abstractmethod
def get_pressure(self, time: numbers.Rational) -> numbers.Rational:
pass
@abc.abstractmethod
def get_length(self) -> numbers.Rational:
pass
def save_wav(self, path: pathlib.Path, hz=8000):
wav_file = wave_module.open(str(path), 'w')
nchannels = 1
sampwidth = 2
nframes = int(hz * self.get_length())
comptype = 'NONE'
compname = 'not compressed'
wav_file.setparams((nchannels, sampwidth, hz, nframes, comptype,
compname))
for i in range(nframes):
time = i / hz
wav_file.writeframes(
struct.pack('h', int(self.get_pressure(time) * 1000))
)
wav_file.close()
class Note(Listenable):
def __init__(self, frequency, instrument: Instrument = piano,
volume: numbers.Rational = 1):
if isinstance(frequency, numbers.Number):
self.frequency = frequency
else:
self.frequency = get_frequency_from_letter(frequency)
self.instrument = instrument
self.volume = volume
def get_length(self):
try:
return self._length
except AttributeError:
self._length = self.instrument.half_life * CUTOFF_FACTOR
return self._length
def get_pressure(self, time: numbers.Rational) -> numbers.Rational:
if time < 0 or time > self.get_length():
return 0
result = 0
for overtone, overtone_volume in \
self.instrument.timbre.overtones.items():
result += overtone_volume * math.sin(
time * self.frequency * overtone * (math.pi * 2)
)
result *= self.volume * 2 ** (- time / self.instrument.half_life)
return result
class Sequence(Listenable):
def __init__(self, members):
self.members = sortedcontainers.SortedListWithKey(
members,
key=lambda pair: pair[0]
)
def get_pressure(self, time: numbers.Rational) -> numbers.Rational:
return sum(member.get_pressure(time - time_) for time_, member in
self.members)
def get_length(self,):
return max(
time + member.get_length() for time, member in self.members
)
bpm = 50
period = 1 / (bpm / 60)
foo = (
(0 * period + period * 0 / 6, Note('c4')),
(0 * period + period * 2 / 6, Note('c4')),
(0 * period + period * 3 / 6, Note('e4')),
(0 * period + period * 5 / 6, Note('g4')),
(1 * period + period * 0 / 6, Note('a3')),
(1 * period + period * 2 / 6, Note('a3')),
(1 * period + period * 3 / 6, Note('c4')),
(1 * period + period * 5 / 6, Note('e4')),
(2 * period + period * 0 / 6, Note('f3')),
(2 * period + period * 2 / 6, Note('f3')),
(2 * period + period * 3 / 6, Note('a3')),
(2 * period + period * 5 / 6, Note('c4')),
(3 * period + period * 0 / 6, Note('g3')),
(3 * period + period * 2 / 6, Note('g3')),
(3 * period + period * 3 / 6, Note('b3')),
(3 * period + period * 5 / 6, Note('d4')),
)
foofoo = []
for i, entry in itertools.product(range(12), foo):
foofoo.append(
(entry[0] + i * 4 * period, entry[1])
)
sequence = Sequence(foofoo)
sequence.save_wav('output.wav')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment