Cabbage Logo
Back to Cabbage Site

Seperating notes from polyphonic midi into seperate midi channels

HI!

Is this possible? What opcodes would I need to look at for this?

My intention is to make my own polyphonic pitch shifter. I want to be able to have three instances of a pitch shifter and one instance of polyphonic midi, and have the separate midi notes automatically allocated to separate midi channels.

If each voice is already assigned a unieq channel, then you can parse the MIDI notes depending on the channel. You’ll probably want to use the low level midiin opcode and then parse the tracks according to their MIDI channel number. If you are hoping to parse polyphonic MIDI on a single channel (for example a fugue), then there is no easy way to do it as far as I know.

If I’m holding down a chord, is there no way to access the midi values for each note?

Yes of course, if you don’t care about which ‘voice’ each note belongs to. You can easily split up all notes. Maybe I was overcomplicating it :rofl:

Yes, that bit doesn’t matter. The pitch shifters will all have the exact same scaling, so it doesn’t matter which one gets which note. How would one go about this?

You’ll need to write each MIDI note to an array based on note on messages, then you can read through the array/table and pass each note to a different output channel. You will also need to listen for note off messages, so you can remove the values from the array table. It might be a little tricky to implement, but it should work :thinking:

Sorry I should’ve explained myself better, the bit I don’t understand is how to actually read the midi events, like what opcode to use. Midi in csound is still a bit of a mystery to me, but I just came across midiin. I must have read past it in my initial searches, sorry! Thank you for your time.

You can use an always on instrument, and then midiin to read the incoming MIDI messages. Don’t use the --midi-key-cps=4 --midi-velocity-amp=5 command line flags as they operate differently. Although :thinking:

Maybe there is an easier way to do this. If instr 1 is triggered with each note then you can simple output each successive note to a new channel? Use a global variable to keep count of the channel, and when it gets to 16, simply start again at 1? you know what I mean? Something like this:

<Cabbage>
form caption("Untitled") size(400, 110), guiMode("queue") pluginId("def1")
keyboard bounds(0, 2, 400, 100) channel("keyboard10000")
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 -m0d --midi-key-cps=4 --midi-velocity-amp=5
</CsOptions>
<CsInstruments>
; Initialize the global variables. 
ksmps = 32
nchnls = 2
0dbfs = 1

giMidiChannel init 1

instr 1
    prints "This note goes to channel %d\n", giMidiChannel
    giMidiChannel = (giMidiChannel < 15 ? giMidiChannel+1 : 1)
endin

</CsInstruments>
<CsScore>
;causes Csound to run for about 7000 years...
f0 z
</CsScore>
</CsoundSynthesizer>

Hi I am trying to achieve the same thing myself. I have made a VST that generates polyphonic midi and I want to send notes in certain ranges to particular channels. I want to then have other tracks/devices pick up the midi on particular channels. The idea is that it is an orchestration tool. However, I cannot even seem to be able to create a plugin that sends midi data to any channel apart from the first one. To test this I created this midi router plugin:

<Cabbage>
form caption("MIDI Channel Router"), size(100,100), colour(0, 0, 0), pluginId("MidR")
rslider bounds(20,20,60,60), channel("midiChnl"), range(1,16,1,1,1), text("Channel")
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-d -+rtmidi=NULL -M0 -Q0 --midi-key=4 --midi-velocity=5 -n  
</CsOptions>
<CsInstruments>
; sr set by host
ksmps     =     16
nchnls     =     2
0dbfs    =    1
instr    1
 kstatus, kchan, kdata1, kdata2 midiin
 kChannelToSendTo cabbageGetValue "midiChnl"
 midiout kstatus, kChannelToSendTo, kdata1, kdata2
endin
</CsInstruments>
<CsScore>
i 1 0 [3600*24*7]
</CsScore>
</CsoundSynthesizer>

The plugin is supposed to just send midi data to a midi channel of your choosing. I tested if this is working by going into bitwig, putting some midi data on the first track, and adding the router plugin. I then added two tracks each with a midi filter plugin that only sends midi that is coming from the desired channel. This plugin is almost identical but with the instrument code slightly different:

kstatus, kchan, kdata1, kdata2 midiin
kChannelToFilter cabbageGetValue "midiChnl"
if changed(kstatus)==1 && kchan == kChannelToFilter then
  midiout kstatus, kchan, kdata1, kdata2
endif

The first track only outputs midi that comes in from channel 1, and the second one only outputs data from channel 2. I add a synth to pick up the filtered midi on both tracks. However, the midi router only outputs midi to channel 1 even if midiChnl is set to something else. I know the midi filter plugin works because if I remove the midi router and set the midi out in bitwig to one of the tracks with the midi filter plugin, I can verify that by changing the midi out channel that each midi filter filter’s the data as I intended.

Do you know how to make the midi router send midi to different channels?

