summaryrefslogtreecommitdiff
path: root/muse2/muse/dssihost.cpp
diff options
context:
space:
mode:
authorFlorian Jung <flo@windfisch.org>2011-12-14 15:08:02 +0000
committerFlorian Jung <flo@windfisch.org>2011-12-14 15:08:02 +0000
commitc36a5508aa42e596b005425208054af9a60734b4 (patch)
treefde0504e0c25b8f39ed6f5f7f7332943e4a95c7f /muse2/muse/dssihost.cpp
parent42126f3b398802eb24c8d9acd2591ef4dbe7257d (diff)
pulled fixes from release into trunk
Diffstat (limited to 'muse2/muse/dssihost.cpp')
-rw-r--r--muse2/muse/dssihost.cpp294
1 files changed, 194 insertions, 100 deletions
diff --git a/muse2/muse/dssihost.cpp b/muse2/muse/dssihost.cpp
index 92120dbb..2e4e6d35 100644
--- a/muse2/muse/dssihost.cpp
+++ b/muse2/muse/dssihost.cpp
@@ -99,6 +99,7 @@ static void scanDSSILib(QFileInfo& fi) // ddskrjo removed const for argument
// That way we cover all bases - effect plugins and synths.
// Non-synths will show up in the ladspa effect dialog, while synths will show up here...
// There should be nothing left out...
+ // TIP: Until we add programs to plugins, comment these four checks to load dssi effects as synths, in order to have programs.
if(descr->run_synth ||
descr->run_synth_adding ||
descr->run_multiple_synths ||
@@ -498,6 +499,9 @@ bool DssiSynthIF::init(DssiSynth* s)
int inports = synth->_inports;
if(inports != 0)
{
+ posix_memalign((void**)&audioInSilenceBuf, 16, sizeof(float) * MusEGlobal::segmentSize);
+ memset(audioInSilenceBuf, 0, sizeof(float) * MusEGlobal::segmentSize);
+
audioInBuffers = new float*[inports];
for(int k = 0; k < inports; ++k)
{
@@ -723,6 +727,7 @@ DssiSynthIF::DssiSynthIF(SynthI* s)
controls = 0;
controlsOut = 0;
audioInBuffers = 0;
+ audioInSilenceBuf = 0;
audioOutBuffers = 0;
}
@@ -785,6 +790,9 @@ DssiSynthIF::~DssiSynthIF()
delete[] audioInBuffers;
}
+ if(audioInSilenceBuf)
+ free(audioInSilenceBuf);
+
if(audioOutBuffers)
{
for(unsigned long i = 0; i < synth->_outports; ++i)
@@ -1420,7 +1428,7 @@ bool DssiSynthIF::processEvent(const MusECore::MidiPlayEvent& e, snd_seq_event_t
// getData
//---------------------------------------------------------
-MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MPEventList* el, MusECore::iMPEvent i, unsigned pos, int ports, unsigned n, float** buffer)
+MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MPEventList* el, MusECore::iMPEvent start_event, unsigned pos, int ports, unsigned nframes, float** buffer)
{
//#ifdef DSSI_DEBUG
// fprintf(stderr, "DssiSynthIF::getData elsize:%d pos:%d ports:%d samples:%d processed already?:%d\n", el->size(), pos, ports, n, synti->processed());
@@ -1440,19 +1448,11 @@ MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MP
// All ports must be connected to something!
unsigned long nop, k;
- // First, copy the given input buffers to our local input buffers.
- //np = portsin > synth->_inports ? synth->_inports : portsin;
- //for(k = 0; k < np; ++k)
- // memcpy(audioInBuffers[k], inbuffer[k], sizeof(float) * n);
- //for(; k < portsin; ++k)
- // memset(audioInBuffers[k], 0, sizeof(float) * n);
// Watch our limits.
//willyfoobar-2011-02-13
//old code//np = ports > synth->_outports ? synth->_outports : ports;
nop = ((unsigned long) ports) > synth->_outports ? synth->_outports : ((unsigned long) ports);
- // TODO Number of inports requested?
- //nip = ((unsigned long) iports) > synth->_inports ? synth->_inports : ((unsigned long) iports);
const DSSI_Descriptor* dssi = synth->dssi;
const LADSPA_Descriptor* descr = dssi->LADSPA_Plugin;
@@ -1484,18 +1484,109 @@ MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MP
// Note for dssi-vst this MUST equal MusEGlobal::audio period. It doesn't like broken-up runs (it stutters),
// even with fixed sizes. Could be a Wine + Jack thing, wanting a full Jack buffer's length.
//unsigned long fixedsize = 2048;
- unsigned long fixedsize = n;
+ unsigned long fixedsize = nframes;
// For now, the fixed size is clamped to the MusEGlobal::audio buffer size.
// TODO: We could later add slower processing over several cycles -
// so that users can select a small MusEGlobal::audio period but a larger control period.
- if(fixedsize > n)
- fixedsize = n;
+ if(fixedsize > nframes)
+ fixedsize = nframes;
unsigned long min_per = MusEGlobal::config.minControlProcessPeriod;
- if(min_per > n)
- min_per = n;
+ if(min_per > nframes)
+ min_per = nframes;
+
+ //
+ // p4.0.38 Handle inputs...
+ //
+ if(!((MusECore::AudioTrack*)synti)->noInRoute())
+ {
+ RouteList* irl = ((MusECore::AudioTrack*)synti)->inRoutes();
+ iRoute i = irl->begin();
+ if(i->track->isMidiTrack())
+ {
+ //if(MusEGlobal::debugMsg)
+ //printf("DssiSynthIF::getData: Error: First route is a midi track route!\n");
+ }
+ else
+ {
+ int ch = i->channel == -1 ? 0 : i->channel;
+ int remch = i->remoteChannel == -1 ? 0 : i->remoteChannel;
+ int chs = i->channels == -1 ? 0 : i->channels;
+
+ if((unsigned)ch < synth->_inports && (unsigned)(ch + chs) <= synth->_inports)
+ {
+ //printf("DssiSynthIF::getData calling copyData on %s ch:%d remch:%d chs:%d\n", i->track->name().toLatin1().constData(), ch, remch, chs);
+
+ int h = remch + chs;
+ for(int j = remch; j < h; ++j)
+ {
+ //printf(" setting used idx:%d\n", j);
+ synth->iUsedIdx[j] = true;
+ }
+
+ ((MusECore::AudioTrack*)i->track)->copyData(pos, chs, ch, -1, nframes, &audioInBuffers[remch]);
+ }
+ }
+
+ ++i;
+ for(; i != irl->end(); ++i)
+ {
+ if(i->track->isMidiTrack())
+ {
+ //if(MusEGlobal::debugMsg)
+ // printf("DssiSynthIF::getData: Error: Route is a midi track route!\n");
+ continue;
+ }
+
+ int ch = i->channel == -1 ? 0 : i->channel;
+ int remch = i->remoteChannel == -1 ? 0 : i->remoteChannel;
+ int chs = i->channels == -1 ? 0 : i->channels;
+ if((unsigned)ch < synth->_inports && (unsigned)(ch + chs) <= synth->_inports)
+ {
+ //printf("DssiSynthIF::getData calling addData on %s ch:%d remch:%d chs:%d\n", i->track->name().toLatin1().constData(), ch, remch, chs);
+
+ bool u1 = synth->iUsedIdx[remch];
+ if(chs >= 2)
+ {
+ bool u2 = synth->iUsedIdx[remch + 1];
+ if(u1 && u2)
+ ((MusECore::AudioTrack*)i->track)->addData(pos, chs, ch, -1, nframes, &audioInBuffers[remch]);
+ else
+ if(!u1 && !u2)
+ ((MusECore::AudioTrack*)i->track)->copyData(pos, chs, ch, -1, nframes, &audioInBuffers[remch]);
+ else
+ {
+ if(u1)
+ ((MusECore::AudioTrack*)i->track)->addData(pos, 1, ch, 1, nframes, &audioInBuffers[remch]);
+ else
+ ((MusECore::AudioTrack*)i->track)->copyData(pos, 1, ch, 1, nframes, &audioInBuffers[remch]);
+
+ if(u2)
+ ((MusECore::AudioTrack*)i->track)->addData(pos, 1, ch + 1, 1, nframes, &audioInBuffers[remch + 1]);
+ else
+ ((MusECore::AudioTrack*)i->track)->copyData(pos, 1, ch + 1, 1, nframes, &audioInBuffers[remch + 1]);
+ }
+ }
+ else
+ {
+ if(u1)
+ ((MusECore::AudioTrack*)i->track)->addData(pos, 1, ch, -1, nframes, &audioInBuffers[remch]);
+ else
+ ((MusECore::AudioTrack*)i->track)->copyData(pos, 1, ch, -1, nframes, &audioInBuffers[remch]);
+ }
+
+ int h = remch + chs;
+ for(int j = remch; j < h; ++j)
+ {
+ //printf(" setting used idx:%d\n", j);
+ synth->iUsedIdx[j] = true;
+ }
+ }
+ }
+ }
+
// Process automation control values now.
// TODO: This needs to be respect frame resolution. Put this inside the sample loop below.
if(MusEGlobal::automation && synti && synti->automationType() != AUTO_OFF && id() != -1)
@@ -1507,11 +1598,11 @@ MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MP
}
}
- while(sample < n)
+ while(sample < nframes)
{
//unsigned long nsamp = n;
//unsigned long nsamp = n - sample;
- unsigned long nsamp = usefixedrate ? fixedsize : n - sample;
+ unsigned long nsamp = usefixedrate ? fixedsize : nframes - sample;
bool found = false;
unsigned long frame = 0;
unsigned long index = 0;
@@ -1525,7 +1616,7 @@ MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MP
// The events happened in the last period or even before that. Shift into this period with + n. This will sync with MusEGlobal::audio.
// If the events happened even before current frame - n, make sure they are counted immediately as zero-frame.
//evframe = (pos + frameOffset > v.frame + n) ? 0 : v.frame - pos - frameOffset + n;
- evframe = (syncFrame > v.frame + n) ? 0 : v.frame - syncFrame + n;
+ evframe = (syncFrame > v.frame + nframes) ? 0 : v.frame - syncFrame + nframes;
// Protection. Observed this condition. Why? Supposed to be linear timestamps.
if(found && evframe < frame)
{
@@ -1545,7 +1636,7 @@ MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MP
//if(v.frame < startPos || v.frame >= (endPos + frameOffset)
//if(evframe < sample || evframe >= n
//if(evframe < sample || evframe >= (n + frameOffset)
- if(evframe >= n
+ if(evframe >= nframes
//|| (found && v.frame != frame)
//|| (!usefixedrate && found && !v.unique && v.frame != frame)
//|| (found && !v.unique && evframe != frame)
@@ -1567,6 +1658,31 @@ MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MP
index = v.idx;
// Set the ladspa control port value.
controls[v.idx].val = v.value;
+
+ // Need to update the automation value, otherwise it overwrites later with the last MusEGlobal::automation value.
+ if(id() != -1)
+ {
+ // Since we are now in the audio thread context, there's no need to send a message,
+ // just modify directly.
+ //MusEGlobal::audio->msgSetPluginCtrlVal(_track, genACnum(_id, k), controls[k].val);
+ synti->setPluginCtrlVal(genACnum(id(), v.idx), v.value);
+
+ // Record automation.
+ // NO! Take care of this immediately in the OSC control handler, because we don't want
+ // any delay.
+ // OTOH Since this is the actual place and time where the control ports values
+ // are set, best to reflect what happens here to automation.
+ // However for dssi-vst it might be best to handle it that way.
+
+ //AutomationType at = _track->automationType();
+ // TODO: Taken from our native gui control handlers.
+ // This may need modification or may cause problems -
+ // we don't have the luxury of access to the dssi gui controls !
+ //if(at == AUTO_WRITE || (MusEGlobal::audio->isPlaying() && at == AUTO_TOUCH))
+ // enableController(k, false);
+ //_track->recordAutomation(id, v.value);
+ }
+
}
// Process automation control values now.
@@ -1583,8 +1699,8 @@ MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MP
if(found && !usefixedrate)
//nsamp = frame - sample + 1;
nsamp = frame - sample;
- if(sample + nsamp >= n) // Safety check.
- nsamp = n - sample;
+ if(sample + nsamp >= nframes) // Safety check.
+ nsamp = nframes - sample;
//printf("DssiSynthIF::getData n:%d frame:%lu sample:%lu nsamp:%lu pos:%d fOffset:%d syncFrame:%lu loopcount:%d\n",
// n, frame, sample, nsamp, pos, frameOffset, syncFrame, loopcount); // REMOVE Tim.
@@ -1596,10 +1712,10 @@ MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MP
nevents = 0;
// Process event list events...
- for(; i != el->end(); ++i)
+ for(; start_event != el->end(); ++start_event)
{
//if(i->time() >= (endPos + frameOffset)) // NOTE: frameOffset? Tested, examined printouts of times: Seems OK for playback.
- if(i->time() >= (pos + sample + nsamp + frameOffset)) // frameOffset? Test again...
+ if(start_event->time() >= (pos + sample + nsamp + frameOffset)) // frameOffset? Test again...
break;
#ifdef DSSI_DEBUG
@@ -1611,40 +1727,40 @@ MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MP
if(synti->midiPort() != -1)
{
MusECore::MidiPort* mp = &MusEGlobal::midiPorts[synti->midiPort()];
- if(i->type() == MusECore::ME_CONTROLLER)
+ if(start_event->type() == MusECore::ME_CONTROLLER)
{
- int da = i->dataA();
- int db = i->dataB();
+ int da = start_event->dataA();
+ int db = start_event->dataB();
db = mp->limitValToInstrCtlRange(da, db);
- if(!mp->setHwCtrlState(i->channel(), da, db))
+ if(!mp->setHwCtrlState(start_event->channel(), da, db))
continue;
}
else
- if(i->type() == MusECore::ME_PITCHBEND)
+ if(start_event->type() == MusECore::ME_PITCHBEND)
{
- int da = mp->limitValToInstrCtlRange(MusECore::CTRL_PITCH, i->dataA());
- if(!mp->setHwCtrlState(i->channel(), MusECore::CTRL_PITCH, da))
+ int da = mp->limitValToInstrCtlRange(MusECore::CTRL_PITCH, start_event->dataA());
+ if(!mp->setHwCtrlState(start_event->channel(), MusECore::CTRL_PITCH, da))
continue;
}
else
- if(i->type() == MusECore::ME_PROGRAM)
+ if(start_event->type() == MusECore::ME_PROGRAM)
{
- if(!mp->setHwCtrlState(i->channel(), MusECore::CTRL_PROGRAM, i->dataA()))
+ if(!mp->setHwCtrlState(start_event->channel(), MusECore::CTRL_PROGRAM, start_event->dataA()))
continue;
}
}
// Returns false if the event was not filled. It was handled, but some other way.
- if(processEvent(*i, &events[nevents]))
+ if(processEvent(*start_event, &events[nevents]))
{
// Time-stamp the event. p4.0.15 Tim.
- int ft = i->time() - frameOffset - pos;
+ int ft = start_event->time() - frameOffset - pos;
if(ft < 0)
ft = 0;
//if (ft >= (int)MusEGlobal::segmentSize)
if (ft >= int(sample + nsamp))
{
- printf("DssiSynthIF::getData: eventlist event time:%d out of range. pos:%d offset:%d ft:%d sample:%lu nsamp:%lu\n", i->time(), pos, frameOffset, ft, sample, nsamp);
+ printf("DssiSynthIF::getData: eventlist event time:%d out of range. pos:%d offset:%d ft:%d sample:%lu nsamp:%lu\n", start_event->time(), pos, frameOffset, ft, sample, nsamp);
///if (ft > (int)MusEGlobal::segmentSize)
//ft = MusEGlobal::segmentSize - 1;
ft = sample + nsamp - 1;
@@ -1696,61 +1812,6 @@ MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MP
}
}
- /*
- //
- // p3.3.39 Handle inputs...
- //
- //if((MusEGlobal::song->bounceTrack != this) && !noInRoute())
- if(!((MusECore::AudioTrack*)synti)->noInRoute())
- {
- RouteList* irl = ((MusECore::AudioTrack*)synti)->inRoutes();
- iRoute i = irl->begin();
- if(!i->track->isMidiTrack())
- {
- //if(MusEGlobal::debugMsg)
- printf("DssiSynthIF::getData: Error: First route is a midi track route!\n");
- }
- else
- {
- int ch = i->channel == -1 ? 0 : i->channel;
- int remch = i->remoteChannel == -1 ? 0 : i->remoteChannel;
- int chs = i->channels == -1 ? 0 : i->channels;
-
- // TODO:
- //if(ch >= synth->_inports)
- //iUsedIdx[ch] = true;
- //if(chs == 2)
- // iUsedIdx[ch + 1] = true;
-
- //((MusECore::AudioTrack*)i->track)->copyData(framePos, channels, nframe, bp);
- ((MusECore::AudioTrack*)i->track)->copyData(pos, ports,
- //(i->track->type() == Track::AUDIO_SOFTSYNTH && i->channel != -1) ? i->channel : 0,
- i->channel,
- i->channels,
- n, bp);
- }
-
- //unsigned pos, int ports, unsigned n, float** buffer
-
- ++i;
- for(; i != irl->end(); ++i)
- {
- if(i->track->isMidiTrack())
- {
- //if(MusEGlobal::debugMsg)
- printf("DssiSynthIF::getData: Error: Route is a midi track route!\n");
- continue;
- }
- //((MusECore::AudioTrack*)i->track)->addData(framePos, channels, nframe, bp);
- ((MusECore::AudioTrack*)i->track)->addData(framePos, channels,
- //(i->track->type() == Track::AUDIO_SOFTSYNTH && i->channel != -1) ? i->channel : 0,
- i->channel,
- i->channels,
- nframe, bp);
- }
- }
- */
-
k = 0;
// Connect the given buffers directly to the ports, up to a max of synth ports.
for(; k < nop; ++k)
@@ -1758,9 +1819,21 @@ MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MP
// Connect the remaining ports to some local buffers (not used yet).
for(; k < synth->_outports; ++k)
descr->connect_port(handle, synth->oIdx[k], audioOutBuffers[k] + sample);
- // Just connect all inputs to some local buffers (not used yet). TODO: Support inputs.
+ // Connect all inputs either to some local buffers, or a silence buffer.
for(k = 0; k < synth->_inports; ++k)
- descr->connect_port(handle, synth->iIdx[k], audioInBuffers[k] + sample);
+ {
+ //printf(" k:%d synth->iIdx[k]:%d\n", k, synth->iIdx[k]);
+ if(synth->iUsedIdx[k])
+ {
+ synth->iUsedIdx[k] = false; // Reset
+ descr->connect_port(handle, synth->iIdx[k], audioInBuffers[k] + sample);
+ }
+ else
+ {
+ //printf(" input used size:%ld idx:%ld = %d silencing...\n", synth->iUsedIdx.size(), k, synth->iUsedIdx[k]);
+ descr->connect_port(handle, synth->iIdx[k], audioInSilenceBuf + sample);
+ }
+ }
// Run the synth for a period of time. This processes events and gets/fills our local buffers...
if(synth->dssi->run_synth)
@@ -1772,10 +1845,10 @@ MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MP
snd_seq_event_t* ev = events;
synth->dssi->run_multiple_synths(1, &handle, nsamp, &ev, &nevents);
}
+ // TIP: Until we add programs to plugins, uncomment these four checks to load dssi effects as synths, in order to have programs.
//else
//if(synth->dssi->LADSPA_Plugin->run)
//{
- // // Just a test, worked OK.
// synth->dssi->LADSPA_Plugin->run(handle, nsamp);
//}
@@ -1783,14 +1856,13 @@ MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MP
loopcount++; // REMOVE Tim.
}
- return i;
+ return start_event;
}
//---------------------------------------------------------
// putEvent
//---------------------------------------------------------
-//bool DssiSynthIF::putEvent(const MidiEvent& ev)
bool DssiSynthIF::putEvent(const MusECore::MidiPlayEvent& ev)
{
#ifdef DSSI_DEBUG
@@ -2033,16 +2105,37 @@ int DssiSynthIF::oscControl(unsigned long port, float value)
fprintf(stderr, "DssiSynthIF::oscControl: fifo overflow: in control number:%lu\n", cport);
}
- MusECore::ciMidiCtl2LadspaPort ip = synth->port2MidiCtlMap.find(cport);
- if(ip != synth->port2MidiCtlMap.end())
+ // Record automation:
+ // Take care of this immediately, because we don't want the silly delay associated with
+ // processing the fifo one-at-a-time in the apply().
+ // NOTE: With some vsts we don't receive control events until the user RELEASES a control.
+ // So the events all arrive at once when the user releases a control.
+ // That makes this pretty useless... But what the heck...
+ if(id() != -1)
{
+ //int id = genACnum(_id, cport);
+ unsigned long pid = genACnum(id(), cport);
+ AutomationType at = synti->automationType();
+
+ // TODO: Taken from our native gui control handlers.
+ // This may need modification or may cause problems -
+ // we don't have the luxury of access to the dssi gui controls !
+ if(at == AUTO_WRITE || (MusEGlobal::audio->isPlaying() && at == AUTO_TOUCH))
+ enableController(cport, false);
+
+ synti->recordAutomation(pid, value);
+ }
+
+ //MusECore::ciMidiCtl2LadspaPort ip = synth->port2MidiCtlMap.find(cport);
+ //if(ip != synth->port2MidiCtlMap.end())
+ //{
// TODO: TODO: Update midi MusE's midi controller knobs, sliders, boxes etc with a call to the midi port's setHwCtrlState() etc.
// But first we need a ladspa2MidiValue() function! ...
//
//
//float val = ladspa2MidiValue(ld, i, ?, ?);
- }
+ //}
return 0;
}
@@ -2064,8 +2157,8 @@ int DssiSynthIF::oscMidi(int a, int b, int c)
{
MusECore::MidiPlayEvent event(0, port, channel, a, b, c);
- #ifdef DSSI_DEBUG
- printf(stderr, "DssiSynthIF::oscMidi midi event chn:%d a:%d b:%d\n", event.channel(), event.dataA(), event.dataB());
+ #ifdef DSSI_DEBUG
+ printf("DssiSynthIF::oscMidi midi event chn:%d a:%d b:%d\n", event.channel(), event.dataA(), event.dataB());
#endif
MusEGlobal::midiPorts[port].sendEvent(event);
@@ -2329,7 +2422,8 @@ QString DssiSynthIF::name() const { return synti->nam
QString DssiSynthIF::lib() const { return synth ? synth->completeBaseName() : QString(); }
QString DssiSynthIF::dirPath() const { return synth ? synth->absolutePath() : QString(); }
QString DssiSynthIF::fileName() const { return synth ? synth->fileName() : QString(); }
-MusECore::AudioTrack* DssiSynthIF::track() { return (MusECore::AudioTrack*)synti; }
+QString DssiSynthIF::titlePrefix() const { return QString(); }
+MusECore::AudioTrack* DssiSynthIF::track() { return (MusECore::AudioTrack*)synti; }
void DssiSynthIF::enableController(unsigned long i, bool v) { controls[i].enCtrl = v; }
bool DssiSynthIF::controllerEnabled(unsigned long i) const { return controls[i].enCtrl; }
bool DssiSynthIF::controllerEnabled2(unsigned long i) const { return controls[i].en2Ctrl; }