# Modifying frequency of listener events?



## neilbaldwin (Aug 2, 2013)

First off, hello! Recently registered though I've been dabbling with KSP for quite a while now for my own uses and have been programming audio stuff for many years.

Something I've been trying to figure out recently:

Is it possible to modify the interrupt timer of listener events outside of the 'on init' callback?

I thought that an easy way to get, say, a steady 16th clock that can constantly change with the current host tempo would be to do something like:

on init
set_listener ($NI_SIGNAL_TIMER_MS,$DURATION_SIXTEENTH)
end on

on listner
(code to determine listener signal)
set_listener ($NI_SIGNAL_TIMER_MS,$DURATION_SIXTEENTH)
end on

so that every time the listener fires you rewrite the timer value to reflect the current duration of a sixteenth note. This would then respond accurately to tempo changes.

I know you can't use 'set listener' outside of init but I wondered if anyone figured a backdoor to this value - it must be written/stored in the KSP engine somewhere.

Neil


----------



## Luca Capozzi (Aug 2, 2013)

you should use change_listener_par to modify listener parameters. this command could be used in every callback or functions.


----------



## Big Bob (Aug 2, 2013)

:? Why can't you just use:

```
on init
  set_listener(NI_SIGNAL_TIMER_BEAT,4)
end on
```

Doesn't this do what you want directly? I think this triggers the listener callback every 1/16th note and should also follow the host tempo.

Rejoice,

Bob


----------



## neilbaldwin (Aug 2, 2013)

@Bob: you're kidding me? Does the 16 really divide the beat by 16? I always thought that you could only set that parameter to 0 (off) or 1 (on). I'm not sure where I picked that up from though 

Assuming that does work (not in a position to test it right now), it still would be extremely handy to be able to modify the beat division. That way if you had a simple step sequencer you'd be able to have the user select the beat division and it be immediately reflected in playback.

@axiom: doh! Never spotted that before!


----------



## Mike Greene (Aug 2, 2013)

Wow! This thread is gonna save me a ton of work, because I had no idea you could change listener parameters, nor did know you could set "Beat" to anything but 1. I was just about to start writing this complicated modification to my listener callback to accommodate half time or double time options, and now it looks like it's going to be much, much easier. Thank you very much Axiom and Big Bob! (And you too, Neil, for asking!) Seriously, I am thrilled with this new knowledge. 8) 

One question about sync, though. If I initially have ($NI_SIGNAL_TIMER_BEAT,1) (every quarter note, in sync with my sequencer) and then change it to ($NI_SIGNAL_TIMER_BEAT,2) then I assume this triggers the listener callback on 1/8 notes, right in sync with my sequencer.

But if I then switch *back* to ($NI_SIGNAL_TIMER_BEAT,1) is there a risk that "on listener" starts triggering on the _upbeats?_


----------



## Hans Adamson (Aug 2, 2013)

Mike Greene @ Fri Aug 02 said:


> Wow! This thread is gonna save me a ton of work, because I had no idea you could change listener parameters, nor did know you could set "Beat" to anything but 1. I was just about to start writing this complicated modification to my listener callback to accommodate half time or double time options, and now it looks like it's going to be much, much easier. Thank you very much Axiom and Big Bob! (And you too, Neil, for asking!) Seriously, I am thrilled with this new knowledge. 8)
> 
> One question about sync, though. If I initially have ($NI_SIGNAL_TIMER_BEAT,1) (every quarter note, in sync with my sequencer) and then change it to ($NI_SIGNAL_TIMER_BEAT,2) then I assume this triggers the listener callback on 1/8 notes, right in sync with my sequencer.
> 
> But if I then switch *back* to ($NI_SIGNAL_TIMER_BEAT,1) is there a risk that "on listener" starts triggering on the _upbeats?_


This sounds like something that could be useful. What is the "listener" - if you don't mind my ignorance... ~o)


----------



## neilbaldwin (Aug 2, 2013)

Mike Greene @ Fri Aug 02 said:


> Wow! This thread is gonna save me a ton of work, because I had no idea you could change listener parameters, nor did know you could set "Beat" to anything but 1. I was just about to start writing this complicated modification to my listener callback to accommodate half time or double time options, and now it looks like it's going to be much, much easier. Thank you very much Axiom and Big Bob! (And you too, Neil, for asking!) Seriously, I am thrilled with this new knowledge. 8)
> 
> One question about sync, though. If I initially have ($NI_SIGNAL_TIMER_BEAT,1) (every quarter note, in sync with my sequencer) and then change it to ($NI_SIGNAL_TIMER_BEAT,2) then I assume this triggers the listener callback on 1/8 notes, right in sync with my sequencer.
> 
> But if I then switch *back* to ($NI_SIGNAL_TIMER_BEAT,1) is there a risk that "on listener" starts triggering on the _upbeats?_



That's where I was: writing complex callback to handle step timing/division and I thought "Surely there's a better way!" 

I guess with the trigger-on-upbeat potential issue you could always maintain your own beat counter and then only change the division on odd (or even) beats?


----------



## Big Bob (Aug 2, 2013)

> But if I then switch *back* to ($NI_SIGNAL_TIMER_BEAT,1) is there a risk that "on listener" starts triggering on the upbeats?



Hi Mike,

I doubt very much that NI would come from a higher resolution to a lower one and sync to anything but the downbeat. However, I have not personally tested this so you should probably try it to see what happens. But, I vote for always locking to the downbeat :lol: 

Rejoice,

Bob


----------



## neilbaldwin (Aug 2, 2013)

Did a quick test and I think Bob is right, changing the beat division seems to maintain the up/down beat.

This was the code which for some reason doesn't respond to tempo changes unless you press one of the two buttons. Which is odd because my original test (just simply firing a TIMER_MS listener and doing a change_listener_par) worked and reacted immediately to tempo changing.

[pre]
on init
declare $currentDivision
$currentDivision := $DURATION_EIGHTH
set_listener($NI_SIGNAL_TIMER_MS,$currentDivision)
declare ui_button $button8
declare ui_button $button16
declare $beatCount := 0
end on

on ui_control($button8)
$currentDivision := $DURATION_EIGHTH
end on

on ui_control($button16)
$currentDivision := $DURATION_SIXTEENTH
end on

on listener
select ($NI_SIGNAL_TYPE)
case $NI_SIGNAL_TIMER_MS
change_listener_par($NI_SIGNAL_TIMER_MS,$currentDivision)
$beatCount := 1-$beatCount
if ($beatCount=0)
play_note(48,40,0,-1)
else
play_note(50,40,0,-1)
end if
end select
end on
[/pre]


----------



## Big Bob (Aug 2, 2013)

Hi Neil,

I think the reason for that is that you need to update current_division (to the current tempo-dependent duration value) in the listener callback also which I don't see you doing. You only seem to update current_division in the button callbacks.

However, I also just realized that I should have used 4 in my post instead of 16 to get 1/16th note callback triggers. I was just about to run a test as to the downbeat issue but then noticed that the max value for the parameter seems to be 4. But, then that is what you would want it to be for 1/16 notes anyway but it looks like finer resolutions are not possible.

Rejoice,

Bob

BTW I edited my initial post to fix the 4 vs 16 value. :oops:


----------



## Big Bob (Aug 2, 2013)

I started to look into the downbeat issue but, it turns out that I don't have a very convenient way to verify my assumption. Until very recently I have always used Kontakt in standalone mode so I'm kind of a noob with using it as a plug (in a host).

So, maybe someone else should run these tests because I'm sure someone else could do this faster. But, I am very interested in hearing what testing might reveal regarding this downbeat issue.

Rejoice,

Bob

*Whoops!* I just noticed that apparently NeilBaldwin already ran such a test and concluded that the downbeat *is* preserved. Thanks for doing that Neil o-[][]-o 

I also just noticed that I didn't even try to answer your question about the button callbacks so I'll go back and try to do that now. Boy, am I missing the stuff today! I must have been smoking something I shouldn't have :lol:


----------



## Mike Greene (Aug 2, 2013)

I can confirm Neil's results that changing the beat division seems to maintain the up/down beat. Whew!


----------



## Big Bob (Aug 2, 2013)

Thanks Mike,

I was pretty confident that it would turn out that way, but with NI, one never knows. 

But now that two titans have confirmed it, I'll be even more confident the next time :lol: 

Rejoice,

Bob


----------



## Mike Greene (Aug 2, 2013)

Hans Adamson @ Fri Aug 02 said:


> This sounds like something that could be useful. What is the "listener" - if you don't mind my ignorance... ~o)


The "on listener" callback is something that can be triggered on every beat, or some set interval of time. This is what I used to make the pattern player work in RealiBanjo, for example. http://www.youtube.com/watch?v=FPYCxFNBzFk It works basically like this, although this is a gross oversimplication:


```
on listener
    if (conditions are right)
        play some note
        wait (sixteenth note)
        play another note
        wait (sixteenth note)
        play another note
        wait (sixteenth note)
        play another note
    end if
end on
```

The above code would play a pattern of 1/16 notes. (4 per beat.) Neil's example code above is more accurate, of course. (My example is just to explain the idea.)

The KSP manual explains it a little bit on page 6, 97 and 99.


----------



## neilbaldwin (Aug 3, 2013)

Related to this, are the transport listener events quite flaky?

I'm using Kontakt 5.01 as a plugin in Ableton Live and am getting very inconsistent results.

I set up a bit of code to trap the events and they don't always trigger (presuming of course that Kontakt is getting the signal through from the Ableton Live transport properly but there's no real way of knowing that apart from assuming it does)

For example, sometimes my $NI_SIGNAL_TRANSP_START event seems to constantly fire (after pressing PLAY) like Kontakt has got stuck in a loop. It doesn't happen all of the time and the code is nothing more than a 'on listener' trap that sends a message to the Kontakt window.


----------



## neilbaldwin (Aug 3, 2013)

Hmmm, possibly something else in my script upsetting it as I made this very simple test script:

[pre]
on init
declare ui_label $debug(4, 1) 
declare $playCount := 0
declare $stopCount := 0
set_listener($NI_SIGNAL_TRANSP_START,1)
set_listener($NI_SIGNAL_TRANSP_STOP,1)
set_listener($NI_SIGNAL_TIMER_BEAT,1)
end on

on listener
select ($NI_SIGNAL_TYPE)
case $NI_SIGNAL_TRANSP_STOP
inc($stopCount)
exit
case $NI_SIGNAL_TRANSP_START
inc($playCount)
exit
case $NI_SIGNAL_TIMER_BEAT
set_text($debug,"Play: " & $playCOunt & " Stop: " & $stopCount)
exit
end select
end on
[/pre]

and it behaves as you'd expect. Though I'm a bit disappointed that Kontakt decides for you whether you want to receive the START or STOP command (if your DAW is stopped, pressing STOP won't trigger the TRANSP_STOP listener event)


----------



## Raptor4 (Aug 3, 2013)

Hi guys,
I had some time to create a complex prototype which can be used "musically", i.e it responds to realtime BPM host changes. I have added a "running" button which is synced to the host Start/Stop automatically. So copy the code, paste and run your host app - this will switch on the "running" automatically. When you stop the Host sequencer the generator will stop as well.
Click any of the Note duration buttons to test the generator (the buttons are auto "OFF" switchable). 
*Edit:* _I just updated the code with an UI menu where you can set the note durations from - the default is 1/16 (16th note)._


```
{***********************************************
LISTENER BEAT GENERATOR v1.0
Author: www.audiogrocery.com
Written by: Ivan Kovachev 
Modified: August 3, 2013
*************************************************}
on init
  make_perfview
  set_script_title("Listener Beat Generator")
  message("")
  declare $beatCount := 0
  declare $currentDivision
  $currentDivision := 1
  set_listener($NI_SIGNAL_TRANSP_STOP,1)
  set_listener($NI_SIGNAL_TRANSP_START,1)
  set_listener($NI_SIGNAL_TIMER_BEAT,$currentDivision)
  declare ui_button $Running
  declare ui_button $button4
  $button4 := 1
  declare ui_button $button8
  declare ui_button $button16
  declare ui_menu $n_dur_menu
  add_menu_item($n_dur_menu,"1/1",0)
  add_menu_item($n_dur_menu,"1/4",1)
  add_menu_item($n_dur_menu,"1/8",2)
  add_menu_item($n_dur_menu,"1/16",3)
  add_menu_item($n_dur_menu,"1/6",4)
  add_menu_item($n_dur_menu,"1/12",5)
  add_menu_item($n_dur_menu,"1/24",6)
  $n_dur_menu:=3
  declare %n_dur[7]
  end on

function time_dur
  %n_dur[0]:=$DURATION_BAR
  %n_dur[1]:=$DURATION_QUARTER
  %n_dur[2]:=$DURATION_EIGHTH
  %n_dur[3]:=$DURATION_SIXTEENTH
  %n_dur[4]:=$DURATION_QUARTER_TRIPLET
  %n_dur[5]:=$DURATION_EIGHTH_TRIPLET
  %n_dur[6]:=$DURATION_SIXTEENTH_TRIPLET     
end function

on ui_control($button4)
  $currentDivision := 1
  $button8 := 0
  $button16 := 0
end on

on ui_control($button8)
  $currentDivision := 2
  $button4 := 0
  $button16 := 0
end on

on ui_control($button16)
  $currentDivision := 4
  $button4 := 0
  $button8 := 0
end on

on listener
  select ($NI_SIGNAL_TYPE)
    case $NI_SIGNAL_TRANSP_STOP
      reset_ksp_timer
      $Running := 0
    case $NI_SIGNAL_TRANSP_START
      reset_ksp_timer
      $Running := 1
    case $NI_SIGNAL_TIMER_BEAT
      if ($Running=1)
        change_listener_par($NI_SIGNAL_TIMER_BEAT,$currentDivision)
        $beatCount := 1-$beatCount
        call time_dur
        if ($beatCount=0)
          play_note(48,40,0,%n_dur[$n_dur_menu])
        else
          play_note(50,40,0,%n_dur[$n_dur_menu])
        end if
      end if
  end select
end on
```

Regards
_____________________
www.audiogrocery.com


----------



## neilbaldwin (Aug 3, 2013)

I realise now what the issue is with the listener event and the way it's shown to be trapped in the KSP Guide.

The problem is, using a SELECT...CASE switch is not the best way of doing it.

This is because the $NI_SIGNAL_ flags are actually bit flags that are set in the $NI_SIGNAL_TYPE variable when a listener is triggered.

This means that you can have multiple listener events occur simultaneously. This then means that trapping them with a SELECT switch is not so straight forward.

The reason for this is that if you have more than one listener trigger, the value of $NI_SIGNAL_TYPE is unexpected. Let me illustrate:

The value of $NI_SIGNAL_TRANSP_START is 1
The value of $NI_SIGNAL_TIMER_BEAT is 4

KSP engine ORs these values together and so if those two events occur simultaneously, the actual value of $NI_SIGNAL_TYPE will be 5

If your SELECT switch does this:


```
on listener
    select ($NI_SIGNAL_TYPE)
        case $NI_SIGNAL_TRANSP_STOP
            
        case $NI_SIGNAL_TRANSP_START
            
        case $NI_SIGNAL_TIMER_BEAT
            
    end select
end on
```

All of the CASEs will fail and your event will not be processed.

The best way to handle the ON LISTENER event if you have multiple listener signals enabled is through bit-wise operations on $NI_SIGNAL_TYPE

Like this:


```
on listener
    
    if ($NI_SIGNAL_TYPE .and. $NI_SIGNAL_TRANSP_STOP # 0)
        message("STOP")
    end if


    if ($NI_SIGNAL_TYPE .and. $NI_SIGNAL_TRANSP_START # 0)
        message("START")
    end if


    if ($NI_SIGNAL_TYPE .and. $NI_SIGNAL_TIMER_BEAT # 0)
        message("BEAT")
    end if
    
end on
```

Which will correctly trap all 3 events if they occur simultaneously (though it's unlikely that START and STOP would I know :wink: )


----------



## mk282 (Aug 3, 2013)

neilbaldwin @ 3.8.2013 said:


> Though I'm a bit disappointed that Kontakt decides for you whether you want to receive the START or STOP command (if your DAW is stopped, pressing STOP won't trigger the TRANSP_STOP listener event)



I do believe that makes sense, it's filtering out identical events coming one after another. Why'd you want to run the stop listener callback after the host has already been stopped?


----------



## Raptor4 (Aug 3, 2013)

neilbaldwin @ Sat Aug 03 said:


> (if your DAW is stopped, pressing STOP won't trigger the TRANSP_STOP listener event)


That's right, but there is an alternative method to make some condition for that...
In my example complex code above, I use a "Running" button as condition, monitor and manual Start/Stop. So, if the DAW is stopped, you can use the UI "Running" button to Start/Stop my generator and tweak the DAW BPM in stop mode if you want as well. 
If you run the DAW the "Running" switches to auto mode etc.
_____________________
www.audiogrocery.com


----------



## neilbaldwin (Aug 3, 2013)

mk282 @ Sat Aug 03 said:


> neilbaldwin @ 3.8.2013 said:
> 
> 
> > Though I'm a bit disappointed that Kontakt decides for you whether you want to receive the START or STOP command (if your DAW is stopped, pressing STOP won't trigger the TRANSP_STOP listener event)
> ...



Well, some sequencers (like Live) use the 'double stop' to set the playback position to the start. I'm not saying it's massively useful but it would be nice to be able to handle that case yourself in case you wanted to implement that kind of behaviour.


----------



## Hans Adamson (Aug 3, 2013)

Mike Greene @ Fri Aug 02 said:


> Hans Adamson @ Fri Aug 02 said:
> 
> 
> > This sounds like something that could be useful. What is the "listener" - if you don't mind my ignorance... ~o)
> ...


Mike,

Thanks for the explanation. Realitone's Realibanjo is the best use I have seen of a functionality like this. Great job.

/Hans


----------



## mk282 (Aug 3, 2013)

neilbaldwin @ 3.8.2013 said:


> Well, some sequencers (like Live) use the 'double stop' to set the playback position to the start. I'm not saying it's massively useful but it would be nice to be able to handle that case yourself in case you wanted to implement that kind of behaviour.



Playback position is reported by $NI_SONG_POSITION built-in variable anyways, so...


----------

