Tuesday, February 26, 2019

ex6. csound GEN10 routine

The GEN10 routine is used to generate sine tables.


Depending on the number of parameters, we can have harmonic partials.


We use a helper file, table.py, to create the string for the f statement for the table.


We use amps of [1,0.5]. That means we have fundamental (f) and first partial (at frequency 2f) with relative amp of 0.5. The actual amps will be adjusted by csound normalization.


This program uses the sine with two components in the oscillator.


# ex6.py
# GEN 10 sine table

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

csd = CSD(__file__)

csd.begin()

csd.add_instrument(
    num = 1,
    opcodes = [envelope.line(
                   sig_out = 'kfreq',
                   end_pts = [2,8]
                   ),
               source.lfo(
                   sig_out = 'kres',
                   amp = 220,
                   freq = 2
                   ),
               envelope.linen(
                   sig_out = 'kenv',
                   rise = 'idur/40',
                   decay = 'idur/40'
                   ),
               envelope.adsr(
                   sig_out = 'kadsr'
                   ),
               source.poscil(
                   sig_out = 'aOsc',
                   amp = 0.5,
                   freq = 'kfreq+kres'
                   ),
               output.outs(
                   sig_in = ['kenv*aOsc', 'kadsr*aOsc' ]
                   )
               ]
    )

csd.middle()

csd.add('i 1 0 40')

csd.add_tables([table.sine(
                    num=1,
                    amps=[1,0.5]
                    )
                ])

csd.end()

We use table.py file:


# table.py
# ver 0

import sys

#1. sine
def sine(**dic):
    ''' GEN10 — Generate composite waveforms made up of weighted sums of
    .. simple sinusoids.
    num, time(0),
    size(2**14), 2 to power or 2 to power + 1
    norm(True), whether signal should be normalized
    amps, [str1, str2, ...] where str1, str2, are relative strengths of
    .. the fixed harmonic partial numbers 1,2,3, etc., beginning in p5.
    .. Partials not required should be given a strength of zero. If only
    .. 1 component, we can write it as 1 and not [1] for simple sine '''
    num = dic.get('num', 0)
    if num == 0:
        print('We need a table number.')
        sys.exit(-1)
    time = dic.get('time', 0)
    norm = dic.get('norm', True)
    if norm: gen = 1
    else: gen = -1
    amps = dic.get('amps', [])
    size = dic.get('size', 2**14)
    if type(amps) == type(0):
        amps = [amps]
    if len(amps) == 0:
        print('amps must be a list of numbers or a number')
        sys.exit(-1)
    amps = [str(amp) for amp in amps]
    string = 'f %d %d %d %d %s' % (num, time, size, gen*10, ' '.join(amps))
    return string

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])

We also use source.py from opcodes folder:


# source.py
# ver 0

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. 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 audio for the output file is here.

ex5. adsr opcode

The adsr opcode is used to create an envelope with attack, decay, release time as well as sustain level.


This program uses an adsr envelope on the right channel as we can see by opening in program like Audacity.


# ex5.py
# adsr envelope function

from csd import CSD
from opcodes import envelope

csd = CSD(__file__)

csd.begin()

csd.add('giSine ftgen 0, 0, 2^10, 10, 1')

csd.add_instrument(
    num = 1,
    opcodes = [envelope.line(
                   sig_out = 'kfreq',
                   end_pts = [2,8]
                   ),
               'kres lfo 220,2',
               envelope.linen(
                   sig_out = 'kenv',
                   rise = 'idur/40',
                   decay = 'idur/40'
                   ),
               envelope.adsr(
                   sig_out = 'kadsr',
                   ),
               'aOsc poscil 0.5, kfreq+kres, giSine',
               'outs kenv*aOsc, kadsr*aOsc' ])

csd.middle()

csd.add('i 1 0 40')

csd.end()

We use envelope.py from opcodes folder.


# envelope.py
# ver 0

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)

The video with the sound is here.

Monday, February 25, 2019

ex4. Notes in Ace Song

We play the MIDI notes from a portion of Ace's How Long song.


The first half is over the notes of track with instrument 5 (Electric Piano). The next half is over all instruments including percussion


For example these are the notes for the first half (time offset, MIDI pitch, duration, velocity):


Instr = 5 (Electric Piano)
+ 0.000,46,1.000,75
+ 0.500,56,0.500,65
+ 0.500,58,0.500,65
+ 0.500,60,0.500,65
+ 0.500,63,0.500,65
+ 1.500,46,0.125,80
+ 1.500,58,3.375,90
+ 1.500,62,3.250,90
+ 1.500,65,3.250,90
+ 2.000,46,1.000,80
+ 3.500,46,0.125,75
+ 4.000,46,1.000,80
+ 5.500,46,0.125,90
+ 5.500,56,2.875,90
+ 5.500,60,2.875,80
+ 5.500,63,2.750,80
+ 6.000,46,1.000,90
+ 7.500,46,0.125,90
+ 8.000,46,0.875,90
+ 8.500,56,0.875,90
+ 8.500,58,0.875,65
+ 8.500,60,0.875,75
+ 8.500,63,0.875,75
+ 9.500,46,0.125,80
+ 9.500,58,2.750,80
+ 9.500,62,2.750,90
+ 9.500,65,2.625,80
+ 10.000,46,1.000,80
+ 11.500,46,0.125,75
+ 12.000,46,1.000,90
+ 12.500,58,0.875,90
+ 12.500,62,0.875,80
+ 12.500,65,0.875,75
+ 13.500,46,0.125,80
+ 13.500,56,2.875,80
+ 13.500,58,2.875,75
+ 13.500,60,2.875,80
+ 13.500,63,2.875,80
+ 14.000,46,1.000,90
+ 15.500,46,0.125,80
+ 16.000,46,1.000,80
+ 16.500,56,0.875,90
+ 16.500,58,0.875,80
+ 16.500,60,0.875,90
+ 16.500,63,0.875,75
+ 17.500,46,0.125,90
+ 17.500,58,2.875,75
+ 17.500,62,3.000,75
+ 17.500,65,2.875,65
+ 18.000,46,1.000,90
+ 19.500,46,0.125,90
+ 20.000,46,0.875,80
+ 20.500,58,0.875,75
+ 20.500,65,0.875,75
+ 21.500,46,0.125,90
+ 21.500,56,2.875,80
+ 21.500,58,2.875,75
+ 21.500,60,3.000,75
+ 21.500,63,2.875,65
+ 22.000,46,1.000,90
+ 23.500,46,0.125,90
+ 24.000,46,0.875,90
+ 24.500,56,0.875,75
+ 24.500,58,0.875,75
+ 24.500,63,0.875,75
+ 25.500,46,0.125,90
+ 25.500,58,2.750,75
+ 25.500,62,2.875,80
+ 25.500,65,2.750,80
+ 26.000,46,1.000,80
+ 27.500,46,0.125,90
+ 28.000,46,0.875,90
+ 28.500,58,0.875,80
+ 28.500,62,0.875,90
+ 28.500,65,0.875,60
+ 29.500,46,0.125,80
+ 29.500,60,2.375,75
+ 29.500,63,2.375,90
+ 29.500,68,2.375,80
+ 30.000,46,1.000,90
+ 31.500,46,0.125,80
+ 32.000,46,1.000,90
+ 32.000,60,0.250,80
+ 32.000,63,1.375,90
+ 32.000,68,0.375,80
+ 32.500,58,0.375,75
+ 32.500,67,0.125,60
+ 33.000,60,0.250,60
+ 33.000,68,0.375,75
+ 33.500,46,0.125,80
+ 33.500,58,2.750,80
+ 33.500,62,2.750,90
+ 33.500,65,2.750,75
+ 34.000,46,1.000,80
+ 35.500,46,0.125,90
+ 36.000,46,0.875,80
+ 36.500,58,0.875,80
+ 36.500,62,0.750,80
+ 36.500,65,0.875,80
+ 37.500,46,0.125,75
+ 37.500,60,2.250,90
+ 37.500,63,2.375,80
+ 37.500,68,2.250,80
+ 38.000,46,1.000,90
+ 39.500,46,0.125,90
+ 40.000,46,1.000,80
+ 40.000,60,0.250,80
+ 40.000,63,0.875,90
+ 40.000,68,0.500,90
+ 40.500,58,0.375,60
+ 41.000,60,0.375,80
+ 41.000,63,0.375,60
+ 41.000,68,0.500,75
+ 41.500,46,0.125,80
+ 41.500,58,2.875,75
+ 41.500,62,2.750,75
+ 41.500,65,2.875,80
+ 42.000,46,1.000,80
+ 43.500,46,0.125,90
+ 44.000,46,0.875,80
+ 44.500,58,0.750,65
+ 44.500,62,0.750,75
+ 44.500,65,0.875,75
+ 45.500,46,0.125,75
+ 45.500,60,2.250,90
+ 45.500,63,2.375,90
+ 45.500,68,2.375,75
+ 46.000,46,1.000,80
+ 47.500,46,0.125,90
+ 48.000,46,1.000,75
+ 48.000,60,0.250,90
+ 48.000,63,0.875,90
+ 48.000,68,0.500,90
+ 48.500,58,0.375,75
+ 48.500,67,0.375,80
+ 49.000,60,0.375,75
+ 49.000,63,0.375,65
+ 49.000,68,0.375,80
+ 49.500,46,0.125,75
+ 49.500,58,3.875,80
+ 49.500,62,3.500,90
+ 49.500,65,1.875,80
+ 50.000,46,1.000,80
+ 51.500,46,0.125,80
+ 51.500,65,0.750,75
+ 52.000,46,0.875,90
+ 52.500,65,1.000,90
+ 53.500,46,0.125,90
+ 53.500,60,2.250,80
+ 53.500,63,2.250,80
+ 53.500,68,2.250,75
+ 54.000,46,1.000,80
+ 55.500,46,0.125,80
+ 56.000,46,0.875,80
+ 56.000,60,0.250,80
+ 56.000,63,1.375,80
+ 56.000,68,0.375,80
+ 56.500,58,0.375,60
+ 56.500,67,0.375,65
+ 57.000,60,0.375,65
+ 57.000,68,0.375,80
+ 57.500,46,0.125,80
+ 57.500,58,2.000,90
+ 57.500,62,1.875,90
+ 57.500,65,1.875,65
+ 58.000,46,1.000,90
+ 59.500,62,1.250,75
+ 61.000,62,1.000,75
+ 62.000,48,1.125,80
+ 62.000,60,3.125,90
+ 62.000,63,2.875,90
+ 62.000,67,2.750,90
+ 63.500,48,0.125,90
+ 64.000,48,1.875,90
+ 66.000,43,2.625,80
+ 66.000,55,1.125,65
+ 66.000,58,1.125,75
+ 66.000,62,1.125,65
+ 66.000,65,1.125,75
+ 67.500,55,1.375,75
+ 67.500,58,1.500,75
+ 67.500,62,1.375,80
+ 67.500,65,1.375,75
+ 69.000,43,0.750,80
+ 70.000,41,4.000,90
+ 70.000,53,3.875,60
+ 70.000,56,3.875,65
+ 70.000,60,4.000,65
+ 70.000,63,3.750,65
+ 74.000,53,1.625,65
+ 74.000,56,1.625,75
+ 74.000,63,1.625,75
+ 76.000,53,0.625,80
+ 76.000,56,0.625,75
+ 76.000,60,0.625,75
+ 76.000,63,0.625,90
+ 77.000,46,0.750,90
+ 77.000,53,0.750,80
+ 77.000,58,0.875,80
+ 77.000,62,0.750,90
+ 78.000,48,1.125,80
+ 78.000,55,3.000,80
+ 78.000,60,3.000,75
+ 78.000,63,2.875,90
+ 79.500,48,0.250,90
+ 80.000,48,1.875,90
+ 81.000,63,0.875,80
+ 82.000,43,1.000,80
+ 82.000,55,1.375,75
+ 82.000,58,1.375,75
+ 82.000,62,1.250,75
+ 83.500,43,0.125,80
+ 83.500,55,2.375,80
+ 83.500,58,2.375,80
+ 83.500,62,2.375,90
+ 84.000,43,1.875,80
+ 86.000,41,1.125,75
+ 86.000,53,3.750,65
+ 86.000,56,3.875,75
+ 86.000,60,3.875,75
+ 86.000,63,3.750,80
+ 87.500,41,1.625,75
+ 90.000,41,1.875,80
+ 90.000,53,1.750,60
+ 90.000,56,1.750,65
+ 90.000,60,1.750,80
+ 90.000,63,1.625,80
+ 92.000,53,0.875,75
+ 92.000,56,0.750,75
+ 92.000,60,0.625,75
+ 92.000,63,0.750,75
+ 93.000,46,0.625,90
+ 93.000,53,0.875,80
+ 93.000,58,1.000,75
+ 93.000,62,0.875,80
+ 94.000,48,3.750,80
+ 94.000,55,1.125,75
+ 94.000,60,1.125,80
+ 94.000,63,1.125,75
+ 95.500,55,2.250,75
+ 95.500,60,2.125,80
+ 95.500,63,2.125,75
+ 98.000,43,3.500,90
+ 98.000,55,2.750,75
+ 98.000,58,2.750,75
+ 98.000,62,2.750,65
+ 98.000,65,2.750,75
+ 101.000,55,0.750,60
+ 101.000,58,0.750,65
+ 101.000,62,0.500,65
+ 101.000,65,0.625,75
+ 102.000,41,4.000,90
+ 102.000,53,3.875,60
+ 102.000,56,3.875,65
+ 102.000,60,3.875,60
+ 102.000,63,3.750,60
+ 106.000,53,1.750,75
+ 106.000,56,1.750,75
+ 106.000,60,1.750,80
+ 106.000,63,1.625,75
+ 108.000,53,0.875,65
+ 108.000,56,0.750,75
+ 108.000,60,0.750,80
+ 108.000,63,0.750,65
+ 109.000,46,0.750,90
+ 109.000,53,0.875,75
+ 109.000,58,0.875,80
+ 109.000,62,0.625,90
+ 110.000,48,3.875,90
+ 110.000,55,2.875,65
+ 110.000,60,2.875,75
+ 110.000,63,2.750,75
+ 113.000,55,1.000,75
+ 113.000,60,0.875,80
+ 113.000,63,0.875,80
+ 114.000,43,3.500,90
+ 114.000,58,2.875,75
+ 114.000,62,2.875,65
+ 117.000,55,0.750,65
+ 117.000,58,0.750,60
+ 117.000,62,0.750,75
+ 118.000,41,4.000,80
+ 118.000,53,1.125,75
+ 118.000,56,1.000,75
+ 118.000,60,1.125,75
+ 118.000,63,1.125,75
+ 119.500,53,2.250,75
+ 119.500,56,2.250,75
+ 119.500,60,2.250,65
+ 119.500,63,2.250,75
+ 122.000,53,1.750,65
+ 122.000,56,1.750,80
+ 122.000,60,1.750,80
+ 122.000,63,1.750,75
+ 124.000,53,0.750,90
+ 124.000,56,0.750,80
+ 124.000,60,0.625,75
+ 124.000,63,0.750,90
+ 125.000,46,0.750,90
+ 125.000,53,0.750,75
+ 125.000,58,0.750,80
+ 125.000,62,0.500,80

A score will have to be generated by another program such as a Digital Audio Workstation or pygame or tkinter program. We usually only have a few patterns (5-10) per instrument track (usually less than 10). Then the patterns are arranged in time.


We have the video here.


ex3. line opcode and Frequency Modulation

The line opcode will interpolate linear values from two endpoints.


We use an lfo's default shape (sine), to modulate the carrier frequency which goes from 220 Hz to 440 Hz during duration of the 40-second sound. It's frequency is modulated by 220 Hz at a rate of 2 Hz.


Now we have added add_instrument method to CSD class. Here we expect a list of strings in opcodes. These are joined to write out the csd for this instrument.


This program generates the frequency modulated program:


# ex3.py
# line opcode and Frequency Modulation

from csd import CSD

csd = CSD(__file__)

csd.s1()

csd.add('giSine ftgen 0, 0, 2^10, 10, 1')

csd.add_instrument(
    num = 1,
    opcodes = ['kfreq line 220, p3, 440',
               'kres lfo 220,2',
               'kenv linen 1, p3/40, p3, p3/40',
               'aOsc poscil 0.5, kfreq+kres, giSine',
               'outs kenv*aOsc, kenv*aOsc' ])

csd.s2()

csd.add('i 1 0 40')

csd.s3()

Now the csd file is:


# csd.py

import os
import time

class CSD:

    def __init__(self, fnam):
        self.lines = []
        self.fnam = fnam.split(os.path.sep)[-1][:-3]

    def add(self, line):
        self.lines.append(line)

    def add_instrument(self, **dic):
        num = dic.get('num', 1)
        self.add('\n\tinstr %d' % num)
        opcodes = dic.get('opcodes',[])
        if len(opcodes)==0:
            print('We need opcodes list')
            return
        string = '\n'.join(opcodes)
        self.add(string)
        self.add('\tendin\n')

    def s1(self, sr=44100, ksmps = 32, nchnls=2, dbfs=1):
        self.add('<CsoundSynthesizer>')
        self.add('<CsOptions>')
        self.add('-o %s.wav -W' % self.fnam)
        self.add('</CsOptions>')
        self.add('<CsInstruments>')
        self.add('sr = %s' % sr)
        self.add('ksmps = %s' % ksmps)
        self.add('nchnls = %s' % nchnls)
        self.add('0dbfs = %s' % dbfs)

    def s2(self):
        self.add('</CsInstruments>')
        self.add('<CsScore>')

    def s3(self):
        self.add('</CsScore>')
        self.add('</CsoundSynthesizer>')
        string = '\n'.join(self.lines)
        fout = open(self.fnam + '.csd', 'w')
        fout.write(string)
        fout.close()
        time.sleep(0.5)
        status = os.system('csound %s.csd' % self.fnam)
        if status!=0: raise Exception ('Could not write wav file')

We have the video here.