Wednesday, March 6, 2019

ex8. pluck csound opcode

The csound pluck opcode generates a sound based on the Karpus Strong Algorithm.


We use a python file (rand_gen.py) to generate a random signal of 16k samples.


# rand_gen.py
# random signal of length N

N = 16 * 1024

import numpy as np
from scipy.io import wavfile

amp = 30000 # <= 32768
rate = 44100

sig = np.random.randint(-amp,amp, size=N).astype('int16')
wavfile.write('rand.wav', rate, sig)

We use this file (ex8.py) to generate instrument 1 which we play 200 times, with different parameters. We use the table genertated by rand.wav file.


# ex8.py
# pluck opcode

import random

from csd import CSD
import table
from opcodes import envelope, output, source

csd = CSD(__file__)

csd.begin()

csd.add_instrument(
    num = 1,
    opcodes = [
        envelope.adsr(
            attack = 'p5*idur',
            sustain = 0.99,
            sig_out = 'kadsr'
            ),
        source.pluck(
            sig_out = 'asig',
            amp = 0.5,
            freq = 'p4',
            table = 1,
            method = 1,
            iparam1 = 0,
            iparam2 = 0
        ),
        output.outs(
            sig_in = ['kadsr*asig','kadsr*asig']
        )
               ]
    )

csd.middle()

t = 0
for i in range(200):
    at = 0.01 + .09 * random.random()
    t = t + .5 + random.random()/5
    f = 220 + 440 * random.random()
    csd.add('i 1 %.4f 0.5 %.4f %.4f' % (t, f, at))

csd.add_tables([table.sample(
                    num=1,
                    name='rand.wav'
                    )
                ])

csd.end()

We use pluck from updated source.py file:


# source.py
# ver 1

import sys

#1. lfo
def lfo(**dic):
    ''' A low frequency oscillator of various shapes.
    itype = 0 - sine, itype = 1 - triangles,
    itype = 2 - square (bipolar), itype = 3 - square (unipolar),
    itype = 4 - saw-tooth, itype = 5 - saw-tooth(down)
    sig_out, amp, freq, type '''
    sig_out = dic.get('sig_out','')
    if sig_out=='':
        print('We need sig_out for lfo')
        sys.exit(-1)
    amp = dic.get('amp', 1)
    freq = dic.get('freq', 440)
    itype = dic.get('itype', 0)
    return '%s lfo %s, %s, %s' % (sig_out,amp,freq,itype)

#2. pluck
def pluck(**dic):
    ''' Produces a naturally decaying plucked string or drum sound.
    - sig_out, amp, freq, table
    - method, method of natural decay. There are 6, some of which use
    - the 2 parameters values that follow.
    - 1: simple averaging. A simple smoothing process, uninfluenced by
    - parameter values.
    - 2: stretched averaging. As above, with smoothing time stretched by
    - a factor of iparm1 (>=1).
    - 3: simple drum. The range from pitch to noise is controlled by a
    - 'roughness factor' in iparm1 (0 to 1). Zero gives the plucked string
    - effect, while 1 reverses the polarity of every sample (octave down,
    - odd harmonics). The setting .5 gives an optimum snare drum.
    - 4: stretched drum. Combines both roughness and stretch factors.
    - iparm1 is roughness (0 to 1), and iparm2 the stretch factor (>=1).
    - 5: weighted averaging. As method 1, with iparm1 weighting the
    - current sample (the status quo) and iparm2 weighting the previous
    - adjacent one. iparm1 + iparm2 must be <= 1.
    - 6: 1st order recursive filter, with coefs .5. Unaffected by
    - parameter values. '''
    sig_out = dic.get('sig_out', '')
    if sig_out == '':
        print('We need sig_out for pluck')
        sys.exit(-1)
    amp = dic.get('amp', 1)
    freq = dic.get('freq', 440)
    table = dic.get('table', 1)
    method = dic.get('method', 1)
    iparam1 = dic.get('iparam1', 0)
    iparam2 = dic.get('iparam2', 0)
    t = sig_out,amp,freq,freq,table,method,iparam1,iparam2
    return '%s pluck %s,%s,%s,%s,%s,%s,%s' % t

#3. poscil
def poscil(**dic):
    ''' High precision oscillator
    sig_out, amp, freq, and table '''
    sig_out = dic.get('sig_out','')
    if sig_out == '':
        print('We need sig_out for poscil')
        return
    amp = dic.get('amp', 1)
    freq = dic.get('freq', 440)
    table = dic.get('table', 1)
    return '%s poscil %s, %s, %d' % (sig_out,amp,freq,table)

The link to the audio is here.

Saturday, March 2, 2019

ex7. linseg envelope opcode

The linseg envelope opcode is used to create envelopes for a sound oscillator.


For the python file to write the csound file, we will need a list in pts of levels and duration between levels.


