You are here

Midi control for QTractor

I'm experimenting with external midi control for qtractor. For example, I have a midi device connected to qtractor that sends cc7 volume changes for a particular channel. Those changes doesn't seem to have any effect on qtractor, in the mixer the volume stays where it is. is there a way, perhaps, to enable such behavior so I could change channel volumes (and maybe other things) from external device?

Best regards,
Marko

Forums: 

I made a little patch, so that qtractor now happily obeys volume/pan controllers from my midi controller. I'm not sure this is "the right way" to do it. It is channel based, meaning that for every volume change received the program tries to find midi tracks corresponding to that channel. This may be a bit awkward, but I don't know how to do it otherwise. Also, qtractorTrackGainCommand with its redo/undo system is omitted; volume and pan is set directly through track->setPanning() and setVolume(). I did this because the first implementation had some feedback issues, events not coming back soon enough; there is a lot of happening if you push many faders at once :) I also had some crashes in appendMessages(), so I commented that out temporarily. Need to investigate that some more.

Below is what I added to void qtractorMainForm::midiControlEvent ( qtractorMidiControlEvent *pCtlEvent )

Bye, Marko

    // handle volume controls
    if (pCtlEvent->controller() == 7) {
        for (int i = 0; i < m_pSession->tracks().count(); i++)  {
            qtractorTrack* pTrack = m_pSession->tracks().at(i);
            if (pTrack && (pTrack->trackType() == qtractorTrack::Midi) &&
                    pTrack->midiChannel() == pCtlEvent->channel()) {
                float fGain  = float(pCtlEvent->value()) / 127.0f;
                // Set track gain directly bypassing undo/redo system
                // to avoid feedback issues with motorized external
                // controllers
                pTrack->setGain(fGain);
                qtractorMidiBus *pMidiBus
                    = static_cast (pTrack->outputBus());
                if (pMidiBus)
                    pMidiBus->setVolume(pTrack, fGain);
                qtractorMixer *pMixer = mixer();
                if (pMixer) {
                    qtractorMixerStrip *pStrip  
                        = pMixer->trackRack()->findStrip(pTrack->monitor());
                    if (pStrip && pStrip->meter())
                        pStrip->meter()->updateGain();
                }
                sCtlText += tr("(track %1, gain %2)").arg(i).arg(fGain);
            }
        }
    }

    // handle pan controls
    if (pCtlEvent->controller() == 10)
    {
        for (int i = 0; i < m_pSession->tracks().count(); i++) {
            qtractorTrack* pTrack = m_pSession->tracks().at(i);
            if (pTrack && (pTrack->trackType() == qtractorTrack::Midi) &&
                    pTrack->midiChannel() == pCtlEvent->channel()) {
                float fPanning  = (float(pCtlEvent->value()) - 63.0f) / 64.0f;
                pTrack->setPanning(fPanning);
                qtractorMidiBus *pMidiBus
                    = static_cast (pTrack->outputBus());
                if (pMidiBus)
                    pMidiBus->setPanning(pTrack, fPanning);
                qtractorMixer *pMixer = mixer();
                if (pMixer) {
                    qtractorMixerStrip *pStrip
                        = pMixer->trackRack()->findStrip(pTrack->monitor());
                    if (pStrip && pStrip->meter())
                        pStrip->meter()->updatePanning();
                }
                sCtlText += tr("(track %1, panning %2)").arg(i).arg(fPanning);
            }
        }
    }
rncbc's picture

Hi Marko,

