Old 08-22-2016, 12:42 PM   #1
teatime
Human being with feelings
 
teatime's Avatar
 
Join Date: Aug 2016
Location: South Africa
Posts: 44
Default teatimes Log - Star Date -306358.2

Good day
I recently started with Reaper, after discovering I had needs nothing else could fulfill. My previous DAW was too restrictive in terms of routing, Supercollider is too low level (I like vsts), Ableton and Max4Live _could_ do what I want it to do... but when I program a computer I prefer to just tell it what to do, instead of drawing pictures. So, lo and befucking hold, I discovered Reaper and JSFX. Goldilocks zone. Fully functioning DAW and VST host, wild routing options, global shared memory between custom written plugins... my kinda jam.



Fact of the matter is I'm going to be spending some time coding, and along the way I am discovering things. Some of these things are obvious and known to everyone. Other might be interesting. For the sake of keeping my thoughts collected, and sharing a bit along the way, I'm going to post what I learn here.

Maybe some of it sparks discussing and more optimal methods are discovered.
teatime is offline   Reply With Quote
Old 08-22-2016, 12:52 PM   #2
teatime
Human being with feelings
 
teatime's Avatar
 
Join Date: Aug 2016
Location: South Africa
Posts: 44
Default First entry

(1) Cheap Sinewave - good for LFO, haven't tested for audio. Valid between -PI and PI. Obeys zero crossings, goes through 1 @ PI/2.
Code:
B = 4/$PI;
C = -4/($PI*$PI);
function quicksine(x) local(y)
(
  y = B*x + C*x*abs(x);
  y = 0.225 * (y * abs(y) - y) + y;
  y; 
);
================================================== ========================
(2) Checking for LFO/time variant line intersections

The plan was to make a note generator by having an LFO1*LFO2, with user definable parameters and and a threshold, so that every time the threshold is crossed a midi note is released (sync and note-off generated downstream).

Depending on the block size (512 on my PC) you can simulate max ~40Hz in the @block section. This is 1/64 at 150BPM, and is more than sufficient for my needs.

For single positive threshold:
T = threshold
y1 = value at start of block
y2 = value at end of block
(T-y1)*(T-y2) < 0 means a crossing has occured.

For a threshold reflected over the x-axis:
(T-abs(y1)) * (T - abs(y2)) < 0 catches both crossings.

This turned out to be interesting but shit, since there are just too many parameters. Currently working on implementation of euclidean algorithms. I am aware of the excellent JSFX plug that already exists for it, but I need a shittier one, and I need to do it myself.

Last edited by teatime; 08-23-2016 at 10:40 AM.
teatime is offline   Reply With Quote
Old 08-22-2016, 01:03 PM   #3
teatime
Human being with feelings
 
teatime's Avatar
 
Join Date: Aug 2016
Location: South Africa
Posts: 44
Default My Implementation of Euclidean Rhythms

The previous posts were for the sake of remembering what I'd done. Current project is the euclidean rhythms one. Specs as I currently see them:

_5 Controllable Controls_
TicksOn -> The number of ticks
TicksTotal -> The total number of slots
Offset -> Amount the pattern should be rotated by.
RhythmicUnits -> 1/16 | 1/8 | 1/8D | 1/4 | 1/4D | 1/2 [triplets later]
Restart -> 1/2 bar | 1 bar | 2 bar | 4 bar

_2 Manual Controls_
NoteOut -> 0-127
OutChannel -> 0-16

gfx:
-Visual indication of complete pattern in a length (restart), rotated properly w.r.t offset. Unlike the other euclid module, this will be linear left-to-right display, including sufficient repeating of the pattern to represent all notes to be played before a restart.
-Gridlines imposed over pattern.

Nice to have:
- "pause" button which allows changes to occur without changing the output. Pressing the pause button again immediately enacts them. Possibly second pattern displayed during editing while pause button is engaged.

Last edited by teatime; 08-22-2016 at 02:43 PM.
teatime is offline   Reply With Quote
Old 08-22-2016, 04:21 PM   #4
teatime
Human being with feelings
 
teatime's Avatar
 
Join Date: Aug 2016
Location: South Africa
Posts: 44
Default Synchronising Events

For this note generator to work I need to be able to reliably output notes on specified time divisions... but before I can output notes on certain time divisions, I have to output notes on every time division. It would've been convenient if I could detect beats by simply going:
Code:
floor(beat_position) == beat_position
...but alas, this isn't accurate enough, nor reliable enough. samplesblock's size changes, and even if you were just to look for crossings, in a worstcase scenario of 2048 samples per block this could be a very ugly 46ms at 44.1kHz. So, the long way around then. This is the abridged version:

Code:
...
slider4:1<1,6,{1/16, 1/8, 1/8D, 1/4, 1/4D, 1/2}1> RhythmicUnits
...


@init
  blockSize = samplesblock / srate; //seconds per block
  bps = tempo/60; //beats per second

  function getBeatDiv(x) local(div) 
  (
    div = 1; // if anything goes wrong, don't divide by zero later

    x  == 0 ? div = 0.25; // 1/16
    x  == 1 ? div = 0.5;  // 1/8
    x  == 2 ? div = 0.75; // 1/8D
    x  == 3 ? div = 1;    // 1/4
    x  == 4 ? div = 1.5;  // 1/4D
    x  == 5 ? div = 2;    // 1/2
   
    div;
  );
------------------------------------
The basic idea is to see how many seconds per sampleblock there are, and if we get close enough to a beat that the next beat division of interest lies within the current sample block, we can dispatch a note in that sample block (or not, depending on the pattern we are following).

To make life easier for ourselves we will adjust the beat_position global variable so that all whole divisions of the adjusted beat_position fall on the rhythmic units we are interested in, eg. beat_position = 1/4 beats; if the unit of choice is 1/2 we know 1/2's occur where beat_position/2 is a whole number ([1/2] / [1/4] = 2).
-----------------------------------
Code:
@block
  beat = beat_position;
  beatadj = beat / beatDiv; // whole beatadj's correspond to our note divisions
  frac = ceil(beatadj) - beatadj; //fractional adjusted beats left to next tick
  beatsLeft = (frac * beatDiv); // beats till next tick
  secondsLeft = (beatsLeft/bps); //bps is beats per second
  secondsLeft < blockSize ? ( //shit happening in this block
    secondsTillEvent = secondsLeft/blockSize;
    samplesTillEvent = secondsTillEvent*samplesblock;
    ding += ceil(samplesTillEvent);
    //do the stuff
    1;
  ); //else do nothing

@sample
  ding > 0 ? ding -= 1; //count out "ding" number of samples
  ding == 0 ? (dong = 1; ding = -1); //after "ding" samples, dong.
------------------------------------
So, when the note is in the current sample block, a sample-timer is started which counts down until the appropriate moment, and does something. In the case of my program, it drew a little white line. To verify that this was indeed on my divisions of interest I also drew lines on every 1/4. I only drew tiny lines, so I couldn't reeeaallly see if it was lining up on a sample accurate basis (and I expect it didn't, I did ceil afterall instead of round to nearest neighbor on the samplesTillEvent), but what's 23 microseconds between friends

If things ever need to be rounded however, this is a reasonable way to do it:
Code:
function round(i) local(flo, ans)
(
 flo = floor(i);
 ans = (i - flo) < 0.5 ? flo : ceil(i);
 ans
);
Next step is to compile the pattern and use that pattern to choose which notes to send and which not to.
Attached Images
File Type: png bars.PNG (1.8 KB, 242 views)

Last edited by teatime; 08-23-2016 at 10:40 AM. Reason: Discovered the [code] block
teatime is offline   Reply With Quote
Old 08-23-2016, 02:37 AM   #5
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,652
Default

Quote:
Originally Posted by teatime View Post
(1) Cheap Sinewave - good for LFO, haven't tested for audio. Valid between -PI and PI. Obeys zero crossings, goes through 1 @ PI/2.
This is pretty good, thanks for sharing!

Here is a script that compares it with a bunch of other (fast) sine functions:

https://forum.cockos.com/showthread.php?t=180631

I have also optimized your quick sine a tiny bit:

Code:
y = (4/$pi - 4/($pi*$pi) * abs(x)) * x;
y = (0.225 * (abs(y) - 1) + 1)*y;
Tale is offline   Reply With Quote
Old 08-23-2016, 10:22 AM   #6
teatime
Human being with feelings
 
teatime's Avatar
 
Join Date: Aug 2016
Location: South Africa
Posts: 44
Default

Good point, variable assignments take cycles. I come from the Java/Scala world, so I'm used to lambda expressions, fancy high level collection handling and many objects. For the sake of getting used to the syntax / structure I'm coding very redundantly to keep things clear.

Just out of interest, if that sine calculation is going to be used over and over (eg. in the @sample block), would it not be faster to precompute the parameters, instead of recalculating them each time?
teatime is offline   Reply With Quote
Old 08-24-2016, 12:18 AM   #7
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,652
Default

Quote:
Originally Posted by teatime View Post
Just out of interest, if that sine calculation is going to be used over and over (eg. in the @sample block), would it not be faster to precompute the parameters, instead of recalculating them each time?
Actually the JSFX compiler is pretty good at spotting (simple) constants. To test this, just run a loop of e.g. 100x this code is @sample, look at the CPU usage, and then replace the 4/$pi etc. with actual constants, and you should see no difference in performance. Because there is no difference I would rather write 4/$pi than 1.2732395447351627.
Tale is offline   Reply With Quote
Old 08-24-2016, 09:30 AM   #8
teatime
Human being with feelings
 
teatime's Avatar
 
Join Date: Aug 2016
Location: South Africa
Posts: 44
Default

58.7 - 59.1%
Code:
@sample
  loop(30000,
   y = (4/$pi - 4/($pi*$pi) * abs(x)) * x;
   y = (0.225 * (abs(y) - 1) + 1)*y;
  )
58.4 - 59%
Code:
@sample
  loop(30000,
   y = (B + C * abs(x)) * x;
   y = (0.225 * (abs(y) - 1) + 1)*y;
  )
Both were pretty wobbly, to the point where I would say I cant say with absolute certainty there was a difference - I mean Windows deciding to do any old arb thing could influence it - but the non-precomputed one never went lower than 58.7, and the precomputed one never lower than 58.4 (after about 15min of testing, recompiling, checking where it jumps to from switching on etc etc.

It would seem as if there is at least a very tiny mathematical difference, I guess the multiplication and division of constants isn't optimised away.

Last edited by teatime; 08-24-2016 at 09:31 AM. Reason: incorrect value in first block
teatime is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -7. The time now is 10:55 AM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.