Source code for sound.instrument

# pylint: disable=unused-argument
from .import SAMPLE_RATE
from .filter import HighPassFilter, FakeFMFilter, LowPassFilter, FMFilter, ring_filter, PitchShift
from .envelope import Decay, envelope, Envelope, ADSR
from .tone import SineWave as Sine, harmonics, Noise, Digitar
from .note import Note
from .sample import RawData

__all__ = ('Instrument', 'SineSustain', 'SineHit', 'KickDrum', 'Shaker', 'BassDrum', 'SquareViolin', 'HardDisk', 'ElectricHorn', 'Bell', 'Bell2', 'ElectricBass', 'Guitar')

[docs] class Instrument(object): """ A base class for instruments. An instrument is an object that can produce notes to a certain specification. """ def __init__(self, tempo=120, volume=1): """ :param tempo: The tempo to produce notes at, in bpm. Optional, defaults to 120. """ self.tempo = tempo self.articulation = 0.8 self.volume = volume @property def beat(self): # convert from beats/minute to seconds/beat # b/m * m/s = b/s return 60. / self.tempo
[docs] def rest(self, beats=1): """ Return a rest for the given number of beats. """ return Note(0, beats, self.beat * SAMPLE_RATE)
[docs] def note(self, freq, beats=1, articulation=None): """ Return a note to the given specification. :param freq: The frequency of the note to produce :param beats: The number of beats to produce a note for, default 1 :param articulation: An optional number describing how legato or staccato notes should be produced, between 0 and 1. Larger values are more legato, smaller values are more staccato. Doesn't work correctly for precussion or string instruments. """ if articulation is None: articulation = self.articulation filling = min(1, articulation / 0.8) legato = max(0, (articulation - 0.8) / 0.2) n = Note(self._note(freq, beats, filling, legato), beats, self.beat * SAMPLE_RATE) * self.volume #n.author = self return n
def _note(self, freq, beats, filling, legato): raise NotImplementedError
[docs] def play(self, *args): """ Play a note to the given specification. Parameters are the same as those to `note`. """ self.note(*args).play()
class SampledDrum(Instrument): def __init__(self, bpm, filenames): super(SampledDrum, self).__init__(bpm) self.samples = [RawData.from_file(fname) for fname in filenames] def _note(self, freq, beats, filling, legato): return self.samples[freq]
[docs] class SineSustain(Instrument): """ A sustained sine tone. """ def _note(self, freq, beats, filling, legato): env = Envelope(beats * self.beat * filling) avgenvelope = 0.5*legato*env + (1-legato)*env.apply_adsr(0.01,0.05,0.1, sustain_level=0.5) return Sine(freq) * avgenvelope
[docs] class SineHit(Instrument): """ A plucked sine tone. Sounds sort of xylophonish. """ def _note(self, freq, beats, filling, legato): env = Envelope(beats * self.beat * filling) env2 = env.apply_decay(0.095*filling**4 + 0.005) assert isinstance(env2, Decay) env3 = env2.apply_adsr(0.005, 0, 0.1 - 0.1*legato, sustain_level=1) return Sine(freq) * env3
#def xylophone(freq): # return Sine(freq/4) * Decay(0.1, 2, 0.01, 0.3) * 0.5 + \ # Sine(freq*8) * Decay(0.01, 2, 0.01, 0.1) * 0.15 + \ # Sine(freq*8*3) * Decay(0.01, 2, 0.01, 0.2) * 0.05 + \ # Sine(50) * Decay(0.0, 0.2, 0) * 0.05 + \ # Sine(60) * Decay(0.0, 0.2, 0) * 0.05 + \ # Sine(70) * Decay(0.0, 0.2, 0) * 0.05 + \ # Sine(80) * Decay(0.0, 0.2, 0) * 0.05 + \ # Sine(90) * Decay(0.0, 0.2, 0) * 0.05 + \ # Sine(100) * Decay(0.0, 0.1, 0.1) * 0.05
[docs] class KickDrum(Instrument): """ A basic kick drum that's just a low-frequency decaying sine wave. Frequency parameters are ignored. """ def _note(self, *args): return Sine(110) * Decay(0, 0.4, 0, 0.0000001)
[docs] class Shaker(Instrument): """ A basic shaker precussion instrument that's just decaying noise. Frequency parameters are ignored. """ def _note(self, *args): return (HighPassFilter(Noise(), 0.1) * envelope(decay=0.0001)).purify()
[docs] class BassDrum(Instrument): """ A tuned bass drum, works by pitch-shifting a sine wave by a decaying value. """ def _note(self, freq, *args): basesound = Sine(freq) pitchdecay = envelope(1, decay=0.0001) + 1 ampdecay = envelope(1, decay=0.0000001) return PitchShift(basesound, pitchdecay) * ampdecay
[docs] class SquareViolin(Instrument): """ A violin made from a square wave. """ def _note(self, freq, beats, filling, legato): #attack = min(0.5, sustain/10.) #env = envelope(attack=attack, # decay=0, # sustain=sustain-attack*1.5, # attack_level=attack/2, # sustain_level=1, # decaying_sustain=False) env = Envelope(beats * self.beat * filling) argenvelope = 0.5*legato*env + (1-legato)*env.apply_adsr(0.01,0.05,0.1, sustain_level=0.5) return sum(Sine(freq*i) * 0.5 * 1./i for i in range(1, 5)) * argenvelope
[docs] class HardDisk(Instrument): """ An instrument that sounds sort of like when people make music out of hard disks """ def _note(self, freq, beats, filling, legato): sample = Digitar(freq, buffersize=2000) << 0.1 env = Envelope(beats * self.beat * filling) return sample * env
[docs] class ElectricHorn(Instrument): """ A horn-like sound made with FM synthesis """ def _note(self, freq, beats, filling, legato): env = ADSR(0.05, 0.05, beats * self.beat * filling - 0.15, 0.15) src = FMFilter(Sine(freq), Sine(freq + 0.5), 30) return env * src
[docs] class Bell(Instrument): """ A bell sound made from ring modulation. Doesn't sound good at all frequencies. """ def _note(self, freq, *args): return envelope(decay=0.1, attack=0.001, attack_level=0.4) * ring_filter(harmonics(freq, (2,3,4)))
[docs] class Bell2(Instrument): """ Another bell, sounding more metallic, made from my horrible failed first attempt at FM synthesis. Doesn't sound good at all frequencies. """ def _note(self, freq, *args): return LowPassFilter( envelope(decay=0.3) * FakeFMFilter(Sine, Sine(100. * freq / 220), carrier_freq=freq*2, mod_quantity=50), 0.1 )
[docs] class ElectricBass(Instrument): """ An electric bass guitar, made with FM synthesis I think this one is my favorite. """ def _note(self, freq, beats, filling, legato): sineA = Sine(freq) sustain = beats * self.beat * filling return FMFilter(sineA, Sine(freq/2) * envelope(200)) * envelope(decay=0.1, sustain=sustain)
[docs] class Guitar(Instrument): """ A simple guitar made from Karplus-Strong plucked string synthesis """ def _note(self, freq, beats, filling, legato): digibase = Digitar(freq) return digibase * Envelope(beats * self.beat * filling)