hi
you type massign 0,47( controlling instrument) in same place where ksmps parameter etc.
then you build controlling instrument this way
instr 47
ichn midichn
iCps notnum
ivl veloc 0,1
if ichn = cabbageGetValue:i(“chn1”) then
a1,a11 subinstr 50, 1 , iCps ,ivl
endif
if ichn = cabbageGetValue:i(“chn2”) then
a1,a11 subinstr 50, 2 , iCps ,ivl
endif
if ichn = cabbageGetValue:i(“chn3”) then
a1,a11 subinstr 50, 3 , iCps ,ivl
endif
if ichn = cabbageGetValue:i(“chn4”) then
a1,a11 subinstr 50, 4 , iCps ,ivl
endif
if ichn = cabbageGetValue:i(“chn5”) then
a1,a11 subinstr 50, 5 , iCps ,ivl
endif
if ichn = cabbageGetValue:i(“chn6”) then
a1,a11 subinstr 50, 6 , iCps ,ivl
endif
if ichn = cabbageGetValue:i(“chn7”) then
a1,a11 subinstr 50, 7 , iCps ,ivl
endif
if ichn = cabbageGetValue:i(“chn8”) then
a1,a11 subinstr 50, 8 , iCps ,ivl
endif
endin
this way you runing instr 50 depending from midi channel, or you can choose to run different instruments.
also im passing midi channel,note and velocity to instr 50. as p4,p5,p6
. here i dont use returned audio, in instr 50 i made outs chanmix to another instrument for further processing.

same way you can select note range to run particular instr
if iCps > 10 && iCps < 30 then
a1,a2 subinstr blablabla
endif

Hi @Kzz thanks for the code example, but it looks like your example takes in multi-channel midi and sends it to different sub-instruments. I was asking how to take midi input from a single channel and output the same midi to a different midi channel. Does your code achieve this? For example, how would you take midi input from channel 1 and output the same midi but on channel 2? Do you know how to do this? Any ideas @rorywalsh?

If I run the following instrument:

instr	90
    prints "\n"
    kNoteIndex init 0
    kTempo chnget "tempo"
    kNoteArray[] fillarray 0, 5, 7, 12
    kMetro metro kTempo
   
    if kMetro == 1 then
        midion2 5, p4+kNoteArray[kNoteIndex], p5, 1
        kNoteIndex = kNoteIndex<3 ? kNoteIndex + 1 : 0
    endif
endin

schedule(90, 0, 1000, 60)

I can patch it to another instrument, and it receives notes on channel 5. Because I’m using midion2, I should be able to change channels in realtime, but it’s not allowing me to do that. I’ll see if I can find out why…

It was a mistake of my own doing :see_no_evil: This instrument works fine, and I can output to different channel.

<Cabbage>
form caption("MIDI Arp Out"), size(400,220), pluginId("Mout"), guiMode("queue")
keyboard bounds(0,0,400,80)
hslider bounds(14, 80, 381, 40) range(0, 10, 5, 1, 0.001), channel("tempo"), text("Tempo")
rslider bounds(14, 130, 60, 60) channel("midiChan") range(1, 16, 1, 1, 1)
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-dm0 -n -Q0 -M0 --midi-velocity-amp=4 --midi-key-cps=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
    kChannel init 0
    kChan cabbageGetValue "midiChan"    
   
    if kMetro == 1 then
        midion2 kChan, p4+kNoteArray[kNoteIndex], p5, 1
    endif
endin

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

For what it’s worth this is the simple MIDI monitor I’m testing it with.

<Cabbage>
form size(500, 300), caption("MIDI Monitor")
csoundoutput bounds(10, 10, 480, 280)
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-odac -M0
</CsOptions>
<CsInstruments>
sr = 44100
ksmps = 64
nchnls = 2

instr 1
    kstatus, kchan, kdata1, kdata2 midiin
    kChanged changed kstatus, kchan, kdata1, kdata2
    if(kChanged == 1) then
        printks "Status:%d Value:%d ChanNo:%d CtrlNo:%d\n", 0, kstatus, kdata2, kchan, kdata1
    endif
endin

</CsInstruments>  
<CsScore>
i1 0 z
</CsScore>
</CsoundSynthesizer>

Thanks for your help @rorywalsh, so I exported your code as a VST and the same thing happens in Bitwig (everything goes out channel 1 regardless). So that narrows it down to probably being a DAW problem. After doing a bit of research, It seems that my version of bitwig (version 2) does not support multi-channel midi instruments. Before I was using Bitwig’s Note Filter plugin but it is tricky to manage many of these on lots of different tracks and would be easier to have a more centralized orchestration tool. I’ll try and think of a workaround and post again if I think of one.

if you can, it would be nice to know if it works in other DAWs? I didn’t have time to test.

I can confirm that the code I originally posted does indeed work in the latest version of bitwig (v4.4.8, I just tried the demo).

1 Like

Hi Rory, I’m trying to achieve something similar to TheGuyWhoo here. I understand your example of outputting each successive note to a new channel but I’m struggling to put it into practice. Is there any way you could explain how to access the MIDI note assigned to channel1 in instrument 1, channel2 in instrument 2 etc? Thanks :slight_smile:

You can use massign to assign a channel to an instrument. When an instrument plays, you’ll then know it was triggered by a note on a particular MIDI channel. :+1: