# Is KSP capable of parallel processing?



## dfhagai (Jun 15, 2020)

Hi,

I'm trying to build a *multi-scripts* that grabs an incoming midi note and spread it across different kontakt instruments (based on their midi channel address).

I'm randomizing the NOTE ON and OFF note timing (for humanize purposes), but because (to my understanding) KSP processes the commands in a serial manner, the different instruments do not perform all at once.

The randomized *wait time* of the first instruments has to be over before the script moves on to the next instrument, and so there's added latency based on the sum of all the wait functions...
The result is that the first instruments play before the instruments further down the MIDI channel route...

Hope I've managed to properly explain myself... 

Is it at all possible to achieve in KSP? if so, how would you approach such a task?

Thank you for your wisdom!
HD


----------



## Levitanus (Jun 15, 2020)

Since kontakt 6 there is wait_async(<id>)
KSP reference p. 124

previously it could be done more or less clean only with listener which is not working in multi-script as I remember.


----------



## Levitanus (Jun 15, 2020)

there is also such thing as taskfunc, which can use async wait


----------



## dfhagai (Jun 15, 2020)

Levitanus said:


> Since kontakt 6 there is wait_async(<id>)
> KSP reference p. 124
> 
> previously it could be done more or less clean only with listener which is not working in multi-script as I remember.


That sounds promising!!! can't find it in the KSP reference manual though...



Levitanus said:


> there is also such thing as taskfunc, which can use async wait


Can you please point me to a refernce about this subject?

Thank you so much!


----------



## Levitanus (Jun 15, 2020)

dfhagai said:


> Can you please point me to a refernce about this subject?





Nils Liberg's Kontakt Script Editor


----------



## EvilDragon (Jun 16, 2020)

Yeah you would need to use taskfuncs for what you want.


----------



## dfhagai (Jun 16, 2020)

Thank you so much! will dig into it.
Can you please point me to the wait_async(<id>) in the manual? can't find it...


----------



## EvilDragon (Jun 16, 2020)

wait_async() won't help you for what you want to do. You need taskfuncs.

As for wait_async(), make sure you have the latest KSP reference: https://www.native-instruments.com/en/products/komplete/samplers/kontakt-6/downloads/


----------



## dfhagai (Jun 16, 2020)

Can tcm.wait work in a *Multiscript*?
For some reason I get the error "pgs_set_key_val() is not allowed in the multiscript!"


----------



## EvilDragon (Jun 16, 2020)

It can but you need to remove any PGS lines from it. PGS is basically just used for error reporting.


----------



## dfhagai (Jun 17, 2020)

Erase it after the compile?


----------



## EvilDragon (Jun 17, 2020)

Yeah.


----------



## dfhagai (Jun 17, 2020)

Thank you very much!!!


----------



## Lindon (Jun 18, 2020)

Just to be clear taskfuncs use an interrupt model - they run a process (lets call it process1), which you can interrupt - then it stores the state of your machine, and runs process2. When you want to go back the taskfunc reloads the state of the machine for process1 and continues. It is VERY VERY clever programming - thanks again (at least in part) to Big Bob. 

There are actually very few (if any) scripting languages that are capable of parallel processing, you get a clue when it includes some sort of mechanism to select the CPU (or core) that you want to run on, or you have a statement like deferUIScript() or similar. Kontakt is doing some level of multi-core running under the hood - but its deciding what and when not KSP. I know of only one sample/scripting environment that has any of these facilities - but I will leave you to guess which that is....


----------



## dfhagai (Jun 18, 2020)

Lindon said:


> Just to be clear taskfuncs use an interrupt model - they run a process (lets call it process1), which you can interrupt - then it stores the state of your machine, and runs process2. When you want to go back the taskfunc reloads the state of the machine for process1 and continues. It is VERY VERY clever programming - thanks again (at least in part) to Big Bob.
> 
> There are actually very few (if any) scripting languages that are capable of parallel processing, you get a clue when it includes some sort of mechanism to select the CPU (or core) that you want to run on, or you have a statement like deferUIScript() or similar. Kontakt is doing some level of multi-core running under the hood - but its deciding what and when not KSP. I know of only one sample/scripting environment that has any of these facilities - but I will leave you to guess which that is....



