Saturday, August 24, 2013

Statescape Wisconsin

So as I wrote in my last post, I've been looking for a while for a way to build a delay line that would allow the recirculating sound to interact with the sound being input in a way other than just being mixed together.  I have wanted to explore other ways in which the input sound could modify the sound looping through the line.  One thing I thought of was to build a delay line in which the input amplitude modulates the recirculating signal.  As you might know, for any pair of sine waves that are input to a form of amplitude modulation, the output will contain sine waves at two new frequencies which are the sum and difference of the frequencies of the two inputs.  If you use more complex waveforms, then each pair of component sines contained within the two input signals will produce sum and differences frequencies, which can produce a whole lot of partials in the output .  Here is a block diagram of what I had in mind:

The first question was how to actually build such a delay line, and for me the answer was obvious: my favorite softsynth-building environment, Csound.  I've coded up a number of delay lines in Csound previously, and the only big change here was to incorporate the amplitude modulation function into the feedback loop.  However, getting that to work the way I wanted proved to be more difficult than I though at first.  To illustrate why, I'll repeat the basic amplitude modulation calculation from my last post:

A = (IG + M) * C

where: M is the modulation signal, C is the carrier signal, IG is the initial gain for the carrier (or, to put it another way, the magnitude of the output when no modulation is present), and A is the amplitude-modulated output.  The problem here is the fact that when you first start up a delay line, it contains no signal.  As you can see in the equation, if the carrier C term is zero, there is no output. So obviously if the AM process is implemented with the delay line feedback as the C term, the sound building process can never get started because no AM output is ever generated. 

So I tried coding it the other way, treating the input signal as the carrier and the delay line feedback as the oscillation.  That solves the problem with the delay initially not containing anything; it gets filled with unmodified input signal until something starts wrapping back out of the line and amplitude-modulating with the input.  However, it creates another problem: there has to be an input signal present all the time.  Whenever there isn't, the AM output, and the signal getting fed back into the delay line, gets "blanked".  And that's bad because I've found that, when doing these long-period delay things, it pays to be sparse with the input; if you are playing notes into it all the time, it quickly gets too busy for the listener to make any sense of it.

I thought about going back to the first way, with the delay line feedback as the carrier, but with a software switch that would route unmodified input signal into the line whenever there was no output from the AM processing.  But what I wound up doing was simpler: I computed the modulation both ways and added the results.  This doesn't effect which frequencies are present in the output, only the relative levels.  For the purpose, I decided it was good enough.  And this had the advantage of not going silent whenever one signal or the other wasn't present.

Once that problem was solved, the next problem was to figure out what kind of input signals would produce interesting results.  I tried some standard synth things like PWM leads and pad sounds, and I found out right away that with those harmonically complex sounds, the results degenerated into a particularly nasty-sounding form of noise very quickly.  So I had to have something harmonically simpler.  For this purpose I chose the Kawai K5m additive synth.  This was sort of overkill, but it worked for the purpose.  I built one basic sound with only a few harmonics, and capable of having its harmonic content varied by use of the mod wheel

I ran into a few problems, including one that I never manged to solve.  The big one was a puzzling popping noise that appears at random times.  I still haven't figured this one out.  Also, I had some problem with subsonics appearing in the output.  To address both of these problems, I added a pair of two-pole Butterworth filters to the algorithm, a low pass and a high pass.  These didn't totally solve the popping problem, which you can still hear in places in the completed track.

As for the results: they were surprisingly musical.  The AM often added notes that I didn't play, and I was pleasantly surprised at how often the added overtones actually worked well with the notes that were played.  Keeping everything harmonically simple helps a lot.  There is a distorted sound that builds up when things get busy; it seems to be characteristic.  All in all, I was fairly pleased.  Now I have to think of what to do for the next delay line.

Listen to Wisconsin here.

And here is the Csound source code for the delay line:

; Basic stereophonic delay line

itimel      = 3.1       ; left channel delay time
itimer      = 4.2       ; right channel delay time
ifbl        = 1.7      ; left channel feedback (keep < 1)
ifbr        = 1.7      ; right channel feedback
kcutlo      init 20.0   ; hi-pass for damping subsonics
kcuthi      init 2500.0 ; low-pass for suppressing pops
imodindex   init 10
kleftch     init 3      ; channel # of left channel (right is assumed +1)

afbl        init 0
afbr        init 0

; Get input audio
ainl, ainr  inch kleftch, kleftch+1

; Scale values to -1..+1 range needed by formula
ainlscaled = ainl / 0dbfs
ainrscaled = ainr / 0dbfs
afblscaled = afbl / 0dbfs
afbrscaled = afbr / 0dbfs

; Compute with feedback as carrier and input as modulation, and rescale
amodinl = (1 + imodindex * ainlscaled) * afblscaled * 0dbfs / 2
amodinl butterhp amodinl, kcutlo
amodinl butterlp amodinl, kcuthi
amodinr = (1 + imodindex * ainrscaled) * afbrscaled * 0dbfs / 2
amodinr butterhp amodinr, kcutlo
amodinr butterlp amodinr, kcuthi

; Compute with input as carrier and feedback as modulation, and rescale
amodfbl = (1 + imodindex * afblscaled) * ainlscaled * 0dbfs / 2
amodfbl butterhp amodfbl, kcutlo
amodfbl butterlp amodfbl, kcuthi
amodfbr = (1 + imodindex * afbrscaled) * ainlscaled * 0dbfs / 2
amodfbr butterhp amodfbr, kcutlo
amodfbr butterlp amodfbr, kcuthi

; Push samples through the left and right delay lines
aoutl       delay amodinl+amodfbl, itimel
aoutr       delay amodinr+amodfbr, itimer

; Output direct + delayed audio
            outch kleftch, (ainl+aoutl)*2
            outch kleftch+1, (ainr+aoutr)*2

; Compute feedback for next cycle
afbl        = aoutl * ifbl
afbr        = aoutr * ifbr


No comments: