I was recently discussing Csound with colleagues, and singing its praises. However I did have to mention an odd corner of concern: for anyone seriously interested in Karplus-Strong style synthesis that didn’t feel comfortable building that universe from scratch, I have to advise them to stay clear. Of all the out of box opcodes I have played with, only “pluck” stays in tune. But it is limited in its parameters and doesn’t support as many options like defining an excitation signal yourself, etc. To get in-tune plucks that are actually in tune to arbitrary accuracy, I’ve had to do one of the following:
resort to ksmps=1 low level implementations (with varied levels of success)
use a Faust custom-built opcode that csound could use.
use other software altogether.
I know that there is a strong drive to preserve legacy opcode behavior, but honestly, I think in reality that doesn’t always come to pass, as I have had .csd files I try to use years later on a new version suddenly fail due to API changes. So, I wonder aloud if it is at all possible for the hardcore DSP devs to look at the limitations (tuning-wise) of the pluck-related opcodes and fix those pitch quantization issues…? Or maybe add some next-gen pitch accurate ones to the mix? It should be possible, because the Faust code plugin ended up working beautifully. And yes, I can and will continue to use it in the meantime, but I think newbs to Csound should also benefit from out-of-the-box flexible, and in tune, “*pluck” opcodes…
Another possibility is that the Faust code I am using can be used as a basis for a new opcode one of the devs could incorporate. I am happy to share it.
Victor did some nice UDOs in the 2016 Csound book on Springer. They might behave more to your expectations.
I attach an include file with the UDOs.
The String opcode is used like:
aout String iamp, icps, ifeedback, ipluck_pos, ipickup_pos
If you are only using one delay line it is possible to implement a pluck waveguide without need to do sample-by-sample processing.
The delayr-w pair can implement feedback without much difficulty. The simple LP filter can be done with delay1, so that can be inserted without any problems. The fine tuning can be done with allpass filter that can be implemented with filter2 or even biquad. Or alternatively you can just used an interpolated tap to give the correct fractional delay.
I think I posted an example here from one of my classes last time this was discussed. I can look for it next time I’m on the computer.
Prof. Victor Lazzarini
Maynooth University
Ireland
yes. i see this:
> WARNING: cngoto: perf-time code in global space, ignored
complete output:
UnifiedCSD: /home/jh/Desktop/test.csd
rtaudio: JACK module enabled
WARNING: cngoto: perf-time code in global space, ignored
Elapsed time at end of orchestra compile: real: 0.002s, CPU: 0.002s
sorting score ...
... done
Elapsed time at end of score sort: real: 0.003s, CPU: 0.003s
--Csound version 7.0 (double samples) Mar 9 2023
[commit: 2503fc0b439f70973a6ee9fdd810fda6d111882c]
libsndfile-1.0.31
sr = 44100.0, kr = 689.062, ksmps = 64
0dBFS level = 1.0, A4 tuning = 440.0
orch now loaded
audio buffered in 256 sample-frame blocks
reading 1024-byte blks of shorts from adc:system:capture_ (RAW)
system sr: 44100.000000
Jack input ports:
0: adc0 (adc:system:capture_1)
1: adc1 (adc:system:capture_2)
2: adc2 (adc:csound6:output1)
3: adc3 (adc:csound6:output2)
connecting channel 0 to system:capture_1
connecting channel 1 to system:capture_2
Jack output ports:
0: dac0 (dac:system:playback_1)
1: dac1 (dac:system:playback_2)
2: dac2 (dac:csound6:input1)
3: dac3 (dac:csound6:input2)
connecting channel 0 to system:playback_1
connecting channel 1 to system:playback_2
writing 512 sample blks of 64-bit floats to dac:system:playback_
SECTION 1:
new alloc for instr 1:
WARNING: rtjack: xrun in real time audio
rtevent: T 0.374 TT 0.374 M: 0.00000 0.00000
new alloc for instr 1:
rtevent: T 0.750 TT 0.750 M: 0.00000 0.00000
new alloc for instr 1:
rtevent: T 1.125 TT 1.125 M: 0.00000 0.00000
new alloc for instr 1:
rtevent: T 1.501 TT 1.501 M: 0.00000 0.00000
new alloc for instr 1:
rtevent: T 1.875 TT 1.875 M: 0.00000 0.00000
new alloc for instr 1:
rtevent: T 2.249 TT 2.249 M: 0.00000 0.00000
rtevent: T 2.625 TT 2.625 M: 0.00000 0.00000
rtevent: T 3.000 TT 3.000 M: 0.00000 0.00000
rtevent: T 3.376 TT 3.376 M: 0.00000 0.00000
rtevent: T 3.750 TT 3.750 M: 0.00000 0.00000
rtevent: T 4.124 TT 4.124 M: 0.00000 0.00000
rtevent: T 4.500 TT 4.500 M: 0.00000 0.00000
rtevent: T 4.875 TT 4.875 M: 0.00000 0.00000
rtevent: T 5.251 TT 5.251 M: 0.00000 0.00000
rtevent: T 5.625 TT 5.625 M: 0.00000 0.00000
rtevent: T 5.999 TT 5.999 M: 0.00000 0.00000
rtevent: T 8.375 TT 8.375 M: 0.00000 0.00000
Score finished in csoundPerformKsmps() with 2.
inactive allocs returned to freespace
end of score. overall amps: 0.00000 0.00000
overall samples out of range: 0 0
0 errors in performance
Elapsed time at end of performance: real: 8.441s, CPU: 0.670s
--Csound version 7.0 (double samples) Mar 9 2023
[commit: 2503fc0b439f70973a6ee9fdd810fda6d111882c]
1443 512 sample blks of 64-bit floats written to dac:system:playback_
FYI, I’ve tried examples before, and they work fine…I was just more lobbying for an out-of-the-box solution that didn’t involve having to “roll-your-own”, as it were.
Another way of asking: can’t these solutions become 1st-class opcode citizens in some future release? If it’s deemed that “correcting” the existing opcodes might break backwards compatibility, perhaps a new opcode called “superpluck” or something like that? (That’s what I affectionately have called my Faust-ian solution locally). I see such an opcode a win overall for existing and future Csound users.
I think it is bad that those opcodes are out of tune. Not sure where to go with it. Possible best to deprecate.
I could certainly make an opcode to implement this code here, but not sure if it is the best thing.
Note that the classic “pluck” opcode is perfectly in tune although it does not have a decay time control.
Yeah, pluck works, as far as tuning, AFAICT…however, having more sculpting options open for the user – excitation signals, decay, stiffness, etc…in an out-of-box solution, would be ideal.
I’ll dig up my “superpluck” signal flow after work later and share the orchestra. Some kind of capturing of what that does, and the core code from Faust that keeps things in-tune, would be an ideal solution.
I like the idea of deprecation of the older opcodes in favor of a modern robust opcode or opcode-family.