69 lines
1.6 KiB
Python
69 lines
1.6 KiB
Python
import math
|
|
import struct
|
|
from itertools import chain
|
|
from operator import mul
|
|
from random import random
|
|
|
|
from player import AplaySink
|
|
|
|
SAMPLE_RATE = 44100
|
|
|
|
|
|
def sin(f, amplitude, duration):
|
|
samples = (amplitude*math.sin(2*math.pi*f*(t/SAMPLE_RATE)) for t in range(int(duration*SAMPLE_RATE)))
|
|
return samples
|
|
|
|
|
|
def square(f, amplitude, duration):
|
|
period = SAMPLE_RATE / f
|
|
half_period = period / 2
|
|
for t in range(int(duration*SAMPLE_RATE)):
|
|
if t % period < half_period:
|
|
yield amplitude
|
|
else:
|
|
yield -amplitude
|
|
|
|
|
|
def noise(amplitude, duration):
|
|
return (amplitude*(2*random()-1) for _ in range(int(duration*SAMPLE_RATE)))
|
|
|
|
|
|
def envelope(samples, attack_duration, release_duration):
|
|
# TODO: rewrite ADSR envelope
|
|
def compose(a, b):
|
|
return map(lambda x: mul(*x), zip(a, b))
|
|
|
|
a = int(attack_duration * SAMPLE_RATE)
|
|
r = int(release_duration * SAMPLE_RATE)
|
|
samples = list(samples)
|
|
attack = (x/a for x in range(0, a))
|
|
sustain = len(samples) - a - r
|
|
release = (x/r for x in range(r, -1, -1))
|
|
|
|
return chain(
|
|
compose(samples[:a], attack),
|
|
samples[a:a+sustain],
|
|
compose(samples[a+sustain:], release))
|
|
|
|
|
|
def mix(*sources):
|
|
return (sum(x) for x in zip(*sources))
|
|
|
|
|
|
def pack(samples) -> bytes:
|
|
return b"".join(struct.pack("<f", sample) for sample in samples)
|
|
|
|
|
|
def main():
|
|
with AplaySink(SAMPLE_RATE) as sink:
|
|
data = mix(
|
|
sin(440, 0.05, 1),
|
|
sin(440*2**(4/12), 0.05, 1),
|
|
sin(440*2**(7/12), 0.05, 1))
|
|
packed = pack(data)
|
|
sink.submit(packed)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|