1. IMPORTANT:
    We launched a new online community and this space is now closed. This community will be available as a read-only resources until further notice.
    JOIN US HERE

Syntax for exponentiation? (KSP Math Library)

Discussion in 'Scripting Workshop' started by Jeremy Z, Apr 22, 2015.

  1. Jeremy Z

    Jeremy Z Member

    Messages:
    38
    Hi

    I'm trying to use this equation in a script: 2^($cents/100/12) but I'm having a tough time finding the exponentiation operator. Could someone enlighten me?

    Thanks!!
    JZ
     
  2. EvilDragon

    EvilDragon Well-Known Member

    Messages:
    19,938
    There's no such thing in KSP. Check out Big Bob's math library for KSP...
     
    • Like Like x 1
  3. Jeremy Z

    Jeremy Z Member

    Messages:
    38
    Got it. Thanks!
     
  4. Jeremy Z

    Jeremy Z Member

    Messages:
    38
    maybe it's best to start a new thread for this, but i just tried compiling Big Bob's math lib and got the following error: 'M.PostState has not been declared'...

    i'm running yosemite and using KScript Editor 1.5.1. is bob on here? maybe i can ask him directly...

    thanks!
     
  5. Big Bob

    Big Bob Forum Member

    Messages:
    606
    Hi Jeremy,

    It sounds like you are trying to compile the library all by itself. The library is an 'import module' and can only be compiled when imported by a hosting script. Please read the User Guide carefully before posting questions.

    Rejoice,

    Bob
     
  6. Jeremy Z

    Jeremy Z Member

    Messages:
    38
    Thanks, Bob. So I've read the User Guide a couple times, spending more time in sections 2 and 3, but I'm unfortunately still pretty unclear of how to use the library.

    Here's a very simple block of code I'm unsuccessfully trying to compile:

    import "/Users/jz/Documents/KSE\ Scripts/KSPMathV700.ksp" {path copied from the terminal}
    on init
    SetMathMode(0, DG)
    message(Exp2(2))
    end on

    I'm getting a 'File does not exist or cannot be read' error. I appreciate your time!

    Edit: I'm getting it to import the library file now (had to delete the '\" from the path) but getting a compiling error: 'M.DefOptionsCodes has not been declared'...

    Same thing happens when trying to compile the demo scripts with the updated library path.
    -jz
     
    Last edited: Apr 23, 2015
  7. Big Bob

    Big Bob Forum Member

    Messages:
    606
    Hi Jeremy,

    First off, V700 of the library cannot be used with V151 of the KSE (see page 3 upper red box). You need to use V152. If you are using V151 because you are on a Mac and were having some problems with V152, you will need to switch to the Sublime Text 3 version (which I understand works quite well with the Mac). The reason you are getting the M.DefOptionsCodes message is because you are using V151 of the KSE. When you get the right KSE version, be sure to use the option settings specified on page 3 also.

    It is not necessary to provide the full file path for the KSE import directive as long as the library is at or lower than the host script in the file heirarchy. Just put the library alongside the host script and then you can simply use:

    import "KSPMathV700.ksp"

    There are several things wrong with your code also.

    1. KSE return-value functions in general cannot be evaluated inline (the exception to this is single line functions such as Neg(X)) This is a KSE restriction and has nothing to do with the math library.

    2. Most math library functions cannot be invoked from the ICB (init callback) because of the KSP limitation on 'called' functions. This is the reason that the library provides the on_post_init callback (see page 8).

    3. Because of the all-integer arithmetic limitation of the KSP, most math functions work with scaled input and/or output values. Always read the header comments for any functions you intend to use (see paragraph 2 on page 7).

    The following code is an illustration of how to code your example so it will compile and run correctly (provided you use the right KSE).

    import "KSPMathV700.ksp"

    on init
    ``SetMathMode(0, DG)
    ``declare temp
    end on

    on_post_init
    ``temp := Exp2(2000000)
    ``message(temp)
    end_on


    I am assuming that you were trying to calculate 2^2 and not 2^0.000002.

    Rejoice,

    Bob
     
    Last edited: Apr 24, 2015
  8. Big Bob

    Big Bob Forum Member

    Messages:
    606
    Hi Jeremy,

    Looking at your original post, I can probably anticipate the next problem you are going to run into ;)

    The function you are trying to evaluate is that of frequency-ratio, FR, versus tuning difference. Of course I don't know what you intend to do with the result when you get it but chances are pretty good that you will need a scaled result in order to have sufficient precision.

    The KSP's integer-arithmetic-only limitation frequently requires that values need to be scaled to provide sufficient precision for the intended range of the value. Since the technique of scaling seems to be a little arcane for many scripters, perhaps if I walk you through an example it would be helpful.

    Suppose that you want to handle tuning differences in the range from -2400 cents to +2400 cents. This would yield frequency ratios from 0.250 to +4.000. But, if you don't scale the result, your ratios could only assume the values of 0, 1, 2, 3 or 4. That is unlikely to be sufficient precision for whatever futher calculations you may use the ratio with.

    So, let's just say that you will want something like 3 decimal digits of precision in your result. In that case, your formula must be changed from FR = 2^(cents/1200) to FR = 1000*2^(cents/1200). That way, for example, when you input 700 cents, the result will be 1498 which you can then interpret as 1.498.

    Now, the Exp2(X) library routine requires that you scale the actual input value X by a factor of 1000000. As a result, your formula then becomes:

    FR := 1000*Exp2(833*cents) where 833 = 1000000/1200

    If cents = 700, this would become 1000*Exp2(583100). Exp2(583100) is equivalent to 2^0.5831 = 1.498 but, the Exp2 library routine only returns an integer result so, it will return 1. Thus, the above formula returns 1000 when cents = 700 and this is clearly in error since we expected to get 1498.

    To solve this kind of problem, we need to scale the Exp2 function itself. What we really need is a new version of Exp2 that provides an output which is scaled by 1000. Suppose we call this new function SExp2. Then our formula would become:

    FR := SExp2(833*cents)

    Now all that remains is to figure out how to create SExp2 from Exp2. For that we have to understand the general properties of logs and exponentials and realize that 1000*2^X = 2^[X + lg(1000)]. Thus we can create SExp2 as follows:

    function SExp2(X) -> result
    ``result := Exp2(X + 9965784)
    end function


    Where 9965784 = 1000000*lg(1000)

    Finally, let me pull all this together with a little demo script.

    import "KSPMathV700.ksp"

    on init
    ``SetMathMode(0, DG)
    ``declare ui_value_edit Cents (-2400,2400,1)
    ``make_persistent(Cents)
    end on

    on_post_init
    ``call show_ratio
    end_on

    on ui_control(Cents)
    ``call show_ratio``
    end on

    function SExp2(X) -> result
    ``result := Exp2(X + 9965784)
    end function

    function show_ratio
    ``declare fr
    ``declare @R
    ``fr := SExp2(Cents*2500/3)``{ 2500/3 = 833.333 }
    ``R := DFmtVal(fr,3)
    ``message(R)``
    end function


    The edit box can be set to any tuning difference between -2400 to 2400 cents and the status line message will display the corresponding frequency ratio with about 3 decimal digit accuracy.

    Rejoice,

    Bob
     
    Last edited: Apr 24, 2015
    • Like Like x 1
  9. Jeremy Z

    Jeremy Z Member

    Messages:
    38
    Wow, Bob. Thanks so much for this. This is fantastic. One question: I have sadly limited Math knowledge so I'm getting 1000000*lg(1000) = 6907755.2789821. What am I doing wrong?

    I'm digging into this so I have the ability to change the resolution of the ratio.

    Thanks so much.
    Jeremy

    EDIT: I see... It's because it's base 2. Ignore my question and thanks again!!
     
    Last edited: Apr 24, 2015
  10. Big Bob

    Big Bob Forum Member

    Messages:
    606
    Your welcome Jeremy, I'm glad to be of help.

    BTW I just saw some more glaring errors in my text which I corrected so you may want to read it again :( I'm starting to do this kind of thing a lot lately, senior moment after senior moment, oh to have a young brain again :)
     
    • Like Like x 1
  11. Jeremy Z

    Jeremy Z Member

    Messages:
    38
    Ha! Well your 'old' brain seems to be sharper than most 'young' brains...Thank you very much for the library (and your help in this thread!)

    I'm getting close but I'm having trouble doing some simple math with the ratios. I think it's another issue with scaling...

    In the following script, I'm trying to generate an onset delay to align instances of a sound file at different playback rates, to a specific point in the file. So all instances played at different notes converge at $syncPoint. But in my code, the wait time returns 0. :/

    import "KSPMathV700.ksp"

    on init
    SetMathMode(0, DG)
    declare $root := 60
    declare $minDiff
    declare $minFr
    declare @minRatio
    declare $currentDiff
    declare $currentFr
    declare @currentRatio
    declare $lowestNote := 48
    declare $waitTime
    declare @waitDisp
    declare $syncPoint := 1
    end on

    on_post_init
    $minDiff := $lowestNote - $root
    $minFr := SExp2($minDiff*83333)
    minRatio := DFmtVal($minFr, 4) {DFmtVal returns a string}
    end_on

    function SExp2(X) -> result
    result := Exp2(X + 13287712) {13287712 = 1000000*log2(10000). 4 decimal places}
    end function

    on note
    $currentDiff := $EVENT_NOTE-$lowestNote
    $currentFr := SExp2($currentDiff*83333)
    currentRatio := DFmtVal($currentFr, 4) {DFmtVal returns a string}
    $waitTime := ( (1/$minFr) - (1/$currentFr) )*$syncPoint {PROBLEM area: returning 0}
    waitDisp := DFmtVal($waitTime, 4) {DFmtVal returns a string}

    ignore_event($EVENT_ID)
    wait($waitTime) {not working}
    play_note($EVENT_NOTE,$EVENT_VELOCITY,0,-1)
    message(waitDisp)
    end on
     
  12. EvilDragon

    EvilDragon Well-Known Member

    Messages:
    19,938
    Division in KSP is integer only (so you can't divide 1 with anything, it will always return 0!), so you cannot really depend on it for correct results... Expand the division by a factor of 1000 or a 1000000 to get the delay time you need.
     
  13. Jeremy Z

    Jeremy Z Member

    Messages:
    38
    Ah. Thanks, Evil. The integer-only thing finally just clicked. Cheers!!
     
  14. Big Bob

    Big Bob Forum Member

    Messages:
    606
    Hi Jeremy,

    I'll be gone for a few hours this morning so I won't be able to address this until I return. However, it would be helpful if you specify your expected range limits for your various parameters. For example can your note differences be as high as +/-127, what range of wait times are you expecting to use, etc. In order to address your scaling issues, all ranges must be clearly understood first.

    As Mario pointed out, your problem will center on retaining precision when divisions are involved. Generally, you have to perform multiplications before divisions and use scaling to drive the dividend as high as possible before performing any divisions.

    I'll check back when I return.

    Rejoice,

    Bob
     
    • Like Like x 1
  15. Jeremy Z

    Jeremy Z Member

    Messages:
    38
    Thanks, Bob.

    I got it! This was a valuable learning experience. Thanks so much!
    -j
     
  16. Big Bob

    Big Bob Forum Member

    Messages:
    606
    Hi Jeremy,

    I'm glad you got it sorted out, kudos :thumbsup:

    I'll just mention a few things about the code you posted. I presume you know that you don't need to use DFmtVal after each Fr calculation? I only used that in the demo script so that I could display a properly formatted ratio on the status line. Normally, you would only use the internal numerical value of Fr and not a string version of it unless you needed to display it to the end user.

    Since you are trying for higher precision (with 4-digit scaling), you probably should use 250000/3 instead of 83333 when calculating ratios. Note that my example script didn't use 833 but rather 2500/3. The 'real' number you want is actually 83333.333333 ---.For higher precision you shouldn't just throw away the residual one third (0.3333333 ...). So, instead of using SExp2($currentDiff*83333) you should use SExp2($currentDiff*250000/3) for better accuracy.

    This is actually a classic example of the integer division problem and how to deal with it. Of course, the other thing you have to watch out for is that you don't overflow the max integer value when you do multiplies like this. But, I assume $currentDiff is at most 127 and therefore the product is less than 32 million (well below the 2 billion plus for a signed, 32-bit integer).

    Rejoice,

    Bob
     
    • Like Like x 1
  17. Jeremy Z

    Jeremy Z Member

    Messages:
    38
    Thanks, Bob! I've made those changes.

    I have run into something very weird that may be due to a lack of calculation precision? Maybe I'm overloading the max integer value as you warned?

    With larger $waitTime values, the instances don't align. For example, I have a file with a sync point at 24.327 seconds (24327 in the script), root note of 60 and lowest note of 36. I'm playing notes 36 and 48. (I verified the $waitTime value in SuperCollider to make sure the equation was correct. Additionally, Kontact is printing the correct $waitTime.)

    Here's my code:
    import "KSPMathV700.ksp"

    on init
    SetMathMode(0, DG)

    make_perfview
    set_ui_height(1)

    declare ui_value_edit $root (0,127,1)
    set_text ($root,"Root MIDI Note #")
    set_control_par (get_ui_id($root),$CONTROL_PAR_WIDTH,125)

    declare ui_value_edit $lowestNote (0,100,1)
    set_text ($lowestNote,"Lowest MIDI Note #")
    set_control_par (get_ui_id($lowestNote),$CONTROL_PAR_WIDTH,125)
    set_control_par (get_ui_id($lowestNote),$CONTROL_PAR_DEFAULT_VALUE,48) {NOT WORKING}

    declare ui_value_edit $syncPoint (0,60000,1)
    set_text ($syncPoint,"Sync (ms)")
    set_control_par (get_ui_id($syncPoint),$CONTROL_PAR_WIDTH,125)
    set_control_par (get_ui_id($syncPoint),$CONTROL_PAR_DEFAULT_VALUE,0) {default not working}

    move_control($root,1,1)
    move_control($lowestNote,3,1)
    move_control($syncPoint,5,1)

    declare $minDiff
    declare $minFr
    declare @minRatio
    declare $currentDiff
    declare $currentFr
    declare @currentRatio
    {declare $lowestNote := 48}
    declare $waitTime
    declare @waitDisp
    end on

    function SExp2(X) -> result
    result := Exp2(X + 13287712) {13287712 = 1000000*log2(10000)}
    end function

    on note
    $minDiff := $lowestNote - $root
    $minFr := SExp2($minDiff*250000/3)
    $currentDiff := $EVENT_NOTE-$root
    $currentFr := SExp2($currentDiff*250000/3)
    $waitTime := ( (10000000/$minFr) - (10000000/$currentFr) )*$syncPoint {scaling numerators by 10000000 }
    message($waitTime)
    {if ($waitTime = 0 or $waitTime < 0)
    play_note($EVENT_NOTE,$EVENT_VELOCITY,0,-1)
    else}
    ignore_event($EVENT_ID)
    wait($waitTime)
    play_note($EVENT_NOTE,$EVENT_VELOCITY,0,-1)
    {end if}
    end on

    Many thanks!
    -j
     
  18. Big Bob

    Big Bob Forum Member

    Messages:
    606
    Hi Jeremy,

    I'm about ready to quit for the day so I won't have time to dig into this now. However, I'm not at all sure I understand the formula you are using for your wait time calculation. Far be it from me to 'go against' Super Collider but, to me it looks wrong.:)

    However, that may well be because I'm not correctly understanding the whole thingo_O I'll check back in again tomorrow to discuss this with you. But, in the meantime, why don't you post some of the actual numerical values you are expecting $waitTime to be for a few certain played notes (and a SyncPoint of 24.327). That may help me to better understand your current logic.

    BTW For discussion purposes, you may want to express everything in floating point numbers to avoid scaling confusions.

    Rejoice,

    Bob
     
  19. Jeremy Z

    Jeremy Z Member

    Messages:
    38
    Thanks, Bob. I'm pretty sure the basic equation is good as I've been using it for years in my fancy SC code :p. (Although it's extremely possible that I've screwed up the implementation of the equation in KSP)

    Here are some numbers plugged in without the scaling:

    root note = 60; lowest note = 36; current note = 36; sync point = 24.327
    ( 1/(2**((36-60)/12)) - (1/(2**((36-60)/12))) )*24.327 = 0

    root note = 60; lowest note = 36; current note = 48; sync point = 24.327
    ( 1/(2**((36-60)/12)) - (1/(2**((48-60)/12))) )*24.327 = 0.25 seconds

    root note = 60; lowest note = 36; current note = 62; sync point = 24.327
    ( 1/(2**((36-60)/12)) - (1/(2**((62-60)/12))) )*24.327 = 75.6351068838 seconds

    Cheers!
    JZ
     
  20. Big Bob

    Big Bob Forum Member

    Messages:
    606
    Oh Jeremy,

    It looks like you changed your posted code:(. I'm pretty sure yesterday that you were computing $currentDiff := $EVENT_NOTE-$lowestNote. If you use $root instead that will make a big difference.

    Before I tackle any scaling issues, please confirm that the way the post reads now is what you are using and then give me a chance to re-study it.

    Rejoice,

    Bob