# Importing MIDI sequences into Kontakt (and playing them back)



## Mike Greene

I seem to remember people talking about being able to import MIDI sequences into K5. I've been doing a little hunting and the only method I can see for doing this is the factory "Session Recorder" script. Is this indeed what everyone has been referring to? In other words, there isn't some special "Import MIDI file" button hiding somewhere, is there?

Question #2 - I want Kontakt to play back a preprogrammed sequence (drum loop) that I include with my Kontakt instrument, but I'd like to add a feature where you can assure that this drum loop plays in sync with the user's sequencer, even if you don't start the Kontakt drum loop exactly on the beat. (My plan would be that a user hits the "Go" button on my instrument slightly *before* the downbeat.)

I plan to accomplish this by using the "on listener" callback with $NI_SIGNAL_TIMER_BEAT as the relevant parameter. In this callback, there would be an "if loop is triggered" statement, in which case my scrupt would play the first note, then use "wait" to delay an 1/8 note's worth of time to play the second note. Then $NI_SIGNAL_TIMER_BEAT gets triggered again for the next beat by the on listener callback, where the sequence continues for the stuff that occurs during beat 2. And so on.

Does this methodology make sense? Or, as is so often the case, am I missing some much easier way of playing back MIDI sequences in Kontakt?


----------



## Lindon

OK so loading and playing back midi files is dealt with in the KSP manual in the section "Working with MIDI Files", where I think you will find you can just load a miidi file then detect the start event you want and have a loop that reads thru the midi file playing notes and issuing wait() statements.. abbreviated as {do stuff} below

As to starting/stopping your "play back activities" what I do (and your mileage may vary) is allow the "event" (here = your start to play the midi file) to be triggered either immediately, on the next beat or on the next bar...the user selects a menu option for this.....

so in the init

set_listener($NI_SIGNAL_TRANSP_START,1)
set_listener($NI_SIGNAL_TIMER_BEAT,1)


and in the listener itself:





Code:


{if starting straight away= true}
if ($NI_SIGNAL_TYPE = $NI_SIGNAL_TRANSP_START)
       {do stuff..}      
end if

{if starting on next beat...}
if ($NI_SIGNAL_TYPE = $NI_SIGNAL_TIMER_BEAT)
    {do stuff}
end if

{if starting on next bar start...}
if ($NI_SIGNAL_TYPE = $NI_SIGNAL_TIMER_BEAT)
     $bar_position := $NI_SONG_POSITION/960
     if ($bar_position mod $sig_num = 0)
         {do stuff..}
      end if           
end if


----------



## ScoringFilm

Mike Greene @ 6/3/2013 said:


> In other words, there isn't some special "Import MIDI file" button hiding somewhere, is there?



Peter O'Regan created a tool to convert midi to KSP:

http://www.peteroregan.com/scripts.html

Justin


----------



## Mike Greene

D-oh! I can't believe I didn't see the MIDI section of the KSP manual. I must be going blind, because believe me, I looked! :oops: 

Thanks for your example, Lindon. An on listener callback along those lines is basically what I was thinking, so it's good to know I'm headed down the right path. What I didn't know was importing MIDI files could be done as a command. This should be interesting as I decide whether I really want to import MIDI files, or just have stuff preloaded as arrays. (My MIDI sequences are pretty basic.)

In any even, now I know the procedure, so thank you! 8) 

Justin, thanks for that link. I remember that from some time ago and had forgotten. It's PC-only, which is a problem for me, but I might fire up my old HP laptop and give it a whirl. I wonder if it's basically the same as the factory MIDI recorder script.


----------



## MacQ

You might be better off with the arrays option, Mike. It's probably easier, and K5.1 introduces a way to load .NKA files from the browser they introduced with Studio Drummer. Previously you could SEE the files, you just couldn't do anything with them.


----------



## Sasje

I fiddled a bit around with it, and thrown together various MIDI scripts and came up with this:

Download: http://www.mediafire.com/download/8sxu5 ... player.rar (requires K5+)

it _requires_ a midi file folder at: C:/midi/ otherwise the midi menu will not populate with the midi files to choose from. If you don't have windows, you'll need to edit the script to set the correct path. 
As an added bonus it also has a pianoroll viewer. It's a bit hacked together, so it's not a stable player. I got pretty bored with it and abandoned it. o

To load a midi file, double click a midi file in the list, then click "play".


----------



## lydianchromaticconcept

i'm confused about what the command is to actually play the midi file!? i can load it fine. 

to start on transport start:





Code:


if ($NI_SIGNAL_TYPE = $NI_SIGNAL_TRANSP_START)
          THEN WHAT??????


----------



## Lindon

OK, so if you read my initial response above it points you at the correct section of the manual. Once the MIDI file is loaded you can then move thru the file with a set of commands to go to each "event" in the file, find out what type of thing it is and respond as you need. 

There is no MIDI player in Kontakt, there is however a set of commands in KSP to load a MIDI file, read/inspect each piece of data in it(including finding out when -in relative terms- these events are "scheduled"), and process them.


----------



## lydianchromaticconcept

Hi Lindon,
Thanks for your reply. So, people are not using this "load midi file feature" to play midi files???!!! What are they using it for? I'm very perplexed by this.


----------



## Lindon

Yes they are using load MIDI file, for exactly the purpose I outlined, you have to load the file then inspect it.


----------



## olmerk

Sasje said:


> I fiddled a bit around with it, and thrown together various MIDI scripts and came up with this:
> 
> Download: http://www.mediafire.com/download/8sxu5 ... player.rar (requires K5+)
> 
> it _requires_ a midi file folder at: C:/midi/ otherwise the midi menu will not populate with the midi files to choose from. If you don't have windows, you'll need to edit the script to set the correct path.
> As an added bonus it also has a pianoroll viewer. It's a bit hacked together, so it's not a stable player. I got pretty bored with it and abandoned it. o
> 
> To load a midi file, double click a midi file in the list, then click "play".



Why in this script there is a delay between the moment I hit "Play_MIDI" button and the first note playback? 
Seems like on listener call back checks in equal time intervals if play_midi:=1 and if it is, then it triggers play_note
And if one clicks the button between those checks there is a delay. How to get rid of it?


----------



## EvilDragon

You can't, listener goes at a predefined rate, and if you clicked a button somewhere before the next listener tick, you're gonna have a slight delay until it starts.


----------



## olmerk

EvilDragon said:


> You can't, listener goes at a predefined rate, and if you clicked a button somewhere before the next listener tick, you're gonna have a slight delay until it starts.



So If I want to have a midi player being triggered by a pressed key, I need to move on listener piece of code to on note call back (with some alterations of course), right?

But as I understand correctly, it's exactly the listener itself provides the rhythmical playback of a midi events being read from a midi file...


----------



## EvilDragon

Correct, it's the listener that goes through MIDI ticks. You cannot work around it. You can do it without listener callback and just using waits, but this is imprecise and can drift off in timing pretty easily (wait() in KSP is not absolutely precise down to the microsecond).


----------



## olmerk

Can I make listener "to listen" like every ms or so? Or will it ruin the playback of a midi-file at a certain tempo?


----------



## EvilDragon

You can, but it's not natural for MIDI, which is based in regular tick intervals (which also ties in to BPM changes). If you run listener in milliseconds, then it's tempo-invariant and you need to handle BPM changes yourself, with listener in ticks, it does this automagically...


Besides, is a delay of less than one 1/32T (fastest listener tick rate currently) really that important? You can always input quantize if you want your playback to end up spot on the grid...


----------



## olmerk

EvilDragon said:


> Besides, is a delay of less than one 1/32T (fastest listener tick rate currently) really that important? You can always input quantize if you want your playback to end up spot on the grid...



Depending on tempo, 1/32T can a noticeable delay....

By the way, as I've plunged in this script I still can't get where from it takes information about absolute time intervals between, say, beats (quarter notes)? If I'm in standalone or transport is not running? 

I set the highest resolution with set_listener($NI_SIGNAL_TIMER_BEAT, 24) to decrease the delay and got a super fast playing of midi file.


----------



## EvilDragon

http://vi-control.net/community/thr...g-the-new-k5-ksp-features.23228/#post-3596492

Here's a different take on MIDI player, you might understand it better since it has more comments.


Changing the listener tick rate shouldn't make the MIDI playback faster. That tells me the script you're using has something weird going on (EDIT: got it - $MIDI_TEMPO should be 960 / $MIDI_BARS, and $MIDI_BARS should be 24 to get the best listener resolution - but in any case, sonaht's script is better, just needs listener being ran at 24 ticks per quarter as well)...


----------



## olmerk

I’ve checked already 3 versions of MIDI players and their on listener block is pretty much the same from version to version


Below is the excerpts from NI Studio Drummer MIDI player:



Code:


on init
declare const $LISTENER_RESOL := 24 
declare const $LISTENER_BEAT_LENGTH_TICKS := 960 / ($LISTENER_RESOL)
set_listener($NI_SIGNAL_TIMER_BEAT, $LISTENER_RESOL)
end on

on listener
    $cur_listener_id := $NI_CALLBACK_ID
    if ($play = 1)

        if ($NI_TRANSPORT_RUNNING = 0)
            $cur_song_pos := $cur_song_pos + $LISTENER_BEAT_LENGTH_TICKS
        else
            $cur_song_pos := $NI_SONG_POSITION
        end if
 
        $beat_start_time := $ENGINE_UPTIME
        mf_get_next_at(0, $cur_song_pos mod $mf_length)
        $start_of_beat := $cur_song_pos mod $mf_length
        $end_of_beat := $cur_song_pos mod $mf_length + $LISTENER_BEAT_LENGTH_TICKS

        while (mf_get_pos() < $end_of_beat and $play = 1 and $cur_listener_id = $NI_CALLBACK_ID)

            $diff := ticks_to_ms(mf_get_pos() - $start_of_beat) - ($ENGINE_UPTIME - $beat_start_time) * 1000
            if ($diff > 0)
                wait($diff)
            end if

 
            if (mf_get_command() = $MIDI_COMMAND_NOTE_ON and mf_get_byte_two() # 0)
                ...play_note...
            else
                if (mf_get_command() = $MIDI_COMMAND_NOTE_OFF or (mf_get_command() = $MIDI_COMMAND_NOTE_ON and mf_get_byte_two() = 0))
                    ...note_off...
                end if
            end if

            mf_get_next(0)

            if (mf_get_command() = 0)
                if ($end_of_beat > $mf_length)
                    mf_get_first(0)
                    $end_of_beat := $end_of_beat mod $mf_length
                    $start_of_beat := $end_of_beat - $LISTENER_BEAT_LENGTH_TICKS
                else
                    exit
                end if
            end if
        end while
    end if
    $play_flag := 0
end on


Even with the highest resolution $LISTENER_RESOL := 24 there is a significant delay. Moreover the delay stays the same even when I change $LISTENER_RESOL, I think because it appears in $LISTENER_BEAT_LENGTH_TICKS := 960 / ($LISTENER_RESOL)

When I put simply $LISTENER_BEAT_LENGTH_TICKS := 960 the midi playback speeds up tremendously, though “reaction time”, i.e. the delay, gets almost unnoticeable.

So I don’t know how to fix it. And it’s still not clear for me where KSP takes info about absolute time measure for a beat when playing back a midi in standalone version or when transport is not running? I supposed that wait($diff) in the code above was responsible for that. But when I commented out that block nothing happened in the script action. So I still can’t get what this wait($diff) is necessary for?

Would really appreciate a clarification...


----------



## EvilDragon

You cannot fix it (at least AFAIK) - it's inherent to the design of listener callback. It is what it is, that delay is something you can't go without. However I don't think that 1/32T is anywhere near "significant", since that's the worst case scenario if you hit a key RIGHT after a listener tick has happened... As mentioned, if you need your MIDI playback to start right smack on the grid, you need to use input quantize in a script slot before your MIDI playback engine. Sure you'll get a delay, but it will be on grid. Not sure if this is your intention, though.

When playing without transport, it just increments $cur_song_pos variable on every listener tick (listener runs even if transport is off).

As for wait($diff), I think this is for MIDI events that are not exactly happening on the listener tick grid. As you know, Kontakt runs at a maximum of 24 ticks per quarter note (which is 96 ticks per 4/4 bar), but most often found, MIDI files have 960 ticks per bar (so, 10 times more), this is what that part of code caters to, I think.


----------



## olmerk

EvilDragon said:


> However I don't think that 1/32T is anywhere near "significant", since that's the worst case scenario if you hit a key RIGHT after a listener tick has happened...



I measured the delay using ENGINE_UPTIME and it is about 0,5sec between I hit play button and the first midi event plays. That's too much I think and I can't decrease it...

Could you please tell me what is a tick timewise? I understand what is setting the listener to ms (with $NI_SIGNAL_TIMER_MS), but setting it to ticks is somewhat still obscure for me. A MIDI file is just a list of events, and if Kontakt disregards tempo information, thus mf_get_next() command, being looped, can run trough all midi events in a split of a second. But it doesn't. Obviously that ticks have some time interval.


----------



## EvilDragon

Tick is a MIDI time unit lower than a beat (quarter note, most often). So when you run your listener in tick mode at maximum of 24 ticks per quarter note, the interval is (1/BPM/24) milliseconds. The benefit of tick mode is that it automatically syncs to incoming tempo information, which ms mode doesn't do, since it's time linear.

What's happening in your note/play button callback? If you get half a second delay, I don't think it's listener callback's fault.


----------



## olmerk

EvilDragon said:


> What's happening in your note/play button callback? If you get half a second delay, I don't think it's listener callback's fault.



Yes, I found the mistake by making more precise measurements of the delay inside script's blocks and discovering I have a delay of about 20-30ms when just pressing play button and 500ms when pressing a key. The reason was missed block of pgs_set_key_vals used in NI Studio Drummer script exactly for grid snapping. That caused a delay as snapping didn't work properly on_note.

So 20-30ms is a justified delay for 24 ticks resolution?



EvilDragon said:


> the interval is (1/BPM/24) milliseconds



That's a valuable input, thanks! But where from Kontakt retrieve info about BMP? If it runs in DAW - does it use a current tempo, set in the host (even if the transport is not running)? And what if Kontakt runs as a standalone? Does it use then some built-in standard BMP?


----------



## EvilDragon

It uses tempo of the DAW yes, even when transport is not running. In standalone, it uses tempo as defined in Master panel (unless you sync Kontakt to external tempo via MIDI controller).


----------



## olmerk

Working with the midi-player I’ve noticed that it gets a midi file length by checking the last midi event. Generally like this:

mf_get_last($track_idx)
$mf_length := mf_get_pos()

But the last event is not always the end of a midi file. What if I want to have a rest (silence) between the release of the last note and end of the file? Check my picture. It visually demonstrates that the script gets mf_length incorrectly in that case. And as soon as it reads the last midi event it starts midi file playback right from the start, disregarding the last rest.

Is there any work-around for that?


----------



## EvilDragon

You can round mf_length to the next bigger number of ticks in that case.


----------



## olmerk

The problem is - no one knows what kind of odd measures user can put in the midi file. So which duration should script round mf_length to?


----------



## EvilDragon

Not much you can do then. There is no way to detect time signature from the imported MIDI file.


----------



## Lindon

So what I did was let the user decide the length of playback, so:
1. User sets playback length to = (say) 8 bars
2. import midi file
3. play midi events: if next event > play-back length then go to start of midi file
if last event in midi file AND less than 8 bars wait(difference between 8 bars and last time)


----------



## EvilDragon

That makes sense.


----------



## Flaneurette

The MIDI player I wrote, was a rather naïve hack. It proposed a practical way, or rather a proof of concept, that one could play midi files and display the notes inside Kontakt. It did not pretend anything beyond that. Perhaps, Kontakt is not the proper environment to work with MIDI. It certainly would not be my first choice. Is there benefit in doing so?


----------



## Lindon

Flaneurette said:


> The MIDI player I wrote, was a rather naïve hack. It proposed a practical way, or rather a proof of concept, that one could play midi files and display the notes inside Kontakt. It did not pretend anything beyond that. Perhaps, Kontakt is not the proper environment to work with MIDI. It certainly would not be my first choice. Is there benefit in doing so?


its simple, quick, easy and reliable. Once you have your concepts sorted out.


----------



## topaz

Is there still no way to have a midi loop in a script to trigger a sliced loop from inside kontakt on a given midi note ??


----------



## Lindon

yes of course, as long as you script it.


----------



## topaz

I can't (lord knows I have tried) 

I want to be able to have ie: this midi file on slot 1 to trigger a sliced drum loop (mapped to c3 up) 
Even a simple script to get me going would help, hit a brick wall on this one :-(


----------



## Lindon

.. theres an example right here in this thread, or you could pay someone to do it for you.


----------



## olmerk

Getting back to the topic) Every version of MIDI player I've seen uses ticks_to_ms(), which, according to the documentation "will not return correct values if specified number of ticks at the current tempo exceeds 2147483648 microseconds, or roughly 35 minutes and 47 seconds"

So does it mean the standard MIDI player will start malfunctioning after 35 minutes? What is a work-around? set listener to $NI_SIGNAL_TIMER_MS rather than $NI_SIGNAL_TIMER_BEAT?


----------