Thanks for the valuable info Lindon!

I still don't get it so let's take a simpler example:

In the following* Multi-Script*, I want the "light_on" function to be triggered x 4 times *simultaneously* for all the lights (buttons), each with its own different randomized wait value.


```
on init
  tcm.init(100)

  declare const $random_time_factor := 5000

  declare ui_button $light_1
  declare ui_button $light_2
  declare ui_button $light_3
  declare ui_button $light_4
  move_control($light_1, 1, 1)
  move_control($light_2, 2, 1)
  move_control($light_3, 3, 1)
  move_control($light_4, 4, 1)
  set_text($light_1, "1")
  set_text($light_2, "2")
  set_text($light_3, "3")
  set_text($light_4, "4")

  declare ui_slider $random_slider_1 (0, 100)
  declare ui_slider $random_slider_2 (0, 100)
  declare ui_slider $random_slider_3 (0, 100)
  declare ui_slider $random_slider_4 (0, 100)
  move_control($random_slider_1, 1, 2)
  move_control($random_slider_2, 2, 2)
  move_control($random_slider_3, 3, 2)
  move_control($random_slider_4, 4, 2)
  make_persistent($random_slider_1)
  make_persistent($random_slider_2)
  make_persistent($random_slider_3)
  make_persistent($random_slider_4)

  declare %random_time[4]
end on

taskfunc light_on (random_time,x_slider,light_button)
  random_time := (random(0, x_slider * $random_time_factor))
  tcm.wait(random_time)
  light_button := 1   
end taskfunc

on midi_in
  if ($MIDI_COMMAND = $MIDI_COMMAND_NOTE_ON)
    light_on (%random_time[0], $random_slider_1, $light_1)
    light_on (%random_time[1], $random_slider_2, $light_2)
    light_on (%random_time[2], $random_slider_3, $light_3)
    light_on (%random_time[3], $random_slider_4, $light_4)
    message(%random_time[0] & "   " & %random_time[1] & "   " & %random_time[2]  & "   "  & %random_time[3])     
  end if

  if ($MIDI_COMMAND = $MIDI_COMMAND_NOTE_OFF)
    $light_1 := 0
    $light_2 := 0
    $light_3 := 0
    $light_4 := 0
  end if
end on
```

The MIDI IN callback still waits for the first light_on function to finish, before it moves on to the second function etc...
The 2nd,3rd and 4th lights will never light up before the 1st light, etc...

How can I break the serial chain?


----------



## Lindon (Jun 18, 2020)

use a small value for wait... let all the lights have multiples of this wait time. On each wait increment a counter for each light, when they reach the specified value for a given light flash the light and reset the counter for that light..


----------



## dfhagai (Jun 18, 2020)

Unfortunatly, I don't understand how to apply what you mean...

Does anybody happen to have a code example of how to apply a parallel NOTE ON/OFF process?


----------



## Lindon (Jun 19, 2020)

dfhagai said:


> Unfortunatly, I don't understand how to apply what you mean...
> 
> Does anybody happen to have a code example of how to apply a parallel NOTE ON/OFF process?


Essentially you are trying to run an arp - time executes and things happen at different points along the time line...


----------



## EvilDragon (Jun 19, 2020)

You need to randomize the times first then calculate the deltas between them, then execute them out of order based on lowest-to-highest sorted timing differences between them. See this post from some time back:









How to make random work independently for each group?


I've tried modifying the Kontakt Factory Script Humanizer to make the 12 groups in my instrument each have their own random timing, tuning etc., but I...




www.native-instruments.com


----------



## dfhagai (Jun 21, 2020)

Thanks guys! appreciate your kind help so much!
Will do my research


----------



## Dewdman42 (May 9, 2021)

Just coming back to this thread... I am wanting to also use a thread-safe `wait` in multi-script.. Not entirely sure I'm doing this right, or the _best way_, but this is what I came up with so far for a simple test....


