ADSR-ääni

Ztane 23.08.07 22:44

Esittelee eri python-ominaisuuksia - luokat, binaaritiedostot, struct.pack, generaattorit, klosuurit, lambda-funktiot, ja tekee samalla myös ääntä

 Tekstiversio  Arvo: 6 (6 ääntä)  Äänestä: +  -
# kaytto: python adsr.py aani.raw
#
# vaatii varmaankin python 2.4:n
#
# soita aani vaikka komennolla aplay -f cd aani.raw

import math
import struct
import sys

class ADSR:
        # luokan konstruktori.
        # parametrit a, d, r, on
        # atakin, dekayn, releasen pituus sekunneissa,
        # s on sustaintaso 0 .. 1
        # length on koko nuotin mitta eli
        # sustainin pituus on length - a - d - r.
        # siistimmankin saa tehda jos osaa.
        # (vahan quick'n'dirty)
        def __init__(self, a, d, s, r, length):
                if a == 0 or r == 0 or d == 0 or length == 0:
                        raise ValueError("Keston pitaa olla != 0")

                self.a = a
                self.d = d
                self.s = s
                self.r = r
                self.suslength = length - self.a - self.d - self.r
                self.length = length

        # laskee adsr-ampin kohdassa time (sekunteja)
        # ihan peisik lineaarinen, vahan rumasti tehty
        # vois ehka kayttaa jotain eksponenttifunktiota
        # tms lisamaan pehmeytta (tietyt atakit napsuu pahasti!)
        def get_amp(self, time):
                # attack?
                if (time < self.a):
                        return time / self.a

                time -= self.a
                # decay?
                if (time < self.d):
                        return 1.0 - (time / self.d) \
                                        * (1 - self.s)

                time -= self.d
                # sustain?
                if (time < self.suslength):
                        return self.s

                # release..
                return self.s * (1 - (time - self.suslength) / self.r)

        # applyttaa adsr:aa sampleratella rate funkkariin wavefunk,
        # palauttaa yksittaisia amplitudeja valilla -1 .. 1 (eli siis
        # sampleja annetulla ratella). Wavefunkin PITAA antaa
        # amplitudit valilla -1..1, kompuraa ei ole, eika limitteria
        # (koodi kaatuu sitten poikkeukseen struct.packissa ;)
        #
        # wavefuncia evaluoidaan diskreeteissa ajankohdissa t
        # missa t on aika sekunteina.
        #
        # metodi on generaattori, ts yield-lause palauttaa arvoja
        # toiseen saikeeseen
        def apply(self, wavefunc, rate):
                # tama vain siksi etta jakolasku pakottuisi floateiksi
                # koska kyseessa on loop invariant, otin sen ulos...
                rate = float(rate)
           
                # kesto s, kertaa samplet per minuutti
                samples = int(self.length * rate)

                # eli 0..samples - 1 silmukkaa
                for i in xrange(samples):
                        # i:nnen samplen ajankohta
                        t = i / rate
 
                        # evaloidaan wavefunkki ja verhokappyra
                        # ja kerrotaan keskinnaan
                        yield wavefunc(t) * self.get_amp(t)

# aanta! palauttaa lambdafunktion: perusaanes
# @f hz, harmonisina yla-aanina vaimeampana
# kvintti ylospain (* 1.5) ja oktaavi (* 2).
# Amplitudien sopivalla skaalauksella ~ -1...1
def aani(f):
        # 2 pi f
        m = 2 * 3.14159 * f

        # lambdafunktio, klosuuri (m-muuttujaa
        # ulkopuolelta kaytetaan palautetussa funktiossa...)
        # palauttaa siis funktio-olion, joka palauttaa
        # diskreetin amplitudin ajanhetkella t [s] ;)
        return lambda t: \
                .75 * math.sin(m * t) + \
                .17 * math.sin(m * 1.5 * t) + \
                .07 * math.sin(m * 2 * t)

# tarkista etta tiedostonnimi annettu
if len(sys.argv) != 2:
        print "Anna tiedostonimi!"
        sys.exit(1)

# ja ala paukuttaa binaaritiedostoon
f = file(sys.argv[1], 'w')

# tee verhokappyra
#
# attack 1ms,
# decay 1ms
# sustaintaso 0,4
# release 200ms,
# koko kesto 250ms
adsr = ADSR(.001, .001, .4, .2, .25);

# ok... c - c1-oktaavi, 44100 samplea / s, stereosamplet 16 bit
for freq in [ 261.63, 293.66, 329.63, 349.23, 392.00, 440.0, 493.88, 523.25 ]:
        # heh, iteroi sample kerrallaan aanifunktiota, johon on
        # applytetty adsr, @ 44100 hz samplaystaajuus
        for i in adsr.apply(aani(freq), 44100):
                # kerro -1 ... 1  32767:lla (tuloksena siis
                # signed shortin alue (-32768 ... 32767)
                s = int(i * 32767);

                # tallenna molemmille kanaville shortteina (h)...
                # eli 16 bittia kanava, vasen ja oikea...
                # silmukan joka kierros siis tallettaa nelja tavua.
                f.write(struct.pack('hh', s, s))

f.close()
 

Entropia 08:38 24.8.07 
Saatan ehkä aistia mikä on kirvoittanut tämän pätkän. :) Hyvää oppimateriaalia, vaikkakaan ihan kaikkea ei ole ruohonjuuritasolta selitetty.
Ztane 11:02 24.8.07 
selitin enämpi