Awesome! It should work for the purpose at hand with flying colors :) I'll have the chance to take your code and commit to CVS as soon you give your blessing :)

	// Handle volume controls...                                                                                                                                         
	if (pCtlEvent->controller() == 7) {                                                                                                                         
		int iTrack = 0;
		for (qtractorTrack *pTrack = m_pSession->tracks().first();
				pTrack; pTrack = pTrack->next()) {
			if (pTrack->trackType() == qtractorTrack::Midi &&
				pTrack->midiChannel() == pCtlEvent->channel()) {
				float fGain = float(pCtlEvent->value()) / 127.0f;
				// Set track gain/volume directly,
				// bypassing the undo/redo system
				// to avoid feedback issues with
				// motorized external controllers...
				pTrack->setGain(fGain);
				qtractorMidiBus *pMidiBus
					= static_cast (pTrack->outputBus());
				if (pMidiBus)                                              
					pMidiBus->setVolume(pTrack, fGain);
				qtractorMixer *pMixer = mixer();
				if (pMixer) {
					qtractorMixerStrip *pStrip
						= pMixer->trackRack()->findStrip(pTrack->monitor());
					if (pStrip && pStrip->meter())
						pStrip->meter()->updateGain();
				}
				sCtlText += tr("(track %1, gain %2)")
					.arg(iTrack).arg(fGain);
			}
			++iTrack;
		}
	}

	// Handle pan controls...
	if (pCtlEvent->controller() == 10) {
		int iTrack = 0;
		for (qtractorTrack *pTrack = m_pSession->tracks().first();
				pTrack; pTrack = pTrack->next()) {
			if (pTrack->trackType() == qtractorTrack::Midi &&
				pTrack->midiChannel() == pCtlEvent->channel()) {
				// Set track panning directly,
				// bypassing the undo/redo system
				// to avoid feedback issues with
				// motorized external controllers...
				float fPanning = (float(pCtlEvent->value()) - 63.0f) / 64.0f;
				pTrack->setPanning(fPanning);
				qtractorMidiBus *pMidiBus
					= static_cast (pTrack->outputBus());
				if (pMidiBus)
					pMidiBus->setPanning(pTrack, fPanning);
				qtractorMixer *pMixer = mixer();
				if (pMixer) {
					qtractorMixerStrip *pStrip
						= pMixer->trackRack()->findStrip(pTrack->monitor());
					if (pStrip && pStrip->meter())
						pStrip->meter()->updatePanning();
				}
				sCtlText += tr("(track %1, panning %2)")
					.arg(iTrack).arg(fPanning);
			}
			iTrack++;
		}
	}

Hi,

I'm not sure it is CVS ready. I'm getting some really random crashes, meaning: sometimes none after 5 minute intensive control flood, other times immediately after moving one fader.

#0 0x00007fe0f8f8d5f4 in ?? () from /lib/libc.so.6
#1 0x00007fe0f8f8ba45 in memmove () from /lib/libc.so.6
#2 0x00007fe0fbb2253e in snd_seq_drain_output () from /usr/lib/libasound.so.2
#3 0x00000000004aef00 in qtractorMidiEngine::capture (this=0x297b500, pEv=0x2d52380) at src/qtractorMidiEngine.cpp:949
#4 0x00000000004b0b21 in qtractorMidiInputThread::run (this=0x2bf0a70) at src/qtractorMidiEngine.cpp:222
#5 0x00007fe0f9a90362 in ?? () from /usr/lib/libQtCore.so.4
#6 0x00007fe0fac043ea in start_thread () from /lib/libpthread.so.0
#7 0x00007fe0f8feec6d in clone () from /lib/libc.so.6
#8 0x0000000000000000 in ?? ()

But almost certainly there would be the following abort when exiting qtractor application. Note that this doesn't happen when not using bcf.

#0 0x00007f1226a7dfd5 in raise () from /lib/libc.so.6
#1 0x00007f1226a7fb43 in abort () from /lib/libc.so.6
#2 0x00007f1226abefa8 in ?? () from /lib/libc.so.6
#3 0x00007f1226ac4938 in ?? () from /lib/libc.so.6
#4 0x00007f1229665d3d in snd_seq_close () from /usr/lib/libasound.so.2
#5 0x00000000004ac6bc in qtractorMidiEngine::clean (this=0x1775740) at src/qtractorMidiEngine.cpp:1306
#6 0x0000000000465170 in qtractorEngine::close (this=0x1775740) at src/qtractorEngine.cpp:191
#7 0x00000000004fe58c in qtractorSession::close (this=0x17751b0) at src/qtractorSession.cpp:181
#8 0x0000000000565e60 in qtractorMainForm::closeSession (this=0x7fff31ce2880) at src/qtractorMainForm.cpp:1498

rncbc's picture

ouch. too late :) it's already committed to cvs (qtractor-0.2.2.1108+) no sweat.

hmm... i do have my doubts about these snippets, which might just be the root of your troubles. (wrt. volume here)

	qtractorMidiBus *pMidiBus
		= static_cast (pTrack->outputBus());
	if (pMidiBus)
		pMidiBus->setVolume(pTrack, fGain);

are you sure you want to send the events all through the output bus, again and replicated as many tracks of same MIDI channel there is? if you comment out those snippets, what do you get then?

P.S. incidentally I've just ordered a BCF2000; after it arrives I'll have all this troubles in first hand too :))

I thought I need that so external synths would get the volume change event?

I'll try without.

On the other hand, this implementation with midi channels has a drawback; you can't control audio tracks. However, I'm not aware of any standard that would be track based instead of channel based. And documentation on the topic seem to be scarce. I looked at your implementation for US-224, is that following any standard or is it just a tweak to make work? If it's a standard, perhaps it could be used also to control pan?

ad PS: Great news :)

rncbc's picture

I believe the US-224 (and US-428) is trying to replicate the JL Cooper control surfaces protocol standards :)

byee

You are right. Removing MidiBus->setVolume updates fixes my MidiEngine crashes. I removed it for pan, also. Works fine now. Thanks.

rncbc's picture

Good to know. I'll remove from CVS as well :)) Anyway, as soon my BCF2000 gets into the picture (hopefully) I'll have a new approach on this MIDI control issues, specially regarding control feedback in for that (lovely) motorized faders :)

Cheers

Great news on the BCF2000 purchase. I too have a BCF2000 and have never used it within Linux. Perhaps soon I'll get the chance under Qtractor. :)

Lexridge

rncbc's picture

Let's see what comes. I bought it on a ebay.co.uk auction and should arrive before this week-end, if the seller wants any kind of feedback :)

Cheers

Subject says it all.

Lexridge

rncbc's picture

Awe! You must have medium capabilities :) It just arrived today, a few minutes ago and is still in the box. Only tonight I'll have the spare time to plug it in and check whether it works as advertised :)

Yeah, development of MIDI controller mapping and learning will start (really?) soon, but give me a couple of weeks to put up with something in my head first :))

Cheers!

I also bought mine from ebay, and it was a great purchase. In fact, at the time I was so excited about it, I almost bought a few more of them, and three of the rotary versions as well. My plan was to disassemble them, and put them all into a single mixer box (sorta like a MidiBox - http://www.ucapps.de/ ), arranged like a typical analog mixer, with several assignable knobs being above each fader. Anyway, as my free time became less and less, and the U.S. economy started slipping away, the idea went nowhere.

Good luck with it, and I'm sure we will see some great results. :) The Behringer B-Control software is JAVA based, and does run under Linux, but I have not had the hardware hooked up to it when running it. Something I will try soon.

Lexridge

rncbc's picture

Yes, I've been told that those rotary knobs are quite rare in retail, and plenty of hardware hackers buy the BCR2000 just to tear it apart for the knobs, which are pretty geeky in the dark, I confess :)

Byee

Hi Rui,
Just curious since you never said here one way or the other...did you ever get your BCR2000 working? I'd guess no, seeing all the new features you have been adding to Qtractor. ;)

The new Tempo Mapping looks very promising!
Is the Normalize feature destructive or non-destructive?
The separate outputs for instruments looks interesting, but I've not tried it yet.

take care,
Lexridge

rncbc's picture

It's a BCF2000 actually. And yes, you're dead right. I've failed to say whether it works or not. Just because, and you can start laughing now, I've also failed to get it out of the box :o) Oh yes, it's still in there :(. But, now for the good news, I have taken the manual out and read it, YES!!! That was the day when it arrived. Never got it on since then. Oh my.

And that's not all. Would you be surprised if I say I do have a dozen of other gear that it's suffering from the same, uh, fate? Uber-Procrastinator should be my middle name :)))

Now that I think of it....

Cheers.

Hi Rui,

Thanks a lot for your beautiful software!
I'm really looking forward to using the knobs on my m-audio axiom 49 to control qtractor and also to be able to draw nice automation. :)
Do you have any ideas of when those features will be implemented?

Cheers.

rncbc's picture

I'm working on this next release, which will bump directly to 0.4.0, featuring a complete time-scale infrastructure redesign to support tempo/time-signature maps. It's been (and still is) a hell of a change, pretty pervasive, although not visible from the outside, it's like a brand new beast in its guts.

All that to say that it's taking all my time in this (re)implementation, since last year's Xmas. Sorry to tell, all other planned features have been put on hold, MIDI learn/control mapping and automation included.

