voxglitch icon indicating copy to clipboard operation
voxglitch copied to clipboard

Autobreak: Pitch is offset for loops not 120 BPM

Open Coirt opened this issue 5 years ago • 9 comments

If a .wav file does not have a BPM of 120 it will not set to the correct pitch when loaded. For example:

Load a .wav with a tempo of 98 bpm, 120 BPM will pitch the loop up, however setting the BPM to 98 will pitch the loop down. If a loop has a bpm which is outside the range of the string value it could be impossible to pitch to the loops correct pitch.

Suggestion:

Have an Offset for the string value, so setting this offsets will change the string only. As most .wav files may contain the BPM in the string. Where the .wav does not contain the BPM value in the file name it can be calculated by counting the beats and sending the EOC to a module with BPM detection.

or

Auto detection might be another way, have a button to disable the bpm until the End Of Cycle, if number of bars is known for a loop this could update the BPM, but you would have to manually set the bars this way. Perhaps a context menu "Number of bars >" and a drop down menu.

Coirt avatar Feb 03 '20 21:02 Coirt

Thank you for reporting this! I'll take a look at soon. This sounds like there may be a bug in my playback rate calculations. A .wav file with a tempo of 98 bpm shouldn't be pitched when the module's bpm is set to 98. Either that's a flaw in the code, or I'm thinking about it wrong. I'll get back to you asap with an update - hopefully tonight.

In the meantime, I'll do my best to explain what's going on internally.

There can be up to 5 actual .wav files loaded into memory. Each .wav file (drum loop) might have a different BPM. Obviously, if we were to play all 5 samples at the same BPM without stretching any of them, samples with more beats per minute would loop before samples with larger beats per minute.

Sample #1: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ (90 BPM) Sample #2: ~~~~~~~~~~~~~~~~~~ (60 BPM) Sample #3: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ (120 BPM) Sample #4: ... etc Sample #5: ... etc

So, how can that be solved? At first I thought about re-sampling the 5 loops to match the longest loaded loop. But all that re-sampling seemed like it would get messy.

So I took another route. There's essentially an imaginary drum loop in the module. The imaginary loop plays at the BPM speed. It is assumed that this imaginary loop's sample rate is the same as VCV's playback rate. You can see proof of that here:

// Step the theoretical playback position
theoretical_playback_position = theoretical_playback_position + 1;

Let's say that this imaginary sample is playing at 80 BPM (because the user has selected 80 BPM on the front panel). A playback counter would step from the beginning to the end of the imaginary sample. How do we know when the playback counter has reached the end of the imaginary sample?

Using this equation:

float samples_to_play_per_loop = ((60.0 / (float) bpm) * args.sampleRate) * 8.0;

To decide where playback should occur in one of the real loaded .wav files, it's a simple mapping Here's an example:

  1. It's time to play back some real .wav file content. So we ask, where is the playback position in the imaginary sample? Let's assume that our playback position is 40% of the entire length of the imaginary sample.
  2. Cool beans, then we should playback the real .wav file at 40% of its entire length. If the real wav file is longer than our imaginary sample, it will be pitched higher, and vice versa.
  3. No matter which of the 5 loaded samples are selected, playback should happen at 40% of that sample's total length. This keeps all beat loops in sync.

I know that nobody asked for an in-depth tutorial, but I thought I'd share anyhow! :-) I'll follow up again on this ticket soon.

clone45 avatar Feb 03 '20 21:02 clone45

Why have 5 different banks without 5 different ways to set their tempo? In theory you could store a knobs value and recall it when needed.

Coirt avatar Feb 03 '20 23:02 Coirt

My intention is to allow users to construct new breakbeats by modulating the Sample input during playback. Because of this, I'd rather have each drum loop be synchronized than to playback at its original tempo.

Here's a very simple patch to illustrate what I'm talking about: https://youtu.be/B7Pds_emIDA. I'm switching between different drum loops while keeping the tempo constant and the loops synchronized. (The beats that I used are a bad examples to use because they're all at the same originally recorded tempo.)

I hope that I'm following your question! OH ... maybe I know what you're talking about? It's assumed that each sample is going to be 8 bars long. If I load in a 4 bar loop, it's going to play at 1/2 speed. Is that perhaps what you're running into? If so, let me know and I'll start thinking of a solution. :-)

clone45 avatar Feb 04 '20 01:02 clone45

It's assumed that each sample is going to be 8 bars long. If I load in a 4 bar loop, it's going to play at 1/2 speed.

Exactly, well 4 bar would double probably idk. All the samples in the above video are the same pitch / BPM try it with loops with different BPM's and you will see what I'm saying.

Coirt avatar Feb 04 '20 02:02 Coirt

Ok, I believe that I understand now! Give me a little time to think this over and come up with a solution. I appreciate the feedback. I'll leave this ticket open until I have time to address it - which will hopefully be within about 5 days.

clone45 avatar Feb 04 '20 02:02 clone45

No worries. They're all great modules, they're getting there to be amazing!

Coirt avatar Feb 04 '20 03:02 Coirt

In theory you could store a knobs value and recall it when needed.

By the way, this sounds like a pretty good solution. Adding a small knob for each sample to set it's length in bars should do the trick! And I should have plenty of panel space once I move things around.

Exactly, well 4 bar would double probably idk.

I'm terrible at math. I think I got this one right though! Imagine that you would need to stretch out a 4 bar sample to fit into the 8 bars of space. When you try and play back a sample that's been stretched out at the same rate as a normal sample, it would take twice as long to reach the end. I think that's right? I could be wrong. Ha ha ha.

Thank you for the compliment! It's nice to have your feedback. For one thing, it forces me not to be lazy. :-)

clone45 avatar Feb 04 '20 03:02 clone45

Sure, once it is a momentary = true; button (briefly 1) it will set a value properly. When the value is 0 you set it to the getValue() which gives control back.

https://github.com/Coirt/Bark/blob/master/src/Clamp.cpp#L62

Another possible solution to 4 bar / 8 bar thing would be to leave the 4 bar sample 4 bars and add itself on the end to make it 8 bar.

A string for the current index / slice on the display would be a nice addition also! Value at the mouse position.

Coirt avatar Feb 04 '20 03:02 Coirt

Another possible solution to 4 bar / 8 bar thing would be to leave the 4 bar sample 4 bars and add itself on the end to make it 8 bar.

That is essentially what I hope to do. I'm sure that we're thinking about the same thing, just describing it differently. 👍

clone45 avatar Feb 04 '20 03:02 clone45