Cabbage Logo
Back to Cabbage Site

How to output midi based on the level on input audio

Hey!

So I’m trying to detect transients.

Flags

-d -n -+rtmidi=null -m0d -Q0 --midi-key=4

Code that doesn’t do anything:

ktrig	init	1

a1 inch 1
a2 inch 2

k1 rms a1

if( k1 > 0 ) then
    midion2 1, 60, 96, ktrig
    ktrig	=	0
endif

Wondering why this doesn’t output midi?

I’m not sure, can you post the full .csd? But looking at this code I see it might result in some issues. If k1 is over 0 for any length of time (which is likely) it will trigger 1000’s of notes at a time. I would first use a metro to test MIDI output, i.ie;

if metro(1) == 1 then
   midion2 1, 60, 96, 1
endif

If that works, move on to the RMS stuff. I’ve attached below the most basic example I have of outputting MIDI. It’s a very simple arp type instrument:

<Cabbage>
form caption("MIDI Out"), size(400,120), pluginId("Mout")
keyboard bounds(0,0,400,80)
hslider bounds(14, 80, 381, 40) range(0, 10, 5, 1, 0.001), channel("tempo"), text("Tempo")
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-dm0 -n -+rtmidi=NULL -M0 -Q0 --midi-key=4 --midi-velocity=5
</CsOptions>
<CsInstruments>
;sr is set by the host
ksmps 	= 	16
nchnls 	= 	2
0dbfs	=	1

instr	1
    prints "\n"
    kNoteIndex init 0
    kTempo chnget "tempo"
    kNoteArray[] fillarray 0, 5, 7, 12
    kMetro metro kTempo

    if kMetro == 1 then
        midion2 1, p4+kNoteArray[kNoteIndex], p5, 1
        kNoteIndex = kNoteIndex<3 ? kNoteIndex + 1 : 0
    endif
endin

</CsInstruments>
<CsScore>
f0 z
</CsScore>
</CsoundSynthesizer>

Ah I see so the likely problem is it’s triggering the midi too fast. Thank you I will take a look with metro for testing purposes and post my results.

triggered.csd (2.5 KB)

Got it to kinda work! It’s surprisingly accurate. One thing that still needs to happen is I need to find a way to lessen the amount of triggers. As you can see in the commented out code I tried to do make it wait a set amount of time every time a transient is detected, but it does not work as I expect it to. Seems to simply delay triggers instead of ignoring them.

The way I am detecting transients is simply by subtracting the current point from the previous point. If the difference is bigger than the threshold, It’s a transient. Any suggestions for improving this? I’m not in a hurry, this one was just for fun and to see If how far I could get.

Two things,

  1. You should probably take a window of samples, and compare it to the previous window. Maybe fill an array/table, take the square mean, and compare it to the previous window. Rolling windows would work best. Or simply use the output from an rms opcode with a slightly delayed rms. You can use one of the k-rate delays to tune it accordingly.

  2. You can use the spectral flux approach to detecting transients, but you must first convert the signal to the frequency domain, which means a little bit of latency, depending on your FFT size. In its simplest from, this method checks for energy across a range of frequency bins. The idea being that onset transients will exhibit energy across most bins due to the chaotic movement of particles in the earliest stages of the note. It’s basically a short burst of noise. I’ve attached a simple Onset detector UDO that Victor Lazzarini shared on the Csound mailing list once. I’ve also attached the default input parameters underneath.

     opcode Onset, k, aiiiiii
     ain,iMinFreq,iMaxFreq,iAboveMed,iOffset,iMinSec,iMedLen xin
     ifftsize = 1024
     iIndexStart limit int(iMinFreq*(ifftsize/sr))*2,0,sr/2
     iIndexEnd limit int(iMaxFreq*(ifftsize/sr))*2,0,sr/2
     fsrc pvsanal ain,ifftsize,ifftsize/4,ifftsize,1
     kArr[] init ifftsize+2
     kflag pvs2array kArr, fsrc
     ksumold init 0
     kMedIndex init 0
     kMedSum init 0
     kMedian[] init iMedLen
     kMinDist init 0
     iMinDist = iMinSec*(sr/ksmps)
     kMinDist limit kMinDist-1,0,100000
    
     if changed(kflag) == 1 && kMinDist == 0 then
         ksum = 0
         kIndex = iIndexStart
    
         until kIndex = iIndexEnd do
             ksum = ksum+kArr[kIndex]
             kIndex += 2
         od
    
         kFLUX = ksum-ksumold
         ksumold = ksum
         kOnset = 0
    
         if kFLUX > (kMedSum*iAboveMed)+iOffset then
             kOnset = 1
             kMinDist = iMinDist
         endif
    
         kMedian[kMedIndex] = (kFLUX>=0?kFLUX:0)
         kMedSum = sumarray(kMedian)/iMedLen
         kMedIndex = (kMedIndex+1)%iMedLen
    
     endif
    
     xout changed(kOnset)==1&&kOnset==1?1:0
    
     endop
    
    gkMinFreq init 1000
    gkMaxFreq init 20000
    gkAboveMed init 1
    gkAmpOffset init 0.001
    gkMinSec init 0.03
    gkMedLen init 10
    

Btw, @Oeyvind’s Feature-Extract-Modulator has a very nice feature extractor. In uses a few different methods. It uses Python and other things, but the main detector Csound code can be found here.

Thank you, very helpful! I will mess around with all of this.

Hi,
The transient detector UDO that Rory pointed to (here has a simple double trigger filter. When a trig occurs, it will not trig again until a set amount of time has passed. It is crude, and as it runs realtime it will of course prioritize the first trig even if a more significant trig happens just after (while it is closed). See line 46-56 in the Csound include file at the link. It could probably be written more elegantly, but it works :-).
The project uses some Python for rewriting/updating of code, but the runtime code that you will use in production is Csound only.