Cabbage Logo
Back to Cabbage Site

New MIDI file opcodes

(MacOS only for now…) Now works on Windows too - and opcodes names have been updated…

The ability to read MIDI files within an instrument has always been missing in Csound, until now :slight_smile: Last week I pushed two new Cabbage opcodes to the code base. You can try them out with the latest dev build of Cabbage. They are relatively untested, so be gentle! I will add them to the docs once they have been properly tested.


Opcodes described below.

cabbageMidiFileInfo Opcode

This opcode takes a path to a MIDI file and prints some useful debug information about the file, including the file type, the last time stamp, the number of tracks, a list and all time changes and a list of all time signature changes, listed by timestamps in seconds

The channels can hold strings or numbers, but only numeric channels work with the optional threshold arguments.

Added in Cabbage v2.9.5

Syntax

cabbageMidiFileInfo SMidifile

Initialization

  • SMidifile – midi filename, must be an absolute path

cabbageMidiFileReader Opcode

This opcode outputs an array of MIDI events in the form of numbers (ala midiin, alongside a trigger signal to indicate an event has been received. The number of events received in each k-cycle is also output.

Added in Cabbage v2.9.5

Syntax

kStatus[], kChan[], kNote[], kVel[], kNumEvents, kTrig cabbageMidiFileReader SMidifile, iTrackNum, kPlay, kSpeedFactor[, iSkipTime]

Initialization

  • SMidifile – midi filename, must be an absolute path
  • iTrackNum – the track number you wish to read. (Use the cabbageMidiFileInfo opcode to determine the number of tracks if you are sure)
  • iSkipTime(optional) – sets the initial skip time in second, defaults to 0.

Performance

  • kPlay – set to 1 start playback, 0 to pause.
  • kSpeedFactor – used to increase/decrease playback speed. This basically acts as a timestamp multiplier. Larger values will slow down playback, while smaller values will increase playback speed.

Example

<Cabbage>
form caption("Midi playback") size(400, 100), colour(40), guiMode("queue") pluginId("def1")
rslider bounds(114, 8, 60, 60) channel("transpose") range(-24, 24, 0, 1, 1), text("Transpose")
checkbox bounds(8, 8, 100, 30) channel("play"), text("Play"), 
rslider bounds(178, 8, 60, 60) channel("speed") range(0.5, 2, 1, 1, 0.001), text("Speed")
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -m0d
</CsOptions>
<CsInstruments>010 
; Initialize the global variables. 
ksmps = 128
nchnls = 2
0dbfs = 1

instr 1
    SCsdPath chnget "CSD_PATH"

    SMidifile init sprintf("%s/Prelude1.mid", SCsdPath)
    cabbageMidiFileInfo SMidifile
    kEventIndex = 0
    kNoteIndex init 0
    kStatus[], kChan[], kNote[], kVel[], kNumEvents, kTrig cabbageMidiFileReader SMidifile, 1, cabbageGetValue("play"), cabbageGetValue("speed"), 0
    knotelength    init    0
    knoteontime    init    0


    if kTrig == 1 then
        while kEventIndex < kNumEvents do
            if (kStatus[kEventIndex] == 128) then            //note off
                turnoff2 2, 1, 1
            elseif (kStatus[kEventIndex] == 144) then        //note on 
                event "i", 2, 0, 10, kNote[kEventIndex]+cabbageGetValue:k("transpose"), kVel[kEventIndex]/127       
            endif
            kEventIndex += 1
        od
    endif
endin

instr 2
    kEnv madsr 0.1, .2, .6, .1 
    a1 oscili kEnv*p5, cpsmidinn(p4), 1
    outs a1*.01, a1*.01
endin


</CsInstruments>
<CsScore>
f1 0 4096 10 1 0 .25 0 .17
;causes Csound to run for about 7000 years...
f0 z
;starts instrument 1 and runs it for a week
i1 0 [60*60*24*7] 
;i3 0 10
</CsScore>
</CsoundSynthesizer>

Nice job, Rory, it works for me.

There’s a small typo in your example: “kStatus[]” should be “kstatus[]

kSpeedFactor seems a bit odd though. I’m not sure what’s going on, I’ll try it with a simpler midi file…

Thank Iain, example updated :+1: Yeah, I’m not sure how to call refer to the speed control. It basically just multiplies each time stamp by a given value. So the following time sequence: 1, 2, 3, 4, 5, 6, becomes 2, 4, 6, 8, 10, 12 when the speed value is 2. I’m open to suggestions about how to refer to it, maybe note spacing is a little more intuitive? I have to say, after all these years of using Csound it feels very strange to be able to load MIDI files :rofl:

Well there has always been the ability to play MIDI files using the -F command line flag, but this is limited to simple playback of one file at a time. What you are doing here is what I’ve always thought would be more useful - MIDI files as assets within the orchestra. I wrote an example before to convert MIDI files to Csound score but this is a much better solution.

I never found this option to be that useful. Having no control over the playback, or tempo, and anything like that was always problematic for me.

Are you planning any options such as looping? I could see that as being a good option to start with, that way you could load up several patterns and use them as loops. If you could control the tempo of several different midi files/patterns playing, you could do a lot of Reich-ish phase playing. Would multiple instances be possible? Would they all share the same tempo?

Also, have you thought about making it a general midi player, and allow someone to build a sequence in real time? Version 2?

The option to loop hadn’t even come into my head. Good idea. Easily added I think. And you can already give each opcode its own independent tempo.