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