For example for first fourth of note duration we go from 220 to 230. Then for half of note duration, we go from 230 to 260. Finally for last fourth, we go linearly to 240.


This program uses the envelope to control frequency of oscillator.


# ex7.py
# linseg opcode

from csd import CSD
import table
from opcodes import envelope, output, source

csd = CSD(__file__)

csd.begin()

csd.add_instrument(
    num = 1,
    opcodes = [envelope.linseg(
                   sig_out = 'kfreq',
                   pts = [220,'idur/4',230,'idur/2',260,'idur/4',240]
                   ),
               source.lfo(
                   sig_out = 'klfo',
                   amp = 5,
                   freq = 2),
               envelope.adsr(
                   sig_out = 'kadsr'
                   ),
               source.poscil(
                   sig_out = 'aOsc',
                   amp = 'kadsr',
                   freq = 'kfreq+klfo'
                   ),
               output.outs(
                   sig_in = ['aOsc', 'aOsc' ]
                   )
               ]
    )

csd.middle()

csd.add('i 1 0 40')

csd.add_tables([table.sine(
                    num=1,
                    amps=[1,1/2,1/3,1/4,1/5,1/6,1/7,1/8]
                    )
                ])

csd.end()

We use table.py file:



We use output.py from opcodes folder:


# output.py
# ver 0

import sys

#1. out
def out(**dic):
    ''' Writes mono audio data to an external device or stream.
    sig_in '''
    sig_in = dic.get('sig_in', '')
    if len(sig_in) == '':
        print('We need sig_in for out')
        sys.exit(-1)
    return 'out %s' % sig_in

#2. outs
def outs(**dic):
    ''' Writes stereo audio data to an external device or stream.
    sig_in (2-len) '''
    sig_in = dic.get('sig_in', [])
    if len(sig_in) != 2:
        print('We need sig_in (2-len) for outs')
        sys.exit(-1)
    return 'outs %s,%s' % (sig_in[0],sig_in[1])

This is the new envelope.py file from opcodes folder:


# envelope.py
# ver 1

import sys

#1. adsr
def adsr(**dic):
    ''' Calculates the classical ADSR envelope using linear segments.
    sig_out
    attack, duration of attack phase
    decay, duration of decay
    sustain_level, level for sustain phase
    release, duration of release phase
    delay, period of zero before the envelope starts '''
    sig_out = dic.get('sig_out', '')
    if sig_out == '':
        print('We need sig_out for adsr')
        sys.exit(-1)
    attack = dic.get('attack','0.1*idur')
    decay = dic.get('decay', '0.1*idur')
    sustain_level = dic.get('sustain_level', 0.5)
    release = dic.get('release', '0.1*idur')
    delay = dic.get('delay', 0)
    t = sig_out,attack,decay,sustain_level,release,delay
    return '%s adsr %s,%s,%s,%s,%s' % t


#2. line
def line(**dic):
    ''' Trace a straight line between specified points.
    sig_out
    end_pts, [ia, ib] where ia is start value and ib is value after dur
    dur, duration in seconds of segment '''
    sig_out = dic.get('sig_out', '')
    if sig_out == '':
        print('We need sig_out for line')
        sys.exit(-1)
    end_pts = dic.get('end_pts', [])
    if len(end_pts) != 2:
        print('We need end_pts (2-len) for line')
        sys.exit(-1)
    dur = dic.get('dur', 1)
    return '%s line %s,%s,%s' % (sig_out,end_pts[0],dur,end_pts[1])

#3. linen
def linen(**dic):
    ''' Applies a straight line rise and decay pattern to an input amp signal
    sig_out, amp
    rise, rise time in seconds. None, if <= 0.
    dur, overall duration in seconds.
    decay, decay time in seconds. Zero means no decay. '''
    sig_out = dic.get('sig_out','')
    if sig_out == '':
        print('We need sig_out for linen')
        sys.exit(-1)
    amp = dic.get('amp', 1)
    rise = dic.get('rise', '0.1*idur')
    dur = dic.get('dur', 'idur')
    decay = dic.get('decay', '0.1*idur')
    return '%s linen %s,%s,%s,%s' % (sig_out,amp,rise,dur,decay)

#4. linseg
def linseg(**dic):
    ''' Trace a series of line segments between specified points
    sig_out
    pts, [ia,idur1,ib,idur2,ic] where ia is starting level, ib, ic are
    ..levels and idur1, idur2 are duration in seconds between levels.'''
    sig_out = dic.get('sig_out', '')
    if sig_out == '':
        print('We need sig_out for linseg')
        sys.exit(-1)
    pts = dic.get('pts', [])
    if len(pts) == 0:
        print('We need pts for lineseg')
        sys.exit(-1)
    pts_str = [str(p) for p in pts]
    return '%s linseg %s' % (sig_out,','.join(pts_str))

The audio for the output file is here.