Old 08-22-2016, 12:42 PM   #1
teatime
Human being with feelings
 
teatime's Avatar
 
Join Date: Aug 2016
Location: South Africa
Posts: 41
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: 41
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: 41
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: 41
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, 33 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: Holland
Posts: 2,926
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: 41
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: Holland
Posts: 2,926
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: 41
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
Old 08-24-2016, 11:03 PM   #9
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: Holland
Posts: 2,926
Default

Quote:
Originally Posted by teatime View Post
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.
The same here, but that is likely not because JSFX doesn't optimise away the divisions. More likely it is because in this particular instance using variables seems good for pipelining, caching, or whatever. If I replace 4/$pi and 4/($pi*$pi) by constants I get exactly the same CPU as without the constants, but still every so slightly more than when using variables:

Code:
@sample
  loop(30000,
   y = (1.2732395447351627 - 0.40528473456935109 * abs(x)) * x;
   y = (0.225 * (abs(y) - 1) + 1)*y;
  );
Tale is offline   Reply With Quote
Old 08-27-2016, 11:14 AM   #10
teatime
Human being with feelings
 
teatime's Avatar
 
Join Date: Aug 2016
Location: South Africa
Posts: 41
Default Euclid (cont.)

After outputting notes every single selected beat division, it was necessary to impose a pattern on them. The easiest way I thought to do this was to use this formula for checking when a step should occur:

Step occurs iff
Code:
(ticks*position) % total_steps < ticks
This can be used to compile the pattern on slider changes then just access memory indices later to see if something should be triggered or not. As it turns out, the aforementioned formula doesn't guarantee a 1 on the first hit, and is only applicable from 2 onwards. After a disappoingly long time in a spreadsheet, I realised the easiest solution was just to place everything at 2*(total_steps), since every pattern will repeat at a multiple of its total length.

Code:
  function compilePattern(tic tot) local(patcount patpos patdet buf)
  (  
      patcount = 0;
      buf = 0;
      while(patcount < tot) (
        patpos = (tot * 2) + patcount;
        patdet = (((tic*patpos) % tot) < tic);
        patdet ? buf[patcount] = 1 : buf[patcount] = 0;
        patcount+=1;
      );
      buf;     
  );
  ppp = compilePattern(ticks, total);
Further difficulties arose with the pattern always playing 1 note division out of sync. I implemented a restart mechanism by checking when enough beats have passed since the last restart point, with the restart point defined as:
Code:
 
lastRestart = floor(beat_position / restart) * restart 
// restart = (note division) / (0.25) ; eg. 2 bars = 8; 1 bar = 4;
This allowed me to check the restart position by comparing against (lastRestart + restart).
Code:
  ((beat_position) >= lastRestart + restart ) ? (
    counter = 1;
    temp = samplesLeft;
    lastRestart = floor(beat_position / restart) * restart;    
  ); 

 ((beat_position + blocksize_beats) >= lastRestart + restart) ? (
   midisend(0, (0x90), noteOut, 100)     
 );

If you look, you will see that there is a counter, referring to an index in the compiled pattern (that holds the on-off states for the pattern). If you are clever, you will immediately understand that it has to restart at 1. If, howevr, you are stupid like me, you might spend a few days debugging until realising that since you check when to send a note by seeing how far into the future it is untill you have to send it, every note sent is for the next block.

I set the offset slider to control a 50% range either way, which is translated to a maximum of (pattern/2) boxes rhythmic boxes offset from the original.
teatime is offline   Reply With Quote
Old 08-28-2016, 05:04 AM   #11
teatime
Human being with feelings
 
teatime's Avatar
 
Join Date: Aug 2016
Location: South Africa
Posts: 41
Default

As a brief interlude, I just realised I haven't actually said what I'm trying to do. The Grand Plan is to make an instrument of some sort. I've been making electronic music for about 9 years, and recently it's been grinding on my nerves that I have to start and stop while producing, and have to pause to tweak notes, then go back to hear the change. The whole process is horrendously inefficient; I can't make things as fast as I think of them, and I in the process of trying in implement an idea, I discover so many other things that might work that I get lost in that sea of infinite possibility. I want to separate the generating bit from the tweaking it as thoroughly and completely as possible.

SO - I recently found this controller second hand:
)