But don't despair; custom MIDI control will follow soon, and only after that, automation. Just can't really tell when :) We're all going through very stressful times, ain't we? ;)

Cheers

Hey guys,

at first. Let me THANK YOU for this great piece of software. I just compiled the newest version (v0.4.1) and have a lot of fun with it!

I am also using the BCF2000 and it would be great if I could use it for level control of some channels. Is there a way to do this already? Should I take the code posted above to activate it? I'm familiar with programming and I am an audio engineer, so if I am able in helping such a cool project, I surely will.

Cheers

rncbc's picture

There's one BCF2000 preset which might be of use but only if you deal with MIDI track/channels. The code posted above is already in place but that doesn't solve the audio tracks issue. You'll have to wait for MIDI controller mapping feature, with planned implementation (see TODO list), when you'll be able to assign/teach each applicable widget and/or plugin parameter to follow an external MIDI controller, like the BCF2000.

Cheers

Hey, thanks for that answer.
I tried it out and level an pan control for midi channels is really working. But is it only implemented into one direction, from Controller->PC? As the BCF includes motor-faders it would be great when the level data could be send out on event change.

I also tried to have a look into the code as I am really interested in helping creating this great software, but unfortunately I am missing a place to start... (At work, I am an embedded programmer for a manufacturer of big Live Audio consoles...)

Btw: I reallized some problems when restarting the playback position or moving around in the song, Qtractor stops for about 3 seconds and then message "dssi-vst-server[1]: reset" and "dssi-vst-server[1]: set sample rate to 44100" is displayed. Is this a problem with dssi-vst and has nothing to do with qtractor?

rncbc's picture

Yes, in general, the current MIDI controller implementation only deals with standard MIDI track's channel volume and pan controls (CC#7 and CC#10) and that is only applicable to MIDI tracks which are mapped to their corresponding MIDI channels. This is the only scenario where you can to get MIDI controller feedback, as channel volume and pan events are always sent out on session initialization and when playback (re)starts.

There's also a very limited JL Cooper control map, which is currently hardwired and only affects track volumes. This has only been set as a (personal) convenience and yes, only works in one direction (controller -> PC). Feedback (PC -> controller) is not supported.

Re. the DSSI-VST issue seems quite annoying and probably is due to the way plugin interface communication is being carried out beyond the sheets. Can't really tell what's what at the moment and with the given information. Try to find out whether the so called stickiness applies to all plugins that get loaded through DSSI-VST (ie. windows win32 plugins, ain't that so?) or are only one or some to blame ;)

Cheers

Are the volume and panel events sent to every midi device? I realized, that i checked the option "dedidaced midi control input/ouput" and connected the BCF to this control input/output. But I could not get any feedback during playback. Can you give me a starting point in the sources where the midi control data is sent to the device? I think it should be somewhere in qtractorMainForm::midiControlEvent(). There's also the JL Cooper control map, you talked about and I'm pretty sure, that I can use this entries to build a custom map. But I am missing the point, where the control data is sent to the midi device.

Please do not mis-interpret my posts: I do NOT want to create more and more TODOs for you. I just want to help you, because this project seems very very promising for me...

The dssi-vst issue: I will make some further testings and maybe I should start a new thread about that...

rncbc's picture

The MIDI track channel volume and panel events are sent to regular output bus, to which the MIDI track is assigned, not the dedicated control bus. If you setup a dedicated output control bus you'll miss those events. If you really want, you can turn the dedicated control outputs off and connect the MIDI Master Out bus to the BCF2000. However now that's where all events go, as it's not dedicated to control events anymore.

Re. qtractorMainForm::midiControlEvent() is where inbound controller events are dispatched (ie. received) and in fact is the single place to look on the JLCooper-like stuff. Take care that, for having proper feedback, you must not replicate the incoming events to output. You would need to send the corresponding MIDI CC events when and only when a session view element (eg. mixer widget slider) or the model data changes--refer to the MVC pattern ;) All that to say that things are not that straight-forward at this time. A direct and naive approach could turn into an endless feedback loop and crash-landing imminent. There are many parts involved and refactoring is always one of such needs. Anyway, all that will be taken care while in proper MIDI controller map implementation so I'll ask for your patience ;)