```
on init
    tcm.init(100)
end on

taskfunc deferNote(delay, channel, pitch, velocity)
    tcm.wait(delay*1000)
    set_midi(channel, $MIDI_COMMAND_NOTE_ON, pitch, velocity)
end taskfunc

on midi_in
    if(MIDI_COMMAND = MIDI_COMMAND_NOTE_ON)
        ignore_midi
        deferNote(500, $MIDI_CHANNEL, $MIDI_BYTE_1, $MIDI_BYTE_2) {500ms}
    end if
end on
```

So the couple points I based this on, which could be wrong or maybe there is easier way, I'd love to hear more...

`ignore_midi` has to be used to disable the incoming midi event, and then the necessary values saved somewhere so that after the wait, a new call to `set_midi` can schedule it again.


`ignore_midi` can't be inside a *taskfunc*


*taskfunc* is used for thread-safety on the pitch, channel and velocity values that will be needed to set_midi after the wait.

Just wondering if I am doing this the hard way or if there is an easier or better way. I'm brand-new to KSP and sublime too...so please forgive me if I'm missing something obvious.

The question I have is about the thread-safety of the $EVENT_ID when using wait without taskfunc.. for example, if I do this:


```
on midi_in
    if($MIDI_COMMAND = $MIDI_COMMAND_NOTE_ON)
        wait(500000)
   end if
end on
```

First the above doesn't actually wait to send the note. It seems to get sent immediately no matter the wait in the callback. Not sure why, if anyone knows the precise explanation?

So I had to use `ignore_midi` and then later `set_midi`. But if I try to do it without *taskfunc*, I think thread-safety would not be right, for example:


```
on midi_in
    if($MIDI_COMMAND = $MIDI_COMMAND_NOTE_ON)
        ignore_midi
        wait(500000)
        set_midi($MIDI_CHANNEL, $MIDI_COMMAND_NOTE_ON, $MIDI_BYTE_1, $MIDI_BYTE_2)
   end if
end on
```

I think in this above, the various $MIDI_COMMAND, etc...after coming back from wait could easily have been overwritten by other callbacks that happened while waiting. yea?

So anyway, thus the use of the taskfunc to cache the event values to use for reconstructing an event after wait.

Any comments welcome...


----------



## EvilDragon (May 10, 2021)

Dewdman42 said:


> First the above doesn't actually wait to send the note. It seems to get sent immediately no matter the wait in the callback. Not sure why, if anyone knows the precise explanation?


That's right. You basically didn't modify the original event, you just added wait to it. Which is why you need to ignore, then wait, then send your event.

This particular case is a good one to use taskfuncs for.


----------



## Dewdman42 (May 10, 2021)

Thanks ED, was I right to assume that doing it the following way would NOT be "thread-safe"? (or perhaps more properly stated, "not re-entrant")


```
on midi_in
    if($MIDI_COMMAND = $MIDI_COMMAND_NOTE_ON)
        ignore_midi
        wait(500000)
        set_midi($MIDI_CHANNEL, $MIDI_COMMAND_NOTE_ON, $MIDI_BYTE_1, $MIDI_BYTE_2)
   end if
end on
```


----------



## EvilDragon (May 11, 2021)

Actually that might even be fine... did you try it and see if you get any hung notes? (Obviously your if condition needs to query both note ons and off)


----------



## Dewdman42 (May 11, 2021)

I have tried it, but the nature of re-entrant race conditions is that it will be unpredictable so hard to test for it to know for sure if its ok. I read up a bunch of KSP stuff about how when callbacks call `wait`, then other callbacks can begin to run, but the other callbacks are, in the default case, sharing the same various variables of the KSP script...so its possible one of the other callbacks (from another note coming in), would overwrite some variable and then when the first one finished waiting it would not have the same state as before the wait.