...which has 8 endless encoders, 8 potentiometer style knobs, 4 rows of 8 buttons, 8 drum pads and 8 sliders...per page. And as you can have up to 40 pages, the potential for easily accessible control is there.

How to use this to generate music though? I mostly make psytrance, but I don't want to fix this thing too tightly to any specific genre. It should have the capacity to make any rhythmic electronic music, and thus I end up at Reaper. The idea is to build a great many interlocking utility plugins, with vsts at the leaves of the tree, and assign a lot of control to a lot of things. I will need ways to generate notes, ways to change generated notes, ways to record automation and have it loop, and a way to visualise everything (or venture into the realm of making guis).

The list of ideas as it currently stands is as follows:

Rhythm:
- LFO-to-Midi: LFO1*LFO2 that generates a note when value goes over T or under -T.
- Euclidean Rhythms Generator
- Metropolis-style sequencer (see: https://www.youtube.com/watch?v=uV9-XA5MPwY )
- Plain Stepsequencer (16 steps, on/off + value per step)
- Imitate TipTop Trigger Riot ( https://youtu.be/AjKLewx17iQ?t=34s )

Transform:
- Note-off gen (generates off-messages for incoming notes. LFO for controlling note length.)
- Note Multiplier (for each incoming note, if LFO > threshold, generate (selectable) number of notes at (beatDivision) intervals. can set multiplier to 0 to filter notes)
- Duplicate filter (midichannel = priority. If two notes are received in (buffer) time, only pass the one that is in the lower-numbered midi channel. Always pass channel 1.)
- Combiner (Reduces 16 channels of midi to 1)

Mod:
- Midi recorder (when (armed) continually write (selected midi control) to a buffer, while passing it through as well. When (disarmed), send output from buffer. Synchronise reading to beat_position., playbackspeed control).
- Audio -> pitch (when (armed) listen to input audio signal. all note on's in buffer get repitched to closest note to (number of zerocrossings in input buffer). When disarmed, loop last recorded input).

Monitoring:
- Inspector: Every plugin has a type-indicating number associated with it, and 2 hidden controls, x and y. Define in each plugin a reference to the inspectors global memory. x and y indicate position in an array. Inspector continually monitors array and uses the [type, x, y, v1, v2, v3, ..] array to call the logic for drawing a plugin of (type) at (x,y). Plugins can be multiple x's and y's long.
teatime is offline   Reply With Quote
Old 02-09-2017, 02:14 PM   #12
teatime
Human being with feelings
 
teatime's Avatar
 
Join Date: Aug 2016
Location: South Africa
Posts: 41
Default

For the sake of closure to this thread (I can't seem to forget it exists, and I don't like leaving things unfinished):

This idea has ballooned something fierce, and half a year later I find myself reading the USB specification, soldering microchips and staying up late pouring over datasheets. I am to make a pretty fancy midi sequencer if all goes well - what is to be a more direct conduit for my thoughts than a keyboard and mouse (and all around A+ fun toy ).

For what it's worth, I find Reaper to be a deeply inspirational piece of software. The modularity and neatness of it; as well as the work ethic of the creators drives me to try achieve similar heights. I felt good buying Reaper, and I'm still feeling good owning it. Somewhere in the hopefully not too distant future I hope to give something back to this community and Cockos; more than $60 and a half-assed thread ^_^
teatime is offline   Reply With Quote
Old 02-09-2017, 02:36 PM   #13
Plazma
Human being with feelings
 
Join Date: May 2014
Posts: 288
Default

Hey Teatime,

I'm eagerly waiting to see what you come up with.

From another safrican.
__________________
Reaper 64bit ~ Win 10 64bit | www.pennysound.co.za (Free & commercial Omnisphere patches)
Plazma is offline   Reply With Quote
Old 02-09-2017, 04:22 PM   #14
teatime
Human being with feelings
 
teatime's Avatar
 
Join Date: Aug 2016
Location: South Africa
Posts: 41
Default

Always good to meet another South African online ^_^ I'd love to share the full idea; but I intend to commercialise the hardware box, so I must resist. Other people are significantly faster than me at implementing this shit, and the magic of the idea is the idea itself. That being said, when the time comes I'll report back here.
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 02:28 AM.


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