Cheers

Wow gizzmo, since you are doing embedded firmware for "big Live Audio consoles", you must be doing lots of MIDI CC coding? This certainly is something that would be a great benefit to Qtractor! What do you think, Rui?

Regarding the msg from dssi-vst-server you're getting, my guess is you need to run winecfg and set your wine audio to use jack and set it to whatever samplerate you run jack. This is what I did, and I don't get that message on any DSSI enabled VST plugs.

Rui: As things are finally starting to setting down at work, I will hopefully be able to dig back into the Qtractor manual by late spring to early summer. I cannot work with the latest version though from our friend from New Mexico, and will revert to the last version I submitted to you (v0.1.8 I think...ouch!), if that's okay by you. I would certainly appreciate any help that anyone can offer.

Lexridge

Unfortunately our Live Consoles do not use anything with MIDI. We also have some System-Controllers, but as they are for big systems, the don't use any MIDI things. I think, the smaller mixing consoles (like for musicians) do have some midi CC ports...

But anyway: As I am really amazed about this cool project and i think I will try to support the projectg as good as I can. I do not know, how you managed to have time, but let's see. I started already to get into the sources, and I tried to code a midi mapping class for the controllers. But unfortunately I am also new to this QT stuff as I am a programmer for the downside of the controllers which talk to the DSP system... I think, when I get deeper into the sources, I will have some questions.... Btw: Can you tell me the right way, to aks questions about implementation?

Thanks for your answer with dssi-vst. Sounds like this is the solution as there's always a messages with this "sample rate setting". I will try it out tonight.

rncbc's picture

@gizzmo, hi

You don't really need to be a proficient C++ and/or die-hard Qt programmer to help in. As I think you've already noticed, I do code in a pretty old-school fashion style. You may even ask why I stick with that infamous Hungarian prefix notation still. Truth is (whispering), I was already a Windows SDK(>= 3.0) programmer even before Linux came out from a Finnish closet :)) That counts for almost 19+ years now and old habits just die hard, I admit.

Ask all questions you like, and feel like. Aim at the goal and... shoot!

Cheers

OK. So I did already some hacks to activate Motorfader feedback. I just added a new model which holds a copy of the controller data which the user wants to have mapped to the device. This implementation works, but not really in a fine way. I think I will try to get an overview by walking through the sources and then, after interviewing you I can try to do something productive...

Hey... here I am again, with some small questions. (I hope that's ok, but after some small hacks did work I want to do it in the right way, and ... at the moment I got some time left....)

Now here's what I think we need for the controller-mapping: I think the user should be able to create a list of midi controllers and be able to connect it to different functions. Maybe something like the guys from Hydrogen did with their midi controller mapping.

So, as I see, the midi control events are registered in qtractorMainForm constructor whith method setNotifyCtlType. I think, when the midi ctl event comes in, one should be able to know from which midi bus the information came as the user wants only to connect special midi inputs, or maybe only using the dedicated controller input. What do you think about it?

The second thing is the MVC pattern, you mentioned. Am I right, that the model data is for example inside the properties of a Track (in this case the GAIN property?. So ... the property is changed with the TrackGainCommand (this is what the user will be able to select in Midi-Controller-Map), and therefore, the control data can not be sent back when the model data changes, as this might end up with a delayed loop?

What I think, is that another model (i.e. MidiControlMap) has to be added, with its own data. The midi controller is able to change the MidiControlMap-Data and is notified when data changes. In the other direction, when ModelData changes, the MidiControlMap-Data is updated and generates the midi control data? What do you think about it?

As a last step, I think, there should be a possibility to store the control data somewhere. Maybe this could be on a dedicated bus (in addition to a audio and midi bus). This control data could than be mapped to anything else ... ?!? ...

Ok ... I'm sure, that i mixed up a lot of things here ... but maybe a small discussion will help me to do things better ;-)

rncbc's picture

Yes, all inbound control events are already beinbg trapped and dispatched to qtractorMainForm::midiControlEvent(). That part is already there for you to take use. What's really missing and is currently wrong, if I may say, is the qtractorTrackGainCommand usage in this functionality, mainly because it's not made for the control event feedback at all, yet. When I (we) get that MIDI controller map functionality in place this should be tamed, yes :)

Cheers

Pages

Add new comment