That all makes sense and its part of why taskfuncs exist..but mostly what there is to read is about Instrument scripts, not so much the multi-script case. I wasn't sure if each callback of each note..would change the value of $MIDI_COMMAND, $MIDI_CHANNEL, etc...and based on everything I read about KSP not being re-entrant in general....I am assuming that would be the case. For it to be re-entrant it would need to have the preserved values of $MIDI_COMMAND, $MIDI_CHANNEL, etc...after coming back from wait, even if other notes triggered callbacks of their own during that time period and overwrote them in the process... am I making sense?


----------



## EvilDragon (May 11, 2021)

Yes but in the above case you're not using any extra variables, you're using the original event variables of the callback. That's why it _should_ work.


----------



## Dewdman42 (May 11, 2021)

That is I guess the open question in my mind, if you don't know for sure.. whether its variables set in the script or variables set by the engine..I don't really see any indication that KSP is keeping track of state for each callback...without SublimeKSP that is... I think I will rather assume its not unless there is some "for sure" information to the contrary. The whole TSM doc goes into great length about this...

Well... maybe I will try to see if I can make some test cases that might test the theory to see what happens.


----------



## EvilDragon (May 11, 2021)

I just tried the above code you posted but delaying both note on and note off by half a second, and playing the same key really fast, and I didn't get any hung notes. So I suppose this stands to reason that, at least when callback's default variables ($MIDI_CHANNEL, $MIDI_COMMAND, $MIDI_BYTE_1, $MIDI_BYTE_2) are concerned, their state is tracked even through wait().

Makes sense because nothing mutated them from the outside (i.e. by trying to set_event_par() on an $EVENT_ID).


----------



## Dewdman42 (May 11, 2021)

If they both have the same wait time I'm not sure that proves it. I will try to put some more thought into it later tonight if I can..

Really I think the best way is to setup some examples where you know for sure there is an incoming event that will occur while a previous event is waiting. Log the values of $MIDI_CHANNEL, $MIDI_COMMAND, etc before and after the wait in all cases and then analyze it.

I read in the TSM guide also how when there isn't a wait, then the default behavior of KSP is to process every event to completion before starting the next one. its only when an event is waiting that another event can be processed in the meantime. I suspect this is not handled internally by threads, but by queues. First event comes in, hits wait, next event in the queue starts to process and either completes or hits wait, etc. The next event is processed only when the previous event either completes the callback or hits wait. A callback that was waiting, then at some point is used as the next event in the input queue to process once it reaches its wait time..etc. 

Taking all of that into consideration a test needs to setup a specific case where we know and incoming note will have changed the values of $MIDI_COMMAND, etc. while the first one was waiting, so that when it comes back..we can see if it still has its original values preserved.

I hope you're right actually, that would make things a lot more simple! I just want to be sure before I do some time oriented KSP multi-scripts.


----------



## Dewdman42 (May 11, 2021)

But I guess, regardless of whether $MIDI_COMMAND, etc are preserved when re-queing an event callback that was waiting.....care still has to go into all the other variables in the program...with any program complexity, it probably still comes down to using taskfuncs to be sure complete state is preserved for each event callback during wait.


----------



## Dewdman42 (May 13, 2021)

So I did my own test like this:


```
on init
    declare $mon
    watch_var($mon)
end on

on midi_in

    $mon := $MIDI_BYTE_1
    wait(1000000)
    $mon := $MIDI_BYTE_1

end on
```

I watched, the variable logging in creatortools as I played different key sequences on the midi keyboard...seems to work fine, as it did in your test ED...so yea..it seems that somehow the various`$MIDI_xxx`variables are protected in a re-entrant way when each of the callbacks is re-queued after `wait`, it brings those values back into the script again.

I wonder what other system variables like that are also safe that way? I'm guessing probably all the ones listed near the end of the manual in ALL CAPS.

In any case then, the code I posted earlier is basically unnecessary...though..if any user-declared variables need to be used after wait...then it should be protected in a taskfunc....like you said already...


----------



## EvilDragon (May 14, 2021)

Yeah I wouldn't necessarily make a sweeping argument that all internal variables are inherently safe in wait() scenarios...


----------



## Dewdman42 (May 14, 2021)

Noted


----------

