Adding a loop player


#1

[color=#0000FF]I posted this at the end of the last thread I posted, but it might have been missed. If not, close it down, or… whatever happens in mod land.[/color]

Next step: Adding functionality to play a (drum) loop in time with everything else.
[color=#0040BF](everything else being the basic drum sequencer I made in the last thread)[/color]

I can set up an AudioTransportSource inside a ResamplingAudioSource, so I can adjust the playback speed, but I can’t seem to figure out how to trigger it in time with the Synthesiser.

I can load the sample into a Synthesiser, and synth.setCurrentPlaybackSampleRate(correct speed), but it’s cutting the playback midway, which isn’t very good.

If I’m going about it the wrong way, could someone tell me? Or do I need to start hacking? I thought I could maybe do something like add a getter/setter to the SamplerSound or the SamplerVoice.

Any wisdom would be appreciated!

[color=#0000FF]I had a little bit of a dig around in the samplersound/voice classes, but with frustration rather than luck…
[/color]


#2

As soon as you start changing playback speeds, everything suddenly gets a whole lot more complicated! If something is getting cut short, then you’re probably just setting a length based on a sample rate that doesn’t take the speed into account.


#3

Oh. “complicated” with exclamation mark is fairly ominous in my book.

I was thinking that a tempo change call back could trigger a little math …

void setTempo(double newTempo){
	samplesPerStep = 44100/(newTempo/60)/4;
		samplesPerBar = samplesPerStep*16;
		resampleRatio=samplesPerBar/sampleLength;
		synth.setCurrentPlaybackSampleRate(filesOriginalSampleRate*resampleRatio);
		}
	}

… and it would work, but then I noted that there is an allNotesOff within setCurrentPlaybackSampleRate(), so that didn’t work. I commented it out, and nothing really changed, but it felt wrong, so I am sure there’s a good reason it’s there.

other than looking at the samplervoice/sound end of it, I would… Stick the whole Synthesiser in a ResamplingAudioSource? I noted the pitch wheel implementation seems to be scant, but I suppose that you have better things to do.

…I’m assuming that in tracktion you don’t put every single wav file onto a sampler…? if not… how do you get an Audio Source to start halfway along a block? Stuff a little of the buffer with silence? Start it in the next block and chop off some of the start (I’m not above doing that)? Or is finding out part of the pilgrimage?

I apologise for my naivety, I just thought… ah. that’s part of the problem. Thoughting.


#4

Hang on, I thought the synthesiser class already does resampling for you? I can’t remember the details, but the demo plays a sampled sound at different pitches, so it’s clearly got a resampler in there.


#5

I done it! or I’m doing it in another window…

Yes, the synth/samplerVoice does do resampling

[code]void SamplerVoice::startNote (const int midiNoteNumber,
const float velocity,
SynthesiserSound* s,
const int /currentPitchWheelPosition/)
{
const SamplerSound* const sound = dynamic_cast <const SamplerSound*> (s);
jassert (sound != 0); // this object can only play SamplerSounds!

if (sound != 0)
{
    const double targetFreq = MidiMessage::getMidiNoteInHertz (midiNoteNumber);
    const double naturalFreq = MidiMessage::getMidiNoteInHertz (sound->midiRootNote);

    pitchRatio = (targetFreq * sound->sourceSampleRate) / (naturalFreq * getSampleRate());


[/code]

and then pitchRatio is being referenced later on in the renderBlock function (method?)

[code]void SamplerVoice::renderNextBlock (AudioSampleBuffer& outputBuffer, int startSample, int numSamples)
{
const SamplerSound* const playingSound = (SamplerSound*) (SynthesiserSound*) getCurrentlyPlayingSound();

        sourceSamplePosition += pitchRatio;
} [/code]

but what is happening is that the pitchRatio is being set in startNote, and this ratio is not being altered until the next time startNote gets called.

So, what I have tried is altering the scope of targetFreq and naturalFreq

//in startNote()
        /*
        const double targetFreq = MidiMessage::getMidiNoteInHertz (midiNoteNumber);
        const double naturalFreq = MidiMessage::getMidiNoteInHertz (sound->midiRootNote);
        */

        targetFreq = MidiMessage::getMidiNoteInHertz (midiNoteNumber);
        naturalFreq = MidiMessage::getMidiNoteInHertz (sound->midiRootNote);

//in juce_Sampler.h
private:
    double targetFreq, naturalFreq; // I declared them here

so that I can access those variables in the renderNextBlock function…

 const double newpitchRatio = (targetFreq * playingSound->sourceSampleRate) / (naturalFreq * getSampleRate()); //added this line, toward the end of the renderNextBlock function

            //sourceSamplePosition += pitchRatio;
            sourceSamplePosition += newpitchRatio;

and it works!
Now, I realise as I type this that ascertaining newPitchRatio in the renderNextBlock function may be a little expensive, cycle wise, but there is no equivalent to SynthesiserVoice::setCurrentPlaybackSampleRate available in the SamplerVoice class.

The more intelligent thing to do would probably be to add a method to alter the pitchRatio within SamplerVoice, but as it subclasses SynthesiserVoice I’m not quite sure how I would access it given that synth.getVoices() returns a SynthesiserVoice and not a SamplerVoice.

Or maybe it is and I have no idea what I am talking about, but I’m not particularly good at this whole poly inheritance subclassing thingy (you may recall my blatant abuse of TextButtons in my début Drum Machine?)

Other than this extra strain in the renderNextBlock method, I can’t see any real problem with this approach, but if alarm bells are going off on your end, do share!

(if none of that makes sense, I’m happy to post the modified classes for your entertainment)

edit: I’m playing with the tempo slider and occasionally (not too often, two pints and you may not notice) it doesn’t loop perfectly, and… this is off topic, but if I try to change the buffer size, it breaks at jassert (lowerLimit <= upperLimit) in juce_MathsFunctions. I dunno what that means exactly, but I have seen it before and not known then either.

more edit: the maths thing seems to pertain to AudioDeviceSelectorComponent: when I try to open the buffer size combo, it breaks. I am running my laptop with an external screen which is my primary, the laptop screen in the secondary screen. This problem only occurs when I try and open the combo box when it is present in the secondary screen, and I think the reason for this happening is that I have my primary monitor set lower than the secondary monitor (because that’s how it is in real life) and something about getting screen sizes is making it confused


#6

I have two monitors as well, but can’t reproduce any problems with the combo box when they’re set up like that - which OS are you using?


#7

Japanese Windows 7, with an ATI Radeon HD4330 graphics card.

Main Secondary
(external) (laptop)
19201080 1366768 X
800600 800600 OK
800600 1366768 OK
800600 1280768 OK
1024768 800600 X
1024768 1024768 OK
12801024 1024768 X
12801024 1366768 X

Main Secondary
(laptop) (external)
1366768 1280800 OK
1280720 1280800 OK
800600 19201080 OK

It seems to be when the laptop is the secondary monitor and set on a lower resolution that the external monitor that this problem occurs. Below is a copy of the first dozen or so lines from the Call Stack window.

>	miprym.exe!juce::jlimit<int>(const int lowerLimit=0x000001e0, const int upperLimit=0x000000bc, const int valueToConstrain=0x00000218)  Line 148 + 0x34 bytes	C++
 	miprym.exe!juce::PopupMenu::Window::ensureItemIsVisible(const int itemId=0x000001e0, int wantedY=0x00000171)  Line 1007 + 0x3b bytes	C++
 	miprym.exe!juce::PopupMenu::Window::create(const juce::PopupMenu & menu={...}, const bool dismissOnMouseUp=true, juce::PopupMenu::Window * const owner_=0x00000000, const int minX=0x00000aa9, const int maxX=0x00000b21, const int minY=0x00000352, const int maxY=0x0000036a, const int minimumWidth=0x00000078, const int maximumNumColumns=0x00000001, const int standardItemHeight=0x00000018, const bool alignToRectangle=true, const int itemIdThatMustBeVisible=0x000001e0, juce::Component * const menuBarComponent=0x00000000, juce::ApplicationCommandManager * * managerOfChosenCommand=0x0030ed94, juce::Component * const componentAttachedTo=0x02e2f150)  Line 372	C++
 	miprym.exe!juce::PopupMenu::createMenuComponent(const int x=0x00000aa9, const int y=0x00000352, const int w=0x00000078, const int h=0x00000018, const int itemIdThatMustBeVisible=0x000001e0, const int minimumWidth=0x00000078, const int maximumNumColumns=0x00000001, const int standardItemHeight=0x00000018, const bool alignToRectangle=true, juce::Component * menuBarComponent=0x00000000, juce::ApplicationCommandManager * * managerOfChosenCommand=0x0030ed94, juce::Component * const componentAttachedTo=0x02e2f150)  Line 1543 + 0x5c bytes	C++
 	miprym.exe!juce::PopupMenu::showMenu(const int x=0x00000aa9, const int y=0x00000352, const int w=0x00000078, const int h=0x00000018, const int itemIdThatMustBeVisible=0x000001e0, const int minimumWidth=0x00000078, const int maximumNumColumns=0x00000001, const int standardItemHeight=0x00000018, const bool alignToRectangle=true, juce::Component * const componentAttachedTo=0x02e2f150)  Line 1582 + 0x55 bytes	C++
 	miprym.exe!juce::PopupMenu::showAt(juce::Component * componentToAttachTo=0x02e2f150, const int itemIdThatMustBeVisible=0x000001e0, const int minimumWidth=0x00000078, const int maximumNumColumns=0x00000001, const int standardItemHeight=0x00000018)  Line 1659 + 0x42 bytes	C++
 	miprym.exe!juce::ComboBox::showPopup()  Line 554 + 0x1f bytes	C++
 	miprym.exe!juce::ComboBox::mouseDown(const juce::MouseEvent & e={...})  Line 578	C++
 	miprym.exe!juce::Component::internalMouseDown(const int x=0x00000048, const int y=0x0000000a, const __int64 time=0x00000126b298ad0c)  Line 2554 + 0x34 bytes	C++
 	miprym.exe!juce::ComponentPeer::handleMouseDown(int x=0x00000048, int y=0x0000000a, const __int64 time=0x00000126b298ad0c)  Line 204	C++
 	miprym.exe!juce::Win32ComponentPeer::doMouseDown(const int x=0x00000372, const int y=0x00000146, const unsigned int wParam=0x00000001)  Line 1367	C++
 	miprym.exe!juce::Win32ComponentPeer::peerWindowProc(HWND__ * h=0x00040cb8, unsigned int message=0x00000201, unsigned int wParam=0x00000001, long lParam=0x01460372)  Line 1864	C++
 	miprym.exe!juce::Win32ComponentPeer::windowProc(HWND__ * h=0x00040cb8, unsigned int message=0x00000201, unsigned int wParam=0x00000001, long lParam=0x01460372)  Line 1808 + 0x18 bytes	C++

taking the window close to the edge of the screen to force other combos to open up doesn’t cause a problem (the “sample rate” or the “midi output” or the “audio device type” combos).

If there are any other measurements I can take, please, yell out.

And another aside (just to derail my own topic properly), while we’re on Japanese windows, this picture may be of interest (no, not school girls with their undies around their ankles).
The tracktion sound card selection doesn’t seem to look real pretty referencing window’s default settings, compared with the native UI of rebirth. When I had a look at the ASIO tab, my Edirol showed up very minimally, but with no funny characters.


#8

Ah, I see - that should be an easy enough fix. Try this, in juce_PopupMenu, line 1005 (just before the jlimit call that’s going wrong).

windowPos.setSize (jmin (windowPos.getWidth(), mon.getWidth()), jmin (windowPos.getHeight(), mon.getHeight()));


#9

fixed!


#10

Back to the loop playing stuff - I’m going to spend my time getting my head around other parts of the library before I get bogged down in fine tuning this, so for others: Beware! My code is flimsy!