Apply_rate_function usable for "intern elasticity of the bpm" that would let the "global bpm" constant

Hi scampters
I’ve watched the Marc’s tutorial “Tempo Changes and Polytempo Music” available here : Tutorial Videos — scamp 0.9.1.post8 documentation
At the end of this tuto Marc shows us the apply_rate_function.
I wish I could manage better this apply_rate_function in order to produce a groove between to beats. I mean an intern elasticity of the bpm that would assign let’s say 26%, 23%, 24%, and 27% of the length of a beat for the first, the second, the third and the fourth subdivision of the beat, instead of the classical 25% 25% 25% 25%. Please note that, besides the obvious fact that sum is in both cases 100%, the global bpm should be unchanged. That’s a little bit more chalenging. I guess some mathematical tool like integration has to be involved here…
In my trial of python - scamp code (see below), the bpm can vary up to 108 and down to 78 between two beats, while the global bpm is supposed to remain unchanged at 90 bpm (which is not precisely realised…).

This code to explicit two questions; those two questions deal with the use of “apply_rate_function”.

1- is there a simpler way to assign to the first, second, third and fourth sixteenth note of a beat a micro-variation of length so that the sum of these 4 lengths keep invariant and let the « global bpm » unchanged.

2- why doesn’t the midi export contain any variation of the bpm ? Is there any hidden quantization applied ?

It must exist some simplier and more precise way to achieve that task with scamp. It would be great if a Scampter could provide us a much simplier code than mine, a code as user-friendly as the Djembe Loops app interface ( or web site https://www.djembeloops.com/ ).

Thanks for reading, and maybe helping ?


############################ My code, to be simplified  !!!! ############
# This code to explicit two questions; those two questions deal with the use of "apply_rate_function".
# My aim is to produce variations of bpm inbetween two beats without changing the length of a beat (let's call that "intern elasticity"):
# 1- is there a simpler way to assign to the first, second, third and fourth sixteenth note of a beat a micro-variation of length so that the sum of these 4 lengths keep invariant and let the « global bpm » unchanged.
# 2- why doesn't the midi export contain any variation of the bpm ? Is there any hidden quantization applied ?
########################################################################

from scamp import *

s = Session() # by default equivalent to s = Session(tempo=60) ?

##############################################################################################################
###      Because of a lack of good knowledges in Scamp, i've tried a complicated way to use  "apply_rate_function" 
### The mathematical function f(t) = (4.155951791592*(t % 1)**3 - 6.261606406157*(t % 1)**2 + 2.105654614565*(t % 1) + 1
### maps n+0 to 1, n+0.25 to 1.2, n+0.51 to 0.99 where n holds for any positiv integer.
### This function f(t) passed as parameter of apply_rate_function is designed to produce intern acceleration and deceleration within each beat around 60 bpm
### The function g(t) = 1.5*f(t) produces intern acceleration and deceleration around 90 bpm.
###########################################################################

s.apply_rate_function(lambda t: 1.5*(4.155951791592*(t % 1)**3 - 6.261606406157*(t % 1)**2 + 2.105654614565*(t % 1) + 1), duration_units="time")#duration_units="beats")
Steel_drums=s.new_part(name = "Steel Drum")

def play_one_beat_drums():
    for i in range(4):
        Steel_drums.play_note(50+i, 0.5, 1/4) # four notes for each beat that would be exact sixteenth if apply_rate_function was not applied.
def test_inter_elasticity(n_beats):
    for _ in range(n_beats):
        play_one_beat_drums()
   
s.start_transcribing()
s.fork(test_inter_elasticity, args=[4]) # to complete the 4/4 measure.
s.wait_for_children_to_finish()
performance=s.stop_transcribing()
# If you check out the score, it takes into acount the intern elasticity produced by apply_rate_function
partition=performance.to_score(title="How is transcribed the intern elasticity in score ?",time_signature="4/4", composer="decomposer & Co")
partition.show()
# If you check out the midi file, SURPRISE, no elasticity is transcribed in the midi file... (is there any hidden automatic quantization ?)
performance.export_to_midi_file(r"C:\Users\Admin\Documents\scamp\SURPRISINGLY_NO_TRANSCRIPTION_of_the_intern_elasticity_in_MIDI.mid")

Hi! Looks like you’re trying to do some weird shit, which I totally approve of.

First of all, I checked the MIDI file output, and I did see some tempo markings exported, but it’s a bit weird to have tempo markings come out every quarter of a beat. Instead, when you export the midi, you can set the flag “flatten tempo markings”, which causes the note lengths to change instead of the tempo markings:

performance.export_to_midi_file("midi.mid", flatten_tempo_changes=True)

That said, I think you should probably just be doing this with note durations. There’s no requirement in scamp that note durations are nice fractions, and if all you want to do is create a MIDI file, just use durations of [0.26, 0.23, 0.24, 0.27]. If you want to generate clean notation, and the weird durations are creating issues, you can do something like the following:

from scamp import *

s = Session()

Steel_drums=s.new_part("Steel Drum")
Steel_drums_notation=s.new_silent_part("Steel Drum (clean notation)")

straight_performance = s.start_transcribing(Steel_drums_notation)
wonky_performance = s.start_transcribing(Steel_drums)


def _play_straight_beat(pitches):
    for pitch, dur in zip(pitches, [0.25] * 4):
        Steel_drums_notation.play_note(pitch, 0.5, dur)
        
def play_one_beat_drums(pitches, subdivision_durs=(0.26, 0.23, 0.24, 0.27)):
    fork(_play_straight_beat, args=[pitches])
    for pitch, dur in zip(pitches, subdivision_durs):
        Steel_drums.play_note(pitch, 0.5, dur)
   

for _ in range(4):  # do it 4 times
    play_one_beat_drums([60, 61, 62, 63])


s.stop_transcribing(straight_performance)
s.stop_transcribing(wonky_performance)
wonky_performance.export_to_midi_file("wonky.mid")
# If you check out the score, it takes into acount the intern elasticity produced by apply_rate_function
partition = straight_performance.to_score(title="Elasticity", time_signature="4/4", composer="decomposer & Co")
partition.show()

This creates two parts: one for playback, and one for notation, and records them to separate performances. We can then use the straight performance for notation, and the wonky performance for creating the MIDI.

Using tempo curves is probably better for larger scale tempo changes, rather than microtiming.

By the way, I did a whole seminar on Algorithmic Approaches to rhythm recently, which I recorded. You can purchase the seminar and materials here: http://workshop.marcevanstein.com/seminars.html#past-seminars. I talk a little bit about microtiming at the end, but it’s mostly about generative approaches.

Oh, and you might even consider signing up for my workshop that starts tomorrow!

Thanks a lot Marc !