diff options
author | Florian Jung <flo@windfisch.org> | 2012-07-01 16:42:16 +0000 |
---|---|---|
committer | Florian Jung <flo@windfisch.org> | 2012-07-01 16:42:16 +0000 |
commit | 9c4664d162c537ba4dd4fd8220971c0fb727103a (patch) | |
tree | 37a28b7cd4e4d8984ad4934a4884cd7b4da0505c /muse2/muse | |
parent | e87fedf1be804f7ec774071d844b1f163be30b96 (diff) |
final merge
Diffstat (limited to 'muse2/muse')
65 files changed, 3690 insertions, 1085 deletions
diff --git a/muse2/muse/arranger/arranger.cpp b/muse2/muse/arranger/arranger.cpp index 29e69582..8d786311 100644 --- a/muse2/muse/arranger/arranger.cpp +++ b/muse2/muse/arranger/arranger.cpp @@ -531,7 +531,7 @@ Arranger::Arranger(ArrangerView* parent, const char* name) connect(canvas, SIGNAL(dropMidiFile(const QString&)), SIGNAL(dropMidiFile(const QString&))); connect(canvas, SIGNAL(toolChanged(int)), SIGNAL(toolChanged(int))); - connect(MusEGlobal::song, SIGNAL(controllerChanged(MusECore::Track*)), SLOT(controllerChanged(MusECore::Track*))); + connect(MusEGlobal::song, SIGNAL(controllerChanged(MusECore::Track*, int)), SLOT(controllerChanged(MusECore::Track*, int))); configChanged(); // set configuration values if(canvas->part()) @@ -677,7 +677,7 @@ void Arranger::songChanged(int type) // Keep this light, partsChanged is a heavy move! TEST p4.0.36 Try these, may need more. if(type & (SC_TRACK_INSERTED | SC_TRACK_REMOVED | SC_TRACK_MODIFIED | SC_PART_INSERTED | SC_PART_REMOVED | SC_PART_MODIFIED | - SC_SIG | SC_TEMPO)) // Maybe sig. Requires tempo. + SC_SIG | SC_TEMPO | SC_MASTER)) // Maybe sig. Requires tempo. canvas->partsChanged(); if (type & SC_SIG) @@ -1110,9 +1110,9 @@ void Arranger::clear() // emit redirectWheelEvent(ev); // } -void Arranger::controllerChanged(MusECore::Track *t) +void Arranger::controllerChanged(MusECore::Track *t, int ctrlId) { - canvas->controllerChanged(t); + canvas->controllerChanged(t, ctrlId); } //--------------------------------------------------------- diff --git a/muse2/muse/arranger/arranger.h b/muse2/muse/arranger/arranger.h index e51ec068..60390a8f 100644 --- a/muse2/muse/arranger/arranger.h +++ b/muse2/muse/arranger/arranger.h @@ -186,7 +186,7 @@ class Arranger : public QWidget { void setTool(int); void updateTrackInfo(int flags); void configChanged(); - void controllerChanged(MusECore::Track *t); + void controllerChanged(MusECore::Track *t, int ctrlId); void focusCanvas(); public: diff --git a/muse2/muse/arranger/pcanvas.cpp b/muse2/muse/arranger/pcanvas.cpp index cc23b59b..c5c3ca6d 100644 --- a/muse2/muse/arranger/pcanvas.cpp +++ b/muse2/muse/arranger/pcanvas.cpp @@ -904,12 +904,25 @@ bool PartCanvas::mousePress(QMouseEvent* event) } } case AutomationTool: - if (event->button() & Qt::RightButton) { - QMenu *automationMenu = new QMenu(this); - QAction* act; - act = automationMenu->addAction(tr("Remove selected")); - act = automationMenu->exec(event->globalPos()); - if (act && automation.currentTrack) { + if (event->button() & Qt::RightButton || + event->button() & Qt::MidButton) { + + bool do_delete; + + if (event->button() & Qt::MidButton) // mid-click + do_delete=true; + else // right-click + { + QMenu *automationMenu = new QMenu(this); + QAction* act; + act = automationMenu->addAction(tr("Remove selected")); + act = automationMenu->exec(event->globalPos()); + if (act) + do_delete=true; + else + do_delete=false; + } + if (do_delete && automation.currentTrack) { foreach(int frame, automation.currentCtrlFrameList) MusEGlobal::audio->msgEraseACEvent((MusECore::AudioTrack*)automation.currentTrack, automation.currentCtrlList->id(), frame); @@ -2683,6 +2696,7 @@ void PartCanvas::cmd(int cmd) case 0: paste_mode=PASTEMODE_MIX; break; case 1: paste_mode=PASTEMODE_MOVEALL; break; case 2: paste_mode=PASTEMODE_MOVESOME; break; + default: paste_mode=PASTEMODE_MIX; // shall never be executed } paste(paste_dialog->clone, paste_mode, paste_dialog->all_in_one_track, @@ -3411,7 +3425,7 @@ void PartCanvas::drawTopItem(QPainter& p, const QRect& rect) yy += th; } - unsigned int startPos = MusEGlobal::audio->getStartRecordPos().tick(); + unsigned int startPos = MusEGlobal::extSyncFlag.value() ? MusEGlobal::audio->getStartExternalRecTick() : MusEGlobal::audio->getStartRecordPos().tick(); if (MusEGlobal::song->punchin()) startPos=MusEGlobal::song->lpos(); int startx = mapx(startPos); @@ -3663,19 +3677,36 @@ void PartCanvas::drawAutomation(QPainter& p, const QRect& rr, MusECore::AudioTra bool checkIfOnLine(double mouseX, double mouseY, double firstX, double lastX, double firstY, double lastY, int circumference) { - double proportion = (mouseX-firstX)/(lastX-firstX); - - // 10 X(15) 20 - // proportion = 0.5 - // 10 - // / - // Y(5) - // / - // 1 - double calcY = (lastY-firstY)*proportion+firstY; - if(ABS(calcY-mouseY) < circumference || (lastX == firstX && ABS(mouseX-lastX) < circumference)) - return true; - return false; + if (lastX==firstX) + return (ABS(mouseX-lastX) < circumference); + else if (mouseX < firstX || mouseX > lastX+circumference) // (*) + return false; + else + { + double proportion = (mouseX-firstX)/(lastX-firstX); // a value between 0 and 1, where firstX->0 and lastX->1 + double calcY = (lastY-firstY)*proportion+firstY; // where the drawn line's y-coord is at mouseX + double slope = (lastY-firstY)/(lastX-firstX); + + return (ABS(calcY-mouseY) < (circumference * sqrt(1+slope*slope))); + // this is equivalent to circumference / cos( atan(slope) ). to + // verify, draw a sloped line (the graph), a 90°-line to it with + // length "circumference". from the (unconnected) endpoint of that + // line, draw a vertical line down to the sloped line. + // use slope=tan(alpha) <==> alpha=atan(slope) and + // cos(alpha) = adjacent side / hypothenuse (hypothenuse is what we + // want, and adjacent = circumference). + // to optimize: this looks similar to abs(slope)+1 + + //return (ABS(calcY-mouseY) < circumference); + } + + /* without the +circumference in the above if statement (*), moving + * the mouse towards a control point from the right would result in + * the line segment from the targeted point to the next to be con- + * sidered, but not the segment from the previous to the targeted. + * however, only points for which the line segment they _end_ is + * under the cursor are considered, so we need to enlengthen this + * a bit (flo93)*/ } //--------------------------------------------------------- @@ -3684,12 +3715,7 @@ bool checkIfOnLine(double mouseX, double mouseY, double firstX, double lastX, do bool checkIfNearPoint(int mouseX, int mouseY, int eventX, int eventY, int circumference) { - int x1 = ABS(mouseX - eventX) ; - int y1 = ABS(mouseY - eventY); - if (x1 < circumference && y1 < circumference) { - return true; - } - return false; + return (ABS(mouseX - eventX) < circumference && ABS(mouseY - eventY) < circumference); } //--------------------------------------------------------- @@ -3703,7 +3729,7 @@ bool checkIfNearPoint(int mouseX, int mouseY, int eventX, int eventY, int circum // controller added. //--------------------------------------------------------- -void PartCanvas::checkAutomation(MusECore::Track * t, const QPoint &pointer, bool NOTaddNewCtrl) +void PartCanvas::checkAutomation(MusECore::Track * t, const QPoint &pointer, bool /*NOTaddNewCtrl*/) { if (t->isMidiTrack()) return; @@ -3751,7 +3777,7 @@ void PartCanvas::checkAutomation(MusECore::Track * t, const QPoint &pointer, boo } else // we have automation, loop through it { - for (; ic !=cl->end(); ic++) + for (; ic!=cl->end(); ic++) { double y = ic->second.val; if (cl->valueType() == MusECore::VAL_LOG ) { // use db scale for volume @@ -3774,7 +3800,7 @@ void PartCanvas::checkAutomation(MusECore::Track * t, const QPoint &pointer, boo eventOldX = eventX; eventOldY = eventY; - + if (onLine) { if (!onPoint) { QWidget::setCursor(Qt::CrossCursor); @@ -3799,7 +3825,7 @@ void PartCanvas::checkAutomation(MusECore::Track * t, const QPoint &pointer, boo // check if we are reasonably close to a line, we only need to check Y // as the line is straight after the last controller //printf("post oldX:%d oldY:%d xpixel:%d ypixel:%d currX:%d currY:%d\n", oldX, oldY, xpixel, ypixel, currX, currY); - if(mouseX >= eventX && eventY == eventOldY && ABS(mouseY-eventY) < circumference) { + if(mouseX >= eventX && ABS(mouseY-eventY) < circumference) { QWidget::setCursor(Qt::CrossCursor); automation.controllerState = addNewController; automation.currentCtrlList = cl; @@ -3817,7 +3843,7 @@ void PartCanvas::checkAutomation(MusECore::Track * t, const QPoint &pointer, boo setCursor(); } -void PartCanvas::controllerChanged(MusECore::Track* t) +void PartCanvas::controllerChanged(MusECore::Track* t, int) { redraw((QRect(0, mapy(t->y()), width(), rmapy(t->height())))); // TODO Check this - correct? } diff --git a/muse2/muse/arranger/pcanvas.h b/muse2/muse/arranger/pcanvas.h index ab227eb2..1b766c5d 100644 --- a/muse2/muse/arranger/pcanvas.h +++ b/muse2/muse/arranger/pcanvas.h @@ -184,7 +184,7 @@ class PartCanvas : public Canvas { public slots: void redirKeypress(QKeyEvent* e) { keyPress(e); } - void controllerChanged(MusECore::Track *t); + void controllerChanged(MusECore::Track *t, int CtrlId); }; } // namespace MusEGui diff --git a/muse2/muse/arranger/tlist.cpp b/muse2/muse/arranger/tlist.cpp index 3d831ba9..05e23321 100644 --- a/muse2/muse/arranger/tlist.cpp +++ b/muse2/muse/arranger/tlist.cpp @@ -22,6 +22,8 @@ #include <cmath> +#include <QAction> +#include <QActionGroup> #include <QKeyEvent> #include <QLineEdit> #include <QMessageBox> @@ -35,6 +37,7 @@ #include <QIcon> #include <QSpinBox> #include <QToolTip> +#include <QList> #include "popupmenu.h" #include "globals.h" @@ -64,6 +67,8 @@ #include "menutitleitem.h" #include "arranger.h" #include "undo.h" +#include "midi_audio_control.h" +#include "ctrl.h" #ifdef DSSI_SUPPORT #include "dssihost.h" @@ -1409,18 +1414,21 @@ MusECore::TrackList TList::getRecEnabledTracks() void TList::changeAutomation(QAction* act) { - if ( (editAutomation->type() == MusECore::Track::MIDI) || (editAutomation->type() == MusECore::Track::DRUM) || (editAutomation->type() == MusECore::Track::NEW_DRUM) ) { - printf("this is wrong, we can't edit automation for midi tracks from arranger yet!\n"); + if(!editAutomation || editAutomation->isMidiTrack()) + return; + if(act->data().toInt() == -1) return; - } int colindex = act->data().toInt() & 0xff; - int id = (act->data().toInt() & 0x00ffffff) / 256; + int id = (act->data().toInt() & 0x00ffffff) >> 8; + // Is it the midi control action or clear action item? + if (colindex == 254 || colindex == 255) + return; + if (colindex < 100) return; // this was meant for changeAutomationColor // one of these days I'll rewrite this so it's understandable // this is just to get it up and running... - MusECore::CtrlListList* cll = ((MusECore::AudioTrack*)editAutomation)->controller(); for(MusECore::CtrlListList::iterator icll =cll->begin();icll!=cll->end();++icll) { MusECore::CtrlList *cl = icll->second; @@ -1435,13 +1443,81 @@ void TList::changeAutomation(QAction* act) //--------------------------------------------------------- void TList::changeAutomationColor(QAction* act) { - if ( (editAutomation->type() == MusECore::Track::MIDI) || (editAutomation->type() == MusECore::Track::DRUM) || (editAutomation->type() == MusECore::Track::NEW_DRUM) ) { - printf("this is wrong, we can't edit automation for midi tracks from arranger yet!\n"); + if(!editAutomation || editAutomation->isMidiTrack()) + return; + if(act->data().toInt() == -1) return; - } int colindex = act->data().toInt() & 0xff; - int id = (act->data().toInt() & 0x00ffffff) / 256; + int id = (act->data().toInt() & 0x00ffffff) >> 8; + // Is it the clear midi control action item? + if(colindex == 254) + { + MusECore::AudioTrack* track = static_cast<MusECore::AudioTrack*>(editAutomation); + MusECore::MidiAudioCtrlMap* macp = track->controller()->midiControls(); + MusECore::AudioMidiCtrlStructMap amcs; + macp->find_audio_ctrl_structs(id, &amcs); + if(!amcs.empty()) + MusEGlobal::audio->msgIdle(true); // Gain access to structures, and sync with audio + for(MusECore::iAudioMidiCtrlStructMap iamcs = amcs.begin(); iamcs != amcs.end(); ++iamcs) + macp->erase(*iamcs); + if(!amcs.empty()) + MusEGlobal::audio->msgIdle(false); + + // Hm, need to remove the 'clear' item, and the status lines below it. Try this: + QActionGroup* midi_actgrp = act->actionGroup(); + if(midi_actgrp) + { + QList<QAction*> act_list = midi_actgrp->actions(); + int sz = act_list.size(); + for(int i = 0; i < sz; ++i) + { + QAction* list_act = act_list.at(i); + ///midi_actgrp->removeAction(list_act); + // list_act has no parent now. + ///delete list_act; + list_act->setVisible(false); // HACK Cannot delete any actions! Causes crash with our PopupMenu due to recent fixes. + } + } + return; + } + + // Is it the midi control action item? + if(colindex == 255) + { + MusECore::AudioTrack* track = static_cast<MusECore::AudioTrack*>(editAutomation); + MusECore::MidiAudioCtrlMap* macm = track->controller()->midiControls(); + MusECore::AudioMidiCtrlStructMap amcs; + macm->find_audio_ctrl_structs(id, &amcs); + + int port = -1, chan = 0, ctrl = 0; + for(MusECore::iAudioMidiCtrlStructMap iamcs = amcs.begin(); iamcs != amcs.end(); ++iamcs) + { + macm->hash_values((*iamcs)->first, &port, &chan, &ctrl); + break; // Only a single item for now, thanks! + } + + MidiAudioControl* pup = new MidiAudioControl(port, chan, ctrl); + + if(pup->exec() == QDialog::Accepted) + { + MusEGlobal::audio->msgIdle(true); // Gain access to structures, and sync with audio + // Erase all for now. + for(MusECore::iAudioMidiCtrlStructMap iamcs = amcs.begin(); iamcs != amcs.end(); ++iamcs) + macm->erase(*iamcs); + + port = pup->port(); chan = pup->chan(); ctrl = pup->ctrl(); + if(port >= 0 && chan >=0 && ctrl >= 0) + // Add will replace if found. + macm->add_ctrl_struct(port, chan, ctrl, MusECore::MidiAudioCtrlStruct(id)); + + MusEGlobal::audio->msgIdle(false); + } + + delete pup; + return; + } + if (colindex > 100) return; // this was meant for changeAutomation // one of these days I'll rewrite this so it's understandable @@ -1461,7 +1537,10 @@ void TList::changeAutomationColor(QAction* act) //--------------------------------------------------------- PopupMenu* TList::colorMenu(QColor c, int id, QWidget* parent) { - PopupMenu * m = new PopupMenu(parent); //, true); //TODO + PopupMenu * m = new PopupMenu(parent, true); + + QActionGroup* col_actgrp = new QActionGroup(m); + col_actgrp->setExclusive(true); for (int i = 0; i< 6; i++) { QPixmap pix(10,10); QPainter p(&pix); @@ -1469,14 +1548,52 @@ PopupMenu* TList::colorMenu(QColor c, int id, QWidget* parent) p.setPen(Qt::black); p.drawRect(0,0,10,10); QIcon icon(pix); - QAction *act = m->addAction(icon,""); + QAction *act = col_actgrp->addAction(icon,""); act->setCheckable(true); if (c == collist[i]) act->setChecked(true); - int data = id * 256; // shift 8 bits - data += i; // color in the bottom 8 bits - act->setData(data); + act->setData((id<<8) + i); // Shift 8 bits. Color in the bottom 8 bits. } + m->addActions(col_actgrp->actions()); + + //m->addSeparator(); + m->addAction(new MenuTitleItem(tr("Midi control"), m)); + + if(editAutomation && !editAutomation->isMidiTrack()) + { + QAction *act = m->addAction(tr("Assign")); + act->setCheckable(false); + act->setData((id<<8) + 255); // Shift 8 bits. Make midi menu the last item at 255. + + MusECore::AudioTrack* track = static_cast<MusECore::AudioTrack*>(editAutomation); + MusECore::MidiAudioCtrlMap* macm = track->controller()->midiControls(); + MusECore::AudioMidiCtrlStructMap amcs; + macm->find_audio_ctrl_structs(id, &amcs); + + // Group only the clear and status items so they can both be easily removed when clear is clicked. + if(!amcs.empty()) + { + QActionGroup* midi_actgrp = new QActionGroup(m); + QAction *cact = midi_actgrp->addAction(tr("Clear")); + cact->setData((id<<8) + 254); // Shift 8 bits. Make clear the second-last item at 254 + for(MusECore::iAudioMidiCtrlStructMap iamcs = amcs.begin(); iamcs != amcs.end(); ++iamcs) + { + int port, chan, mctrl; + macm->hash_values((*iamcs)->first, &port, &chan, &mctrl); + //QString s = QString("Port:%1 Chan:%2 Ctl:%3-%4").arg(port + 1) + QString s = QString("Port:%1 Chan:%2 Ctl:%3").arg(port + 1) + .arg(chan + 1) + //.arg((mctrl >> 8) & 0xff) + //.arg(mctrl & 0xff); + .arg(MusECore::midiCtrlName(mctrl, true)); + QAction *mact = midi_actgrp->addAction(s); + mact->setEnabled(false); + mact->setData(-1); // Not used + } + m->addActions(midi_actgrp->actions()); + } + } + connect(m, SIGNAL(triggered(QAction*)), SLOT(changeAutomationColor(QAction*))); return m; @@ -1611,14 +1728,53 @@ void TList::mousePressEvent(QMouseEvent* ev) p->setTitle(tr("Viewable automation")); MusECore::CtrlListList* cll = ((MusECore::AudioTrack*)t)->controller(); QAction* act = 0; + int last_rackpos = -1; + bool internal_found = false; + bool synth_found = false; for(MusECore::CtrlListList::iterator icll =cll->begin();icll!=cll->end();++icll) { MusECore::CtrlList *cl = icll->second; if (cl->dontShow()) continue; + + int ctrl = cl->id(); + + if(ctrl < AC_PLUGIN_CTL_BASE) + { + if(!internal_found) + p->addAction(new MusEGui::MenuTitleItem(tr("Internal"), p)); + internal_found = true; + } + else + { + if(ctrl < (int)MusECore::genACnum(MAX_PLUGINS, 0)) // The beginning of the special dssi synth controller block. + { + int rackpos = (ctrl - AC_PLUGIN_CTL_BASE) >> AC_PLUGIN_CTL_BASE_POW; + if(rackpos < PipelineDepth) + { + if(rackpos != last_rackpos) + { + QString s = ((MusECore::AudioTrack*)t)->efxPipe()->name(rackpos); + p->addAction(new MusEGui::MenuTitleItem(s, p)); + } + last_rackpos = rackpos; + } + } + else + { + if(t->type() == MusECore::Track::AUDIO_SOFTSYNTH) + { + if(!synth_found) + p->addAction(new MusEGui::MenuTitleItem(tr("Synth"), p)); + synth_found = true; + } + } + } + act = p->addAction(cl->name()); act->setCheckable(true); act->setChecked(cl->isVisible()); - int data = cl->id() * 256; // shift 8 bits + + int data = ctrl<<8; // shift 8 bits data += 150; // illegal color > 100 act->setData(data); PopupMenu *m = colorMenu(cl->color(), cl->id(), p); diff --git a/muse2/muse/audio.cpp b/muse2/muse/audio.cpp index cbcbd922..6349971b 100644 --- a/muse2/muse/audio.cpp +++ b/muse2/muse/audio.cpp @@ -45,6 +45,19 @@ #include "pos.h" #include "ticksynth.h" +// Experimental for now - allow other Jack timebase masters to control our midi engine. +// TODO: Be friendly to other apps and ask them to be kind to us by using jack_transport_reposition. +// It is actually required IF we want the extra position info to show up +// in the sync callback, otherwise we get just the frame only. +// This information is shared on the server, it is directly passed around. +// jack_transport_locate blanks the info from sync until the timebase callback reads +// it again right after, from some timebase master. +// Sadly not many of us use jack_transport_reposition. So we need to work around it ! +//#define _JACK_TIMEBASE_DRIVES_MIDI_ + +#ifdef _JACK_TIMEBASE_DRIVES_MIDI_ +#include "jackaudio.h" +#endif namespace MusEGlobal { MusECore::Audio* audio; @@ -102,6 +115,7 @@ const char* seqMsgList[] = { "AUDIO_ADD_AC_EVENT", "AUDIO_CHANGE_AC_EVENT", "AUDIO_SET_SOLO", "AUDIO_SET_SEND_METRONOME", + "AUDIO_START_MIDI_LEARN", "MS_PROCESS", "MS_STOP", "MS_SET_RTC", "MS_UPDATE_POLL_FD", "SEQM_IDLE", "SEQM_SEEK" }; @@ -127,6 +141,10 @@ Audio::Audio() _pos.setType(Pos::FRAMES); _pos.setFrame(0); +#ifdef _AUDIO_USE_TRUE_FRAME_ + _previousPos.setType(Pos::FRAMES); + _previousPos.setFrame(0); +#endif nextTickPos = curTickPos = 0; midiClick = 0; @@ -143,6 +161,8 @@ Audio::Audio() startRecordPos.setType(Pos::FRAMES); // Tim endRecordPos.setType(Pos::FRAMES); + startExternalRecTick = 0; + endExternalRecTick = 0; _audioMonitor = 0; _audioMaster = 0; @@ -384,7 +404,10 @@ void Audio::process(unsigned frames) (*i)->processInit(frames); int samplePos = _pos.frame(); int offset = 0; // buffer offset in audio buffers - +#ifdef _JACK_TIMEBASE_DRIVES_MIDI_ + bool use_jack_timebase = false; +#endif + if (isPlaying()) { if (!freewheel()) MusEGlobal::audioPrefetch->msgTick(); @@ -394,7 +417,22 @@ void Audio::process(unsigned frames) write(sigFd, "F", 1); return; } - + +#ifdef _JACK_TIMEBASE_DRIVES_MIDI_ + unsigned curr_jt_tick, next_jt_ticks; + use_jack_timebase = + MusEGlobal::audioDevice->deviceType() == AudioDevice::JACK_AUDIO && + !MusEGlobal::jackTransportMaster && + !MusEGlobal::song->masterFlag() && + !MusEGlobal::extSyncFlag.value() && + static_cast<MusECore::JackAudioDevice*>(MusEGlobal::audioDevice)->timebaseQuery( + frames, NULL, NULL, NULL, &curr_jt_tick, &next_jt_ticks); + // NOTE: I would rather trust the reported current tick than rely solely on the stream of + // tempos to correctly advance to the next position (which did actually test OK anyway). + if(use_jack_timebase) + curTickPos = curr_jt_tick; +#endif + // // check for end of song // @@ -452,10 +490,19 @@ void Audio::process(unsigned frames) } else { - - Pos ppp(_pos); - ppp += frames; - nextTickPos = ppp.tick(); + +#ifdef _JACK_TIMEBASE_DRIVES_MIDI_ + if(use_jack_timebase) + // With jack timebase this might not be accurate - + // we are relying on the tempo to figure out the next tick. + nextTickPos = curTickPos + next_jt_ticks; + else +#endif + { + Pos ppp(_pos); + ppp += frames; + nextTickPos = ppp.tick(); + } } } // @@ -468,9 +515,15 @@ void Audio::process(unsigned frames) process1(samplePos, offset, frames); for (iAudioOutput i = ol->begin(); i != ol->end(); ++i) (*i)->processWrite(); + +#ifdef _AUDIO_USE_TRUE_FRAME_ + _previousPos = _pos; +#endif if (isPlaying()) { _pos += frames; - curTickPos = nextTickPos; + // With jack timebase this might not be accurate if we + // set curTickPos (above) from the reported current tick. + curTickPos = nextTickPos; } } @@ -626,6 +679,13 @@ void Audio::processMsg(AudioMsg* msg) msg->snode->setSendMetronome((bool)msg->ival); break; + case AUDIO_START_MIDI_LEARN: + // Reset the values. The engine will fill these from driver events. + MusEGlobal::midiLearnPort = -1; + MusEGlobal::midiLearnChan = -1; + MusEGlobal::midiLearnCtrl = -1; + break; + case AUDIO_SET_SEG_SIZE: MusEGlobal::segmentSize = msg->ival; MusEGlobal::sampleRate = msg->iival; @@ -690,6 +750,9 @@ void Audio::processMsg(AudioMsg* msg) MusEGlobal::song->processMsg(msg); if (isPlaying()) { if (!MusEGlobal::checkAudioDevice()) return; +#ifdef _AUDIO_USE_TRUE_FRAME_ + _previousPos = _pos; +#endif _pos.setTick(curTickPos); int samplePos = _pos.frame(); syncFrame = MusEGlobal::audioDevice->framePos(); @@ -742,10 +805,25 @@ void Audio::seek(const Pos& p) if (MusEGlobal::heavyDebugMsg) printf("Audio::seek frame:%d\n", p.frame()); +#ifdef _AUDIO_USE_TRUE_FRAME_ + _previousPos = _pos; +#endif _pos = p; if (!MusEGlobal::checkAudioDevice()) return; syncFrame = MusEGlobal::audioDevice->framePos(); frameOffset = syncFrame - _pos.frame(); + +#ifdef _JACK_TIMEBASE_DRIVES_MIDI_ + unsigned curr_jt_tick; + if(MusEGlobal::audioDevice->deviceType() == AudioDevice::JACK_AUDIO && + !MusEGlobal::jackTransportMaster && + !MusEGlobal::song->masterFlag() && + !MusEGlobal::extSyncFlag.value() && + static_cast<MusECore::JackAudioDevice*>(MusEGlobal::audioDevice)->timebaseQuery( + MusEGlobal::segmentSize, NULL, NULL, NULL, &curr_jt_tick, NULL)) + curTickPos = curr_jt_tick; + else +#endif curTickPos = _pos.tick(); // ALSA support @@ -802,6 +880,7 @@ void Audio::startRolling() if(_loopCount == 0) { startRecordPos = _pos; + startExternalRecTick = curTickPos; } if (MusEGlobal::song->record()) { recording = true; @@ -916,6 +995,7 @@ void Audio::stopRolling() } recording = false; endRecordPos = _pos; + endExternalRecTick = curTickPos; write(sigFd, "0", 1); // STOP } @@ -926,8 +1006,10 @@ void Audio::stopRolling() void Audio::recordStop() { + MusEGlobal::song->processMasterRec(); + if (MusEGlobal::debugMsg) - printf("recordStop - startRecordPos=%d\n", startRecordPos.tick()); + printf("recordStop - startRecordPos=%d\n", MusEGlobal::extSyncFlag.value() ? startExternalRecTick : startRecordPos.tick()); MusEGlobal::audio->msgIdle(true); // gain access to all data structures @@ -937,7 +1019,7 @@ void Audio::recordStop() for (iWaveTrack it = wl->begin(); it != wl->end(); ++it) { WaveTrack* track = *it; if (track->recordFlag() || MusEGlobal::song->bounceTrack == track) { - MusEGlobal::song->cmdAddRecordedWave(track, startRecordPos, endRecordPos); + MusEGlobal::song->cmdAddRecordedWave(track, startRecordPos, endRecordPos); // The track's _recFile pointer may have been kept and turned // into a SndFileR and added to a new part. // Or _recFile may have been discarded (no new recorded part created). @@ -960,7 +1042,8 @@ void Audio::recordStop() // Do SysexMeta. Do loops. buildMidiEventList(el, mpel, mt, MusEGlobal::config.division, true, true); - MusEGlobal::song->cmdAddRecordedEvents(mt, el, startRecordPos.tick()); + MusEGlobal::song->cmdAddRecordedEvents(mt, el, + MusEGlobal::extSyncFlag.value() ? startExternalRecTick : startRecordPos.tick()); el->clear(); mpel->clear(); } @@ -981,6 +1064,7 @@ void Audio::recordStop() msgSetRecord(ao, false); } } + MusEGlobal::audio->msgIdle(false); MusEGlobal::song->endUndo(0); MusEGlobal::song->setRecord(false); @@ -993,7 +1077,11 @@ void Audio::recordStop() unsigned Audio::framesSinceCycleStart() const { - return lrint((curTime() - syncTime) * MusEGlobal::sampleRate); + unsigned f = lrint((curTime() - syncTime) * MusEGlobal::sampleRate); + // Safety due to inaccuracies. It cannot be after the segment, right? + if(f >= MusEGlobal::segmentSize) + f = MusEGlobal::segmentSize - 1; + return f; } //--------------------------------------------------------- diff --git a/muse2/muse/audio.h b/muse2/muse/audio.h index a9d2cc82..7c3d73ce 100644 --- a/muse2/muse/audio.h +++ b/muse2/muse/audio.h @@ -31,6 +31,12 @@ #include "route.h" #include "event.h" +// An experiment to use true frames for time-stamping all recorded input. +// (All recorded data actually arrived in the previous period.) +// TODO: Some more work needs to be done in WaveTrack::getData() in order to +// make everything line up and sync correctly. Cannot use this yet! +//#define _AUDIO_USE_TRUE_FRAME_ + namespace MusECore { class AudioDevice; class AudioTrack; @@ -94,6 +100,7 @@ enum { AUDIO_ADD_AC_EVENT, AUDIO_CHANGE_AC_EVENT, AUDIO_SET_SOLO, AUDIO_SET_SEND_METRONOME, + AUDIO_START_MIDI_LEARN, MS_PROCESS, MS_STOP, MS_SET_RTC, MS_UPDATE_POLL_FD, SEQM_IDLE, SEQM_SEEK, }; @@ -147,7 +154,11 @@ class Audio { int _loopCount; // Number of times we have looped so far Pos _pos; // current play position - + +#ifdef _AUDIO_USE_TRUE_FRAME_ + Pos _previousPos; // previous play position +#endif + unsigned curTickPos; // pos at start of frame during play/record unsigned nextTickPos; // pos at start of next frame during play/record @@ -172,7 +183,8 @@ class Audio { // record values: Pos startRecordPos; Pos endRecordPos; - + unsigned startExternalRecTick; + unsigned endExternalRecTick; AudioOutput* _audioMaster; AudioOutput* _audioMonitor; @@ -288,6 +300,7 @@ class Audio { void msgRemapPortDrumCtlEvents(int, int, int, int); void msgChangeAllPortDrumCtrlEvents(bool, bool); void msgSetSendMetronome(AudioTrack*, bool); + void msgStartMidiLearn(); void msgPlayMidiEvent(const MidiPlayEvent* event); void rescanAlsaPorts(); @@ -295,8 +308,13 @@ class Audio { void midiPortsChanged(); const Pos& pos() const { return _pos; } +#ifdef _AUDIO_USE_TRUE_FRAME_ + const Pos& previousPos() const { return _previousPos; } +#endif const Pos& getStartRecordPos() const { return startRecordPos; } const Pos& getEndRecordPos() const { return endRecordPos; } + unsigned getStartExternalRecTick() const { return startExternalRecTick; } + unsigned getEndExternalRecTick() const { return endExternalRecTick; } int loopCount() { return _loopCount; } // Number of times we have looped so far unsigned loopFrame() { return _loopFrame; } diff --git a/muse2/muse/audiotrack.cpp b/muse2/muse/audiotrack.cpp index b0c52a54..dac496d7 100644 --- a/muse2/muse/audiotrack.cpp +++ b/muse2/muse/audiotrack.cpp @@ -39,6 +39,7 @@ #include "synth.h" #include "dssihost.h" #include "app.h" +#include "controlfifo.h" namespace MusECore { @@ -385,6 +386,10 @@ void AudioTrack::addController(CtrlList* list) void AudioTrack::removeController(int id) { + AudioMidiCtrlStructMap amcs; + _controller.midiControls()->find_audio_ctrl_structs(id, &amcs); + for(ciAudioMidiCtrlStructMap iamcs = amcs.begin(); iamcs != amcs.end(); ++ iamcs) + _controller.midiControls()->erase(*iamcs); iCtrlList i = _controller.find(id); if (i == _controller.end()) { printf("AudioTrack::removeController id %d not found\n", id); @@ -399,20 +404,14 @@ void AudioTrack::removeController(int id) void AudioTrack::swapControllerIDX(int idx1, int idx2) { - // FIXME This code is ugly. - // At best we would like to modify the keys (IDXs) in-place and - // do some kind of deferred re-sort, but it can't be done... - - if(idx1 == idx2) - return; - - if(idx1 < 0 || idx2 < 0 || idx1 >= PipelineDepth || idx2 >= PipelineDepth) + if(idx1 == idx2 || idx1 < 0 || idx2 < 0 || idx1 >= PipelineDepth || idx2 >= PipelineDepth) return; CtrlList *cl; CtrlList *newcl; int id1 = (idx1 + 1) * AC_PLUGIN_CTL_BASE; int id2 = (idx2 + 1) * AC_PLUGIN_CTL_BASE; + int id_mask = ~((int)AC_PLUGIN_CTL_ID_MASK); int i, j; CtrlListList tmpcll; @@ -422,7 +421,7 @@ void AudioTrack::swapControllerIDX(int idx1, int idx2) { cl = icl->second; i = cl->id() & AC_PLUGIN_CTL_ID_MASK; - j = cl->id() & ~((unsigned long)AC_PLUGIN_CTL_ID_MASK); + j = cl->id() & id_mask; if(j == id1 || j == id2) { newcl = new CtrlList(i | (j == id1 ? id2 : id1)); @@ -460,74 +459,21 @@ void AudioTrack::swapControllerIDX(int idx1, int idx2) _controller.insert(std::pair<const int, CtrlList*>(newcl->id(), newcl)); } - // DELETETHIS 67 - /* - unsigned int idmask = ~AC_PLUGIN_CTL_ID_MASK; - - CtrlList* cl; - CtrlList* ctl1 = 0; - CtrlList* ctl2 = 0; - CtrlList* newcl1 = 0; - CtrlList* newcl2 = 0; - CtrlVal cv(0, 0.0); - int id1 = (idx1 + 1) * AC_PLUGIN_CTL_BASE; - int id2 = (idx2 + 1) * AC_PLUGIN_CTL_BASE; - int i, j; - double min, max; - - for(ciCtrlList icl = _controller.begin(); icl != _controller.end(); ++icl) + // Remap midi to audio controls... + MidiAudioCtrlMap* macm = _controller.midiControls(); + for(iMidiAudioCtrlMap imacm = macm->begin(); imacm != macm->end(); ++imacm) { - cl = icl->second; - i = cl->id() & AC_PLUGIN_CTL_ID_MASK; - j = cl->id() & idmask; - - if(j == id1) - { - ctl1 = cl; - newcl1 = new CtrlList( i | id2 ); - newcl1->setMode(cl->mode()); - newcl1->setValueType(cl->valueType()); - newcl1->setName(cl->name()); - cl->range(&min, &max); - newcl1->setRange(min, max); - newcl1->setCurVal(cl->curVal()); - newcl1->setDefault(cl->getDefault()); - for(iCtrl ic = cl->begin(); ic != cl->end(); ++ic) - { - cv = ic->second; - newcl1->insert(std::pair<const int, CtrlVal>(cv.frame, cv)); - } - } - //else - if(j == id2) - { - ctl2 = cl; - newcl2 = new CtrlList( i | id1 ); - newcl2->setMode(cl->mode()); - newcl2->setValueType(cl->valueType()); - newcl2->setName(cl->name()); - cl->range(&min, &max); - newcl2->setRange(min, max); - newcl2->setCurVal(cl->curVal()); - newcl2->setDefault(cl->getDefault()); - for(iCtrl ic = cl->begin(); ic != cl->end(); ++ic) - { - cv = ic->second; - newcl2->insert(std::pair<const int, CtrlVal>(cv.frame, cv)); - } - } - } - if(ctl1) - _controller.erase(ctl1->id()); - if(ctl2) - _controller.erase(ctl2->id()); - if(newcl1) - //_controller.add(newcl1); - _controller.insert(std::pair<const int, CtrlList*>(newcl1->id(), newcl1)); - if(newcl2) - _controller.insert(std::pair<const int, CtrlList*>(newcl2->id(), newcl2)); - //_controller.add(newcl2); - */ + int actrl = imacm->second.audioCtrlId(); + int id = actrl & id_mask; + actrl &= AC_PLUGIN_CTL_ID_MASK; + if(id == id1) + actrl |= id2; + else if(id == id2) + actrl |= id1; + else + continue; + imacm->second.setAudioCtrlId(actrl); + } } //--------------------------------------------------------- @@ -610,11 +556,7 @@ void AudioTrack::processAutomationEvents() if(icr->id == id && icr->type == ARVT_STOP) { int end = icr->frame; - // Erase everything up to, not including, this stop event's frame. - // Because an event was already stored directly when slider released. - if(end > start) - --end; - + iCtrl s = cl->lower_bound(start); iCtrl e = cl->lower_bound(end); @@ -636,8 +578,21 @@ void AudioTrack::processAutomationEvents() // from CtrlRecList and put into cl. for (iCtrlRec icr = _recEvents.begin(); icr != _recEvents.end(); ++icr) { - if (icr->id == id && (icr->type == ARVT_VAL || icr->type == ARVT_START)) + if (icr->id == id) + { + // Must optimize these types otherwise multiple vertices appear on flat straight lines in the graphs. + CtrlValueType vtype = cl->valueType(); + if(!cl->empty() && (cl->mode() == CtrlList::DISCRETE || vtype == VAL_BOOL || vtype == VAL_INT)) + { + iCtrl icl_prev = cl->lower_bound(icr->frame); + if(icl_prev != cl->begin()) + --icl_prev; + if(icl_prev->second.val == icr->val) + continue; + } + // Now add the value. cl->add(icr->frame, icr->val); + } } } @@ -790,7 +745,7 @@ void AudioTrack::changeACEvent(int id, int frame, int newframe, double newval) iCtrl ic = cl->find(frame); if(ic != cl->end()) cl->erase(ic); - cl->insert(std::pair<const int, CtrlVal> (newframe, CtrlVal(newframe, newval))); + cl->insert(std::pair<const int, CtrlVal> (newframe, CtrlVal(newframe, newval))); } //--------------------------------------------------------- @@ -825,7 +780,7 @@ void AudioTrack::setVolume(double val) double AudioTrack::pan() const { return _controller.value(AC_PAN, MusEGlobal::audio->curFramePos(), - !MusEGlobal::automation || automationType() == AUTO_OFF || !_volumeEnCtrl || !_volumeEn2Ctrl); + !MusEGlobal::automation || automationType() == AUTO_OFF || !_panEnCtrl || !_panEn2Ctrl); } //--------------------------------------------------------- @@ -848,8 +803,49 @@ void AudioTrack::setPan(double val) double AudioTrack::pluginCtrlVal(int ctlID) const { + bool en_1 = true, en_2 = true; + if(ctlID < AC_PLUGIN_CTL_BASE) + { + if(ctlID == AC_VOLUME) + { + en_1 = _volumeEnCtrl; + en_2 = _volumeEn2Ctrl; + } + else + if(ctlID == AC_PAN) + { + en_1 = _panEnCtrl; + en_2 = _panEn2Ctrl; + } + } + else + { + if(ctlID < (int)genACnum(MAX_PLUGINS, 0)) // The beginning of the special dssi synth controller block. + { + _efxPipe->controllersEnabled(ctlID, &en_1, &en_2); + } + else + { + if(type() == AUDIO_SOFTSYNTH) + { + const SynthI* synth = static_cast<const SynthI*>(this); + if(synth->synth() && synth->synth()->synthType() == Synth::DSSI_SYNTH) + { + SynthIF* sif = synth->sif(); + if(sif) + { + const DssiSynthIF* dssi_sif = static_cast<const DssiSynthIF*>(sif); + int in_ctrl_idx = ctlID & AC_PLUGIN_CTL_ID_MASK; + en_1 = dssi_sif->controllerEnabled(in_ctrl_idx); + en_2 = dssi_sif->controllerEnabled2(in_ctrl_idx); + } + } + } + } + } + return _controller.value(ctlID, MusEGlobal::audio->curFramePos(), - !MusEGlobal::automation || automationType() == AUTO_OFF); + !MusEGlobal::automation || automationType() == AUTO_OFF || !en_1 || !en_2); } //--------------------------------------------------------- @@ -865,6 +861,140 @@ void AudioTrack::setPluginCtrlVal(int param, double val) cl->second->setCurVal(val); } +//--------------------------------------------------------- +// addScheduledControlEvent +// returns true if event cannot be delivered +//--------------------------------------------------------- + +bool AudioTrack::addScheduledControlEvent(int track_ctrl_id, float val, unsigned frame) +{ + if(track_ctrl_id < AC_PLUGIN_CTL_BASE) // FIXME: These controllers (three so far - vol, pan, mute) have no vari-run-length support. + { + iCtrlList icl = _controller.find(track_ctrl_id); + if(icl == _controller.end()) + return true; + icl->second->setCurVal(val); + return false; + } + else + { + if(track_ctrl_id < (int)genACnum(MAX_PLUGINS, 0)) // The beginning of the special dssi synth controller block. + return _efxPipe->addScheduledControlEvent(track_ctrl_id, val, frame); + else + { + if(type() == AUDIO_SOFTSYNTH) + { + const SynthI* synth = static_cast<const SynthI*>(this); + if(synth->synth() && synth->synth()->synthType() == Synth::DSSI_SYNTH) + { + SynthIF* sif = synth->sif(); + if(sif) + { + DssiSynthIF* dssi_sif = static_cast<DssiSynthIF*>(sif); + int in_ctrl_idx = track_ctrl_id & AC_PLUGIN_CTL_ID_MASK; + return dssi_sif->addScheduledControlEvent(in_ctrl_idx, val, frame); + } + } + } + } + } + return true; +} + +//--------------------------------------------------------- +// enableController +// Enable or disable gui controls. +// Used during automation recording to inhibit gui controls +// from playback controller stream +//--------------------------------------------------------- + +void AudioTrack::enableController(int track_ctrl_id, bool en) +{ + if(track_ctrl_id < AC_PLUGIN_CTL_BASE) + { + if(track_ctrl_id == AC_VOLUME) + enableVolumeController(en); + else + if(track_ctrl_id == AC_PAN) + enablePanController(en); + } + else + { + if(track_ctrl_id < (int)genACnum(MAX_PLUGINS, 0)) // The beginning of the special dssi synth controller block. + _efxPipe->enableController(track_ctrl_id, en); + else + { + if(type() == AUDIO_SOFTSYNTH) + { + SynthI* synth = static_cast<SynthI*>(this); + if(synth->synth() && synth->synth()->synthType() == Synth::DSSI_SYNTH) + { + SynthIF* sif = synth->sif(); + if(sif) + { + DssiSynthIF* dssi_sif = static_cast<DssiSynthIF*>(sif); + int in_ctrl_idx = track_ctrl_id & AC_PLUGIN_CTL_ID_MASK; + dssi_sif->enableController(in_ctrl_idx, en); + } + } + } + } + } +} + +//--------------------------------------------------------- +// controllersEnabled +//--------------------------------------------------------- + +void AudioTrack::controllersEnabled(int track_ctrl_id, bool* en1, bool* en2) const + { + bool en_1 = true, en_2 = true; + if(track_ctrl_id < AC_PLUGIN_CTL_BASE) + { + if(track_ctrl_id == AC_VOLUME) + { + en_1 = _volumeEnCtrl; + en_2 = _volumeEn2Ctrl; + } + else + if(track_ctrl_id == AC_PAN) + { + en_1 = _panEnCtrl; + en_2 = _panEn2Ctrl; + } + } + else + { + if(track_ctrl_id < (int)genACnum(MAX_PLUGINS, 0)) // The beginning of the special dssi synth controller block. + { + _efxPipe->controllersEnabled(track_ctrl_id, &en_1, &en_2); + } + else + { + if(type() == AUDIO_SOFTSYNTH) + { + const SynthI* synth = static_cast<const SynthI*>(this); + if(synth->synth() && synth->synth()->synthType() == Synth::DSSI_SYNTH) + { + SynthIF* sif = synth->sif(); + if(sif) + { + const DssiSynthIF* dssi_sif = static_cast<const DssiSynthIF*>(sif); + int in_ctrl_idx = track_ctrl_id & AC_PLUGIN_CTL_ID_MASK; + en_1 = dssi_sif->controllerEnabled(in_ctrl_idx); + en_2 = dssi_sif->controllerEnabled2(in_ctrl_idx); + } + } + } + } + } + + if(en1) + *en1 = en_1; + if(en2) + *en2 = en_2; + } + void AudioTrack::recordAutomation(int n, double v) { if(!MusEGlobal::automation) @@ -883,7 +1013,7 @@ void AudioTrack::recordAutomation(int n, double v) if (cl == _controller.end()) return; // Add will replace if found. - cl->second->add(MusEGlobal::audio->curFramePos(), v); + cl->second->add(MusEGlobal::audio->curFramePos(), v); } } } @@ -895,10 +1025,10 @@ void AudioTrack::startAutoRecord(int n, double v) if(MusEGlobal::audio->isPlaying()) { if(automationType() == AUTO_TOUCH) - _recEvents.push_back(CtrlRecVal(MusEGlobal::audio->curFramePos(), n, v, ARVT_START)); - else + _recEvents.push_back(CtrlRecVal(MusEGlobal::audio->curFramePos(), n, v, ARVT_START)); + else if(automationType() == AUTO_WRITE) - _recEvents.push_back(CtrlRecVal(MusEGlobal::audio->curFramePos(), n, v)); + _recEvents.push_back(CtrlRecVal(MusEGlobal::audio->curFramePos(), n, v)); } else { @@ -926,7 +1056,7 @@ void AudioTrack::stopAutoRecord(int n, double v) { if(automationType() == AUTO_TOUCH) { - MusEGlobal::audio->msgAddACEvent(this, n, MusEGlobal::audio->curFramePos(), v); + MusEGlobal::audio->msgAddACEvent(this, n, MusEGlobal::audio->curFramePos(), v); _recEvents.push_back(CtrlRecVal(MusEGlobal::audio->curFramePos(), n, v, ARVT_STOP)); } } @@ -953,26 +1083,7 @@ void AudioTrack::writeProperties(int level, Xml& xml) const if (*ip) (*ip)->writeConfiguration(level, xml); } - for (ciCtrlList icl = _controller.begin(); icl != _controller.end(); ++icl) { - const CtrlList* cl = icl->second; - - QString s= QString("controller id=\"%1\" cur=\"%2\"").arg(cl->id()).arg(cl->curVal()).toAscii().constData(); - s += QString(" color=\"%1\" visible=\"%2\"").arg(cl->color().name()).arg(cl->isVisible()); - xml.tag(level++, s.toAscii().constData()); - int i = 0; - for (ciCtrl ic = cl->begin(); ic != cl->end(); ++ic) { - QString s("%1 %2, "); - xml.nput(level, s.arg(ic->second.frame).arg(ic->second.val).toAscii().constData()); - ++i; - if (i >= 4) { - xml.put(level, ""); - i = 0; - } - } - if (i) - xml.put(level, ""); - xml.etag(level--, "controller"); - } + _controller.write(level, xml); } //--------------------------------------------------------- @@ -1113,6 +1224,8 @@ bool AudioTrack::readProperties(Xml& xml, const QString& tag) l->setMode(p->ctrlMode(m)); } } + else if (tag == "midiMapper") + _controller.midiControls()->read(xml); else return Track::readProperties(xml, tag); return false; @@ -1230,20 +1343,20 @@ void AudioTrack::mapRackPluginsToControllers() unsigned param = id & AC_PLUGIN_CTL_ID_MASK; int idx = (id >> AC_PLUGIN_CTL_BASE_POW) - 1; - PluginIBase* p = 0; + const PluginIBase* p = 0; if(idx >= 0 && idx < PipelineDepth) p = (*_efxPipe)[idx]; // Support a special block for dssi synth ladspa controllers. else if(idx == MAX_PLUGINS && type() == AUDIO_SOFTSYNTH) { - SynthI* synti = dynamic_cast < SynthI* > (this); + const SynthI* synti = dynamic_cast < const SynthI* > (this); if(synti) { SynthIF* sif = synti->sif(); if(sif) { #ifdef DSSI_SUPPORT - DssiSynthIF* dsif = dynamic_cast < DssiSynthIF* > (sif); + const DssiSynthIF* dsif = dynamic_cast < const DssiSynthIF* > (sif); if(dsif) p = dsif; #endif diff --git a/muse2/muse/conf.cpp b/muse2/muse/conf.cpp index 6c5c93db..cca3cb94 100644 --- a/muse2/muse/conf.cpp +++ b/muse2/muse/conf.cpp @@ -561,6 +561,23 @@ void readConfiguration(Xml& xml, bool doReadMidiPortConfig, bool doReadGlobalCon if(MusEGlobal::audioDevice) MusEGlobal::audioDevice->setMaster(MusEGlobal::jackTransportMaster); } + else if (tag == "syncRecFilterPreset") + { + int p = xml.parseInt(); + if(p >= 0 && p < MidiSyncInfo::TYPE_END) + { + MusEGlobal::syncRecFilterPreset = MidiSyncInfo::SyncRecFilterPresetType(p); + if(MusEGlobal::midiSeq) + MusEGlobal::midiSeq->setSyncRecFilterPreset(MusEGlobal::syncRecFilterPreset); + } + } + else if (tag == "syncRecTempoValQuant") + { + double qv = xml.parseDouble(); + MusEGlobal::syncRecTempoValQuant = qv; + if(MusEGlobal::midiSeq) + MusEGlobal::midiSeq->setRecTempoValQuant(qv); + } else if (tag == "mtcoffset") { QString qs(xml.parse1()); QByteArray ba = qs.toLatin1(); @@ -1348,6 +1365,8 @@ void MusE::writeConfiguration(int level, MusECore::Xml& xml) const xml.uintTag(level, "sendClockDelay", MusEGlobal::syncSendFirstClockDelay); xml.intTag(level, "useJackTransport", MusEGlobal::useJackTransport.value()); xml.intTag(level, "jackTransportMaster", MusEGlobal::jackTransportMaster); + xml.intTag(level, "syncRecFilterPreset", MusEGlobal::syncRecFilterPreset); + xml.doubleTag(level, "syncRecTempoValQuant", MusEGlobal::syncRecTempoValQuant); MusEGlobal::extSyncFlag.save(level, xml); xml.intTag(level, "bigtimeVisible", viewBigtimeAction->isChecked()); diff --git a/muse2/muse/confmport.cpp b/muse2/muse/confmport.cpp index 637e927e..32e1dc91 100644 --- a/muse2/muse/confmport.cpp +++ b/muse2/muse/confmport.cpp @@ -888,7 +888,7 @@ void MPConfig::rbClicked(QTableWidgetItem* item) #endif MusEGlobal::midiSeq->msgSetMidiDevice(port, sdev); - MusEGlobal::muse->changeConfig(true); // save configuration file + MusEGlobal::muse->changeConfig(true); // save configuration file // Add all track routes to/from this port... if(sdev) @@ -904,7 +904,7 @@ void MPConfig::rbClicked(QTableWidgetItem* item) MusEGlobal::audio->msgAddRoute(MusECore::Route(no, chbits), MusECore::Route(*it, chbits)); } } - chbits = MusEGlobal::midiPorts[no].defaultOutChannels(); +// chbits = MusEGlobal::midiPorts[no].defaultOutChannels(); // Turn on if and when multiple output routes are supported. DELETETHIS? #if 0 for(MusECore::iMidiTrack it = mtl->begin(); it != mtl->end(); ++it) @@ -914,22 +914,27 @@ void MPConfig::rbClicked(QTableWidgetItem* item) MusEGlobal::audio->msgAddRoute(MusECore::Route(no, chbits), MusECore::Route(*it, chbits)); } #else - for(int ch = 0; ch < MIDI_CHANNELS; ++ch) - if(chbits & (1 << ch)) - { - MusEGlobal::audio->msgIdle(true); - for(MusECore::iMidiTrack it = mtl->begin(); it != mtl->end(); ++it) - { - // Leave drum track channel at current setting. - if((*it)->type() == MusECore::Track::DRUM) - (*it)->setOutPortAndUpdate(no); - else - (*it)->setOutPortAndChannelAndUpdate(no, ch); - } - MusEGlobal::audio->msgIdle(false); - // Stop at the first output channel found. - break; - } +// REMOVE Tim. +// for(int ch = 0; ch < MIDI_CHANNELS; ++ch) +// if(chbits & (1 << ch)) +// { +// MusEGlobal::audio->msgIdle(true); +// for(MusECore::iMidiTrack it = mtl->begin(); it != mtl->end(); ++it) +// { +// // We are only interested in tracks which use this port being changed now. +// if((*it)->outPort() != no) +// continue; +// // Leave drum track channel at current setting. // REMOVE Tim. +// //if((*it)->type() == MusECore::Track::DRUM) +// // (*it)->setOutPortAndUpdate(no); +// //else +// // (*it)->setOutPortAndChannelAndUpdate(no, ch); +// (*it)->setOutPortAndUpdate(no); +// } +// MusEGlobal::audio->msgIdle(false); +// // Stop at the first output channel found. +// break; +// } #endif } diff --git a/muse2/muse/ctrl.cpp b/muse2/muse/ctrl.cpp index 8071491e..d7d42770 100644 --- a/muse2/muse/ctrl.cpp +++ b/muse2/muse/ctrl.cpp @@ -6,7 +6,7 @@ // controller handling for mixer automation // // (C) Copyright 2003 Werner Schweer (ws@seh.de) -// (C) Copyright 2011 Time E. Real (terminator356 on users dot sourceforge dot net) +// (C) Copyright 2011-2012 Tim E. Real (terminator356 on users dot sourceforge dot net) // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -24,15 +24,20 @@ // //========================================================= +// Turn on debugging messages +//#define _CTRL_DEBUG_ #include <QLocale> #include <QColor> +#include <map> + +#include <math.h> #include "gconfig.h" #include "fastlog.h" -#include "math.h" #include "globals.h" #include "ctrl.h" +#include "midictrl.h" #include "xml.h" namespace MusECore { @@ -48,8 +53,268 @@ void CtrlList::initColor(int i) _visible = false; } +//--------------------------------------------------------- +// midi2AudioCtrlValue +// Apply mapper if it is non-null +//--------------------------------------------------------- + +double midi2AudioCtrlValue(const CtrlList* audio_ctrl_list, const MidiAudioCtrlStruct* /*mapper*/, int midi_ctlnum, int midi_val) +{ + double fmin, fmax; + audio_ctrl_list->range(&fmin, &fmax); + double frng = fmax - fmin; // The audio control range. + + MidiController::ControllerType t = midiControllerType(midi_ctlnum); + CtrlValueType aud_t = audio_ctrl_list->valueType(); + + #ifdef _CTRL_DEBUG_ + printf("midi2AudioCtrlValue: midi_ctlnum:%d val:%d fmin:%f fmax:%f\n", midi_ctlnum, midi_val, fmin, fmax); + #endif + + int ctlmn = 0; + int ctlmx = 127; + + int bval = midi_val; + switch(t) + { + case MidiController::RPN: + case MidiController::NRPN: + case MidiController::Controller7: + ctlmn = 0; + ctlmx = 127; + break; + case MidiController::Controller14: + case MidiController::RPN14: + case MidiController::NRPN14: + ctlmn = 0; + ctlmx = 16383; + break; + case MidiController::Program: + ctlmn = 0; + ctlmx = 0xffffff; + break; + case MidiController::Pitch: + ctlmn = -8192; + ctlmx = 8191; + bval += 8192; + break; + case MidiController::Velo: // cannot happen + default: + break; + } + double fictlrng = double(ctlmx - ctlmn); // Float version of the integer midi range. + double normval = double(bval) / fictlrng; // Float version of the normalized midi value. + + // ---------- TODO: Do stuff with the mapper, if supplied. + + if(aud_t == VAL_LOG) + { + // FIXME: Although this should be correct, some sliders show "---" at top end, some don't. + // Possibly because of use of fast_log10 in value(), and in sliders and automation IIRC. + fmin = 20.0*log10(fmin); + fmax = 20.0*log10(fmax); + frng = fmax - fmin; + double ret = exp10((normval * frng + fmin) / 20.0); + #ifdef _CTRL_DEBUG_ + printf("midi2AudioCtrlValue: is VAL_LOG normval:%f frng:%f returning:%f\n", normval, frng, ret); + #endif + return ret; + } + + if(aud_t == VAL_LINEAR) + { + double ret = normval * frng + fmin; + #ifdef _CTRL_DEBUG_ + printf("midi2AudioCtrlValue: is VAL_LINEAR normval:%f frng:%f returning:%f\n", normval, frng, ret); + #endif + return ret; + } + + if(aud_t == VAL_INT) + { + double ret = int(normval * frng + fmin); + #ifdef _CTRL_DEBUG_ + printf("midi2AudioCtrlValue: is VAL_INT returning:%f\n", ret); + #endif + return ret; + } + + if(aud_t == VAL_BOOL) + { + #ifdef _CTRL_DEBUG_ + printf("midi2AudioCtrlValue: is VAL_BOOL\n"); + #endif + //if(midi_val > ((ctlmx - ctlmn)/2 + ctlmn)) + if((normval * frng + fmin) > (frng/2.0 + fmin)) + return fmax; + else + return fmin; + } + + printf("midi2AudioCtrlValue: unknown audio controller type:%d\n", aud_t); + return 0.0; +} + +//--------------------------------------------------------- +// Midi to audio controller stuff +//--------------------------------------------------------- + +MidiAudioCtrlStruct::MidiAudioCtrlStruct() +{ + _audio_ctrl_id = 0; +}; + +MidiAudioCtrlStruct::MidiAudioCtrlStruct(int audio_ctrl_id) : _audio_ctrl_id(audio_ctrl_id) +{ +}; + +MidiAudioCtrlMap_idx_t MidiAudioCtrlMap::index_hash(int midi_port, int midi_chan, int midi_ctrl_num) const +{ + return ((MidiAudioCtrlMap_idx_t(midi_port) & 0xff) << 24) | + ((MidiAudioCtrlMap_idx_t(midi_chan) & 0xf) << 20) | + (MidiAudioCtrlMap_idx_t(midi_ctrl_num) & 0xfffff); +} + +void MidiAudioCtrlMap::hash_values(MidiAudioCtrlMap_idx_t hash, int* midi_port, int* midi_chan, int* midi_ctrl_num) const +{ + if(midi_ctrl_num) + *midi_ctrl_num = hash & 0xfffff; + if(midi_chan) + *midi_chan = (hash >> 20) & 0xf; + if(midi_port) + *midi_port = (hash >> 24) & 0xff; +} + +iMidiAudioCtrlMap MidiAudioCtrlMap::add_ctrl_struct(int midi_port, int midi_chan, int midi_ctrl_num, + const MidiAudioCtrlStruct& macs) +{ + MidiAudioCtrlMap_idx_t h = index_hash(midi_port, midi_chan, midi_ctrl_num); + std::pair<iMidiAudioCtrlMap, iMidiAudioCtrlMap> range = equal_range(h); + for(iMidiAudioCtrlMap imacp = range.first; imacp != range.second; ++imacp) + if(imacp->second.audioCtrlId() == macs.audioCtrlId()) + return imacp; + return insert(std::pair<MidiAudioCtrlMap_idx_t, MidiAudioCtrlStruct >(h, macs)); +} +void MidiAudioCtrlMap::erase_ctrl_struct(int midi_port, int midi_chan, int midi_ctrl_num, int audio_ctrl_id) +{ + MidiAudioCtrlMap_idx_t h = index_hash(midi_port, midi_chan, midi_ctrl_num); + std::pair<iMidiAudioCtrlMap, iMidiAudioCtrlMap> range = equal_range(h); + MidiAudioCtrlMap macm; + macm.insert(range.first, range.second); + for(iMidiAudioCtrlMap imacm = macm.begin(); imacm != macm.end(); ++imacm) + if(imacm->second.audioCtrlId() == audio_ctrl_id) + erase(imacm); +} + +void MidiAudioCtrlMap::find_audio_ctrl_structs(int audio_ctrl_id, AudioMidiCtrlStructMap* amcs) //const +{ + for(iMidiAudioCtrlMap imacm = begin(); imacm != end(); ++imacm) + if(imacm->second.audioCtrlId() == audio_ctrl_id) + amcs->push_back(imacm); +} + +void MidiAudioCtrlMap::write(int level, Xml& xml) const +{ + for(ciMidiAudioCtrlMap imacm = begin(); imacm != end(); ++imacm) + { + int port, chan, mctrl; + hash_values(imacm->first, &port, &chan, &mctrl); + int actrl = imacm->second.audioCtrlId(); + QString s= QString("midiMapper port=\"%1\" ch=\"%2\" mctrl=\"%3\" actrl=\"%4\"") + .arg(port) + .arg(chan) + .arg(mctrl) + .arg(actrl); + xml.tag(level++, s.toAscii().constData()); + + // TODO + //const MidiAudioCtrlStruct& macs = imacs->second; + //xml.intTag(level, "macs ???", macs.); + + xml.etag(level--, "midiMapper"); + } +} + +//--------------------------------------------------------- +// read +//--------------------------------------------------------- + +void MidiAudioCtrlMap::read(Xml& xml) + { + int port = -1, chan = -1, midi_ctrl = -1; + MidiAudioCtrlStruct macs(-1); + + QLocale loc = QLocale::c(); + bool ok; + int errcount = 0; + for (;;) { + Xml::Token token = xml.parse(); + const QString& tag = xml.s1(); + switch (token) { + case Xml::Error: + case Xml::End: + return; + case Xml::Attribut: + if (tag == "port") + { + port = loc.toInt(xml.s2(), &ok); + if(!ok) + { + ++errcount; + printf("MidiAudioCtrlPortMap::read failed reading port string: %s\n", xml.s2().toLatin1().constData()); + } + } + else if (tag == "ch") + { + chan = loc.toInt(xml.s2(), &ok); + if(!ok) + { + ++errcount; + printf("MidiAudioCtrlPortMap::read failed reading ch string: %s\n", xml.s2().toLatin1().constData()); + } + } + else if (tag == "mctrl") + { + midi_ctrl = loc.toInt(xml.s2(), &ok); + if(!ok) + { + ++errcount; + printf("MidiAudioCtrlPortMap::read failed reading mctrl string: %s\n", xml.s2().toLatin1().constData()); + } + } + else if (tag == "actrl") + { + macs.setAudioCtrlId(loc.toInt(xml.s2(), &ok)); + if(!ok) + { + ++errcount; + printf("MidiAudioCtrlPortMap::read failed reading actrl string: %s\n", xml.s2().toLatin1().constData()); + } + } + else + printf("unknown tag %s\n", tag.toLatin1().constData()); + break; + case Xml::TagStart: + // TODO + //if (tag == "???") { + // } + //else + xml.unknown("midiMapper"); + break; + case Xml::TagEnd: + if (xml.s1() == "midiMapper") + { + if(errcount == 0 && port != -1 && chan != -1 && midi_ctrl != -1 && macs.audioCtrlId() != -1) + add_ctrl_struct(port, chan, midi_ctrl, macs); + return; + } + default: + break; + } + } + } //--------------------------------------------------------- // CtrlList //--------------------------------------------------------- @@ -62,6 +327,7 @@ CtrlList::CtrlList() _mode = INTERPOLATE; _dontShow = false; _visible = false; + _guiUpdatePending = false; initColor(0); } @@ -73,6 +339,7 @@ CtrlList::CtrlList(int id) _mode = INTERPOLATE; _dontShow = false; _visible = false; + _guiUpdatePending = false; initColor(id); } @@ -88,6 +355,7 @@ CtrlList::CtrlList(int id, QString name, double min, double max, CtrlValueType v _valueType = v; _dontShow = dontShow; _visible = false; + _guiUpdatePending = false; initColor(id); } @@ -115,39 +383,54 @@ void CtrlList::assign(const CtrlList& l, int flags) if(flags & ASSIGN_VALUES) { *this = l; // Let the vector assign values. + _guiUpdatePending = true; } } //--------------------------------------------------------- // value +// Returns value at frame. +// cur_val_only means read the current 'manual' value, not from the list even if it is not empty. +// If passed a nextFrame, sets nextFrame to the next event frame, or -1 if no next frame (wide-open), or, +// since CtrlList is a map, ZERO if should be replaced with some other frame by the caller (interpolation). //--------------------------------------------------------- -double CtrlList::value(int frame) const +double CtrlList::value(int frame, bool cur_val_only, int* nextFrame) const { - if(empty()) + if(cur_val_only || empty()) + { + if(nextFrame) + *nextFrame = -1; return _curVal; + } double rv; - ciCtrl i = upper_bound(frame); // get the index after current frame + int nframe; + ciCtrl i = upper_bound(frame); // get the index after current frame if (i == end()) { // if we are past all items just return the last value --i; - rv = i->second.val; + if(nextFrame) + *nextFrame = -1; + return i->second.val; } else if(_mode == DISCRETE) { if(i == begin()) { + nframe = i->second.frame; rv = i->second.val; } else { + nframe = i->second.frame; --i; rv = i->second.val; } } - else { + else { // INTERPOLATE if (i == begin()) { + nframe = i->second.frame; rv = i->second.val; } else { @@ -157,6 +440,12 @@ double CtrlList::value(int frame) const int frame1 = i->second.frame; double val1 = i->second.val; + + if(val2 != val1) + nframe = 0; // Zero signifies the next frame should be determined by caller. + else + nframe = frame2; + if (_valueType == VAL_LOG) { val1 = 20.0*fast_log10(val1); if (val1 < MusEGlobal::config.minSlider) @@ -166,10 +455,8 @@ double CtrlList::value(int frame) const val2=MusEGlobal::config.minSlider; } - frame -= frame1; val2 -= val1; - frame2 -= frame1; - val1 += (double(frame) * val2)/double(frame2); + val1 += (double(frame - frame1) * val2)/double(frame2 - frame1); if (_valueType == VAL_LOG) { val1 = exp10(val1/20.0); @@ -178,6 +465,10 @@ double CtrlList::value(int frame) const rv = val1; } } + + if(nextFrame) + *nextFrame = nframe; + return rv; } @@ -196,7 +487,101 @@ double CtrlList::curVal() const //--------------------------------------------------------- void CtrlList::setCurVal(double val) { +#ifdef _CTRL_DEBUG_ + printf("CtrlList::setCurVal val:%f\n", val); +#endif + + bool upd = (val != _curVal); _curVal = val; + // If empty, any controller graphs etc. will be displaying this value. + // Otherwise they'll be displaying the list, so update is not required. + if(empty() && upd) + _guiUpdatePending = true; +} + +//--------------------------------------------------------- +// +// Catch all insert, erase, clear etc. +// +//--------------------------------------------------------- + +CtrlList& CtrlList::operator=(const CtrlList& cl) +{ +#ifdef _CTRL_DEBUG_ + printf("CtrlList::operator= id:%d\n", cl.id()); +#endif + std::map<int, CtrlVal, std::less<int> >::operator=(cl); + _guiUpdatePending = true; + return *this; +} + +void CtrlList::swap(CtrlList& cl) +{ +#ifdef _CTRL_DEBUG_ + printf("CtrlList::swap id:%d\n", cl.id()); +#endif + std::map<int, CtrlVal, std::less<int> >::swap(cl); + cl.setGuiUpdatePending(true); + _guiUpdatePending = true; +} + +std::pair<iCtrl, bool> CtrlList::insert(const std::pair<int, CtrlVal>& p) +{ +#ifdef _CTRL_DEBUG_ + printf("CtrlList::insert frame:%d val:%f\n", p.first, p.second.val); +#endif + std::pair<iCtrl, bool> res = std::map<int, CtrlVal, std::less<int> >::insert(p); + _guiUpdatePending = true; + return res; +} + +iCtrl CtrlList::insert(iCtrl ic, const std::pair<int, CtrlVal>& p) +{ +#ifdef _CTRL_DEBUG_ + printf("CtrlList::insert2 frame:%d val:%f\n", p.first, p.second.val); +#endif + iCtrl res = std::map<int, CtrlVal, std::less<int> >::insert(ic, p); + _guiUpdatePending = true; + return res; +} + +void CtrlList::erase(iCtrl ictl) +{ +#ifdef _CTRL_DEBUG_ + printf("CtrlList::erase iCtrl frame:%d val:%f\n", ictl->second.frame, ictl->second.val); +#endif + std::map<int, CtrlVal, std::less<int> >::erase(ictl); + _guiUpdatePending = true; +} + +std::map<int, CtrlVal, std::less<int> >::size_type CtrlList::erase(int frame) +{ +#ifdef _CTRL_DEBUG_ + printf("CtrlList::erase frame:%d\n", frame); +#endif + std::map<int, CtrlVal, std::less<int> >::size_type res = std::map<int, CtrlVal, std::less<int> >::erase(frame); + _guiUpdatePending = true; + return res; +} + +void CtrlList::erase(iCtrl first, iCtrl last) +{ +#ifdef _CTRL_DEBUG_ + printf("CtrlList::erase range first frame:%d val:%f second frame:%d val:%f\n", + first->second.frame, first->second.val, + last->second.frame, last->second.val); +#endif + std::map<int, CtrlVal, std::less<int> >::erase(first, last); + _guiUpdatePending = true; +} + +void CtrlList::clear() +{ +#ifdef _CTRL_DEBUG_ + printf("CtrlList::clear\n"); +#endif + std::map<int, CtrlVal, std::less<int> >::clear(); + _guiUpdatePending = true; } //--------------------------------------------------------- @@ -208,7 +593,15 @@ void CtrlList::add(int frame, double val) { iCtrl e = find(frame); if (e != end()) + { + bool upd = (val != e->second.val); e->second.val = val; +#ifdef _CTRL_DEBUG_ + printf("CtrlList::add frame:%d val:%f\n", frame, val); +#endif + if(upd) + _guiUpdatePending = true; + } else insert(std::pair<const int, CtrlVal> (frame, CtrlVal(frame, val))); } @@ -234,7 +627,13 @@ void CtrlList::del(int frame) void CtrlList::updateCurValue(int frame) { - _curVal = value(frame); + double v = value(frame); + bool upd = (v != _curVal); + _curVal = v; + // If empty, any controller graphs etc. will be displaying this value. + // Otherwise they'll be displaying the list, so update is not required. + if(empty() && upd) + _guiUpdatePending = true; } //--------------------------------------------------------- @@ -361,18 +760,23 @@ void CtrlListList::add(CtrlList* vl) //--------------------------------------------------------- // value +// Returns value at frame for controller with id ctrlId. +// cur_val_only means read the current 'manual' value, not from the list even if it is not empty. +// If passed a nextFrame, sets nextFrame to the next event frame, or -1 if no next frame (wide-open), or, +// since CtrlList is a map, ZERO if should be replaced with some other frame by the caller (interpolation). //--------------------------------------------------------- -double CtrlListList::value(int ctrlId, int frame, bool cur_val_only) const +double CtrlListList::value(int ctrlId, int frame, bool cur_val_only, int* nextFrame) const { ciCtrlList cl = find(ctrlId); if (cl == end()) - return 0.0; - - if(cur_val_only) - return cl->second->curVal(); + { + if(nextFrame) + *nextFrame = -1; + return 0.0; + } - return cl->second->value(frame); + return cl->second->value(frame, cur_val_only, nextFrame); } //--------------------------------------------------------- @@ -391,5 +795,36 @@ void CtrlListList::updateCurValues(int frame) for(ciCtrlList cl = begin(); cl != end(); ++cl) cl->second->updateCurValue(frame); } - + +//--------------------------------------------------------- +// value +//--------------------------------------------------------- + +void CtrlListList::write(int level, Xml& xml) const +{ + for (ciCtrlList icl = begin(); icl != end(); ++icl) { + const CtrlList* cl = icl->second; + + QString s= QString("controller id=\"%1\" cur=\"%2\"").arg(cl->id()).arg(cl->curVal()).toAscii().constData(); + s += QString(" color=\"%1\" visible=\"%2\"").arg(cl->color().name()).arg(cl->isVisible()); + xml.tag(level++, s.toAscii().constData()); + int i = 0; + for (ciCtrl ic = cl->begin(); ic != cl->end(); ++ic) { + QString s("%1 %2, "); + xml.nput(level, s.arg(ic->second.frame).arg(ic->second.val).toAscii().constData()); + ++i; + if (i >= 4) { + xml.put(level, ""); + i = 0; + } + } + if (i) + xml.put(level, ""); + xml.etag(level--, "controller"); + } + + _midi_controls.write(level, xml); +} + + } // namespace MusECore diff --git a/muse2/muse/ctrl.h b/muse2/muse/ctrl.h index 687c5610..c56abe28 100644 --- a/muse2/muse/ctrl.h +++ b/muse2/muse/ctrl.h @@ -6,7 +6,7 @@ // controller for mixer automation // // (C) Copyright 2003-2004 Werner Schweer (ws@seh.de) -// (C) Copyright 2011 Time E. Real (terminator356 on users dot sourceforge dot net) +// (C) Copyright 2011-2012 Tim E. Real (terminator356 on users dot sourceforge dot net) // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -29,7 +29,9 @@ #include <map> #include <list> +#include <vector> #include <qcolor.h> +#include <lo/lo_osc_types.h> #define AC_PLUGIN_CTL_BASE 0x1000 #define AC_PLUGIN_CTL_BASE_POW 12 @@ -85,6 +87,47 @@ class CtrlRecList : public std::list<CtrlRecVal> { typedef CtrlRecList::iterator iCtrlRec; //--------------------------------------------------------- +// MidiAudioCtrlMap +// Describes midi control of audio controllers +//--------------------------------------------------------- + +class MidiAudioCtrlStruct { + int _audio_ctrl_id; + public: + MidiAudioCtrlStruct(); + MidiAudioCtrlStruct(int audio_ctrl_id); + int audioCtrlId() const { return _audio_ctrl_id; } + void setAudioCtrlId(int actrl) { _audio_ctrl_id = actrl; } + }; + +typedef uint32_t MidiAudioCtrlMap_idx_t; + +typedef std::multimap<MidiAudioCtrlMap_idx_t, MidiAudioCtrlStruct, std::less<MidiAudioCtrlMap_idx_t> >::iterator iMidiAudioCtrlMap; +typedef std::multimap<MidiAudioCtrlMap_idx_t, MidiAudioCtrlStruct, std::less<MidiAudioCtrlMap_idx_t> >::const_iterator ciMidiAudioCtrlMap; + +// Reverse lookup based on audio control. +typedef std::vector<iMidiAudioCtrlMap>::iterator iAudioMidiCtrlStructMap; +typedef std::vector<iMidiAudioCtrlMap>::const_iterator ciAudioMidiCtrlStructMap; +class AudioMidiCtrlStructMap : public std::vector<iMidiAudioCtrlMap> { + public: + + }; + +// Midi to audio controller map. +// The index is a hash of port, chan, and midi control number. +class MidiAudioCtrlMap : public std::multimap<MidiAudioCtrlMap_idx_t, MidiAudioCtrlStruct, std::less<MidiAudioCtrlMap_idx_t> > { + public: + MidiAudioCtrlMap_idx_t index_hash(int midi_port, int midi_chan, int midi_ctrl_num) const; + void hash_values(MidiAudioCtrlMap_idx_t hash, int* midi_port, int* midi_chan, int* midi_ctrl_num) const; + iMidiAudioCtrlMap add_ctrl_struct(int midi_port, int midi_chan, int midi_ctrl_num, const MidiAudioCtrlStruct& amcs); + void find_audio_ctrl_structs(int audio_ctrl_id, AudioMidiCtrlStructMap* amcs); // const; + void erase_ctrl_struct(int midi_port, int midi_chan, int midi_ctrl_num, int audio_ctrl_id); + void write(int level, Xml& xml) const; + void read(Xml& xml); + }; + + +//--------------------------------------------------------- // CtrlList // arrange controller events of a specific type in a // list for easy retrieval @@ -109,6 +152,7 @@ class CtrlList : public std::map<int, CtrlVal, std::less<int> > { QColor _displayColor; bool _visible; bool _dontShow; // when this is true the control exists but is not compatible with viewing in the arranger + volatile bool _guiUpdatePending; // Gui heartbeat routines read this. Checked and cleared in Song::beat(). void initColor(int i); public: @@ -117,6 +161,15 @@ class CtrlList : public std::map<int, CtrlVal, std::less<int> > { CtrlList(int id, QString name, double min, double max, CtrlValueType v, bool dontShow=false); void assign(const CtrlList& l, int flags); + void swap(CtrlList&); + std::pair<iCtrl, bool> insert(const std::pair<int, CtrlVal>& p); + iCtrl insert(iCtrl ic, const std::pair<int, CtrlVal>& p); + void erase(iCtrl ictl); + size_type erase(int frame); + void erase(iCtrl first, iCtrl last); + void clear(); + CtrlList& operator=(const CtrlList&); + Mode mode() const { return _mode; } void setMode(Mode m) { _mode = m; } double getDefault() const { return _default; } @@ -138,7 +191,7 @@ class CtrlList : public std::map<int, CtrlVal, std::less<int> > { CtrlValueType valueType() const { return _valueType; } void setValueType(CtrlValueType t) { _valueType = t; } - double value(int frame) const; + double value(int frame, bool cur_val_only = false, int* nextFrame = NULL) const; void add(int frame, double value); void del(int frame); void read(Xml& xml); @@ -148,6 +201,8 @@ class CtrlList : public std::map<int, CtrlVal, std::less<int> > { void setVisible(bool v) { _visible = v; } bool isVisible() const { return _visible; } bool dontShow() const { return _dontShow; } + bool guiUpdatePending() const { return _guiUpdatePending; } + void setGuiUpdatePending(bool v) { _guiUpdatePending = v; } }; //--------------------------------------------------------- @@ -161,6 +216,8 @@ typedef std::map<int, CtrlList*, std::less<int> >::iterator iCtrlList; typedef std::map<int, CtrlList*, std::less<int> >::const_iterator ciCtrlList; class CtrlListList : public std::map<int, CtrlList*, std::less<int> > { + private: + MidiAudioCtrlMap _midi_controls; // For midi control of audio controllers. public: void add(CtrlList* vl); void clearDelete() { @@ -176,14 +233,19 @@ class CtrlListList : public std::map<int, CtrlList*, std::less<int> > { return std::map<int, CtrlList*, std::less<int> >::find(id); } - double value(int ctrlId, int frame, bool cur_val_only = false) const; + MidiAudioCtrlMap* midiControls() { return &_midi_controls; } + + double value(int ctrlId, int frame, bool cur_val_only = false, int* nextFrame = NULL) const; void updateCurValues(int frame); void clearAllAutomation() { for(iCtrlList i = begin(); i != end(); ++i) i->second->clear(); } + void write(int level, Xml& xml) const; }; +extern double midi2AudioCtrlValue(const CtrlList* audio_ctrl_list, const MidiAudioCtrlStruct* mapper, int midi_ctlnum, int midi_val); + } // namespace MusECore #endif diff --git a/muse2/muse/driver/alsamidi.cpp b/muse2/muse/driver/alsamidi.cpp index f75b9c33..e3e71365 100644 --- a/muse2/muse/driver/alsamidi.cpp +++ b/muse2/muse/driver/alsamidi.cpp @@ -1318,24 +1318,24 @@ void alsaProcessMidiInput() break; case SND_SEQ_EVENT_CLOCK: - MusEGlobal::midiSeq->realtimeSystemInput(curPort, ME_CLOCK); + MusEGlobal::midiSeq->realtimeSystemInput(curPort, ME_CLOCK, curTime()); //mdev->syncInfo().trigMCSyncDetect(); break; case SND_SEQ_EVENT_START: - MusEGlobal::midiSeq->realtimeSystemInput(curPort, ME_START); + MusEGlobal::midiSeq->realtimeSystemInput(curPort, ME_START, curTime()); break; case SND_SEQ_EVENT_CONTINUE: - MusEGlobal::midiSeq->realtimeSystemInput(curPort, ME_CONTINUE); + MusEGlobal::midiSeq->realtimeSystemInput(curPort, ME_CONTINUE, curTime()); break; case SND_SEQ_EVENT_STOP: - MusEGlobal::midiSeq->realtimeSystemInput(curPort, ME_STOP); + MusEGlobal::midiSeq->realtimeSystemInput(curPort, ME_STOP, curTime()); break; case SND_SEQ_EVENT_TICK: - MusEGlobal::midiSeq->realtimeSystemInput(curPort, ME_TICK); + MusEGlobal::midiSeq->realtimeSystemInput(curPort, ME_TICK, curTime()); //mdev->syncInfo().trigTickDetect(); break; diff --git a/muse2/muse/driver/jack.cpp b/muse2/muse/driver/jack.cpp index 4cc8bfb8..05d47955 100644 --- a/muse2/muse/driver/jack.cpp +++ b/muse2/muse/driver/jack.cpp @@ -42,6 +42,7 @@ #include "tempo.h" #include "sync.h" #include "utils.h" +#include "gconfig.h" #include "midi.h" #include "mididev.h" @@ -50,7 +51,7 @@ #include "jackmidi.h" -#define JACK_DEBUG 0 +#define JACK_DEBUG 0 //#include "errorhandler.h" @@ -176,7 +177,7 @@ int JackAudioDevice::processAudio(jack_nframes_t frames, void*) } } } - + //if(jackAudio->getState() != Audio::START_PLAY) // Don't process while we're syncing. TODO: May need to deliver silence in process! MusEGlobal::audio->process((unsigned long)frames); } @@ -196,8 +197,21 @@ int JackAudioDevice::processAudio(jack_nframes_t frames, void*) static int processSync(jack_transport_state_t state, jack_position_t* pos, void*) { if (JACK_DEBUG) - printf("processSync()\n"); + { + printf("processSync frame:%u\n", pos->frame); + if(pos->valid & JackPositionBBT) + { + if(JACK_DEBUG) + { + printf("processSync BBT:\n bar:%d beat:%d tick:%d\n bar_start_tick:%f beats_per_bar:%f beat_type:%f ticks_per_beat:%f beats_per_minute:%f\n", + pos->bar, pos->beat, pos->tick, pos->bar_start_tick, pos->beats_per_bar, pos->beat_type, pos->ticks_per_beat, pos->beats_per_minute); + if(pos->valid & JackBBTFrameOffset) + printf("processSync BBTFrameOffset: %u\n", pos->bbt_offset); + } + } + } + if(!MusEGlobal::useJackTransport.value()) return 1; @@ -237,49 +251,54 @@ static int processSync(jack_transport_state_t state, jack_position_t* pos, void* //--------------------------------------------------------- static void timebase_callback(jack_transport_state_t /* state */, - jack_nframes_t /* nframes */, + jack_nframes_t nframes, jack_position_t* pos, - int /* new_pos */, + int new_pos, void*) { - if (JACK_DEBUG) - printf("Jack timebase_callback pos->frame:%u MusEGlobal::audio->tickPos:%d MusEGlobal::song->cpos:%d\n", pos->frame, MusEGlobal::audio->tickPos(), MusEGlobal::song->cpos()); - - //Pos p(pos->frame, false); + + if (JACK_DEBUG) + { + if(pos->valid & JackPositionBBT) + printf("timebase_callback BBT:\n bar:%d beat:%d tick:%d\n bar_start_tick:%f beats_per_bar:%f beat_type:%f ticks_per_beat:%f beats_per_minute:%f\n", + pos->bar, pos->beat, pos->tick, pos->bar_start_tick, pos->beats_per_bar, pos->beat_type, pos->ticks_per_beat, pos->beats_per_minute); + if(pos->valid & JackBBTFrameOffset) + printf("timebase_callback BBTFrameOffset: %u\n", pos->bbt_offset); + if(pos->valid & JackPositionTimecode) + printf("timebase_callback JackPositionTimecode: frame_time:%f next_time:%f\n", pos->frame_time, pos->next_time); + if(pos->valid & JackAudioVideoRatio) + printf("timebase_callback JackAudioVideoRatio: %f\n", pos->audio_frames_per_video_frame); + if(pos->valid & JackVideoFrameOffset) + printf("timebase_callback JackVideoFrameOffset: %u\n", pos->video_offset); + } + + //Pos p(pos->frame, false); Pos p(MusEGlobal::extSyncFlag.value() ? MusEGlobal::audio->tickPos() : pos->frame, MusEGlobal::extSyncFlag.value() ? true : false); // Can't use song pos - it is only updated every (slow) GUI heartbeat ! //Pos p(MusEGlobal::extSyncFlag.value() ? MusEGlobal::song->cpos() : pos->frame, MusEGlobal::extSyncFlag.value() ? true : false); pos->valid = JackPositionBBT; p.mbt(&pos->bar, &pos->beat, &pos->tick); + pos->bar_start_tick = Pos(pos->bar, 0, 0).tick(); pos->bar++; pos->beat++; - pos->bar_start_tick = Pos(pos->bar, 0, 0).tick(); - - // - // dummy: - // - //pos->beats_per_bar = 4; - //pos->beat_type = 4; - //pos->ticks_per_beat = 384; - // - /* // From example client transport.c : - float time_beats_per_bar = 4.0; - float time_beat_type = 0.25; // Huh? Inverted? From docs: "Time signature 'denominator'" - double time_ticks_per_beat = 1920.0; // Huh? Ticks per beat should be 24 etc. not 384 or 1920 etc. Otherwise it would be called 'frames_per_beat'. - double time_beats_per_minute = 120.0; - */ - // int z, n; AL::sigmap.timesig(p.tick(), z, n); pos->beats_per_bar = z; pos->beat_type = n; - //pos->ticks_per_beat = config.division; - pos->ticks_per_beat = 24; + pos->ticks_per_beat = MusEGlobal::config.division; + //pos->ticks_per_beat = 24; + + double tempo = MusEGlobal::tempomap.tempo(p.tick()); + pos->beats_per_minute = (60000000.0 / tempo) * double(MusEGlobal::tempomap.globalTempo())/100.0; + if (JACK_DEBUG) + { + printf("timebase_callback is new_pos:%d nframes:%u frame:%u tickPos:%d cpos:%d\n", new_pos, nframes, pos->frame, MusEGlobal::audio->tickPos(), MusEGlobal::song->cpos()); + printf(" new: bar:%d beat:%d tick:%d\n bar_start_tick:%f beats_per_bar:%f beat_type:%f ticks_per_beat:%f beats_per_minute:%f\n", + pos->bar, pos->beat, pos->tick, pos->bar_start_tick, pos->beats_per_bar, pos->beat_type, pos->ticks_per_beat, pos->beats_per_minute); + } - int tempo = MusEGlobal::tempomap.tempo(p.tick()); - pos->beats_per_minute = (60000000.0 / tempo) * MusEGlobal::tempomap.globalTempo()/100.0; } //--------------------------------------------------------- @@ -1226,6 +1245,69 @@ jack_transport_state_t JackAudioDevice::transportQuery(jack_position_t* pos) } //--------------------------------------------------------- +// timebaseQuery +// Given the number of frames in this period, get the bar, beat, tick, +// and current absolute tick, and number of ticks in this period. +// Return false if information could not be obtained. +//--------------------------------------------------------- + +bool JackAudioDevice::timebaseQuery(unsigned frames, unsigned* bar, unsigned* beat, unsigned* tick, unsigned* curr_abs_tick, unsigned* next_ticks) +{ + jack_position_t jp; + jack_transport_query(_client, &jp); + + if(JACK_DEBUG) + printf("timebaseQuery frame:%u\n", jp.frame); + + if(jp.valid & JackPositionBBT) + { + if(JACK_DEBUG) + { + printf("timebaseQuery BBT:\n bar:%d beat:%d tick:%d\n bar_start_tick:%f beats_per_bar:%f beat_type:%f ticks_per_beat:%f beats_per_minute:%f\n", + jp.bar, jp.beat, jp.tick, jp.bar_start_tick, jp.beats_per_bar, jp.beat_type, jp.ticks_per_beat, jp.beats_per_minute); + if(jp.valid & JackBBTFrameOffset) + printf("timebaseQuery BBTFrameOffset: %u\n", jp.bbt_offset); + } + + if(jp.ticks_per_beat > 0.0) + { + unsigned muse_tick = unsigned((double(jp.tick) / jp.ticks_per_beat) * double(MusEGlobal::config.division)); + unsigned curr_tick = ((jp.bar - 1) * jp.beats_per_bar + (jp.beat - 1)) * double(MusEGlobal::config.division) + muse_tick; + // Prefer the reported frame rate over the app's rate if possible. + double f_rate = jp.frame_rate != 0 ? jp.frame_rate : MusEGlobal::sampleRate; + // beats_per_minute is "supposed" to be quantized to period size - that is, computed + // so that mid-period changes are averaged out to produce a single tempo which + // produces the same tick in the end. If we can rely on that, we should be good accuracy. + unsigned ticks = double(MusEGlobal::config.division) * (jp.beats_per_minute / 60.0) * double(frames) / f_rate; + + if(JACK_DEBUG) + printf("timebaseQuery curr_tick:%u f_rate:%f ticks:%u\n", curr_tick, f_rate, ticks); + + if(bar) *bar = jp.bar; + if(beat) *beat = jp.beat; + if(tick) *tick = muse_tick; + + if(curr_abs_tick) *curr_abs_tick = curr_tick; + if(next_ticks) *next_ticks = ticks; + + return true; + } + } + + if(JACK_DEBUG) + { + if(jp.valid & JackPositionTimecode) + printf("timebaseQuery JackPositionTimecode: frame_time:%f next_time:%f\n", jp.frame_time, jp.next_time); + if(jp.valid & JackAudioVideoRatio) + printf("timebaseQuery JackAudioVideoRatio: %f\n", jp.audio_frames_per_video_frame); + if(jp.valid & JackVideoFrameOffset) + printf("timebaseQuery JackVideoFrameOffset: %u\n", jp.video_offset); + } + + return false; +} + +//--------------------------------------------------------- // systemTime // Return system time. Depends on selected clock source. // With Jack, may be based upon wallclock time, the @@ -1617,7 +1699,7 @@ void JackAudioDevice::seekTransport(unsigned frame) void JackAudioDevice::seekTransport(const Pos &p) { if (JACK_DEBUG) - printf("JackAudioDevice::seekTransport() frame:%d\n", p.frame()); + printf("JackAudioDevice::seekTransport(Pos) frame:%d\n", p.frame()); if(!MusEGlobal::useJackTransport.value()) { @@ -1628,25 +1710,29 @@ void JackAudioDevice::seekTransport(const Pos &p) } if(!checkJackClient(_client)) return; + +// TODO: Be friendly to other apps... Sadly not many of us use jack_transport_reposition. +// This is actually required IF we want the extra position info to show up +// in the sync callback, otherwise we get just the frame only. +// This information is shared on the server, it is directly passed around. +// jack_transport_locate blanks the info from sync until the timebase callback reads +// it again right after, from some timebase master. See process in audio.cpp + +// jack_position_t jp; +// jp.frame = p.frame(); +// +// jp.valid = JackPositionBBT; +// p.mbt(&jp.bar, &jp.beat, &jp.tick); +// jp.bar_start_tick = Pos(jp.bar, 0, 0).tick(); +// jp.bar++; +// jp.beat++; +// jp.beats_per_bar = 5; // TODO Make this correct ! +// jp.beat_type = 8; // +// jp.ticks_per_beat = MusEGlobal::config.division; +// int tempo = MusEGlobal::tempomap.tempo(p.tick()); +// jp.beats_per_minute = (60000000.0 / tempo) * MusEGlobal::tempomap.globalTempo()/100.0; +// jack_transport_reposition(_client, &jp); - /* - jack_position_t jp; - jp.valid = JackPositionBBT; - p.mbt(&jp.bar, &jp.beat, &jp.tick); - jp.bar++; - jp.beat++; - jp.bar_start_tick = Pos(jp.bar, 0, 0).tick(); - // - // dummy: - // - jp.beats_per_bar = 4; - jp.beat_type = 4; - jp.ticks_per_beat = 384; - int tempo = MusEGlobal::tempomap.tempo(p.tick()); - jp.beats_per_minute = (60000000.0 / tempo) * MusEGlobal::tempomap.globalTempo()/100.0; - - jack_transport_reposition(_client, &jp); - */ jack_transport_locate(_client, p.frame()); } diff --git a/muse2/muse/driver/jackaudio.h b/muse2/muse/driver/jackaudio.h index 9640ca81..aab60d88 100644 --- a/muse2/muse/driver/jackaudio.h +++ b/muse2/muse/driver/jackaudio.h @@ -80,6 +80,7 @@ class JackAudioDevice : public AudioDevice { virtual std::list<QString> outputPorts(bool midi = false, int aliases = -1); virtual std::list<QString> inputPorts(bool midi = false, int aliases = -1); + jack_client_t* jackClient() const { return _client; } virtual void registerClient(); virtual const char* clientName() { return jackRegisteredName; } @@ -105,6 +106,7 @@ class JackAudioDevice : public AudioDevice { virtual void seekTransport(const Pos &p); virtual void setFreewheel(bool f); jack_transport_state_t transportQuery(jack_position_t* pos); + bool timebaseQuery(unsigned frames, unsigned* bar, unsigned* beat, unsigned* tick, unsigned* curr_abs_tick, unsigned* next_ticks); void graphChanged(); void registrationChanged(); void connectJackMidiPorts(); diff --git a/muse2/muse/driver/jackmidi.cpp b/muse2/muse/driver/jackmidi.cpp index 706fa269..e3e67dfb 100644 --- a/muse2/muse/driver/jackmidi.cpp +++ b/muse2/muse/driver/jackmidi.cpp @@ -30,6 +30,7 @@ //#include <jack/midiport.h> #include "jackmidi.h" +#include "jackaudio.h" #include "song.h" #include "globals.h" #include "midi.h" @@ -50,10 +51,6 @@ // Turn on debug messages. //#define JACK_MIDI_DEBUG -namespace MusEGlobal { -extern unsigned int volatile lastExtMidiSyncTick; -} - namespace MusECore { //--------------------------------------------------------- @@ -422,7 +419,7 @@ void MidiJackDevice::recordEvent(MidiRecordEvent& event) // Split the events up into channel fifos. Special 'channel' number 17 for sysex events. unsigned int ch = (typ == ME_SYSEX)? MIDI_CHANNELS : event.channel(); - if(_recordFifo[ch].put(MidiPlayEvent(event))) + if(_recordFifo[ch].put(event)) printf("MidiJackDevice::recordEvent: fifo channel %d overflow\n", ch); } @@ -446,10 +443,18 @@ void MidiJackDevice::eventReceived(jack_midi_event_t* ev) // catch process play // - //int frameOffset = MusEGlobal::audio->getFrameOffset(); - unsigned pos = MusEGlobal::audio->pos().frame(); - - event.setTime(MusEGlobal::extSyncFlag.value() ? MusEGlobal::lastExtMidiSyncTick : (pos + ev->time)); // p3.3.25 + // These Jack events arrived in the previous period, and it may not have been at the audio position before this one (after a seek). + // This is how our ALSA driver works, events there are timestamped asynchronous of any process, referenced to the CURRENT audio + // position, so that by the time of the NEXT process, THOSE events have also occured in the previous period. + // So, technically this is correct. What MATTERS is how we adjust the times for storage, and/or simultaneous playback in THIS period, + // and TEST: we'll need to make sure any non-contiguous previous period is handled correctly by process - will it work OK as is? + // If ALSA works OK than this should too... +#ifdef _AUDIO_USE_TRUE_FRAME_ + event.setTime(MusEGlobal::audio->previousPos().frame() + ev->time); +#else + event.setTime(MusEGlobal::audio->pos().frame() + ev->time); +#endif + event.setTick(MusEGlobal::lastExtMidiSyncTick); event.setChannel(*(ev->buffer) & 0xf); int type = *(ev->buffer) & 0xf0; @@ -509,9 +514,20 @@ void MidiJackDevice::eventReceived(jack_midi_event_t* ev) case ME_START: case ME_CONTINUE: case ME_STOP: - if(_port != -1) - MusEGlobal::midiSeq->realtimeSystemInput(_port, type); + { + if(MusEGlobal::audioDevice && MusEGlobal::audioDevice->deviceType() == JACK_MIDI && _port != -1) + { + MusECore::JackAudioDevice* jad = static_cast<MusECore::JackAudioDevice*>(MusEGlobal::audioDevice); + jack_client_t* jc = jad->jackClient(); + if(jc) + { + jack_nframes_t abs_ft = jack_last_frame_time(jc) + ev->time; + double abs_ev_t = double(jack_frames_to_time(jc, abs_ft)) / 1000000.0; + MusEGlobal::midiSeq->realtimeSystemInput(_port, type, abs_ev_t); + } + } return; + } //case ME_SYSEX_END: //break; // return; diff --git a/muse2/muse/dssihost.cpp b/muse2/muse/dssihost.cpp index 5000e338..01f9c0f3 100644 --- a/muse2/muse/dssihost.cpp +++ b/muse2/muse/dssihost.cpp @@ -680,7 +680,7 @@ bool DssiSynthIF::init(DssiSynth* s) // Set current program. if(dssi->select_program) - dssi->select_program(handle, synti->_curBankL, synti->_curProgram); + doSelectProgram(handle, synti->_curBankL, synti->_curProgram); // // For stored initial control values, let SynthI::initInstance() take care of that via ::setParameter(). @@ -839,27 +839,7 @@ float DssiSynthIF::getParameterOut(unsigned long n) const void DssiSynthIF::setParameter(unsigned long n, float v) { - if(n >= synth->_controlInPorts) - { - printf("DssiSynthIF::setParameter param number %lu out of range of ports:%lu\n", n, synth->_controlInPorts); - return; - } - - ControlEvent ce; - ce.unique = false; - ce.idx = n; - ce.value = v; - // Time-stamp the event. This does a possibly slightly slow call to gettimeofday via timestamp(). - // timestamp() is more or less an estimate of the current frame. (This is exactly how ALSA events - // are treated when they arrive in our ALSA driver.) - //ce.frame = MusEGlobal::audio->timestamp(); - // p4.0.23 timestamp() is circular, which is making it impossible to deal with 'modulo' events which - // slip in 'under the wire' before processing the ring buffers. So try this linear timestamp instead: - ce.frame = MusEGlobal::audio->curFrame(); - if(_controlFifo.put(ce)) - { - fprintf(stderr, "DssiSynthIF::setParameter: fifo overflow: in control number:%lu\n", n); - } + addScheduledControlEvent(n, v, MusEGlobal::audio->curFrame()); } //--------------------------------------------------------- @@ -1084,7 +1064,7 @@ bool DssiSynthIF::processEvent(const MusECore::MidiPlayEvent& e, snd_seq_event_t synti->_curProgram = prog; if(dssi->select_program) - dssi->select_program(handle, bank, prog); + doSelectProgram(handle, bank, prog); // Event pointer not filled. Return false. return false; @@ -1113,7 +1093,7 @@ bool DssiSynthIF::processEvent(const MusECore::MidiPlayEvent& e, snd_seq_event_t synti->_curProgram = prog; if(dssi->select_program) - dssi->select_program(handle, bank, prog); + doSelectProgram(handle, bank, prog); // Event pointer not filled. Return false. return false; @@ -1220,6 +1200,11 @@ bool DssiSynthIF::processEvent(const MusECore::MidiPlayEvent& e, snd_seq_event_t // Set the ladspa port value. controls[k].val = val; + // Need to update the automation value, otherwise it overwrites later with the last automation value. + if(id() != -1) + // We're in the audio thread context: no need to send a message, just modify directly. + synti->setPluginCtrlVal(genACnum(id(), k), val); + // Since we absorbed the message as a ladspa control change, return false - the event is not filled. return false; } @@ -1404,7 +1389,7 @@ MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MP if(fixedsize > nframes) fixedsize = nframes; - unsigned long min_per = MusEGlobal::config.minControlProcessPeriod; + unsigned long min_per = MusEGlobal::config.minControlProcessPeriod; // Must be power of 2 ! if(min_per > nframes) min_per = nframes; @@ -1412,7 +1397,7 @@ MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MP fprintf(stderr, "DssiSynthIF::getData: Handling inputs...\n"); #endif - // p4.0.38 Handle inputs... + // Handle inputs... if(!((MusECore::AudioTrack*)synti)->noInRoute()) { RouteList* irl = ((MusECore::AudioTrack*)synti)->inRoutes(); @@ -1485,21 +1470,57 @@ MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MP #ifdef DSSI_DEBUG_PROCESS fprintf(stderr, "DssiSynthIF::getData: Processing automation control values...\n"); #endif - - // 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) - { - for(unsigned long k = 0; k < synth->_controlInPorts; ++k) - { - if(controls[k].enCtrl && controls[k].en2Ctrl ) - controls[k].val = (static_cast<MusECore::AudioTrack*>(synti))->controller()->value(genACnum(id(), k), pos); - } - } - + while(sample < nframes) { unsigned long nsamp = usefixedrate ? fixedsize : nframes - sample; + + // + // Process automation control values, while also determining the maximum acceptable + // size of this run. Further processing, from FIFOs for example, can lower the size + // from there, but this section determines where the next highest maximum frame + // absolutely needs to be for smooth playback of the controller value stream... + // + if(id() != -1) + { + unsigned long frame = pos + sample; + AutomationType at = AUTO_OFF; + at = synti->automationType(); + bool no_auto = !MusEGlobal::automation || at == AUTO_OFF; + AudioTrack* track = (static_cast<AudioTrack*>(synti)); + int nextFrame; + for(unsigned long k = 0; k < synth->_controlInPorts; ++k) + { + controls[k].val = track->controller()->value(genACnum(id(), k), frame, + no_auto || !controls[k].enCtrl || !controls[k].en2Ctrl, + &nextFrame); +#ifdef DSSI_DEBUG_PROCESS + printf("DssiSynthIF::getData k:%lu sample:%lu frame:%lu nextFrame:%d nsamp:%lu \n", k, sample, frame, nextFrame, nsamp); +#endif + if(MusEGlobal::audio->isPlaying() && !usefixedrate && nextFrame != -1) + { + // Returned value of nextFrame can be zero meaning caller replaces with some (constant) value. + unsigned long samps = (unsigned long)nextFrame; + if(samps > frame + min_per) + { + unsigned long diff = samps - frame; + unsigned long mask = min_per-1; // min_per must be power of 2 + samps = diff & ~mask; + if((diff & mask) != 0) + samps += min_per; + } + else + samps = min_per; + + if(samps < nsamp) + nsamp = samps; + } + } +#ifdef DSSI_DEBUG + printf("DssiSynthIF::getData sample:%lu nsamp:%lu\n", sample, nsamp); +#endif + } + bool found = false; unsigned long frame = 0; unsigned long index = 0; @@ -1528,11 +1549,13 @@ MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MP continue; } - if(evframe >= nframes - || (found && !v.unique && (evframe - sample >= min_per)) - || (usefixedrate && found && v.unique && v.idx == index)) + if(evframe >= nframes // Next events are for a later period. + || (!usefixedrate && !found && !v.unique && (evframe - sample >= nsamp)) // Next events are for a later run in this period. (Autom took prio.) + || (found && !v.unique && (evframe - sample >= min_per)) // Eat up events within minimum slice - they're too close. + || (usefixedrate && found && v.unique && v.idx == index)) // Special for dssi-vst: Fixed rate and must reply to all. break; _controlFifo.remove(); // Done with the ring buffer's item. Remove it. + if(v.idx >= synth->_controlInPorts) // Sanity check. break; found = true; @@ -1543,36 +1566,16 @@ MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MP // Need to update the automation value, otherwise it overwrites later with the last automation value. if(id() != -1) - { - // We're in the audio thread context: no need to send a message, just modify directly. synti->setPluginCtrlVal(genACnum(id(), v.idx), v.value); - - /* Record automation. DELETETHIS? - * 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. - - // 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 ! - AutomationType at = _track->automationType(); - if ((at == AUTO_WRITE) || - (at == AUTO_TOUCH && MusEGlobal::audio->isPlaying())) - enableController(k, false); - _track->recordAutomation(id, v.value); - */ - } } - if(found && !usefixedrate) + if(found && !usefixedrate) // If a control FIFO item was found, takes priority over automation controller stream. nsamp = frame - sample; if(sample + nsamp >= nframes) // Safety check. nsamp = nframes - sample; - // TODO: TESTING: Don't allow zero-length runs. This could/should be checked in the control loop instead. + // TODO: Don't allow zero-length runs. This could/should be checked in the control loop instead. // Note this means it is still possible to get stuck in the top loop (at least for a while). if(nsamp == 0) continue; @@ -1586,8 +1589,13 @@ MusECore::iMPEvent DssiSynthIF::getData(MusECore::MidiPort* /*mp*/, MusECore::MP #endif if(start_event->time() >= (pos + sample + nsamp + frameOffset)) // frameOffset? Test again... + { + #ifdef DSSI_DEBUG + fprintf(stderr, " event is for future:%lu, breaking loop now\n", start_event->time() - frameOffset - pos - sample); + #endif break; - + } + // Update hardware state so knobs and boxes are updated. Optimize to avoid re-setting existing values. // Same code as in MidiPort::sendEvent() if(synti->midiPort() != -1) @@ -2083,6 +2091,25 @@ void DssiSynthIF::queryPrograms() } } +void DssiSynthIF::doSelectProgram(LADSPA_Handle handle, int bank, int prog) +{ + const DSSI_Descriptor* dssi = synth->dssi; + dssi->select_program(handle, bank, prog); + + // Need to update the automation value, otherwise it overwrites later with the last automation value. + // "A plugin is permitted to re-write the values of its input control ports when select_program is called. + // The host should re-read the input control port values and update its own records appropriately. + // (This is the only circumstance in which a DSSI plugin is allowed to modify its own input ports.)" From dssi.h + if(id() != -1) + { + for(unsigned long k = 0; k < synth->_controlInPorts; ++k) + { + // We're in the audio thread context: no need to send a message, just modify directly. + synti->setPluginCtrlVal(genACnum(id(), k), controls[k].val); + } + } +} + //--------------------------------------------------------- // getPatchName //--------------------------------------------------------- @@ -2235,14 +2262,30 @@ 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; } +void DssiSynthIF::enable2Controller(unsigned long i, bool v) { controls[i].en2Ctrl = v; } bool DssiSynthIF::controllerEnabled2(unsigned long i) const { return controls[i].en2Ctrl; } +void DssiSynthIF::enableAllControllers(bool v) +{ + if(!synth) + return; + for(unsigned long i = 0; i < synth->_controlInPorts; ++i) + controls[i].enCtrl = v; +} +void DssiSynthIF::enable2AllControllers(bool v) +{ + if(!synth) + return; + for(unsigned long i = 0; i < synth->_controlInPorts; ++i) + controls[i].en2Ctrl = v; +} + void DssiSynthIF::updateControllers() { } void DssiSynthIF::writeConfiguration(int /*level*/, Xml& /*xml*/) { } bool DssiSynthIF::readConfiguration(Xml& /*xml*/, bool /*readPreset*/) { return false; } unsigned long DssiSynthIF::parameters() const { return synth ? synth->_controlInPorts : 0; } unsigned long DssiSynthIF::parametersOut() const { return synth ? synth->_controlOutPorts : 0; } -void DssiSynthIF::setParam(unsigned long i, float val) { setParameter(i, val); } +void DssiSynthIF::setParam(unsigned long i, float val) { setParameter(i, val); } float DssiSynthIF::param(unsigned long i) const { return getParameter(i); } float DssiSynthIF::paramOut(unsigned long i) const { return getParameterOut(i); } const char* DssiSynthIF::paramName(unsigned long i) { return (synth && synth->dssi) ? synth->dssi->LADSPA_Plugin->PortNames[controls[i].idx] : 0; } diff --git a/muse2/muse/dssihost.h b/muse2/muse/dssihost.h index 238b468e..46c9a07b 100644 --- a/muse2/muse/dssihost.h +++ b/muse2/muse/dssihost.h @@ -128,6 +128,7 @@ class DssiSynthIF : public SynthIF, public PluginIBase std::vector<DSSI_Program_Descriptor> programs; void queryPrograms(); + void doSelectProgram(LADSPA_Handle handle, int bank, int prog); bool processEvent(const MusECore::MidiPlayEvent&, snd_seq_event_t*); float** audioInBuffers; @@ -209,7 +210,10 @@ class DssiSynthIF : public SynthIF, public PluginIBase MusECore::AudioTrack* track(); void enableController(unsigned long i, bool v = true); bool controllerEnabled(unsigned long i) const; + void enable2Controller(unsigned long i, bool v = true); bool controllerEnabled2(unsigned long i) const; + void enableAllControllers(bool v = true); + void enable2AllControllers(bool v = true); void updateControllers(); void writeConfiguration(int level, Xml& xml); bool readConfiguration(Xml& xml, bool readPreset=false); diff --git a/muse2/muse/gconfig.cpp b/muse2/muse/gconfig.cpp index 1a0426a7..302007b3 100644 --- a/muse2/muse/gconfig.cpp +++ b/muse2/muse/gconfig.cpp @@ -186,7 +186,7 @@ GlobalConfigValues config = { QString("./"), // projectBaseFolder true, // projectStoreInFolder true, // useProjectSaveDialog - 64, // minControlProcessPeriod + 256, // minControlProcessPeriod false, // popupsDefaultStayOpen false, // leftMouseButtonCanDecrease false, // rangeMarkerWithoutMMB diff --git a/muse2/muse/globals.cpp b/muse2/muse/globals.cpp index d92e6abf..b3765074 100644 --- a/muse2/muse/globals.cpp +++ b/muse2/muse/globals.cpp @@ -275,6 +275,12 @@ unsigned char rcPlayNote = 29; unsigned char rcSteprecNote = 36; bool automation = true; +// Midi learn params. These will be initialized to -1 by any midi learn function, +// and then filled by the midi engine in response to the drivers. +int midiLearnPort = -1; +int midiLearnChan = -1; +int midiLearnCtrl = -1; + uid_t euid, ruid; // effective user id, real user id bool midiSeqRunning = false; diff --git a/muse2/muse/globals.h b/muse2/muse/globals.h index c64fdf89..bdf383c8 100644 --- a/muse2/muse/globals.h +++ b/muse2/muse/globals.h @@ -172,6 +172,10 @@ extern unsigned char rcGotoLeftMarkNote; extern unsigned char rcPlayNote; extern unsigned char rcSteprecNote; +extern int midiLearnPort; +extern int midiLearnChan; +extern int midiLearnCtrl; + extern bool midiSeqRunning; extern bool automation; diff --git a/muse2/muse/midi.cpp b/muse2/muse/midi.cpp index ae348b5f..503208e6 100644 --- a/muse2/muse/midi.cpp +++ b/muse2/muse/midi.cpp @@ -34,6 +34,7 @@ #include "marker/marker.h" #include "midiport.h" #include "midictrl.h" +#include "sync.h" #include "audio.h" #include "mididev.h" #include "driver/alsamidi.h" @@ -860,31 +861,149 @@ void Audio::collectEvents(MusECore::MidiTrack* track, unsigned int cts, unsigned void Audio::processMidi() { MusEGlobal::midiBusy=true; + + bool extsync = MusEGlobal::extSyncFlag.value(); + // // TODO: syntis should directly write into recordEventList // - for (iMidiDevice id = MusEGlobal::midiDevices.begin(); id != MusEGlobal::midiDevices.end(); ++id) { - MidiDevice* md = *id; - - // klumsy hack for synti devices: - if(md->isSynti()) + for (iMidiDevice id = MusEGlobal::midiDevices.begin(); id != MusEGlobal::midiDevices.end(); ++id) + { + MidiDevice* md = *id; + + // klumsy hack for MESS synti devices: + if(md->isSynti()) + { + SynthI* s = (SynthI*)md; + while (s->eventsPending()) + { + MusECore::MidiRecordEvent ev = s->receiveEvent(); + md->recordEvent(ev); + } + } + + md->collectMidiEvents(); + + // Take snapshots of the current sizes of the recording fifos, + // because they may change while here in process, asynchronously. + md->beforeProcess(); + + // + // --------- Handle midi events for audio tracks ----------- + // + + int port = md->midiPort(); // Port should be same as event.port() from this device. Same idea event.channel(). + if(port < 0) + continue; + + for(int chan = 0; chan < MIDI_CHANNELS; ++chan) + { + MusECore::MidiRecFifo& rf = md->recordEvents(chan); + int count = md->tmpRecordCount(chan); + for(int i = 0; i < count; ++i) + { + MusECore::MidiRecordEvent event(rf.peek(i)); + + int etype = event.type(); + if(etype == MusECore::ME_CONTROLLER || etype == MusECore::ME_PITCHBEND || etype == MusECore::ME_PROGRAM) { - SynthI* s = (SynthI*)md; - while (s->eventsPending()) + int ctl, val; + if(etype == MusECore::ME_CONTROLLER) { - MusECore::MidiRecordEvent ev = s->receiveEvent(); - md->recordEvent(ev); + ctl = event.dataA(); + val = event.dataB(); } + else if(etype == MusECore::ME_PITCHBEND) + { + ctl = MusECore::CTRL_PITCH; + val = event.dataA(); + } + else if(etype == MusECore::ME_PROGRAM) + { + ctl = MusECore::CTRL_PROGRAM; + val = event.dataA(); + } + + // Midi learn! + MusEGlobal::midiLearnPort = port; + MusEGlobal::midiLearnChan = chan; + MusEGlobal::midiLearnCtrl = ctl; + + // Send to audio tracks... + for (MusECore::iTrack t = MusEGlobal::song->tracks()->begin(); t != MusEGlobal::song->tracks()->end(); ++t) + { + if((*t)->isMidiTrack()) + continue; + MusECore::AudioTrack* track = static_cast<MusECore::AudioTrack*>(*t); + MidiAudioCtrlMap* macm = track->controller()->midiControls(); + int h = macm->index_hash(port, chan, ctl); + std::pair<ciMidiAudioCtrlMap, ciMidiAudioCtrlMap> range = macm->equal_range(h); + for(ciMidiAudioCtrlMap imacm = range.first; imacm != range.second; ++imacm) + { + const MidiAudioCtrlStruct* macs = &imacm->second; + int actrl = macs->audioCtrlId(); + + iCtrlList icl = track->controller()->find(actrl); + if(icl == track->controller()->end()) + continue; + CtrlList* cl = icl->second; + double dval = midi2AudioCtrlValue(cl, macs, ctl, val); + + // Time here needs to be frames always. + unsigned int ev_t = event.time(); + unsigned int t = ev_t; + +#ifdef _AUDIO_USE_TRUE_FRAME_ + unsigned int pframe = _previousPos.frame(); +#else + unsigned int pframe = _pos.frame(); +#endif + if(pframe > t) // Technically that's an error, shouldn't happen + t = 0; + else + // Subtract the current audio position frame + t -= pframe; + + // Add the current running sync frame to make the control processing happy + t += syncFrame; + track->addScheduledControlEvent(actrl, dval, t); + + // Rec automation... + + // For the record time, if stopped we don't want the circular running position, + // just the static one. + unsigned int rec_t = isPlaying() ? ev_t : pframe; + + if(!MusEGlobal::automation) + continue; + AutomationType at = track->automationType(); + // Unlike our built-in gui controls, there is not much choice here but to + // just do this: + if ( (at == AUTO_WRITE) || + (at == AUTO_TOUCH && MusEGlobal::audio->isPlaying()) ) + //if(isPlaying() && (at == AUTO_WRITE || at == AUTO_TOUCH)) DELETETHIS + track->enableController(actrl, false); + if(isPlaying()) + { + if(at == AUTO_WRITE || at == AUTO_TOUCH) + track->recEvents()->push_back(CtrlRecVal(rec_t, actrl, dval)); + } + else + { + if(at == AUTO_WRITE) + track->recEvents()->push_back(CtrlRecVal(rec_t, actrl, dval)); + else if(at == AUTO_TOUCH) + // In touch mode and not playing. Send directly to controller list. + // Add will replace if found. + cl->add(rec_t, dval); + } + } + } } - - md->collectMidiEvents(); - - // Take snapshots of the current sizes of the recording fifos, - // because they may change while here in process, asynchronously. - md->beforeProcess(); - } + } + } + } - bool extsync = MusEGlobal::extSyncFlag.value(); for (MusECore::iMidiTrack t = MusEGlobal::song->midis()->begin(); t != MusEGlobal::song->midis()->end(); ++t) { MusECore::MidiTrack* track = *t; @@ -934,15 +1053,30 @@ void Audio::processMidi() for(int i = 0; i < count; ++i) { - MusECore::MidiPlayEvent event(rf.peek(i)); + MusECore::MidiRecordEvent event(rf.peek(i)); event.setPort(port); // dont't echo controller changes back to software // synthesizer: if(!dev->isSynti() && md && track->recEcho()) + { + // All recorded events arrived in the previous period. Shift into this period for playback. + unsigned int et = event.time(); +#ifdef _AUDIO_USE_TRUE_FRAME_ + unsigned int t = et - _previousPos.frame() + _pos.frame() + frameOffset; +#else + unsigned int t = et + frameOffset; +#endif + event.setTime(t); md->addScheduledEvent(event); - // If syncing externally the event time is already in units of ticks, set above. p3.3.25 - if(!extsync) - event.setTime(MusEGlobal::tempomap.frame2tick(event.time())); // set tick time + event.setTime(et); // Restore for recording. + } + + // Make sure the event is recorded in units of ticks. + if(extsync) + event.setTime(event.tick()); // HACK: Transfer the tick to the frame time + else + event.setTime(MusEGlobal::tempomap.frame2tick(event.time())); + if(recording) rl->add(event); } @@ -953,7 +1087,7 @@ void Audio::processMidi() int count = dev->tmpRecordCount(channel); for(int i = 0; i < count; ++i) { - MusECore::MidiPlayEvent event(rf.peek(i)); + MusECore::MidiRecordEvent event(rf.peek(i)); int defaultPort = devport; int drumRecPitch=0; //prevent compiler warning: variable used without initialization MusECore::MidiController *mc = 0; @@ -1073,7 +1207,16 @@ void Audio::processMidi() if (!dev->isSynti()) { - //Check if we're outputting to another port than default: + // All recorded events arrived in previous period. Shift into this period for playback. + // frameoffset needed to make process happy. + unsigned int et = event.time(); +#ifdef _AUDIO_USE_TRUE_FRAME_ + unsigned int t = et - _previousPos.frame() + _pos.frame() + frameOffset; +#else + unsigned int t = et + frameOffset; +#endif + event.setTime(t); + // Check if we're outputting to another port than default: if (devport == defaultPort) { event.setPort(port); if(md && track->recEcho()) @@ -1085,14 +1228,18 @@ void Audio::processMidi() if(mdAlt && track->recEcho()) mdAlt->addScheduledEvent(event); } + event.setTime(et); // Restore for recording. + // Shall we activate meters even while rec echo is off? Sure, why not... if(event.isNote() && event.dataB() > track->activity()) track->setActivity(event.dataB()); } - // If syncing externally the event time is already in units of ticks, set above. p3.3.25 - if(!extsync) - event.setTime(MusEGlobal::tempomap.frame2tick(event.time())); // set tick time + // Make sure the event is recorded in units of ticks. + if(extsync) + event.setTime(event.tick()); // HACK: Transfer the tick to the frame time + else + event.setTime(MusEGlobal::tempomap.frame2tick(event.time())); // Special handling of events stored in rec-lists. a bit hACKish. TODO: Clean up (after 0.7)! :-/ (ml) if (recording) diff --git a/muse2/muse/midictrl.cpp b/muse2/muse/midictrl.cpp index b95ccf77..63ce6fe6 100644 --- a/muse2/muse/midictrl.cpp +++ b/muse2/muse/midictrl.cpp @@ -306,6 +306,39 @@ MidiController::ControllerType midiControllerType(int num) } //--------------------------------------------------------- +// midiCtrlTerms2Number +//--------------------------------------------------------- + +int midiCtrlTerms2Number(int type_num, int ctrl) +{ + ctrl &= 0xffff; + switch(type_num) + { + case MidiController::Controller7: + return ctrl & 0xff; + case MidiController::Controller14: + return CTRL_14_OFFSET + ctrl; + case MidiController::RPN: + return CTRL_RPN_OFFSET + ctrl; + case MidiController::NRPN: + return CTRL_NRPN_OFFSET + ctrl; + case MidiController::Pitch: + return CTRL_PITCH; + case MidiController::Program: + return CTRL_PROGRAM; + case MidiController::Velo: + return CTRL_VELOCITY; + case MidiController::RPN14: + return CTRL_RPN14_OFFSET + ctrl; + case MidiController::NRPN14: + return CTRL_NRPN14_OFFSET + ctrl; + default: + printf("MusE: unknown ctrl type in midiCtrTerms2Number()\n"); + return ctrl; + } +} + +//--------------------------------------------------------- // updateBias //--------------------------------------------------------- diff --git a/muse2/muse/midictrl.h b/muse2/muse/midictrl.h index 74902bc2..4c9a4097 100644 --- a/muse2/muse/midictrl.h +++ b/muse2/muse/midictrl.h @@ -242,6 +242,7 @@ typedef MidiControllerList MidiControllerList; extern MidiControllerList defaultMidiController; extern void initMidiController(); extern MidiController::ControllerType midiControllerType(int num); +extern int midiCtrlTerms2Number(int type_num, int ctrl); extern const QString& int2ctrlType(int n); diff --git a/muse2/muse/mididev.cpp b/muse2/muse/mididev.cpp index becab6f7..5ff8bf94 100644 --- a/muse2/muse/mididev.cpp +++ b/muse2/muse/mididev.cpp @@ -46,7 +46,6 @@ namespace MusEGlobal { MusECore::MidiDeviceList midiDevices; -extern unsigned int volatile lastExtMidiSyncTick; } namespace MusECore { @@ -215,10 +214,14 @@ void MidiDevice::beforeProcess() void MidiDevice::recordEvent(MidiRecordEvent& event) { - // TODO: Tested, but record resolution not so good. Switch to wall clock based separate list in MidiDevice. And revert this line. - //event.setTime(MusEGlobal::audio->timestamp()); - event.setTime(MusEGlobal::extSyncFlag.value() ? MusEGlobal::lastExtMidiSyncTick : MusEGlobal::audio->timestamp()); - + // TODO: Tested, but record resolution not so good. Switch to wall clock based separate list in MidiDevice. + unsigned frame_ts = MusEGlobal::audio->timestamp(); +#ifndef _AUDIO_USE_TRUE_FRAME_ + if(MusEGlobal::audio->isPlaying()) + frame_ts += MusEGlobal::segmentSize; // Shift forward into this period if playing +#endif + event.setTime(frame_ts); + event.setTick(MusEGlobal::lastExtMidiSyncTick); if(MusEGlobal::audio->isPlaying()) event.setLoopNum(MusEGlobal::audio->loopCount()); @@ -300,7 +303,7 @@ void MidiDevice::recordEvent(MidiRecordEvent& event) // Split the events up into channel fifos. Special 'channel' number 17 for sysex events. unsigned int ch = (typ == ME_SYSEX)? MIDI_CHANNELS : event.channel(); - if(_recordFifo[ch].put(MidiPlayEvent(event))) + if(_recordFifo[ch].put(event)) printf("MidiDevice::recordEvent: fifo channel %d overflow\n", ch); } diff --git a/muse2/muse/midiseq.cpp b/muse2/muse/midiseq.cpp index 300382e9..2cfc1917 100644 --- a/muse2/muse/midiseq.cpp +++ b/muse2/muse/midiseq.cpp @@ -287,6 +287,18 @@ MidiSeq::MidiSeq(const char* name) lastTempo = 0; storedtimediffs = 0; playStateExt = false; // not playing + + _clockAveragerStages = new int[16]; // Max stages is 16! + setSyncRecFilterPreset(MusEGlobal::syncRecFilterPreset); + + for(int i = 0; i < _clockAveragerPoles; ++i) + { + _avgClkDiffCounter[i] = 0; + _averagerFull[i] = false; + } + _tempoQuantizeAmount = 1.0; + _lastRealTempo = 0.0; + MusEGlobal::doSetuid(); timerFd=selectTimer(); MusEGlobal::undoSetuid(); @@ -300,6 +312,7 @@ MidiSeq::MidiSeq(const char* name) MidiSeq::~MidiSeq() { delete timer; + delete _clockAveragerStages; } //--------------------------------------------------------- @@ -505,11 +518,70 @@ void MidiSeq::checkAndReportTimingResolution() "Timing source frequency is %1hz, which is below the recommended minimum: 500hz!\n" \ "This could lead to audible timing problems for MIDI.\n" \ "Please see the FAQ on http://muse-sequencer.org for remedies.\n" \ - "Also please check console output for any further error messages\n ")).arg(freq) ); + "Also please check console output for any further error messages.\n ")).arg(freq) ); } } //--------------------------------------------------------- +// setSyncRecFilterPreset +// To be called in realtime thread only. +//--------------------------------------------------------- +void MidiSeq::setSyncRecFilterPreset(MidiSyncInfo::SyncRecFilterPresetType type) +{ + _syncRecFilterPreset = type; + alignAllTicks(); + + switch(_syncRecFilterPreset) + { + // NOTE: Max _clockAveragerPoles is 16 and maximum stages is 48 per pole ! + case MidiSyncInfo::NONE: + _clockAveragerPoles = 0; + _preDetect = false; + break; + case MidiSyncInfo::TINY: + _clockAveragerPoles = 2; + _clockAveragerStages[0] = 4; + _clockAveragerStages[1] = 4; + _preDetect = false; + break; + case MidiSyncInfo::SMALL: + _clockAveragerPoles = 3; + _clockAveragerStages[0] = 12; + _clockAveragerStages[1] = 8; + _clockAveragerStages[2] = 4; + _preDetect = false; + break; + case MidiSyncInfo::MEDIUM: + _clockAveragerPoles = 3; + _clockAveragerStages[0] = 28; + _clockAveragerStages[1] = 12; + _clockAveragerStages[2] = 8; + _preDetect = false; + break; + case MidiSyncInfo::LARGE: + _clockAveragerPoles = 4; + _clockAveragerStages[0] = 48; + _clockAveragerStages[1] = 48; + _clockAveragerStages[2] = 48; + _clockAveragerStages[3] = 48; + _preDetect = false; + break; + case MidiSyncInfo::LARGE_WITH_PRE_DETECT: + _clockAveragerPoles = 4; + _clockAveragerStages[0] = 8; + _clockAveragerStages[1] = 48; + _clockAveragerStages[2] = 48; + _clockAveragerStages[3] = 48; + _preDetect = true; + break; + + default: + printf("MidiSeq::setSyncRecFilterPreset unknown preset type:%d\n", (int)type); + } +} + + +//--------------------------------------------------------- // processMidiClock //--------------------------------------------------------- diff --git a/muse2/muse/midiseq.h b/muse2/muse/midiseq.h index b5ed1099..08adcdce 100644 --- a/muse2/muse/midiseq.h +++ b/muse2/muse/midiseq.h @@ -28,6 +28,7 @@ #include "mpevent.h" #include "driver/alsatimer.h" #include "driver/rtctimer.h" +#include "sync.h" namespace MusECore { @@ -55,9 +56,17 @@ class MidiSeq : public Thread { double songtick1, songtick2; int recTick1, recTick2; int lastTempo; - double timediff[24]; + double timediff[16][48]; int storedtimediffs; - + int _avgClkDiffCounter[16]; + double _lastRealTempo; + bool _averagerFull[16]; + int _clockAveragerPoles; + int* _clockAveragerStages; + bool _preDetect; + double _tempoQuantizeAmount; + MidiSyncInfo::SyncRecFilterPresetType _syncRecFilterPreset; + void alignAllTicks(int frameOverride = 0); /* Testing */ @@ -87,13 +96,17 @@ class MidiSeq : public Thread { bool externalPlayState() const { return playStateExt; } void setExternalPlayState(bool v) { playStateExt = v; } - void realtimeSystemInput(int, int); + void realtimeSystemInput(int port, int type, double time = 0.0); void mtcInputQuarter(int, unsigned char); void setSongPosition(int, int); void mmcInput(int, const unsigned char*, int); void mtcInputFull(int, const unsigned char*, int); void nonRealtimeSystemSysex(int, const unsigned char*, int); void checkAndReportTimingResolution(); + MidiSyncInfo::SyncRecFilterPresetType syncRecFilterPreset() const { return _syncRecFilterPreset; } + void setSyncRecFilterPreset(MidiSyncInfo::SyncRecFilterPresetType type); + double recTempoValQuant() const { return _tempoQuantizeAmount; } + void setRecTempoValQuant(double q) { _tempoQuantizeAmount = q; } void msgMsg(int id); void msgSeek(); diff --git a/muse2/muse/mixer/astrip.cpp b/muse2/muse/mixer/astrip.cpp index 7699af41..3b0a8707 100644 --- a/muse2/muse/mixer/astrip.cpp +++ b/muse2/muse/mixer/astrip.cpp @@ -455,10 +455,11 @@ void AudioStrip::auxLabelChanged(double val, unsigned int idx) // volumeChanged //--------------------------------------------------------- -void AudioStrip::volumeChanged(double val) +void AudioStrip::volumeChanged(double val, int, bool shift_pressed) { AutomationType at = ((MusECore::AudioTrack*)track)->automationType(); - if(at == AUTO_WRITE || (MusEGlobal::audio->isPlaying() && at == AUTO_TOUCH)) + if ( (at == AUTO_WRITE) || + (at == AUTO_TOUCH && MusEGlobal::audio->isPlaying()) ) track->enableVolumeController(false); double vol; @@ -472,12 +473,7 @@ void AudioStrip::volumeChanged(double val) //MusEGlobal::audio->msgSetVolume((MusECore::AudioTrack*)track, vol); // p4.0.21 MusEGlobal::audio->msgXXX waits. Do we really need to? ((MusECore::AudioTrack*)track)->setVolume(vol); - MusEGlobal::song->controllerChange(track); - - ((MusECore::AudioTrack*)track)->recordAutomation(MusECore::AC_VOLUME, vol); - - //MusEGlobal::song->update(SC_TRACK_MODIFIED); // for graphical automation update - //MusEGlobal::song->controllerChange(track); + if (!shift_pressed) ((MusECore::AudioTrack*)track)->recordAutomation(MusECore::AC_VOLUME, vol); } //--------------------------------------------------------- @@ -487,7 +483,7 @@ void AudioStrip::volumeChanged(double val) void AudioStrip::volumePressed() { AutomationType at = ((MusECore::AudioTrack*)track)->automationType(); - if(at == AUTO_WRITE || (at == AUTO_READ || at == AUTO_TOUCH)) + if (at == AUTO_READ || at == AUTO_TOUCH || at == AUTO_WRITE) track->enableVolumeController(false); double val = slider->value(); @@ -502,8 +498,6 @@ void AudioStrip::volumePressed() //MusEGlobal::audio->msgSetVolume((MusECore::AudioTrack*)track, volume); // p4.0.21 MusEGlobal::audio->msgXXX waits. Do we really need to? ((MusECore::AudioTrack*)track)->setVolume(volume); - MusEGlobal::song->controllerChange(track); - ((MusECore::AudioTrack*)track)->startAutoRecord(MusECore::AC_VOLUME, volume); } @@ -513,7 +507,8 @@ void AudioStrip::volumePressed() void AudioStrip::volumeReleased() { - if(track->automationType() != AUTO_WRITE) + AutomationType at = track->automationType(); + if (at == AUTO_OFF || at == AUTO_READ || at == AUTO_TOUCH) track->enableVolumeController(true); ((MusECore::AudioTrack*)track)->stopAutoRecord(MusECore::AC_VOLUME, volume); @@ -534,7 +529,8 @@ void AudioStrip::volumeRightClicked(const QPoint &p) void AudioStrip::volLabelChanged(double val) { AutomationType at = ((MusECore::AudioTrack*)track)->automationType(); - if(at == AUTO_WRITE || (MusEGlobal::audio->isPlaying() && at == AUTO_TOUCH)) + if ( (at == AUTO_WRITE) || + (at == AUTO_TOUCH && MusEGlobal::audio->isPlaying()) ) track->enableVolumeController(false); double vol; @@ -549,8 +545,6 @@ void AudioStrip::volLabelChanged(double val) //audio->msgSetVolume((MusECore::AudioTrack*)track, vol); // p4.0.21 audio->msgXXX waits. Do we really need to? ((MusECore::AudioTrack*)track)->setVolume(vol); - MusEGlobal::song->controllerChange(track); - ((MusECore::AudioTrack*)track)->startAutoRecord(MusECore::AC_VOLUME, vol); } @@ -558,19 +552,18 @@ void AudioStrip::volLabelChanged(double val) // panChanged //--------------------------------------------------------- -void AudioStrip::panChanged(double val) +void AudioStrip::panChanged(double val, int, bool shift_pressed) { AutomationType at = ((MusECore::AudioTrack*)track)->automationType(); - if(at == AUTO_WRITE || (MusEGlobal::audio->isPlaying() && at == AUTO_TOUCH)) + if ( (at == AUTO_WRITE) || + (at == AUTO_TOUCH && MusEGlobal::audio->isPlaying()) ) track->enablePanController(false); panVal = val; //MusEGlobal::audio->msgSetPan(((MusECore::AudioTrack*)track), val); // p4.0.21 MusEGlobal::audio->msgXXX waits. Do we really need to? ((MusECore::AudioTrack*)track)->setPan(val); - MusEGlobal::song->controllerChange(track); - - ((MusECore::AudioTrack*)track)->recordAutomation(MusECore::AC_PAN, val); + if (!shift_pressed) ((MusECore::AudioTrack*)track)->recordAutomation(MusECore::AC_PAN, val); } //--------------------------------------------------------- @@ -580,15 +573,13 @@ void AudioStrip::panChanged(double val) void AudioStrip::panPressed() { AutomationType at = ((MusECore::AudioTrack*)track)->automationType(); - if(at == AUTO_WRITE || (at == AUTO_READ || at == AUTO_TOUCH)) + if (at == AUTO_READ || at == AUTO_TOUCH || at == AUTO_WRITE) track->enablePanController(false); panVal = pan->value(); //MusEGlobal::audio->msgSetPan(((MusECore::AudioTrack*)track), panVal); // p4.0.21 MusEGlobal::audio->msgXXX waits. Do we really need to? ((MusECore::AudioTrack*)track)->setPan(panVal); - MusEGlobal::song->controllerChange(track); - ((MusECore::AudioTrack*)track)->startAutoRecord(MusECore::AC_PAN, panVal); } @@ -598,7 +589,8 @@ void AudioStrip::panPressed() void AudioStrip::panReleased() { - if(track->automationType() != AUTO_WRITE) + AutomationType at = track->automationType(); + if (at == AUTO_OFF || at == AUTO_READ || at == AUTO_TOUCH) track->enablePanController(true); ((MusECore::AudioTrack*)track)->stopAutoRecord(MusECore::AC_PAN, panVal); } @@ -617,8 +609,9 @@ void AudioStrip::panRightClicked(const QPoint &p) void AudioStrip::panLabelChanged(double val) { - AutomationType at = ((MusECore::AudioTrack*)track)->automationType(); - if(at == AUTO_WRITE || (MusEGlobal::audio->isPlaying() && at == AUTO_TOUCH)) + AutomationType at = ((MusECore::AudioTrack*)track)->automationType(); + if ( (at == AUTO_WRITE) || + (at == AUTO_TOUCH && MusEGlobal::audio->isPlaying()) ) track->enablePanController(false); panVal = val; @@ -626,8 +619,6 @@ void AudioStrip::panLabelChanged(double val) //MusEGlobal::audio->msgSetPan((MusECore::AudioTrack*)track, val); // p4.0.21 MusEGlobal::audio->msgXXX waits. Do we really need to? ((MusECore::AudioTrack*)track)->setPan(val); - MusEGlobal::song->controllerChange(track); - ((MusECore::AudioTrack*)track)->startAutoRecord(MusECore::AC_PAN, val); } @@ -727,11 +718,10 @@ MusEGui::Knob* AudioStrip::addKnob(int type, int id, MusEGui::DoubleLabel** dlab _curGridRow += 2; connect(knob, SIGNAL(valueChanged(double,int)), pl, SLOT(setValue(double))); - //connect(pl, SIGNAL(valueChanged(double, int)), SLOT(panChanged(double))); if (type == 0) { connect(pl, SIGNAL(valueChanged(double, int)), SLOT(panLabelChanged(double))); - connect(knob, SIGNAL(sliderMoved(double,int)), SLOT(panChanged(double))); + connect(knob, SIGNAL(sliderMoved(double,int,bool)), SLOT(panChanged(double,int,bool))); connect(knob, SIGNAL(sliderPressed(int)), SLOT(panPressed())); connect(knob, SIGNAL(sliderReleased(int)), SLOT(panReleased())); connect(knob, SIGNAL(sliderRightClicked(const QPoint &, int)), SLOT(panRightClicked(const QPoint &))); @@ -894,9 +884,8 @@ AudioStrip::AudioStrip(QWidget* parent, MusECore::AudioTrack* at) sl->setValue(MusECore::fast_log10(t->volume()) * 20.0); connect(sl, SIGNAL(valueChanged(double,int)), SLOT(volLabelChanged(double))); - //connect(sl, SIGNAL(valueChanged(double,int)), SLOT(volumeChanged(double))); connect(slider, SIGNAL(valueChanged(double,int)), sl, SLOT(setValue(double))); - connect(slider, SIGNAL(sliderMoved(double,int)), SLOT(volumeChanged(double))); + connect(slider, SIGNAL(sliderMoved(double,int,bool)), SLOT(volumeChanged(double,int,bool))); connect(slider, SIGNAL(sliderPressed(int)), SLOT(volumePressed())); connect(slider, SIGNAL(sliderReleased(int)), SLOT(volumeReleased())); connect(slider, SIGNAL(sliderRightClicked(const QPoint &, int)), SLOT(volumeRightClicked(const QPoint &))); diff --git a/muse2/muse/mixer/astrip.h b/muse2/muse/mixer/astrip.h index f5406652..c0df5360 100644 --- a/muse2/muse/mixer/astrip.h +++ b/muse2/muse/mixer/astrip.h @@ -94,10 +94,10 @@ class AudioStrip : public Strip { void iRoutePressed(); void oRoutePressed(); void auxChanged(double, int); - void volumeChanged(double); + void volumeChanged(double,int,bool); void volumePressed(); void volumeReleased(); - void panChanged(double); + void panChanged(double,int,bool); void panPressed(); void panReleased(); void volLabelChanged(double); diff --git a/muse2/muse/mixer/panknob.cpp b/muse2/muse/mixer/panknob.cpp index dc2564a7..c54a112f 100644 --- a/muse2/muse/mixer/panknob.cpp +++ b/muse2/muse/mixer/panknob.cpp @@ -48,7 +48,6 @@ void PanKnob::valueChanged(double val) //audio->msgSetPan(src, val); // p4.0.21 audio->msgXXX waits. Do we really need to? src->setPan(val); - MusEGlobal::song->controllerChange(src); } } // namespace MusEGui diff --git a/muse2/muse/mpevent.cpp b/muse2/muse/mpevent.cpp index d3709b1f..a8596224 100644 --- a/muse2/muse/mpevent.cpp +++ b/muse2/muse/mpevent.cpp @@ -178,7 +178,7 @@ void MidiFifo::remove() // return true on fifo overflow //--------------------------------------------------------- -bool MidiRecFifo::put(const MidiPlayEvent& event) +bool MidiRecFifo::put(const MidiRecordEvent& event) { if (size < MIDI_REC_FIFO_SIZE) { fifo[wIndex] = event; @@ -193,9 +193,9 @@ bool MidiRecFifo::put(const MidiPlayEvent& event) // get //--------------------------------------------------------- -MidiPlayEvent MidiRecFifo::get() +MidiRecordEvent MidiRecFifo::get() { - MidiPlayEvent event(fifo[rIndex]); + MidiRecordEvent event(fifo[rIndex]); rIndex = (rIndex + 1) % MIDI_REC_FIFO_SIZE; --size; return event; @@ -205,7 +205,7 @@ MidiPlayEvent MidiRecFifo::get() // peek //--------------------------------------------------------- -const MidiPlayEvent& MidiRecFifo::peek(int n) +const MidiRecordEvent& MidiRecFifo::peek(int n) { int idx = (rIndex + n) % MIDI_REC_FIFO_SIZE; return fifo[idx]; diff --git a/muse2/muse/mpevent.h b/muse2/muse/mpevent.h index 9b64f9cd..903a8126 100644 --- a/muse2/muse/mpevent.h +++ b/muse2/muse/mpevent.h @@ -111,6 +111,8 @@ class MEvent { //--------------------------------------------------------- class MidiRecordEvent : public MEvent { + private: + unsigned int _tick; // To store tick when external sync is on, required besides frame. public: MidiRecordEvent() : MEvent() {} MidiRecordEvent(const MEvent& e) : MEvent(e) {} @@ -121,6 +123,9 @@ class MidiRecordEvent : public MEvent { MidiRecordEvent(unsigned t, int p, int type, EvData data) : MEvent(t, p, type, data) {} ~MidiRecordEvent() {} + + unsigned int tick() {return _tick;} + void setTick(unsigned int tick) {_tick = tick;} }; //--------------------------------------------------------- @@ -200,20 +205,19 @@ class MidiFifo { //--------------------------------------------------------- // MidiRecFifo -// (Same as MidiFifo, but with a smaller size.) //--------------------------------------------------------- class MidiRecFifo { - MidiPlayEvent fifo[MIDI_REC_FIFO_SIZE]; + MidiRecordEvent fifo[MIDI_REC_FIFO_SIZE]; volatile int size; int wIndex; int rIndex; public: MidiRecFifo() { clear(); } - bool put(const MidiPlayEvent& event); // returns true on fifo overflow - MidiPlayEvent get(); - const MidiPlayEvent& peek(int = 0); + bool put(const MidiRecordEvent& event); // returns true on fifo overflow + MidiRecordEvent get(); + const MidiRecordEvent& peek(int = 0); void remove(); bool isEmpty() const { return size == 0; } void clear() { size = 0, wIndex = 0, rIndex = 0; } diff --git a/muse2/muse/node.cpp b/muse2/muse/node.cpp index e56949aa..02264a37 100644 --- a/muse2/muse/node.cpp +++ b/muse2/muse/node.cpp @@ -401,10 +401,10 @@ void AudioTrack::copyData(unsigned pos, int dstChannels, int srcStartChan, int s // precalculate stereo volume double vol[2]; - //double _volume = volume(); - //double _pan = pan(); - double _volume = controller()->value(AC_VOLUME, pos); - double _pan = controller()->value(AC_PAN, pos); + double _volume = controller()->value(AC_VOLUME, pos, + !MusEGlobal::automation || automationType() == AUTO_OFF || !_volumeEnCtrl || !_volumeEn2Ctrl); + double _pan = controller()->value(AC_PAN, pos, + !MusEGlobal::automation || automationType() == AUTO_OFF || !_panEnCtrl || !_panEn2Ctrl); vol[0] = _volume * (1.0 - _pan); vol[1] = _volume * (1.0 + _pan); @@ -739,10 +739,10 @@ void AudioTrack::addData(unsigned pos, int dstChannels, int srcStartChan, int sr // precalculate stereo volume double vol[2]; - //double _volume = volume(); - //double _pan = pan(); - double _volume = controller()->value(AC_VOLUME, pos); - double _pan = controller()->value(AC_PAN, pos); + double _volume = controller()->value(AC_VOLUME, pos, + !MusEGlobal::automation || automationType() == AUTO_OFF || !_volumeEnCtrl || !_volumeEn2Ctrl); + double _pan = controller()->value(AC_PAN, pos, + !MusEGlobal::automation || automationType() == AUTO_OFF || !_panEnCtrl || !_panEn2Ctrl); vol[0] = _volume * (1.0 - _pan); vol[1] = _volume * (1.0 + _pan); diff --git a/muse2/muse/osc.cpp b/muse2/muse/osc.cpp index 0d4a1750..381e4acc 100644 --- a/muse2/muse/osc.cpp +++ b/muse2/muse/osc.cpp @@ -1092,7 +1092,7 @@ int OscDssiIF::oscControl(lo_arg** argv) if(_oscSynthIF) { _oscSynthIF->oscControl(argv[0]->i, argv[1]->f); - if (port<maxDssiPort) + if (port<(int)maxDssiPort) old_control[control_port_mapper->at(port)]=argv[1]->f; } @@ -1170,7 +1170,7 @@ int OscEffectIF::oscControl(lo_arg** argv) if(_oscPluginI) { _oscPluginI->oscControl(argv[0]->i, argv[1]->f); - if (port<maxDssiPort) + if (port<(int)maxDssiPort) old_control[control_port_mapper->at(port)]=argv[1]->f; } diff --git a/muse2/muse/part.cpp b/muse2/muse/part.cpp index a632bc9c..9950c362 100644 --- a/muse2/muse/part.cpp +++ b/muse2/muse/part.cpp @@ -864,17 +864,18 @@ void Song::cmdResizePart(Track* track, Part* oPart, unsigned int len, bool doClo unsigned event_endframe = event_startframe + e.lenFrame(); if (event_endframe < new_partlength) continue; - if (event_startframe > new_partlength) { // If event start was after the new length, remove it from part - // Do not do port controller values and clone parts. - operations.push_back(UndoOp(UndoOp::DeleteEvent, e, nPart, false, false)); - continue; - } - if (event_endframe > new_partlength) { // If this event starts before new length and ends after, shrink it - Event newEvent = e.clone(); - newEvent.setLenFrame(new_partlength - event_startframe); - // Do not do port controller values and clone parts. - operations.push_back(UndoOp(UndoOp::ModifyEvent, newEvent, e, nPart, false,false)); - } +// REMOVE Tim. +// if (event_startframe > new_partlength) { // If event start was after the new length, remove it from part +// // Do not do port controller values and clone parts. +// operations.push_back(UndoOp(UndoOp::DeleteEvent, e, nPart, false, false)); +// continue; +// } +// if (event_endframe > new_partlength) { // If this event starts before new length and ends after, shrink it +// Event newEvent = e.clone(); +// newEvent.setLenFrame(new_partlength - event_startframe); +// // Do not do port controller values and clone parts. +// operations.push_back(UndoOp(UndoOp::ModifyEvent, newEvent, e, nPart, false,false)); +// } } nPart->setLenFrame(new_partlength); // Do not do port controller values and clone parts. @@ -893,19 +894,20 @@ void Song::cmdResizePart(Track* track, Part* oPart, unsigned int len, bool doClo iEvent i = el->end(); i--; Event last = i->second; - unsigned last_start = last.frame(); +// REMOVE Tim. unsigned last_start = last.frame(); MusECore::SndFileR file = last.sndFile(); if (file.isNull()) return; - unsigned clipframes = (file.samples() - last.spos());// / file.channels(); +// unsigned clipframes = (file.samples() - last.spos());// / file.channels(); Event newEvent = last.clone(); - unsigned new_eventlength = new_partlength - last_start; - if (new_eventlength > clipframes) // Shrink event length if new partlength exceeds last clip - new_eventlength = clipframes; - - newEvent.setLenFrame(new_eventlength); +// REMOVE Tim. +// unsigned new_eventlength = new_partlength - last_start; +// if (new_eventlength > clipframes) // Shrink event length if new partlength exceeds last clip +// new_eventlength = clipframes; +// +// newEvent.setLenFrame(new_eventlength); // Do not do port controller values and clone parts. operations.push_back(UndoOp(UndoOp::ModifyEvent, newEvent, last, nPart, false, false)); } @@ -1199,13 +1201,12 @@ WavePart* WavePart::clone() const return new WavePart(*this); } - //--------------------------------------------------------- // hasHiddenEvents // Returns combination of HiddenEventsType enum. //--------------------------------------------------------- -int Part::hasHiddenEvents() +int MidiPart::hasHiddenEvents() { unsigned len = lenTick(); @@ -1222,7 +1223,27 @@ int Part::hasHiddenEvents() return _hiddenEvents; } +//--------------------------------------------------------- +// hasHiddenEvents +// Returns combination of HiddenEventsType enum. +//--------------------------------------------------------- +int WavePart::hasHiddenEvents() +{ + unsigned len = lenFrame(); + + // TODO: For now, we don't support events before the left border, only events past the right border. + for(iEvent ev=events()->begin(); ev!=events()->end(); ev++) + { + if(ev->second.endFrame() > len) + { + _hiddenEvents = RightEventsHidden; // Cache the result for later. + return _hiddenEvents; + } + } + _hiddenEvents = NoEventsHidden; // Cache the result for later. + return _hiddenEvents; +} //--------------------------------------------------------- // ClonePart diff --git a/muse2/muse/part.h b/muse2/muse/part.h index f2bc342b..357ec1db 100644 --- a/muse2/muse/part.h +++ b/muse2/muse/part.h @@ -76,14 +76,13 @@ class Part : public PosLen { bool _mute; int _colorIndex; - int _hiddenEvents; // Combination of HiddenEventsType. - protected: Track* _track; EventList* _events; Part* _prevClone; Part* _nextClone; - + int _hiddenEvents; // Combination of HiddenEventsType. + public: Part(Track*); Part(Track*, EventList*); @@ -114,7 +113,7 @@ class Part : public PosLen { void setNextClone(Part* p) { _nextClone = p; } // Returns combination of HiddenEventsType enum. - int hasHiddenEvents(); + virtual int hasHiddenEvents() = 0; // If repeated calls to hasHiddenEvents() are desired, then to avoid re-iteration of the event list, // call this after hasHiddenEvents(). int cachedHasHiddenEvents() const { return _hiddenEvents; } @@ -140,7 +139,9 @@ class MidiPart : public Part { virtual ~MidiPart() {} virtual MidiPart* clone() const; MidiTrack* track() const { return (MidiTrack*)Part::track(); } - + // Returns combination of HiddenEventsType enum. + int hasHiddenEvents(); + virtual void dump(int n = 0) const; }; @@ -161,6 +162,8 @@ class WavePart : public Part { virtual ~WavePart() {} virtual WavePart* clone() const; WaveTrack* track() const { return (WaveTrack*)Part::track(); } + // Returns combination of HiddenEventsType enum. + int hasHiddenEvents(); virtual void dump(int n = 0) const; }; diff --git a/muse2/muse/plugin.cpp b/muse2/muse/plugin.cpp index 8bf35143..e8b0489c 100644 --- a/muse2/muse/plugin.cpp +++ b/muse2/muse/plugin.cpp @@ -32,6 +32,7 @@ #include <QButtonGroup> #include <QCheckBox> #include <QComboBox> +#include <QCursor> #include <QDir> #include <QFile> #include <QGridLayout> @@ -78,6 +79,9 @@ // Turn on debugging messages. //#define PLUGIN_DEBUGIN +// Turn on constant stream of debugging messages. +//#define PLUGIN_DEBUGIN_PROCESS + namespace MusEGlobal { MusECore::PluginList plugins; } @@ -119,7 +123,7 @@ bool ladspa2MidiControlValues(const LADSPA_Descriptor* plugin, unsigned long por *min = 0; *max = 1; - *def = (int)lrint(fdef); + *def = (int)lrintf(fdef); return hasdef; } @@ -156,8 +160,8 @@ bool ladspa2MidiControlValues(const LADSPA_Descriptor* plugin, unsigned long por fmax = 1.0; frng = fmax - fmin; - imin = lrint(fmin); - imax = lrint(fmax); + imin = lrintf(fmin); + imax = lrintf(fmax); int ctlmn = 0; int ctlmx = 127; @@ -230,7 +234,7 @@ bool ladspa2MidiControlValues(const LADSPA_Descriptor* plugin, unsigned long por *min = imin; *max = imax; - *def = (int)lrint(fdef); + *def = (int)lrintf(fdef); return hasdef; } @@ -244,7 +248,7 @@ bool ladspa2MidiControlValues(const LADSPA_Descriptor* plugin, unsigned long por // FIXME: TODO: Incorrect... Fix this somewhat more trivial stuff later.... - *def = (int)lrint(fdef) + bias; + *def = (int)lrintf(fdef) + bias; #ifdef PLUGIN_DEBUGIN printf("ladspa2MidiControlValues: setting default:%d\n", *def); @@ -305,7 +309,7 @@ float midi2LadspaValue(const LADSPA_Descriptor* plugin, unsigned long port, int fmax = 1.0; frng = fmax - fmin; - imin = lrint(fmin); + imin = lrintf(fmin); if(desc & LADSPA_HINT_TOGGLED) { @@ -619,40 +623,6 @@ void ladspaControlRange(const LADSPA_Descriptor* plugin, unsigned long port, flo *max = 1.0; } -// DELETETHIS 35 -/* -//--------------------------------------------------------- -// PluginBase -//--------------------------------------------------------- - -//--------------------------------------------------------- -// range -//--------------------------------------------------------- - -void PluginBase::range(unsigned long i, float* min, float* max) const - { - LADSPA_PortRangeHint range = plugin->PortRangeHints[i]; - LADSPA_PortRangeHintDescriptor desc = range.HintDescriptor; - if (desc & LADSPA_HINT_TOGGLED) { - *min = 0.0; - *max = 1.0; - return; - } - float m = 1.0; - if (desc & LADSPA_HINT_SAMPLE_RATE) - m = float(MusEGlobal::sampleRate); - - if (desc & LADSPA_HINT_BOUNDED_BELOW) - *min = range.LowerBound * m; - else - *min = 0.0; - if (desc & LADSPA_HINT_BOUNDED_ABOVE) - *max = range.UpperBound * m; - else - *max = 1.0; - } -*/ - //--------------------------------------------------------- // Plugin //--------------------------------------------------------- @@ -792,8 +762,7 @@ int Plugin::incReferences(int val) if(dssi) { const DSSI_Descriptor* descr; - //for(int i = 0;; ++i) - for(unsigned long i = 0;; ++i) // p4.0.21 + for(unsigned long i = 0;; ++i) { descr = dssi(i); if(descr == NULL) @@ -817,7 +786,7 @@ int Plugin::incReferences(int val) if(ladspadf) { const LADSPA_Descriptor* descr; - for(unsigned long i = 0;; ++i) // p4.0.21 + for(unsigned long i = 0;; ++i) { descr = ladspadf(i); if(descr == NULL) @@ -913,7 +882,7 @@ int Plugin::incReferences(int val) void Plugin::range(unsigned long i, float* min, float* max) const { - ladspaControlRange(plugin, i, min, max); // p4.0.20 + ladspaControlRange(plugin, i, min, max); } //--------------------------------------------------------- @@ -922,59 +891,9 @@ void Plugin::range(unsigned long i, float* min, float* max) const float Plugin::defaultValue(unsigned long port) const { - // p4.0.21 float val; ladspaDefaultValue(plugin, port, &val); return val; - - // DELETETHIS 50 - /* - if(port >= plugin->PortCount) - return 0.0; - - LADSPA_PortRangeHint range = plugin->PortRangeHints[port]; - LADSPA_PortRangeHintDescriptor rh = range.HintDescriptor; - //double val = 1.0; - float val = 1.0; - if (LADSPA_IS_HINT_DEFAULT_MINIMUM(rh)) - val = range.LowerBound; - else if (LADSPA_IS_HINT_DEFAULT_LOW(rh)) - if (LADSPA_IS_HINT_LOGARITHMIC(range.HintDescriptor)) - //val = exp(fast_log10(range.LowerBound) * .75 + - // log(range.UpperBound) * .25); - val = expf(fast_log10(range.LowerBound) * .75 + - logf(range.UpperBound) * .25); - else - val = range.LowerBound*.75 + range.UpperBound*.25; - else if (LADSPA_IS_HINT_DEFAULT_MIDDLE(rh)) - if (LADSPA_IS_HINT_LOGARITHMIC(range.HintDescriptor)) - //val = exp(log(range.LowerBound) * .5 + - // log(range.UpperBound) * .5); - val = expf(logf(range.LowerBound) * .5 + - logf(range.UpperBound) * .5); - else - val = range.LowerBound*.5 + range.UpperBound*.5; - else if (LADSPA_IS_HINT_DEFAULT_HIGH(rh)) - if (LADSPA_IS_HINT_LOGARITHMIC(range.HintDescriptor)) - //val = exp(log(range.LowerBound) * .25 + - // log(range.UpperBound) * .75); - val = expf(logf(range.LowerBound) * .25 + - logf(range.UpperBound) * .75); - else - val = range.LowerBound*.25 + range.UpperBound*.75; - else if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(rh)) - val = range.UpperBound; - else if (LADSPA_IS_HINT_DEFAULT_0(rh)) - val = 0.0; - else if (LADSPA_IS_HINT_DEFAULT_1(rh)) - val = 1.0; - else if (LADSPA_IS_HINT_DEFAULT_100(rh)) - val = 100.0; - else if (LADSPA_IS_HINT_DEFAULT_440(rh)) - val = 440.0; - - return val; - */ } //--------------------------------------------------------- @@ -1013,7 +932,7 @@ static void loadPluginLib(QFileInfo* fi) if(dssi) { const DSSI_Descriptor* descr; - for (unsigned long i = 0;; ++i) // p4.0.21 + for (unsigned long i = 0;; ++i) { descr = dssi(i); if (descr == 0) @@ -1060,7 +979,7 @@ static void loadPluginLib(QFileInfo* fi) } const LADSPA_Descriptor* descr; - for (unsigned long i = 0;; ++i) // p4.0.21 + for (unsigned long i = 0;; ++i) { descr = ladspa(i); if (descr == NULL) @@ -1224,6 +1143,76 @@ Pipeline::~Pipeline() } //--------------------------------------------------------- +// addScheduledControlEvent +// track_ctrl_id is the fully qualified track audio controller number +// Returns true if event cannot be delivered +//--------------------------------------------------------- + +bool Pipeline::addScheduledControlEvent(int track_ctrl_id, float val, unsigned frame) +{ + // If a track controller, or the special dssi synth controller block, just return. + if(track_ctrl_id < AC_PLUGIN_CTL_BASE || track_ctrl_id >= (int)genACnum(MAX_PLUGINS, 0)) + return true; + int rack_idx = (track_ctrl_id - AC_PLUGIN_CTL_BASE) >> AC_PLUGIN_CTL_BASE_POW; + for (int i = 0; i < PipelineDepth; ++i) + { + PluginI* p = (*this)[i]; + if(p && p->id() == rack_idx) + return p->addScheduledControlEvent(track_ctrl_id & AC_PLUGIN_CTL_ID_MASK, val, frame); + } + return true; +} + +//--------------------------------------------------------- +// controllersEnabled +// Returns whether automation control stream is enabled or disabled. +// Used during automation recording to inhibit gui controls +//--------------------------------------------------------- + +void Pipeline::controllersEnabled(int track_ctrl_id, bool* en1, bool* en2) +{ + // If a track controller, or the special dssi synth controller block, just return. + if(track_ctrl_id < AC_PLUGIN_CTL_BASE || track_ctrl_id >= (int)genACnum(MAX_PLUGINS, 0)) + return; + int rack_idx = (track_ctrl_id - AC_PLUGIN_CTL_BASE) >> AC_PLUGIN_CTL_BASE_POW; + for (int i = 0; i < PipelineDepth; ++i) + { + PluginI* p = (*this)[i]; + if(p && p->id() == rack_idx) + { + if(en1) + *en1 = p->controllerEnabled(track_ctrl_id & AC_PLUGIN_CTL_ID_MASK); + if(en2) + *en2 = p->controllerEnabled2(track_ctrl_id & AC_PLUGIN_CTL_ID_MASK); + return; + } + } +} + +//--------------------------------------------------------- +// enableController +// Enable or disable gui automation control stream. +// Used during automation recording to inhibit gui controls +//--------------------------------------------------------- + +void Pipeline::enableController(int track_ctrl_id, bool en) +{ + // If a track controller, or the special dssi synth controller block, just return. + if(track_ctrl_id < AC_PLUGIN_CTL_BASE || track_ctrl_id >= (int)genACnum(MAX_PLUGINS, 0)) + return; + int rack_idx = (track_ctrl_id - AC_PLUGIN_CTL_BASE) >> AC_PLUGIN_CTL_BASE_POW; + for (int i = 0; i < PipelineDepth; ++i) + { + PluginI* p = (*this)[i]; + if(p && p->id() == rack_idx) + { + p->enableController(track_ctrl_id & AC_PLUGIN_CTL_ID_MASK, en); + return; + } + } +} + +//--------------------------------------------------------- // setChannels //--------------------------------------------------------- @@ -1524,6 +1513,39 @@ PluginIBase::~PluginIBase() delete _gui; } +//--------------------------------------------------------- +// addScheduledControlEvent +// i is the specific index of the control input port +// Returns true if event cannot be delivered +//--------------------------------------------------------- + +bool PluginIBase::addScheduledControlEvent(unsigned long i, float val, unsigned frame) +{ + if(i >= parameters()) + { + printf("PluginIBase::addScheduledControlEvent param number %lu out of range of ports:%lu\n", i, parameters()); + return true; + } + ControlEvent ce; + ce.unique = false; + ce.idx = i; + ce.value = val; + // Time-stamp the event. This does a possibly slightly slow call to gettimeofday via timestamp(). + // timestamp() is more or less an estimate of the current frame. (This is exactly how ALSA events + // are treated when they arrive in our ALSA driver.) + //ce.frame = MusEGlobal::audio->timestamp(); + // p4.0.23 timestamp() is circular, which is making it impossible to deal with 'modulo' events which + // slip in 'under the wire' before processing the ring buffers. So try this linear timestamp instead: + ce.frame = frame; + + if(_controlFifo.put(ce)) + { + fprintf(stderr, "PluginIBase::addScheduledControlEvent: fifo overflow: in control number:%lu\n", i); + return true; + } + return false; +} + QString PluginIBase::dssi_ui_filename() const { QString libr(lib()); @@ -1658,8 +1680,6 @@ void PluginI::updateControllers() for(unsigned long i = 0; i < controlPorts; ++i) _track->setPluginCtrlVal(genACnum(_id, i), controls[i].val); // TODO A faster bulk message - - MusEGlobal::song->controllerChange(_track); } //--------------------------------------------------------- @@ -1698,7 +1718,7 @@ void PluginI::setChannels(int c) } } - unsigned long curPort = 0; // p4.0.21 + unsigned long curPort = 0; unsigned long curOutPort = 0; unsigned long ports = _plugin->ports(); for (unsigned long k = 0; k < ports; ++k) @@ -1733,27 +1753,7 @@ void PluginI::setChannels(int c) void PluginI::setParam(unsigned long i, float val) { - if(i >= _plugin->_controlInPorts) - { - printf("PluginI::setParameter param number %lu out of range of ports:%lu\n", i, _plugin->_controlInPorts); - return; - } - ControlEvent ce; - ce.unique = false; - ce.idx = i; - ce.value = val; - // Time-stamp the event. This does a possibly slightly slow call to gettimeofday via timestamp(). - // timestamp() is more or less an estimate of the current frame. (This is exactly how ALSA events - // are treated when they arrive in our ALSA driver.) - //ce.frame = MusEGlobal::audio->timestamp(); - // p4.0.23 timestamp() is circular, which is making it impossible to deal with 'modulo' events which - // slip in 'under the wire' before processing the ring buffers. So try this linear timestamp instead: - ce.frame = MusEGlobal::audio->curFrame(); - - if(_controlFifo.put(ce)) - { - fprintf(stderr, "PluginI::setParameter: fifo overflow: in control number:%lu\n", i); - } + addScheduledControlEvent(i, val, MusEGlobal::audio->curFrame()); } //--------------------------------------------------------- @@ -1865,7 +1865,7 @@ bool PluginI::initPluginInstance(Plugin* plug, int c) { if(pd & LADSPA_PORT_INPUT) { - float val = _plugin->defaultValue(k); // p4.0.21 + float val = _plugin->defaultValue(k); controls[curPort].val = val; controls[curPort].tmpVal = val; controls[curPort].enCtrl = true; @@ -1917,11 +1917,11 @@ bool PluginI::initPluginInstance(Plugin* plug, int c) void PluginI::connect(unsigned long ports, unsigned long offset, float** src, float** dst) { - unsigned long port = 0; // p4.0.21 + unsigned long port = 0; for (int i = 0; i < instances; ++i) { for (unsigned long k = 0; k < _plugin->ports(); ++k) { if (isAudioIn(k)) { - _plugin->connectPort(handle[i], k, src[port] + offset); // p4.0.21 + _plugin->connectPort(handle[i], k, src[port] + offset); port = (port + 1) % ports; } } @@ -1930,7 +1930,7 @@ void PluginI::connect(unsigned long ports, unsigned long offset, float** src, fl for (int i = 0; i < instances; ++i) { for (unsigned long k = 0; k < _plugin->ports(); ++k) { if (isAudioOut(k)) { - _plugin->connectPort(handle[i], k, dst[port] + offset); // p4.0.21 + _plugin->connectPort(handle[i], k, dst[port] + offset); port = (port + 1) % ports; // overwrite output? } } @@ -1979,7 +1979,7 @@ bool PluginI::setControl(const QString& s, float val) { for (unsigned long i = 0; i < controlPorts; ++i) { if (_plugin->portName(controls[i].idx) == s) { - setParam(i, val); // p4.0.21 + setParam(i, val); return false; } } @@ -1997,7 +1997,7 @@ void PluginI::writeConfiguration(int level, Xml& xml) xml.tag(level++, "plugin file=\"%s\" label=\"%s\" channel=\"%d\"", Xml::xmlString(_plugin->lib()).toLatin1().constData(), Xml::xmlString(_plugin->label()).toLatin1().constData(), channel); - for (unsigned long i = 0; i < controlPorts; ++i) { // p4.0.21 + for (unsigned long i = 0; i < controlPorts; ++i) { unsigned long idx = controls[i].idx; QString s("control name=\"%1\" val=\"%2\" /"); xml.tag(level, s.arg(Xml::xmlString(_plugin->portName(idx)).toLatin1().constData()).arg(controls[i].tmpVal).toLatin1().constData()); @@ -2040,7 +2040,7 @@ bool PluginI::loadControl(Xml& xml) if (tag == "name") name = xml.s2(); else if (tag == "val") - val = xml.s2().toFloat(); // p4.0.21 + val = xml.s2().toFloat(); break; case Xml::TagEnd: if (tag == "control") { @@ -2102,7 +2102,7 @@ bool PluginI::readConfiguration(Xml& xml, bool readPreset) xml.parse1(); printf("Error initializing plugin instance (%s, %s)\n", file.toLatin1().constData(), label.toLatin1().constData()); - //break; // Don't break - let it read any control tags. DELETETHIS + //break; // Don't break - let it read any control tags. } } } @@ -2352,28 +2352,152 @@ void PluginI::apply(unsigned long n, unsigned long ports, float** bufIn, float** if(min_per > n) min_per = n; - // Process automation control values now. - // TODO: This needs to be respect frame resolution. Put this inside the sample loop below. - if(MusEGlobal::automation && _track && _track->automationType() != AUTO_OFF && _id != -1) + // CtrlListList* cll = NULL; // WIP + AutomationType at = AUTO_OFF; + if(_track) { - for(unsigned long k = 0; k < controlPorts; ++k) - { - if(controls[k].enCtrl && controls[k].en2Ctrl ) - controls[k].tmpVal = _track->controller()->value(genACnum(_id, k), MusEGlobal::audio->pos().frame()); - } + at = _track->automationType(); + //cll = _track->controller(); // WIP } - + bool no_auto = !MusEGlobal::automation || at == AUTO_OFF; + while(sample < n) { // nsamp is the number of samples the plugin->process() call will be supposed to do unsigned long nsamp = usefixedrate ? fixedsize : n - sample; + // + // Process automation control values, while also determining the maximum acceptable + // size of this run. Further processing, from FIFOs for example, can lower the size + // from there, but this section determines where the next highest maximum frame + // absolutely needs to be for smooth playback of the controller value stream... + // + if(_track && _id != -1 && ports != 0) // Don't bother if not 'running'. + { + unsigned long frame = MusEGlobal::audio->pos().frame() + sample; + int nextFrame; + //double val; // WIP + for(unsigned long k = 0; k < controlPorts; ++k) + { + + +#if 0 // WIP - Work in progress. Tim. + + ciCtrlList icl = cll->find(genACnum(_id, k)); + if(icl == cll->end()) + continue; + CtrlList* cl = icl->second; + if(no_auto || !controls[k].enCtrl || !controls[k].en2Ctrl || cl->empty()) + { + nextFrame = -1; + val = cl->curVal(); + } + else + { + ciCtrl i = cl->upper_bound(frame); // get the index after current frame + if (i == cl->end()) { // if we are past all items just return the last value + --i; + nextFrame = -1; + val = i->second.val; + } + else if(cl->mode() == CtrlList::DISCRETE) + { + if(i == cl->begin()) + { + nextFrame = i->second.frame; + val = i->second.val; + } + else + { + nextFrame = i->second.frame; + --i; + val = i->second.val; + } + } + else { // INTERPOLATE + if (i == cl->begin()) { + nextFrame = i->second.frame; + val = i->second.val; + } + else { + int frame2 = i->second.frame; + double val2 = i->second.val; + --i; + int frame1 = i->second.frame; + double val1 = i->second.val; + + + if(val2 != val1) + nextFrame = 0; // Zero signifies the next frame should be determined by caller. + else + nextFrame = frame2; + + if (cl->valueType() == VAL_LOG) { + val1 = 20.0*fast_log10(val1); + if (val1 < MusEGlobal::config.minSlider) + val1=MusEGlobal::config.minSlider; + val2 = 20.0*fast_log10(val2); + if (val2 < MusEGlobal::config.minSlider) + val2=MusEGlobal::config.minSlider; + } + + val2 -= val1; + val1 += (double(frame - frame1) * val2)/double(frame2 - frame1); + + if (cl->valueType() == VAL_LOG) { + val1 = exp10(val1/20.0); + } + + val = val1; + } + } + } + + controls[k].tmpVal = val; + + +#else + controls[k].tmpVal = _track->controller()->value(genACnum(_id, k), frame, + no_auto || !controls[k].enCtrl || !controls[k].en2Ctrl, + &nextFrame); +#endif + + +#ifdef PLUGIN_DEBUGIN_PROCESS + printf("PluginI::apply k:%lu sample:%lu frame:%lu nextFrame:%d nsamp:%lu \n", k, sample, frame, nextFrame, nsamp); +#endif + if(MusEGlobal::audio->isPlaying() && !usefixedrate && nextFrame != -1) + { + // Returned value of nextFrame can be zero meaning caller replaces with some (constant) value. + unsigned long samps = (unsigned long)nextFrame; + if(samps > frame + min_per) + { + unsigned long diff = samps - frame; + unsigned long mask = min_per-1; // min_per must be power of 2 + samps = diff & ~mask; + if((diff & mask) != 0) + samps += min_per; + } + else + samps = min_per; + + if(samps < nsamp) + nsamp = samps; + } + } + +#ifdef PLUGIN_DEBUGIN_PROCESS + printf("PluginI::apply sample:%lu nsamp:%lu\n", sample, nsamp); +#endif + } + + // + // Process all control ring buffer items valid for this time period... + // bool found = false; unsigned long frame = 0; unsigned long index = 0; unsigned long evframe; - - // Get all control ring buffer items valid for this time period... while(!_controlFifo.isEmpty()) { ControlEvent v = _controlFifo.peek(); @@ -2398,9 +2522,10 @@ void PluginI::apply(unsigned long n, unsigned long ports, float** bufIn, float** // but stop after a control event was found (then process(), // then loop here again), but ensure that process() must process // at least min_per frames. - if(evframe >= n - || (found && !v.unique && (evframe - sample >= min_per)) - || (usefixedrate && found && v.unique && v.idx == index)) + if(evframe >= n // Next events are for a later period. + || (!usefixedrate && !found && !v.unique && (evframe - sample >= nsamp)) // Next events are for a later run in this period. (Autom took prio.) + || (found && !v.unique && (evframe - sample >= min_per)) // Eat up events within minimum slice - they're too close. + || (usefixedrate && found && v.unique && v.idx == index)) // Special for dssi-vst: Fixed rate and must reply to all. break; _controlFifo.remove(); // Done with the ring buffer's item. Remove it. @@ -2415,27 +2540,20 @@ void PluginI::apply(unsigned long n, unsigned long ports, float** bufIn, float** // Need to update the automation value, otherwise it overwrites later with the last automation value. if(_track && _id != -1) - { - // We're in the audio thread context: no need to send a message, just modify directly. _track->setPluginCtrlVal(genACnum(_id, v.idx), v.value); - - /* Recording automation is done immediately in the * - * OSC control handler, because we don't want any delay. * - * we might want to handle dssi-vst synthes here, however! */ - } } // Now update the actual values from the temporary values... for(unsigned long k = 0; k < controlPorts; ++k) controls[k].val = controls[k].tmpVal; - if(found && !usefixedrate) - nsamp = frame - sample; + if(found && !usefixedrate) // If a control FIFO item was found, takes priority over automation controller stream. + nsamp = frame - sample; - if(sample + nsamp >= n) // Safety check. + if(sample + nsamp >= n) // Safety check. nsamp = n - sample; - // Don't allow zero-length runs. This could/should be checked in the control loop instead. + // TODO: Don't allow zero-length runs. This could/should be checked in the control loop instead. // Note this means it is still possible to get stuck in the top loop (at least for a while). if(nsamp == 0) continue; @@ -2563,13 +2681,8 @@ int PluginI::oscUpdate() usleep(300000); // Send current control values. - //unsigned long ports = controlPorts; DELETETHIS 2 - //for(int i = 0; i < controlPorts; ++i) for(unsigned long i = 0; i < controlPorts; ++i) { - //unsigned long k = synth->pIdx(i); DELETETHIS 2 - //_oscIF.oscSendControl(k, controls[i], true /*force*/); - //printf("PluginI::oscUpdate() sending control:%lu val:%f\n", i, controls[i].val); _oscif.oscSendControl(controls[i].idx, controls[i].val, true /*force*/); // Avoid overloading the GUI if there are lots and lots of ports. if((i+1) % 50 == 0) @@ -2638,7 +2751,6 @@ int PluginI::oscControl(unsigned long port, float value) } } */ - // p4.0.21 ControlEvent ce; ce.unique = _plugin->_isDssiVst; // Special for messages from vst gui to host - requires processing every message. ce.idx = cport; @@ -2693,27 +2805,6 @@ int PluginI::oscControl(unsigned long port, float value) } */ -// DELETETHIS 20 -#if 0 - int port = argv[0]->i; - LADSPA_Data value = argv[1]->f; - - if (port < 0 || port > instance->plugin->descriptor->LADSPA_Plugin->PortCount) { - fprintf(stderr, "MusE: OSC: %s port number (%d) is out of range\n", - instance->friendly_name, port); - return 0; - } - if (instance->pluginPortControlInNumbers[port] == -1) { - fprintf(stderr, "MusE: OSC: %s port %d is not a control in\n", - instance->friendly_name, port); - return 0; - } - pluginControlIns[instance->pluginPortControlInNumbers[port]] = value; - if (verbose) { - printf("MusE: OSC: %s port %d = %f\n", - instance->friendly_name, port, value); - } -#endif return 0; } @@ -2795,7 +2886,7 @@ PluginDialog::PluginDialog(QWidget* parent) ok_lo->addWidget(cancelB); QGroupBox* plugSelGroup = new QGroupBox(this); - plugSelGroup->setTitle("Show plugs:"); + plugSelGroup->setTitle(tr("Show plugs:")); plugSelGroup->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); QGridLayout* psl = new QGridLayout; plugSelGroup->setLayout(psl); @@ -2972,7 +3063,7 @@ void PluginDialog::fillPlugs() QString type_name; pList->clear(); for (MusECore::iPlugin i = MusEGlobal::plugins.begin(); i != MusEGlobal::plugins.end(); ++i) { - unsigned long ai = i->inports(); // p4.0.21 + unsigned long ai = i->inports(); unsigned long ao = i->outports(); unsigned long ci = i->controlInPorts(); unsigned long co = i->controlOutPorts(); @@ -3115,7 +3206,7 @@ PluginGui::PluginGui(MusECore::PluginIBase* p) const char* name = ba.constData(); if (*name !='P') continue; - unsigned long parameter; // p4.0.21 + unsigned long parameter; int rv = sscanf(name, "P%lu", ¶meter); if(rv != 1) continue; @@ -3126,15 +3217,17 @@ PluginGui::PluginGui(MusECore::PluginIBase* p) nobj = 0; QSignalMapper* mapper = new QSignalMapper(this); - // FIXME: There's no unsigned for gui params. We would need to limit nobj to MAXINT. // p4.0.21 - // FIXME: Our MusEGui::Slider class uses doubles for values, giving some problems with float conversion. // p4.0.21 + // FIXME: There's no unsigned for gui params. We would need to limit nobj to MAXINT. + // FIXME: Our MusEGui::Slider class uses doubles for values, giving some problems with float conversion. connect(mapper, SIGNAL(mapped(int)), SLOT(guiParamChanged(int))); - QSignalMapper* mapperPressed = new QSignalMapper(this); - QSignalMapper* mapperReleased = new QSignalMapper(this); + QSignalMapper* mapperPressed = new QSignalMapper(this); + QSignalMapper* mapperReleased = new QSignalMapper(this); + QSignalMapper* mapperContextMenuReq = new QSignalMapper(this); connect(mapperPressed, SIGNAL(mapped(int)), SLOT(guiParamPressed(int))); connect(mapperReleased, SIGNAL(mapped(int)), SLOT(guiParamReleased(int))); + connect(mapperContextMenuReq, SIGNAL(mapped(int)), SLOT(guiContextMenuReq(int))); for (it = l.begin(); it != l.end(); ++it) { obj = *it; @@ -3142,7 +3235,7 @@ PluginGui::PluginGui(MusECore::PluginIBase* p) const char* name = ba.constData(); if (*name !='P') continue; - unsigned long parameter; // p4.0.21 + unsigned long parameter; int rv = sscanf(name, "P%lu", ¶meter); if(rv != 1) continue; @@ -3150,6 +3243,7 @@ PluginGui::PluginGui(MusECore::PluginIBase* p) mapper->setMapping(obj, nobj); mapperPressed->setMapping(obj, nobj); mapperReleased->setMapping(obj, nobj); + mapperContextMenuReq->setMapping(obj, nobj); gw[nobj].widget = (QWidget*)obj; gw[nobj].param = parameter; @@ -3159,15 +3253,15 @@ PluginGui::PluginGui(MusECore::PluginIBase* p) gw[nobj].type = GuiWidgets::SLIDER; ((Slider*)obj)->setId(nobj); ((Slider*)obj)->setCursorHoming(true); - for(unsigned long i = 0; i < nobj; i++) // p4.0.21 + for(unsigned long i = 0; i < nobj; i++) { if(gw[i].type == GuiWidgets::DOUBLE_LABEL && gw[i].param == parameter) ((DoubleLabel*)gw[i].widget)->setSlider((Slider*)obj); } - connect(obj, SIGNAL(sliderMoved(double,int)), mapper, SLOT(map())); - connect(obj, SIGNAL(sliderPressed(int)), SLOT(guiSliderPressed(int))); - connect(obj, SIGNAL(sliderReleased(int)), SLOT(guiSliderReleased(int))); - connect(obj, SIGNAL(sliderRightClicked(const QPoint &, int)), SLOT(guiSliderRightClicked(const QPoint &, int))); + connect((Slider*)obj, SIGNAL(sliderMoved(double,int)), mapper, SLOT(map())); + connect((Slider*)obj, SIGNAL(sliderPressed(int)), SLOT(guiSliderPressed(int))); + connect((Slider*)obj, SIGNAL(sliderReleased(int)), SLOT(guiSliderReleased(int))); + connect((Slider*)obj, SIGNAL(sliderRightClicked(const QPoint &, int)), SLOT(guiSliderRightClicked(const QPoint &, int))); } else if (strcmp(obj->metaObject()->className(), "MusEGui::DoubleLabel") == 0) { gw[nobj].type = GuiWidgets::DOUBLE_LABEL; @@ -3180,17 +3274,23 @@ PluginGui::PluginGui(MusECore::PluginIBase* p) break; } } - connect(obj, SIGNAL(valueChanged(double,int)), mapper, SLOT(map())); + connect((DoubleLabel*)obj, SIGNAL(valueChanged(double,int)), mapper, SLOT(map())); } else if (strcmp(obj->metaObject()->className(), "QCheckBox") == 0) { gw[nobj].type = GuiWidgets::QCHECKBOX; - connect(obj, SIGNAL(toggled(bool)), mapper, SLOT(map())); - connect(obj, SIGNAL(pressed()), mapperPressed, SLOT(map())); - connect(obj, SIGNAL(released()), mapperReleased, SLOT(map())); + gw[nobj].widget->setContextMenuPolicy(Qt::CustomContextMenu); + connect((QCheckBox*)obj, SIGNAL(toggled(bool)), mapper, SLOT(map())); + connect((QCheckBox*)obj, SIGNAL(pressed()), mapperPressed, SLOT(map())); + connect((QCheckBox*)obj, SIGNAL(released()), mapperReleased, SLOT(map())); + connect((QCheckBox*)obj, SIGNAL(customContextMenuRequested(const QPoint &)), + mapperContextMenuReq, SLOT(map())); } else if (strcmp(obj->metaObject()->className(), "QComboBox") == 0) { gw[nobj].type = GuiWidgets::QCOMBOBOX; - connect(obj, SIGNAL(activated(int)), mapper, SLOT(map())); + gw[nobj].widget->setContextMenuPolicy(Qt::CustomContextMenu); + connect((QComboBox*)obj, SIGNAL(activated(int)), mapper, SLOT(map())); + connect((QComboBox*)obj, SIGNAL(customContextMenuRequested(const QPoint &)), + mapperContextMenuReq, SLOT(map())); } else { printf("unknown widget class %s\n", obj->metaObject()->className()); @@ -3201,7 +3301,6 @@ PluginGui::PluginGui(MusECore::PluginIBase* p) updateValues(); // otherwise the GUI won't have valid data } else { - // p3.4.43 view = new QScrollArea; view->setWidgetResizable(true); setCentralWidget(view); @@ -3212,13 +3311,13 @@ PluginGui::PluginGui(MusECore::PluginIBase* p) mw->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); - unsigned long n = plugin->parameters(); // p4.0.21 + unsigned long n = plugin->parameters(); params = new GuiParam[n]; QFontMetrics fm = fontMetrics(); int h = fm.height() + 4; - for (unsigned long i = 0; i < n; ++i) { // p4.0.21 + for (unsigned long i = 0; i < n; ++i) { QLabel* label = 0; LADSPA_PortRangeHint range = plugin->range(i); double lower = 0.0; // default values @@ -3281,7 +3380,7 @@ PluginGui::PluginGui(MusECore::PluginIBase* p) grid->addWidget(params[i].actuator, i, 0, 1, 3); } if (params[i].type == GuiParam::GUI_SLIDER) { - connect(params[i].actuator, SIGNAL(sliderMoved(double,int)), SLOT(sliderChanged(double,int))); + connect(params[i].actuator, SIGNAL(sliderMoved(double,int,bool)), SLOT(sliderChanged(double,int,bool))); connect(params[i].label, SIGNAL(valueChanged(double,int)), SLOT(labelChanged(double,int))); connect(params[i].actuator, SIGNAL(sliderPressed(int)), SLOT(ctrlPressed(int))); connect(params[i].actuator, SIGNAL(sliderReleased(int)), SLOT(ctrlReleased(int))); @@ -3429,21 +3528,17 @@ void PluginGui::ctrlPressed(int param) if(track) { track->setPluginCtrlVal(id, val); - MusEGlobal::song->controllerChange(track); - track->startAutoRecord(id, val); } } else if(params[param].type == GuiParam::GUI_SWITCH) { - float val = (float)((CheckBox*)params[param].actuator)->isChecked(); // p4.0.21 + float val = (float)((CheckBox*)params[param].actuator)->isChecked(); plugin->setParam(param, val); if(track) { track->setPluginCtrlVal(id, val); - MusEGlobal::song->controllerChange(track); - track->startAutoRecord(id, val); } } @@ -3498,13 +3593,17 @@ void PluginGui::ctrlRightClicked(const QPoint &p, int param) // sliderChanged //--------------------------------------------------------- -void PluginGui::sliderChanged(double val, int param) +void PluginGui::sliderChanged(double val, int param, bool shift_pressed) { AutomationType at = AUTO_OFF; MusECore::AudioTrack* track = plugin->track(); if(track) at = track->automationType(); + if ( (at == AUTO_WRITE) || + (at == AUTO_TOUCH && MusEGlobal::audio->isPlaying()) ) + plugin->enableController(param, false); + if (LADSPA_IS_HINT_LOGARITHMIC(params[param].hint)) val = pow(10.0, val/20.0); else if (LADSPA_IS_HINT_INTEGER(params[param].hint)) @@ -3523,9 +3622,7 @@ void PluginGui::sliderChanged(double val, int param) if(track) { track->setPluginCtrlVal(id, val); - MusEGlobal::song->controllerChange(track); - - track->recordAutomation(id, val); + if (!shift_pressed) track->recordAutomation(id, val); //with shift, we get straight lines :) } } @@ -3540,6 +3637,10 @@ void PluginGui::labelChanged(double val, int param) if(track) at = track->automationType(); + if ( (at == AUTO_WRITE) || + (at == AUTO_TOUCH && MusEGlobal::audio->isPlaying()) ) + plugin->enableController(param, false); + double dval = val; if (LADSPA_IS_HINT_LOGARITHMIC(params[param].hint)) dval = MusECore::fast_log10(val) * 20.0; @@ -3559,8 +3660,6 @@ void PluginGui::labelChanged(double val, int param) if(track) { track->setPluginCtrlVal(id, val); - MusEGlobal::song->controllerChange(track); - track->startAutoRecord(id, val); } } @@ -3689,7 +3788,7 @@ void PluginGui::setOn(bool val) void PluginGui::updateValues() { if (params) { - for (unsigned long i = 0; i < plugin->parameters(); ++i) { // p4.0.21 + for (unsigned long i = 0; i < plugin->parameters(); ++i) { GuiParam* gp = ¶ms[i]; if (gp->type == GuiParam::GUI_SLIDER) { double lv = plugin->param(i); @@ -3710,10 +3809,10 @@ void PluginGui::updateValues() } } else if (gw) { - for (unsigned long i = 0; i < nobj; ++i) { // p4.0.21 + for (unsigned long i = 0; i < nobj; ++i) { QWidget* widget = gw[i].widget; int type = gw[i].type; - unsigned long param = gw[i].param; // p4.0.21 + unsigned long param = gw[i].param; float val = plugin->param(param); switch(type) { case GuiWidgets::SLIDER: @@ -3766,12 +3865,16 @@ void PluginGui::updateControls() if (params) { - for (unsigned long i = 0; i < plugin->parameters(); ++i) { // p4.0.21 + for (unsigned long i = 0; i < plugin->parameters(); ++i) { GuiParam* gp = ¶ms[i]; if (gp->type == GuiParam::GUI_SLIDER) { - if( plugin->controllerEnabled(i) && plugin->controllerEnabled2(i) ) { - double lv = plugin->track()->pluginCtrlVal(MusECore::genACnum(plugin->id(), i)); + double lv = plugin->track()->controller()->value(MusECore::genACnum(plugin->id(), i), + MusEGlobal::audio->curFramePos(), + !MusEGlobal::automation || + plugin->track()->automationType() == AUTO_OFF || + !plugin->controllerEnabled(i) || + !plugin->controllerEnabled2(i)); double sv = lv; if (LADSPA_IS_HINT_LOGARITHMIC(params[i].hint)) sv = MusECore::fast_log10(lv) * 20.0; @@ -3791,12 +3894,15 @@ void PluginGui::updateControls() gp->label->blockSignals(false); } } - } else if (gp->type == GuiParam::GUI_SWITCH) { - if( plugin->controllerEnabled(i) && plugin->controllerEnabled2(i) ) { - bool v = (int)plugin->track()->pluginCtrlVal(MusECore::genACnum(plugin->id(), i)); + bool v = (int)plugin->track()->controller()->value(MusECore::genACnum(plugin->id(), i), + MusEGlobal::audio->curFramePos(), + !MusEGlobal::automation || + plugin->track()->automationType() == AUTO_OFF || + !plugin->controllerEnabled(i) || + !plugin->controllerEnabled2(i)); if(((CheckBox*)(gp->actuator))->isChecked() != v) { ((CheckBox*)(gp->actuator))->blockSignals(true); @@ -3808,15 +3914,19 @@ void PluginGui::updateControls() } } else if (gw) { - for (unsigned long i = 0; i < nobj; ++i) { // p4.0.21 + for (unsigned long i = 0; i < nobj; ++i) { QWidget* widget = gw[i].widget; int type = gw[i].type; - unsigned long param = gw[i].param; // p4.0.21 + unsigned long param = gw[i].param; switch(type) { case GuiWidgets::SLIDER: - if( plugin->controllerEnabled(param) && plugin->controllerEnabled2(param) ) { - double v = plugin->track()->pluginCtrlVal(MusECore::genACnum(plugin->id(), param)); + double v = plugin->track()->controller()->value(MusECore::genACnum(plugin->id(), param), + MusEGlobal::audio->curFramePos(), + !MusEGlobal::automation || + plugin->track()->automationType() == AUTO_OFF || + !plugin->controllerEnabled(param) || + !plugin->controllerEnabled2(param)); if(((Slider*)widget)->value() != v) { ((Slider*)widget)->blockSignals(true); @@ -3826,9 +3936,13 @@ void PluginGui::updateControls() } break; case GuiWidgets::DOUBLE_LABEL: - if( plugin->controllerEnabled(param) && plugin->controllerEnabled2(param) ) { - double v = plugin->track()->pluginCtrlVal(MusECore::genACnum(plugin->id(), param)); + double v = plugin->track()->controller()->value(MusECore::genACnum(plugin->id(), param), + MusEGlobal::audio->curFramePos(), + !MusEGlobal::automation || + plugin->track()->automationType() == AUTO_OFF || + !plugin->controllerEnabled(param) || + !plugin->controllerEnabled2(param)); if(((DoubleLabel*)widget)->value() != v) { ((DoubleLabel*)widget)->blockSignals(true); @@ -3838,9 +3952,13 @@ void PluginGui::updateControls() } break; case GuiWidgets::QCHECKBOX: - if( plugin->controllerEnabled(param) && plugin->controllerEnabled2(param) ) { - bool b = (bool) plugin->track()->pluginCtrlVal(MusECore::genACnum(plugin->id(), param)); + bool b = (bool) plugin->track()->controller()->value(MusECore::genACnum(plugin->id(), param), + MusEGlobal::audio->curFramePos(), + !MusEGlobal::automation || + plugin->track()->automationType() == AUTO_OFF || + !plugin->controllerEnabled(param) || + !plugin->controllerEnabled2(param)); if(((QCheckBox*)widget)->isChecked() != b) { ((QCheckBox*)widget)->blockSignals(true); @@ -3850,9 +3968,13 @@ void PluginGui::updateControls() } break; case GuiWidgets::QCOMBOBOX: - if( plugin->controllerEnabled(param) && plugin->controllerEnabled2(param) ) { - int n = (int) plugin->track()->pluginCtrlVal(MusECore::genACnum(plugin->id(), param)); + int n = (int) plugin->track()->controller()->value(MusECore::genACnum(plugin->id(), param), + MusEGlobal::audio->curFramePos(), + !MusEGlobal::automation || + plugin->track()->automationType() == AUTO_OFF || + !plugin->controllerEnabled(param) || + !plugin->controllerEnabled2(param)); if(((QComboBox*)widget)->currentIndex() != n) { ((QComboBox*)widget)->blockSignals(true); @@ -3873,7 +3995,7 @@ void PluginGui::updateControls() void PluginGui::guiParamChanged(int idx) { QWidget* w = gw[idx].widget; - unsigned long param = gw[idx].param; // p4.0.21 + unsigned long param = gw[idx].param; int type = gw[idx].type; AutomationType at = AUTO_OFF; @@ -3881,6 +4003,10 @@ void PluginGui::guiParamChanged(int idx) if(track) at = track->automationType(); + if ( (at == AUTO_WRITE) || + (at == AUTO_TOUCH && MusEGlobal::audio->isPlaying()) ) + plugin->enableController(param, false); + double val = 0.0; switch(type) { case GuiWidgets::SLIDER: @@ -3897,7 +4023,7 @@ void PluginGui::guiParamChanged(int idx) break; } - for (unsigned long i = 0; i < nobj; ++i) { // p4.0.21 + for (unsigned long i = 0; i < nobj; ++i) { QWidget* widget = gw[i].widget; if (widget == w || param != gw[i].param) continue; @@ -3922,10 +4048,7 @@ void PluginGui::guiParamChanged(int idx) if(track && id != -1) { id = MusECore::genACnum(id, param); - track->setPluginCtrlVal(id, val); - MusEGlobal::song->controllerChange(track); - switch(type) { case GuiWidgets::DOUBLE_LABEL: @@ -3946,7 +4069,7 @@ void PluginGui::guiParamChanged(int idx) void PluginGui::guiParamPressed(int idx) { - unsigned long param = gw[idx].param; // p4.0.21 + unsigned long param = gw[idx].param; AutomationType at = AUTO_OFF; MusECore::AudioTrack* track = plugin->track(); @@ -3965,8 +4088,8 @@ void PluginGui::guiParamPressed(int idx) // NOTE: For this to be of any use, the freeverb gui 2142.ui // would have to be used, and changed to use CheckBox and ComboBox // instead of QCheckBox and QComboBox, since both of those would - // need customization (Ex. QCheckBox doesn't check on click). - /* DELETETHIS 10 plus above + // need customization (Ex. QCheckBox doesn't check on click). RECHECK: Qt4 it does? + /* switch(type) { case GuiWidgets::QCHECKBOX: double val = (double)((CheckBox*)w)->isChecked(); @@ -3986,7 +4109,7 @@ void PluginGui::guiParamPressed(int idx) void PluginGui::guiParamReleased(int idx) { - unsigned long param = gw[idx].param; // p4.0.21 + unsigned long param = gw[idx].param; int type = gw[idx].type; AutomationType at = AUTO_OFF; @@ -4011,8 +4134,8 @@ void PluginGui::guiParamReleased(int idx) // NOTE: For this to be of any use, the freeverb gui 2142.ui // would have to be used, and changed to use CheckBox and ComboBox // instead of QCheckBox and QComboBox, since both of those would - // need customization (Ex. QCheckBox doesn't check on click). - /* DELETETHIS 10 plus above + // need customization (Ex. QCheckBox doesn't check on click). // RECHECK Qt4 it does? + /* switch(type) { case GuiWidgets::QCHECKBOX: double val = (double)((CheckBox*)w)->isChecked(); @@ -4032,7 +4155,7 @@ void PluginGui::guiParamReleased(int idx) void PluginGui::guiSliderPressed(int idx) { - unsigned long param = gw[idx].param; // p4.0.21 + unsigned long param = gw[idx].param; QWidget *w = gw[idx].widget; AutomationType at = AUTO_OFF; @@ -4054,12 +4177,10 @@ void PluginGui::guiSliderPressed(int idx) plugin->setParam(param, val); track->setPluginCtrlVal(id, val); - MusEGlobal::song->controllerChange(track); - track->startAutoRecord(id, val); // Needed so that paging a slider updates a label or other buddy control. - for (unsigned long i = 0; i < nobj; ++i) { // p4.0.21 + for (unsigned long i = 0; i < nobj; ++i) { QWidget* widget = gw[i].widget; if (widget == w || param != gw[i].param) continue; @@ -4127,6 +4248,15 @@ void PluginGui::guiSliderRightClicked(const QPoint &p, int idx) } //--------------------------------------------------------- +// guiContextMenuReq +//--------------------------------------------------------- + +void PluginGui::guiContextMenuReq(int idx) +{ + guiSliderRightClicked(QCursor().pos(), idx); +} + +//--------------------------------------------------------- // PluginLoader //--------------------------------------------------------- QWidget* PluginLoader::createWidget(const QString & className, QWidget * parent, const QString & name) diff --git a/muse2/muse/plugin.h b/muse2/muse/plugin.h index 9c671097..06e99564 100644 --- a/muse2/muse/plugin.h +++ b/muse2/muse/plugin.h @@ -246,12 +246,16 @@ class PluginIBase virtual void enableController(unsigned long i, bool v = true) = 0; virtual bool controllerEnabled(unsigned long i) const = 0; + virtual void enable2Controller(unsigned long i, bool v = true) = 0; virtual bool controllerEnabled2(unsigned long i) const = 0; + virtual void enableAllControllers(bool v = true) = 0; + virtual void enable2AllControllers(bool v = true) = 0; virtual void updateControllers() = 0; virtual void writeConfiguration(int level, Xml& xml) = 0; virtual bool readConfiguration(Xml& xml, bool readPreset=false) = 0; + virtual bool addScheduledControlEvent(unsigned long i, float val, unsigned frame); // returns true if event cannot be delivered virtual unsigned long parameters() const = 0; virtual unsigned long parametersOut() const = 0; virtual void setParam(unsigned long i, float val) = 0; @@ -412,6 +416,10 @@ class Pipeline : public std::vector<PluginI*> { void move(int idx, bool up); bool empty(int idx) const; void setChannels(int); + bool addScheduledControlEvent(int track_ctrl_id, float val, unsigned frame); // returns true if event cannot be delivered + void enableController(int track_ctrl_id, bool en); + void enable2Controller(int track_ctrl_id, bool en); + void controllersEnabled(int track_ctrl_id, bool* en1, bool* en2); }; typedef Pipeline::iterator iPluginI; @@ -497,7 +505,7 @@ class PluginGui : public QMainWindow { void load(); void save(); void bypassToggled(bool); - void sliderChanged(double, int); + void sliderChanged(double, int, bool); void labelChanged(double, int); void guiParamChanged(int); void ctrlPressed(int); @@ -508,6 +516,7 @@ class PluginGui : public QMainWindow { void guiSliderReleased(int); void ctrlRightClicked(const QPoint &, int); void guiSliderRightClicked(const QPoint &, int); + void guiContextMenuReq(int idx); protected slots: void heartBeat(); diff --git a/muse2/muse/seqmsg.cpp b/muse2/muse/seqmsg.cpp index d5257f80..f60a2d51 100644 --- a/muse2/muse/seqmsg.cpp +++ b/muse2/muse/seqmsg.cpp @@ -46,6 +46,7 @@ namespace MusECore { // sendMsg //--------------------------------------------------------- +// this function blocks until the request has been processed void Audio::sendMsg(AudioMsg* m) { static int sno = 0; @@ -522,7 +523,6 @@ void Audio::msgSwapControllerIDX(AudioTrack* node, int idx1, int idx2) msg.a = idx1; msg.b = idx2; sendMsg(&msg); - MusEGlobal::song->controllerChange(node); } //--------------------------------------------------------- @@ -537,7 +537,6 @@ void Audio::msgClearControllerEvents(AudioTrack* node, int acid) msg.snode = node; msg.ival = acid; sendMsg(&msg); - MusEGlobal::song->controllerChange(node); } //--------------------------------------------------------- @@ -581,7 +580,6 @@ void Audio::msgEraseACEvent(AudioTrack* node, int acid, int frame) msg.ival = acid; msg.a = frame; sendMsg(&msg); - MusEGlobal::song->controllerChange(node); } //--------------------------------------------------------- @@ -598,7 +596,6 @@ void Audio::msgEraseRangeACEvents(AudioTrack* node, int acid, int frame1, int fr msg.a = frame1; msg.b = frame2; sendMsg(&msg); - MusEGlobal::song->controllerChange(node); } //--------------------------------------------------------- @@ -615,7 +612,6 @@ void Audio::msgAddACEvent(AudioTrack* node, int acid, int frame, double val) msg.a = frame; msg.dval = val; sendMsg(&msg); - MusEGlobal::song->controllerChange(node); } //--------------------------------------------------------- @@ -633,7 +629,6 @@ void Audio::msgChangeACEvent(AudioTrack* node, int acid, int frame, int newFrame msg.b = newFrame; msg.dval = val; sendMsg(&msg); - MusEGlobal::song->controllerChange(node); } //--------------------------------------------------------- @@ -1297,6 +1292,18 @@ void Audio::msgSetSendMetronome(AudioTrack* track, bool b) } //--------------------------------------------------------- +// msgStartMidiLearn +// Start learning midi +//--------------------------------------------------------- + +void Audio::msgStartMidiLearn() +{ + AudioMsg msg; + msg.id = AUDIO_START_MIDI_LEARN; + sendMessage(&msg, false); +} + +//--------------------------------------------------------- // msgBounce // start bounce operation //--------------------------------------------------------- diff --git a/muse2/muse/song.cpp b/muse2/muse/song.cpp index 0c7a0c73..020d620c 100644 --- a/muse2/muse/song.cpp +++ b/muse2/muse/song.cpp @@ -57,11 +57,13 @@ #include "sync.h" #include "midictrl.h" #include "menutitleitem.h" +#include "midi_audio_control.h" #include "tracks_duplicate.h" #include "midi.h" #include "al/sig.h" #include "keyevent.h" #include <sys/wait.h> +#include "tempo.h" namespace MusEGlobal { MusECore::Song* song = 0; @@ -762,18 +764,11 @@ void Song::changeAllPortDrumCtrlEvents(bool add, bool drumonly) void Song::addACEvent(AudioTrack* t, int acid, int frame, double val) { MusEGlobal::audio->msgAddACEvent(t, acid, frame, val); - emit controllerChanged(t); } void Song::changeACEvent(AudioTrack* t, int acid, int frame, int newFrame, double val) { MusEGlobal::audio->msgChangeACEvent(t, acid, frame, newFrame, val); - emit controllerChanged(t); -} - -void Song::controllerChange(Track* t) -{ - emit controllerChanged(t); } //--------------------------------------------------------- @@ -883,7 +878,6 @@ void Song::cmdAddRecordedEvents(MidiTrack* mt, EventList* events, unsigned start if (endTick < tick) endTick = tick; } - // Added by Tim. p3.3.8 // Round the end up (again) using the Arranger part snap raster value. endTick = AL::sigmap.raster2(endTick, arrangerRaster()); @@ -1626,6 +1620,26 @@ void Song::beat() if (MusEGlobal::audio->isPlaying()) setPos(0, MusEGlobal::audio->tickPos(), true, false, true); + // Process external tempo changes: + while(!_tempoFifo.isEmpty()) + MusEGlobal::tempo_rec_list.addTempo(_tempoFifo.get()); + + // Update anything related to audio controller graphs etc. + for(ciTrack it = _tracks.begin(); it != _tracks.end(); ++ it) + { + if((*it)->isMidiTrack()) + continue; + AudioTrack* at = static_cast<AudioTrack*>(*it); + CtrlListList* cll = at->controller(); + for(ciCtrlList icl = cll->begin(); icl != cll->end(); ++icl) + { + CtrlList* cl = icl->second; + if(cl->isVisible() && !cl->dontShow() && cl->guiUpdatePending()) + emit controllerChanged(at, cl->id()); + cl->setGuiUpdatePending(false); + } + } + // Update synth native guis at the heartbeat rate. for(ciSynthI is = _synthIs.begin(); is != _synthIs.end(); ++is) (*is)->guiHeartBeat(); @@ -2078,6 +2092,7 @@ void Song::clear(bool signal, bool clear_all) while (loop); MusEGlobal::tempomap.clear(); + MusEGlobal::tempo_rec_list.clear(); AL::sigmap.clear(); MusEGlobal::keymap.clear(); @@ -2419,13 +2434,14 @@ void Song::recordEvent(MidiTrack* mt, Event& event) int Song::execAutomationCtlPopup(AudioTrack* track, const QPoint& menupos, int acid) { - enum { PREV_EVENT, NEXT_EVENT, ADD_EVENT, CLEAR_EVENT, CLEAR_RANGE, CLEAR_ALL_EVENTS }; + enum { PREV_EVENT=0, NEXT_EVENT, ADD_EVENT, CLEAR_EVENT, CLEAR_RANGE, CLEAR_ALL_EVENTS, MIDI_ASSIGN, MIDI_CLEAR }; QMenu* menu = new QMenu; int count = 0; bool isEvent = false, canSeekPrev = false, canSeekNext = false, canEraseRange = false; bool canAdd = false; double ctlval = 0.0; + int frame = 0; if(track) { ciCtrlList icl = track->controller()->find(acid); @@ -2434,11 +2450,17 @@ int Song::execAutomationCtlPopup(AudioTrack* track, const QPoint& menupos, int a CtrlList *cl = icl->second; canAdd = true; - //int frame = pos[0].frame(); DELETETHIS - int frame = MusEGlobal::audio->pos().frame(); // Try this. p4.0.33 DELETETHIS + frame = MusEGlobal::audio->pos().frame(); + + bool en1, en2; + track->controllersEnabled(acid, &en1, &en2); + + AutomationType at = track->automationType(); + if(!MusEGlobal::automation || at == AUTO_OFF || !en1 || !en2) + ctlval = cl->curVal(); + else + ctlval = cl->value(frame); - ctlval = cl->curVal(); - count = cl->size(); if(count) { @@ -2491,6 +2513,40 @@ int Song::execAutomationCtlPopup(AudioTrack* track, const QPoint& menupos, int a clearAction->setData(CLEAR_ALL_EVENTS); clearAction->setEnabled((bool)count); + + menu->addSeparator(); + menu->addAction(new MusEGui::MenuTitleItem(tr("Midi control"), menu)); + + QAction *assign_act = menu->addAction(tr("Assign")); + assign_act->setCheckable(false); + assign_act->setData(MIDI_ASSIGN); + + MidiAudioCtrlMap* macm = track->controller()->midiControls(); + AudioMidiCtrlStructMap amcs; + macm->find_audio_ctrl_structs(acid, &amcs); + + if(!amcs.empty()) + { + QAction *cact = menu->addAction(tr("Clear")); + cact->setData(MIDI_CLEAR); + menu->addSeparator(); + } + + for(iAudioMidiCtrlStructMap iamcs = amcs.begin(); iamcs != amcs.end(); ++iamcs) + { + int port, chan, mctrl; + macm->hash_values((*iamcs)->first, &port, &chan, &mctrl); + //QString s = QString("Port:%1 Chan:%2 Ctl:%3-%4").arg(port + 1) + QString s = QString("Port:%1 Chan:%2 Ctl:%3").arg(port + 1) + .arg(chan + 1) + //.arg((mctrl >> 8) & 0xff) + //.arg(mctrl & 0xff); + .arg(midiCtrlName(mctrl, true)); + QAction *mact = menu->addAction(s); + mact->setEnabled(false); + mact->setData(-1); // Not used + } + QAction* act = menu->exec(menupos); if (!act || !track) { @@ -2504,10 +2560,10 @@ int Song::execAutomationCtlPopup(AudioTrack* track, const QPoint& menupos, int a switch(sel) { case ADD_EVENT: - MusEGlobal::audio->msgAddACEvent(track, acid, pos[0].frame(), ctlval); + MusEGlobal::audio->msgAddACEvent(track, acid, frame, ctlval); break; case CLEAR_EVENT: - MusEGlobal::audio->msgEraseACEvent(track, acid, pos[0].frame()); + MusEGlobal::audio->msgEraseACEvent(track, acid, frame); break; case CLEAR_RANGE: @@ -2529,6 +2585,45 @@ int Song::execAutomationCtlPopup(AudioTrack* track, const QPoint& menupos, int a MusEGlobal::audio->msgSeekNextACEvent(track, acid); break; + case MIDI_ASSIGN: + { + int port = -1, chan = 0, ctrl = 0; + for(MusECore::iAudioMidiCtrlStructMap iamcs = amcs.begin(); iamcs != amcs.end(); ++iamcs) + { + macm->hash_values((*iamcs)->first, &port, &chan, &ctrl); + break; // Only a single item for now, thanks! + } + + MusEGui::MidiAudioControl* pup = new MusEGui::MidiAudioControl(port, chan, ctrl); + + if(pup->exec() == QDialog::Accepted) + { + MusEGlobal::audio->msgIdle(true); // Gain access to structures, and sync with audio + // Erase all for now. + for(MusECore::iAudioMidiCtrlStructMap iamcs = amcs.begin(); iamcs != amcs.end(); ++iamcs) + macm->erase(*iamcs); + + port = pup->port(); chan = pup->chan(); ctrl = pup->ctrl(); + if(port >= 0 && chan >=0 && ctrl >= 0) + // Add will replace if found. + macm->add_ctrl_struct(port, chan, ctrl, MusECore::MidiAudioCtrlStruct(acid)); + + MusEGlobal::audio->msgIdle(false); + } + + delete pup; + } + break; + + case MIDI_CLEAR: + if(!amcs.empty()) + MusEGlobal::audio->msgIdle(true); // Gain access to structures, and sync with audio + for(MusECore::iAudioMidiCtrlStructMap iamcs = amcs.begin(); iamcs != amcs.end(); ++iamcs) + macm->erase(*iamcs); + if(!amcs.empty()) + MusEGlobal::audio->msgIdle(false); + break; + default: return -1; break; @@ -2755,8 +2850,62 @@ void Song::processAutomationEvents() // Process (and clear) rec events. ((AudioTrack*)(*i))->processAutomationEvents(); } + + MusEGlobal::audio->msgIdle(false); +} + +//--------------------------------------------------------- +// processMasterRec +//--------------------------------------------------------- + +void Song::processMasterRec() +{ + bool do_tempo = false; + + // Wait a few seconds for the tempo fifo to be empty. + int tout = 30; + while(!_tempoFifo.isEmpty()) + { + usleep(100000); + --tout; + if(tout == 0) + break; + } + + int tempo_rec_list_sz = MusEGlobal::tempo_rec_list.size(); + if(tempo_rec_list_sz != 0) + { + if(QMessageBox::question(MusEGlobal::muse, + tr("MusE: Tempo list"), + tr("External tempo changes were recorded.\nTransfer them to master tempo list?"), + QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel) == QMessageBox::Ok) + do_tempo = true; + } + + MusEGlobal::audio->msgIdle(true); // gain access to all data structures + + if(do_tempo) + { + // Erase from master tempo the (approximate) recording start/end tick range according to the recorded tempo map, + //MusEGlobal::tempomap.eraseRange(MusEGlobal::tempo_rec_list.frame2tick(MusEGlobal::audio->getStartRecordPos().frame()), + // MusEGlobal::tempo_rec_list.frame2tick(MusEGlobal::audio->getEndRecordPos().frame())); + // This is more accurate but lacks resolution: + MusEGlobal::tempomap.eraseRange(MusEGlobal::audio->getStartExternalRecTick(), MusEGlobal::audio->getEndExternalRecTick()); + + // Add the recorded tempos to the master tempo list: + for(int i = 0; i < tempo_rec_list_sz; ++i) + MusEGlobal::tempomap.addTempo(MusEGlobal::tempo_rec_list[i].tick, + MusEGlobal::tempo_rec_list[i].tempo, + false); // False: Defer normalize + MusEGlobal::tempomap.normalize(); + } + + MusEGlobal::tempo_rec_list.clear(); MusEGlobal::audio->msgIdle(false); + + if(do_tempo) + update(SC_TEMPO); } //--------------------------------------------------------- diff --git a/muse2/muse/song.h b/muse2/muse/song.h index 82b8cf18..6570ad8d 100644 --- a/muse2/muse/song.h +++ b/muse2/muse/song.h @@ -126,6 +126,8 @@ class Song : public QObject { int noteFifoWindex; int noteFifoRindex; + TempoFifo _tempoFifo; // External tempo changes, processed in heartbeat. + int updateFlags; TrackList _tracks; // tracklist as seen by arranger @@ -263,7 +265,7 @@ class Song : public QObject { // event manipulations //----------------------------------------- - void cmdAddRecordedWave(WaveTrack* track, Pos, Pos); + void cmdAddRecordedWave(WaveTrack* track, Pos, Pos); void cmdAddRecordedEvents(MidiTrack*, EventList*, unsigned); bool addEvent(Event&, Part*); void changeEvent(Event&, Event&, Part*); @@ -274,8 +276,8 @@ class Song : public QObject { void addACEvent(AudioTrack* t, int acid, int frame, double val); void changeACEvent(AudioTrack* t, int acid, int frame, int newFrame, double val); - void controllerChange(Track* t); - + void addExternalTempo(const TempoRecEvent& e) { _tempoFifo.put(e); } + //----------------------------------------- // part manipulations //----------------------------------------- @@ -332,6 +334,7 @@ class Song : public QObject { void msgInsertTrack(Track* track, int idx, bool u = true); void clearRecAutomation(bool clearList); void processAutomationEvents(); + void processMasterRec(); int execAutomationCtlPopup(AudioTrack*, const QPoint&, int); int execMidiAutomationCtlPopup(MidiTrack*, MidiPart*, const QPoint&, int); void connectJackRoutes(AudioTrack* track, bool disconnect); @@ -428,7 +431,7 @@ class Song : public QObject { void markerChanged(int); void midiPortsChanged(); void midiNote(int pitch, int velo); - void controllerChanged(MusECore::Track* t); // maybe DELETETHIS: this only triggers a redraw in pcanvas.cpp; what is this for? + void controllerChanged(MusECore::Track*, int); void newPartsCreated(const std::map< MusECore::Part*, std::set<MusECore::Part*> >&); }; diff --git a/muse2/muse/sync.cpp b/muse2/muse/sync.cpp index 56560a5e..bf9d2613 100644 --- a/muse2/muse/sync.cpp +++ b/muse2/muse/sync.cpp @@ -21,6 +21,7 @@ // //========================================================= +#include <stdlib.h> #include <cmath> #include "sync.h" #include "song.h" @@ -59,12 +60,13 @@ static unsigned int curExtMidiSyncTick = 0; unsigned int volatile lastExtMidiSyncTick = 0; double volatile curExtMidiSyncTime = 0.0; double volatile lastExtMidiSyncTime = 0.0; +MusECore::MidiSyncInfo::SyncRecFilterPresetType syncRecFilterPreset = MusECore::MidiSyncInfo::SMALL; +double syncRecTempoValQuant = 1.0; // Not used yet. DELETETHIS? // static bool mcStart = false; // static int mcStartTick; -// p3.3.25 // From the "Introduction to the Volatile Keyword" at Embedded dot com /* A variable should be declared volatile whenever its value could change unexpectedly. ... <such as> global variables within a multi-threaded application @@ -820,18 +822,25 @@ void MidiSeq::alignAllTicks(int frameOverride) recTick2 = 0; if (MusEGlobal::debugSync) printf("alignAllTicks curFrame=%d recTick=%d tempo=%.3f frameOverride=%d\n",curFrame,recTick,(float)((1000000.0 * 60.0)/tempo), frameOverride); - + + lastTempo = 0; + for(int i = 0; i < _clockAveragerPoles; ++i) + { + _avgClkDiffCounter[i] = 0; + _averagerFull[i] = false; + } + _lastRealTempo = 0.0; } //--------------------------------------------------------- // realtimeSystemInput // real time message received //--------------------------------------------------------- -void MidiSeq::realtimeSystemInput(int port, int c) +void MidiSeq::realtimeSystemInput(int port, int c, double time) { if (MusEGlobal::midiInputTrace) - printf("realtimeSystemInput port:%d 0x%x\n", port+1, c); + printf("realtimeSystemInput port:%d 0x%x time:%f\n", port+1, c, time); MidiPort* mp = &MusEGlobal::midiPorts[port]; @@ -873,6 +882,9 @@ void MidiSeq::realtimeSystemInput(int port, int c) if(p != port && MusEGlobal::midiPorts[p].syncInfo().MCOut()) MusEGlobal::midiPorts[p].sendClock(); + MusEGlobal::lastExtMidiSyncTime = MusEGlobal::curExtMidiSyncTime; + MusEGlobal::curExtMidiSyncTime = time; + if(MusEGlobal::playPendingFirstClock) { MusEGlobal::playPendingFirstClock = false; @@ -887,12 +899,158 @@ void MidiSeq::realtimeSystemInput(int port, int c) // Can't check audio state, might not be playing yet, we might miss some increments. if(playStateExt) { - MusEGlobal::lastExtMidiSyncTime = MusEGlobal::curExtMidiSyncTime; - MusEGlobal::curExtMidiSyncTime = curTime(); int div = MusEGlobal::config.division/24; MusEGlobal::midiExtSyncTicks += div; MusEGlobal::lastExtMidiSyncTick = MusEGlobal::curExtMidiSyncTick; MusEGlobal::curExtMidiSyncTick += div; + + if(MusEGlobal::song->record() && MusEGlobal::lastExtMidiSyncTime > 0.0) + { + double diff = MusEGlobal::curExtMidiSyncTime - MusEGlobal::lastExtMidiSyncTime; + if(diff != 0.0) + { + if(_clockAveragerPoles == 0) + { + double real_tempo = 60.0/(diff * 24.0); + if(_tempoQuantizeAmount > 0.0) + { + double f_mod = fmod(real_tempo, _tempoQuantizeAmount); + if(f_mod < _tempoQuantizeAmount/2.0) + real_tempo -= f_mod; + else + real_tempo += _tempoQuantizeAmount - f_mod; + } + int new_tempo = ((1000000.0 * 60.0) / (real_tempo)); + if(new_tempo != lastTempo) + { + lastTempo = new_tempo; + // Compute tick for this tempo - it is one step back in time. + int add_tick = MusEGlobal::curExtMidiSyncTick - div; + if(MusEGlobal::debugSync) + printf("adding new tempo tick:%d curExtMidiSyncTick:%d avg_diff:%f real_tempo:%f new_tempo:%d = %f\n", add_tick, MusEGlobal::curExtMidiSyncTick, diff, real_tempo, new_tempo, (double)((1000000.0 * 60.0)/new_tempo)); + MusEGlobal::song->addExternalTempo(TempoRecEvent(add_tick, new_tempo)); + } + } + else + { + double avg_diff = diff; + for(int pole = 0; pole < _clockAveragerPoles; ++pole) + { + timediff[pole][_avgClkDiffCounter[pole]] = avg_diff; + ++_avgClkDiffCounter[pole]; + if(_avgClkDiffCounter[pole] >= _clockAveragerStages[pole]) + { + _avgClkDiffCounter[pole] = 0; + _averagerFull[pole] = true; + } + + // Each averager needs to be full before we can pass the data to + // the next averager or use the data if all averagers are full... + if(!_averagerFull[pole]) + break; + else + { + avg_diff = 0.0; + for(int i = 0; i < _clockAveragerStages[pole]; ++i) + avg_diff += timediff[pole][i]; + avg_diff /= _clockAveragerStages[pole]; + + int fin_idx = _clockAveragerPoles - 1; + + // On the first pole? Check for large differences. + if(_preDetect && pole == 0) + { + double real_tempo = 60.0/(avg_diff * 24.0); + double real_tempo_diff = abs(real_tempo - _lastRealTempo); + + // If the tempo changed a large amount, reset. + if(real_tempo_diff >= 10.0) // TODO: User-adjustable? + { + if(_tempoQuantizeAmount > 0.0) + { + double f_mod = fmod(real_tempo, _tempoQuantizeAmount); + if(f_mod < _tempoQuantizeAmount/2.0) + real_tempo -= f_mod; + else + real_tempo += _tempoQuantizeAmount - f_mod; + } + _lastRealTempo = real_tempo; + int new_tempo = ((1000000.0 * 60.0) / (real_tempo)); + + if(new_tempo != lastTempo) + { + lastTempo = new_tempo; + // Compute tick for this tempo - it is way back in time. + int add_tick = MusEGlobal::curExtMidiSyncTick - _clockAveragerStages[0] * div; + if(add_tick < 0) + { + printf("FIXME sync: adding restart tempo curExtMidiSyncTick:%d: add_tick:%d < 0 !\n", MusEGlobal::curExtMidiSyncTick, add_tick); + add_tick = 0; + } + if(MusEGlobal::debugSync) + printf("adding restart tempo tick:%d curExtMidiSyncTick:%d tick_idx_sub:%d avg_diff:%f real_tempo:%f real_tempo_diff:%f new_tempo:%d = %f\n", add_tick, MusEGlobal::curExtMidiSyncTick, _clockAveragerStages[0], avg_diff, real_tempo, real_tempo_diff, new_tempo, (double)((1000000.0 * 60.0)/new_tempo)); + MusEGlobal::song->addExternalTempo(TempoRecEvent(add_tick, new_tempo)); + } + + // Reset all the poles. + //for(int i = 0; i < clockAveragerPoles; ++i) + // We have a value for this pole, let's keep it but reset the other poles. + for(int i = 1; i < _clockAveragerPoles; ++i) + { + _avgClkDiffCounter[i] = 0; + _averagerFull[i] = false; + } + break; + } + } + + // On the last pole? + // All averagers need to be full before we can use the data... + if(pole == fin_idx) + { + double real_tempo = 60.0/(avg_diff * 24.0); + double real_tempo_diff = abs(real_tempo - _lastRealTempo); + + if(real_tempo_diff >= _tempoQuantizeAmount/2.0) // Anti-hysteresis + { + if(_tempoQuantizeAmount > 0.0) + { + double f_mod = fmod(real_tempo, _tempoQuantizeAmount); + if(f_mod < _tempoQuantizeAmount/2.0) + real_tempo -= f_mod; + else + real_tempo += _tempoQuantizeAmount - f_mod; + } + _lastRealTempo = real_tempo; + int new_tempo = ((1000000.0 * 60.0) / (real_tempo)); + + if(new_tempo != lastTempo) + { + lastTempo = new_tempo; + // Compute tick for this tempo - it is way back in time. + int tick_idx_sub = 0; + for(int i = 0; i <= pole; ++i) + tick_idx_sub += _clockAveragerStages[i]; + // Compensate: Each pole > 0 has a delay one less than its number of stages. + // For example three pole {8, 8, 8} has a delay of 22 not 24. + tick_idx_sub -= pole; + int add_tick = MusEGlobal::curExtMidiSyncTick - tick_idx_sub * div; + if(add_tick < 0) + { + printf("FIXME sync: adding new tempo curExtMidiSyncTick:%d: add_tick:%d < 0 !\n", MusEGlobal::curExtMidiSyncTick, add_tick); + add_tick = 0; + } + if(MusEGlobal::debugSync) + printf("adding new tempo tick:%d curExtMidiSyncTick:%d tick_idx_sub:%d avg_diff:%f real_tempo:%f new_tempo:%d = %f\n", add_tick, MusEGlobal::curExtMidiSyncTick, tick_idx_sub, avg_diff, real_tempo, new_tempo, (double)((1000000.0 * 60.0)/new_tempo)); + MusEGlobal::song->addExternalTempo(TempoRecEvent(add_tick, new_tempo)); + } + } + } + } + } + } + } + } } //BEGIN : Original code: DELETETHIS 250 @@ -1185,8 +1343,6 @@ void MidiSeq::realtimeSystemInput(int port, int c) alignAllTicks(); storedtimediffs = 0; - for (int i=0; i<24; i++) - timediff[i] = 0.0; // p3.3.26 1/23/10 DELETETHIS 6 // Changed because msgPlay calls MusEGlobal::audioDevice->seekTransport(song->cPos()) @@ -1243,7 +1399,7 @@ void MidiSeq::realtimeSystemInput(int port, int c) if (MusEGlobal::debugSync) printf("realtimeSystemInput stop\n"); - + //DELETETHIS 7 // Just in case the process still runs a cycle or two and causes the // audio tick position to increment, reset the incrementer and force diff --git a/muse2/muse/sync.h b/muse2/muse/sync.h index 41ad34ad..09ea06e9 100644 --- a/muse2/muse/sync.h +++ b/muse2/muse/sync.h @@ -32,9 +32,11 @@ namespace MusECore { class Xml; - class MidiSyncInfo { + public: + enum SyncRecFilterPresetType { NONE=0, TINY, SMALL, MEDIUM, LARGE, LARGE_WITH_PRE_DETECT, TYPE_END }; + private: int _port; @@ -151,6 +153,9 @@ extern int volatile curMidiSyncInPort; extern MusECore::BValue useJackTransport; extern bool volatile jackTransportMaster; extern unsigned int syncSendFirstClockDelay; // In milliseconds. +extern unsigned int volatile lastExtMidiSyncTick; +extern MusECore::MidiSyncInfo::SyncRecFilterPresetType syncRecFilterPreset; +extern double syncRecTempoValQuant; } // namespace MusEGlobal diff --git a/muse2/muse/tempo.cpp b/muse2/muse/tempo.cpp index 1147fd78..d339f516 100644 --- a/muse2/muse/tempo.cpp +++ b/muse2/muse/tempo.cpp @@ -32,6 +32,7 @@ namespace MusEGlobal { MusECore::TempoList tempomap; +MusECore::TempoRecList tempo_rec_list; } namespace MusECore { @@ -59,7 +60,7 @@ TempoList::~TempoList() // add //--------------------------------------------------------- -void TempoList::add(unsigned tick, int tempo) +void TempoList::add(unsigned tick, int tempo, bool do_normalize) { if (tick > MAX_TICK) tick = MAX_TICK; @@ -74,7 +75,8 @@ void TempoList::add(unsigned tick, int tempo) ne->tick = tick; insert(std::pair<const unsigned, TEvent*> (tick, ev)); } - normalize(); + if(do_normalize) + normalize(); } //--------------------------------------------------------- @@ -120,6 +122,33 @@ void TempoList::clear() } //--------------------------------------------------------- +// eraseRange +//--------------------------------------------------------- + +void TempoList::eraseRange(unsigned stick, unsigned etick) +{ + if(stick >= etick || stick > MAX_TICK) + return; + if(etick > MAX_TICK) + etick = MAX_TICK; + + iTEvent se = MusEGlobal::tempomap.upper_bound(stick); + if(se == end() || (se->first == MAX_TICK+1)) + return; + + iTEvent ee = MusEGlobal::tempomap.upper_bound(etick); + + ee->second->tempo = se->second->tempo; + ee->second->tick = se->second->tick; + + for(iTEvent ite = se; ite != ee; ++ite) + delete ite->second; + erase(se, ee); // Erase range does NOT include the last element. + normalize(); + ++_tempoSN; +} + +//--------------------------------------------------------- // tempo //--------------------------------------------------------- @@ -224,9 +253,9 @@ void TempoList::setGlobalTempo(int val) // addTempo //--------------------------------------------------------- -void TempoList::addTempo(unsigned t, int tempo) +void TempoList::addTempo(unsigned t, int tempo, bool do_normalize) { - add(t, tempo); + add(t, tempo, do_normalize); ++_tempoSN; } @@ -538,5 +567,54 @@ int TEvent::read(Xml& xml) return 0; } +//--------------------------------------------------------- +// put +// return true on fifo overflow +//--------------------------------------------------------- + +bool TempoFifo::put(const TempoRecEvent& event) + { + if (size < TEMPO_FIFO_SIZE) { + fifo[wIndex] = event; + wIndex = (wIndex + 1) % TEMPO_FIFO_SIZE; + // q_atomic_increment(&size); + ++size; + return false; + } + return true; + } + +//--------------------------------------------------------- +// get +//--------------------------------------------------------- + +TempoRecEvent TempoFifo::get() + { + TempoRecEvent event(fifo[rIndex]); + rIndex = (rIndex + 1) % TEMPO_FIFO_SIZE; + --size; + return event; + } + +//--------------------------------------------------------- +// peek +//--------------------------------------------------------- + +const TempoRecEvent& TempoFifo::peek(int n) + { + int idx = (rIndex + n) % TEMPO_FIFO_SIZE; + return fifo[idx]; + } + +//--------------------------------------------------------- +// remove +//--------------------------------------------------------- + +void TempoFifo::remove() + { + rIndex = (rIndex + 1) % TEMPO_FIFO_SIZE; + --size; + } + } // namespace MusECore diff --git a/muse2/muse/tempo.h b/muse2/muse/tempo.h index 7a3f413b..71f1580c 100644 --- a/muse2/muse/tempo.h +++ b/muse2/muse/tempo.h @@ -25,11 +25,16 @@ #define __TEMPO_H__ #include <map> +#include <vector> #ifndef MAX_TICK #define MAX_TICK (0x7fffffff/100) #endif +// Tempo ring buffer size +#define TEMPO_FIFO_SIZE 1024 + + namespace MusECore { class Xml; @@ -70,8 +75,7 @@ class TempoList : public TEMPOLIST { int _tempo; // tempo if not using tempo list int _globalTempo; // %percent 50-200% - void normalize(); - void add(unsigned tick, int tempo); + void add(unsigned tick, int tempo, bool do_normalize = true); void change(unsigned tick, int newTempo); void del(iTEvent); void del(unsigned tick); @@ -79,7 +83,9 @@ class TempoList : public TEMPOLIST { public: TempoList(); ~TempoList(); + void normalize(); void clear(); + void eraseRange(unsigned stick, unsigned etick); void read(Xml&); void write(int, Xml&) const; @@ -96,18 +102,62 @@ class TempoList : public TEMPOLIST { int tempoSN() const { return _tempoSN; } void setTempo(unsigned tick, int newTempo); - void addTempo(unsigned t, int tempo); + void addTempo(unsigned t, int tempo, bool do_normalize = true); void delTempo(unsigned tick); void changeTempo(unsigned tick, int newTempo); + bool masterFlag() const { return useList; } bool setMasterFlag(unsigned tick, bool val); int globalTempo() const { return _globalTempo; } void setGlobalTempo(int val); }; +//--------------------------------------------------------- +// Tempo Record Event +//--------------------------------------------------------- + +struct TempoRecEvent { + int tempo; + unsigned tick; + TempoRecEvent() { } + TempoRecEvent(unsigned tk, unsigned t) { + tick = tk; + tempo = t; + } + }; + +class TempoRecList : public std::vector<TempoRecEvent > +{ + public: + void addTempo(int tick, int tempo) { push_back(TempoRecEvent(tick, tempo)); } + void addTempo(const TempoRecEvent& e) { push_back(e); } +}; + +//--------------------------------------------------------- +// TempoFifo +//--------------------------------------------------------- + +class TempoFifo { + TempoRecEvent fifo[TEMPO_FIFO_SIZE]; + volatile int size; + int wIndex; + int rIndex; + + public: + TempoFifo() { clear(); } + bool put(const TempoRecEvent& event); // returns true on fifo overflow + TempoRecEvent get(); + const TempoRecEvent& peek(int = 0); + void remove(); + bool isEmpty() const { return size == 0; } + void clear() { size = 0, wIndex = 0, rIndex = 0; } + int getSize() const { return size; } + }; + } // namespace MusECore namespace MusEGlobal { extern MusECore::TempoList tempomap; +extern MusECore::TempoRecList tempo_rec_list; } #endif diff --git a/muse2/muse/thread.cpp b/muse2/muse/thread.cpp index 69238922..14f9750e 100644 --- a/muse2/muse/thread.cpp +++ b/muse2/muse/thread.cpp @@ -250,61 +250,11 @@ void Thread::removePollFd(int fd, int action) void Thread::loop() { - // Changed by Tim. p3.3.17 - if (!MusEGlobal::debugMode) { if (mlockall(MCL_CURRENT | MCL_FUTURE)) perror("WARNING: Cannot lock memory:"); } -/* DELETETHIS 46 - pthread_attr_t* attributes = 0; - attributes = (pthread_attr_t*) malloc(sizeof(pthread_attr_t)); - pthread_attr_init(attributes); - - if (MusEGlobal::realTimeScheduling && realTimePriority > 0) { - - doSetuid(); -// if (pthread_attr_setschedpolicy(attributes, SCHED_FIFO)) { -// printf("cannot set FIFO scheduling class for RT thread\n"); -// } -// if (pthread_attr_setscope (attributes, PTHREAD_SCOPE_SYSTEM)) { -// printf("Cannot set scheduling scope for RT thread\n"); -// } -// struct sched_param rt_param; -// memset(&rt_param, 0, sizeof(rt_param)); -// rt_param.sched_priority = realTimePriority; -// if (pthread_attr_setschedparam (attributes, &rt_param)) { -// printf("Cannot set scheduling priority %d for RT thread (%s)\n", -// realTimePriority, strerror(errno)); -// } - - // do the SCHED_FIFO stuff _after_ thread creation: - struct sched_param *param = new struct sched_param; - param->sched_priority = realTimePriority; - int error = pthread_setschedparam(pthread_self(), SCHED_FIFO, param); - if (error != 0) - perror( "error set_schedparam 2:"); - -// if (!MusEGlobal::debugMode) { -// if (mlockall(MCL_CURRENT|MCL_FUTURE)) -// perror("WARNING: Cannot lock memory:"); -// } - - undoSetuid(); - } - -*/ - - -/* -#define BIG_ENOUGH_STACK (1024*1024*1) - char buf[BIG_ENOUGH_STACK]; - for (int i = 0; i < BIG_ENOUGH_STACK; i++) - buf[i] = i; -#undef BIG_ENOUGH_STACK -*/ - #ifdef __APPLE__ #define BIG_ENOUGH_STACK (1024*256*1) #else @@ -318,7 +268,8 @@ void Thread::loop() pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0); pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0); - int policy = 0; + int policy = buf[0]; // Initialize using buf[0] to keep the compiler from complaining about unused buf. + policy = 0; // Now set the true desired inital value. if ((policy = sched_getscheduler (0)) < 0) { printf("Thread: Cannot get current client scheduler: %s\n", strerror(errno)); } diff --git a/muse2/muse/track.cpp b/muse2/muse/track.cpp index 905f9030..d353b4bb 100644 --- a/muse2/muse/track.cpp +++ b/muse2/muse/track.cpp @@ -37,6 +37,7 @@ #include "midictrl.h" #include "helper.h" #include "limits.h" +#include "dssihost.h" namespace MusECore { @@ -384,7 +385,21 @@ void Track::clearRecAutomation(bool clearList) continue; p->enableAllControllers(true); } - + + if(type() == AUDIO_SOFTSYNTH) + { + SynthI* synth = static_cast<SynthI*>(this); + if(synth->synth() && synth->synth()->synthType() == Synth::DSSI_SYNTH) + { + SynthIF* sif = synth->sif(); + if(sif) + { + DssiSynthIF* dssi_sif = static_cast<DssiSynthIF*>(sif); + dssi_sif->enableAllControllers(true); + } + } + } + if(clearList) t->recEvents()->clear(); } diff --git a/muse2/muse/track.h b/muse2/muse/track.h index 93f2f673..3da34912 100644 --- a/muse2/muse/track.h +++ b/muse2/muse/track.h @@ -46,7 +46,7 @@ class PluginI; class SynthI; class Xml; class DrumMap; - +class ControlEvent; //--------------------------------------------------------- // Track @@ -447,6 +447,9 @@ class AudioTrack : public Track { virtual void setAutomationType(AutomationType t); void processAutomationEvents(); CtrlRecList* recEvents() { return &_recEvents; } + bool addScheduledControlEvent(int track_ctrl_id, float val, unsigned frame); // return true if event cannot be delivered + void enableController(int track_ctrl_id, bool en); + void controllersEnabled(int track_ctrl_id, bool* en1, bool* en2) const; void recordAutomation(int n, double v); void startAutoRecord(int, double); void stopAutoRecord(int, double); diff --git a/muse2/muse/undo.h b/muse2/muse/undo.h index b88a9457..2f582d8e 100644 --- a/muse2/muse/undo.h +++ b/muse2/muse/undo.h @@ -125,12 +125,12 @@ struct UndoOp { UndoOp(UndoType type, Track* track, const char* old_name, const char* new_name); UndoOp(UndoType type, Track* track, int old_chan, int new_chan); UndoOp(UndoType type); - }; +}; class Undo : public std::list<UndoOp> { public: bool empty() const; - }; +}; typedef Undo::iterator iUndoOp; typedef Undo::reverse_iterator riUndoOp; @@ -141,7 +141,7 @@ class UndoList : public std::list<Undo> { public: void clearDelete(); UndoList(bool _isUndo) : std::list<Undo>() { isUndo=_isUndo; } - }; +}; typedef UndoList::iterator iUndo; typedef UndoList::reverse_iterator riUndo; diff --git a/muse2/muse/wave.cpp b/muse2/muse/wave.cpp index 5e19648d..8d17a10d 100644 --- a/muse2/muse/wave.cpp +++ b/muse2/muse/wave.cpp @@ -964,7 +964,7 @@ int ClipList::idx(const Clip& clip) const // cmdAddRecordedWave //--------------------------------------------------------- -void Song::cmdAddRecordedWave(MusECore::WaveTrack* track, MusECore::Pos s, MusECore::Pos e) +void Song::cmdAddRecordedWave(MusECore::WaveTrack* track, MusECore::Pos s, MusECore::Pos e) { if (MusEGlobal::debugMsg) printf("cmdAddRecordedWave - loopCount = %d, punchin = %d", MusEGlobal::audio->loopCount(), punchin()); @@ -976,15 +976,31 @@ void Song::cmdAddRecordedWave(MusECore::WaveTrack* track, MusECore::Pos s, MusEC return; } + // If externally clocking (and therefore master was forced off), + // tempos may have been recorded. We really should temporarily force + // the master tempo map on in order to properly determine the ticks below. + // Else internal clocking, the user decided to record either with or without + // master on, so let it be. + // FIXME: We really should allow the master flag to be on at the same time as + // the external sync flag! AFAIR when external sync is on, no part of the app shall + // depend on the tempo map anyway, so it should not matter whether it's on or off. + // If we do that, then we may be able to remove this section and user simply decides + // whether master is on/off, because we may be able to use the flag to determine + // whether to record external tempos at all, because we may want a switch for it! + bool master_was_on = MusEGlobal::tempomap.masterFlag(); + if(MusEGlobal::extSyncFlag.value() && !master_was_on) + MusEGlobal::tempomap.setMasterFlag(0, true); + if((MusEGlobal::audio->loopCount() > 0 && s.tick() > lPos().tick()) || (punchin() && s.tick() < lPos().tick())) s.setTick(lPos().tick()); // If we are looping, just set the end to the right marker, since we don't know how many loops have occurred. // (Fixed: Added Audio::loopCount) // Otherwise if punchout is on, limit the end to the right marker. - if((MusEGlobal::audio->loopCount() > 0) || (punchout() && e.tick() > rPos().tick()) ) + if((MusEGlobal::audio->loopCount() > 0) || (punchout() && e.tick() > rPos().tick()) ) e.setTick(rPos().tick()); + // No part to be created? Delete the rec sound file. - if(s.tick() >= e.tick()) + if(s.frame() >= e.frame()) { QString st = f->path(); // The function which calls this function already does this immediately after. But do it here anyway. @@ -992,19 +1008,30 @@ void Song::cmdAddRecordedWave(MusECore::WaveTrack* track, MusECore::Pos s, MusEC // counter has dropped by 2 and _recFile will probably deleted then remove(st.toLatin1().constData()); if(MusEGlobal::debugMsg) - printf("Song::cmdAddRecordedWave: remove file %s - start=%d end=%d\n", st.toLatin1().constData(), s.tick(), e.tick()); + printf("Song::cmdAddRecordedWave: remove file %s - startframe=%d endframe=%d\n", st.toLatin1().constData(), s.frame(), e.frame()); + + // Restore master flag. + if(MusEGlobal::extSyncFlag.value() && !master_was_on) + MusEGlobal::tempomap.setMasterFlag(0, false); + return; } // Round the start down using the Arranger part snap raster value. - unsigned startTick = AL::sigmap.raster1(s.tick(), MusEGlobal::song->arrangerRaster()); + int a_rast = MusEGlobal::song->arrangerRaster(); + unsigned sframe = (a_rast == 1) ? s.frame() : Pos(AL::sigmap.raster1(s.tick(), MusEGlobal::song->arrangerRaster())).frame(); // Round the end up using the Arranger part snap raster value. - unsigned endTick = AL::sigmap.raster2(e.tick(), MusEGlobal::song->arrangerRaster()); + unsigned eframe = (a_rast == 1) ? e.frame() : Pos(AL::sigmap.raster2(e.tick(), MusEGlobal::song->arrangerRaster())).frame(); + unsigned etick = Pos(eframe).tick(); + + // Done using master tempo map. Restore master flag. + if(MusEGlobal::extSyncFlag.value() && !master_was_on) + MusEGlobal::tempomap.setMasterFlag(0, false); f->update(); MusECore::WavePart* part = new MusECore::WavePart(track); - part->setTick(startTick); - part->setLenTick(endTick - startTick); + part->setFrame(sframe); + part->setLenFrame(eframe - sframe); part->setName(track->name()); // create Event @@ -1015,20 +1042,18 @@ void Song::cmdAddRecordedWave(MusECore::WaveTrack* track, MusECore::Pos s, MusEC track->setRecFile(0); event.setSpos(0); - // Since the part start was snapped down, we must apply the difference so that the // wave event tick lines up with when the user actually started recording. - // Added by Tim. p3.3.8 - event.setTick(s.tick() - startTick); - - + event.setFrame(s.frame() - sframe); + // NO Can't use this. SF reports too long samples at first part recorded in sequence. See samples() - funny business with SEEK ? + //event.setLenFrame(f.samples()); event.setLenFrame(e.frame() - s.frame()); part->addEvent(event); MusEGlobal::song->cmdAddPart(part); - if (MusEGlobal::song->len() < endTick) - MusEGlobal::song->setLen(endTick); + if (MusEGlobal::song->len() < etick) + MusEGlobal::song->setLen(etick); } //--------------------------------------------------------- diff --git a/muse2/muse/wavetrack.cpp b/muse2/muse/wavetrack.cpp index dd890b42..b55a67d6 100644 --- a/muse2/muse/wavetrack.cpp +++ b/muse2/muse/wavetrack.cpp @@ -223,7 +223,17 @@ bool WaveTrack::getData(unsigned framePos, int channels, unsigned nframe, float* if (MusEGlobal::audio->freewheel()) { } else { - if (fifo.put(channels, nframe, bp, MusEGlobal::audio->pos().frame())) +#ifdef _AUDIO_USE_TRUE_FRAME_ + // TODO: Tested: This is the line that would be needed for Audio Inputs, + // because the data arrived in the previous period! Test OK, the waves are in sync. + // So we need to do Audio Inputs separately above, AND find a way to mix two overlapping + // periods into the file! Nothing wrong with the FIFO per se, we could stamp overlapping + // times. But the soundfile just writes, does not mix. + //if (fifo.put(channels, nframe, bp, MusEGlobal::audio->previousPos().frame())) + // + // Tested: This line is OK for track-to-track recording, the waves are in sync: +#endif + if (fifo.put(channels, nframe, bp, MusEGlobal::audio->pos().frame())) printf("WaveTrack::getData(%d, %d, %d): fifo overrun\n", framePos, channels, nframe); } diff --git a/muse2/muse/widgets/CMakeLists.txt b/muse2/muse/widgets/CMakeLists.txt index fae0d614..88706339 100644 --- a/muse2/muse/widgets/CMakeLists.txt +++ b/muse2/muse/widgets/CMakeLists.txt @@ -57,6 +57,7 @@ QT4_WRAP_CPP (widget_mocs menutitleitem.h meter.h metronome.h + midi_audio_control.h midisyncimpl.h mixdowndialog.h mlabel.h @@ -122,6 +123,7 @@ file (GLOB widgets_ui_files itransformbase.ui metronomebase.ui midisync.ui + midi_audio_control_base.ui mittransposebase.ui mixdowndialogbase.ui mtrackinfobase.ui @@ -169,6 +171,7 @@ file (GLOB widgets_source_files menutitleitem.cpp meter.cpp metronome.cpp + midi_audio_control.cpp midisyncimpl.cpp mixdowndialog.cpp mlabel.cpp diff --git a/muse2/muse/widgets/aboutbox.ui b/muse2/muse/widgets/aboutbox.ui index 250f656f..8b4d5b37 100644 --- a/muse2/muse/widgets/aboutbox.ui +++ b/muse2/muse/widgets/aboutbox.ui @@ -48,7 +48,7 @@ <item> <widget class="QLabel" name="versionLabel"> <property name="text"> - <string>Version 2 pre-alpha</string> + <string>Version 2</string> </property> <property name="wordWrap"> <bool>false</bool> @@ -58,7 +58,7 @@ <item> <widget class="QLabel" name="textLabel1"> <property name="text"> - <string>(C) Copyright 1999-2010 Werner Schweer and others. + <string>(C) Copyright 1999-2012 Werner Schweer and others. See http://www.muse-sequencer.org for new versions and more information. diff --git a/muse2/muse/widgets/bigtime.cpp b/muse2/muse/widgets/bigtime.cpp index 0b213f28..5adf4966 100644 --- a/muse2/muse/widgets/bigtime.cpp +++ b/muse2/muse/widgets/bigtime.cpp @@ -32,6 +32,7 @@ #include "song.h" #include "app.h" #include "gconfig.h" +#include "audio.h" namespace MusEGlobal { extern int mtcType; @@ -229,7 +230,9 @@ bool BigTime::setString(unsigned v) return true; } - unsigned absFrame = MusEGlobal::tempomap.tick2frame(v); + // Quick fix: Not much to do but ignore the supplied tick: We need the exact frame here. + unsigned absFrame = MusEGlobal::audio->pos().frame(); + int bar, beat; unsigned tick; AL::sigmap.tickValues(v, &bar, &beat, &tick); diff --git a/muse2/muse/widgets/filedialog.cpp b/muse2/muse/widgets/filedialog.cpp index 6e7d6882..aa8c5df1 100644 --- a/muse2/muse/widgets/filedialog.cpp +++ b/muse2/muse/widgets/filedialog.cpp @@ -102,6 +102,7 @@ void MFileDialog::globalToggled(bool flag) { if (flag) { buttons.readMidiPortsButton->setChecked(false); + readMidiPortsSaved = false; if (lastGlobalDir.isEmpty()) lastGlobalDir = MusEGlobal::museGlobalShare + QString("/") + baseDir; // Initialize if first time setDirectory(lastGlobalDir); @@ -117,6 +118,7 @@ void MFileDialog::userToggled(bool flag) { if (flag) { buttons.readMidiPortsButton->setChecked(true); + readMidiPortsSaved = true; if (lastUserDir.isEmpty()) { //lastUserDir = MusEGlobal::museUser + QString("/") + baseDir; // Initialize if first time lastUserDir = MusEGlobal::configPath + QString("/") + baseDir; // Initialize if first time // p4.0.39 @@ -140,6 +142,7 @@ void MFileDialog::projectToggled(bool flag) { if (flag) { buttons.readMidiPortsButton->setChecked(true); + readMidiPortsSaved = true; QString s; if (MusEGlobal::museProject == MusEGlobal::museProjectInitPath ) { // if project path is uninitialized, meaning it is still set to museProjectInitPath. @@ -158,6 +161,29 @@ void MFileDialog::projectToggled(bool flag) } } +void MFileDialog::fileChanged(const QString& path) +{ + bool is_mid = path.endsWith(".mid", Qt::CaseInsensitive) || + path.endsWith(".midi", Qt::CaseInsensitive) || + path.endsWith(".kar", Qt::CaseInsensitive); + + if (is_mid) + { + readMidiPortsSaved=buttons.readMidiPortsButton->isChecked(); + buttons.readMidiPortsButton->setEnabled(false); + buttons.readMidiPortsButton->setChecked(false); + } + else + { + if (!buttons.readMidiPortsButton->isEnabled()) + { + buttons.readMidiPortsButton->setEnabled(true); + buttons.readMidiPortsButton->setChecked(readMidiPortsSaved); + } + } + +} + //--------------------------------------------------------- // MFileDialog @@ -167,6 +193,7 @@ MFileDialog::MFileDialog(const QString& dir, const QString& filter, QWidget* parent, bool writeFlag) : QFileDialog(parent, QString(), QString("."), filter) { + readMidiPortsSaved = true; showButtons = false; lastUserDir = ""; lastGlobalDir = ""; @@ -201,10 +228,11 @@ MFileDialog::MFileDialog(const QString& dir, buttons.userButton->setAutoExclusive(true); buttons.projectButton->setAutoExclusive(true); - connect(buttons.globalButton, SIGNAL(toggled(bool)), this, SLOT(globalToggled(bool))); + connect(buttons.globalButton, SIGNAL(toggled(bool)), this, SLOT(globalToggled(bool))); connect(buttons.userButton, SIGNAL(toggled(bool)), this, SLOT(userToggled(bool))); connect(buttons.projectButton, SIGNAL(toggled(bool)), this, SLOT(projectToggled(bool))); connect(this, SIGNAL(directoryEntered(const QString&)), SLOT(directoryChanged(const QString&))); + connect(this, SIGNAL(currentChanged(const QString&)), SLOT(fileChanged(const QString&))); if (writeFlag) { setAcceptMode(QFileDialog::AcceptSave); diff --git a/muse2/muse/widgets/filedialog.h b/muse2/muse/widgets/filedialog.h index 1e2616da..582e943d 100644 --- a/muse2/muse/widgets/filedialog.h +++ b/muse2/muse/widgets/filedialog.h @@ -52,9 +52,12 @@ class MFileDialog : public QFileDialog { QString lastUserDir, lastGlobalDir; bool showButtons; QString baseDir; + + bool readMidiPortsSaved; private slots: void directoryChanged(const QString& directory); + void fileChanged(const QString&); public slots: void globalToggled(bool); void userToggled(bool); diff --git a/muse2/muse/widgets/midi_audio_control.cpp b/muse2/muse/widgets/midi_audio_control.cpp new file mode 100644 index 00000000..78c8de3c --- /dev/null +++ b/muse2/muse/widgets/midi_audio_control.cpp @@ -0,0 +1,340 @@ +//========================================================= +// MusE +// Linux Music Editor +// +// midi_audio_control.cpp +// Copyright (C) 2012 by Tim E. Real (terminator356 at users.sourceforge.net) +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +//========================================================= +#include "midi_audio_control.h" + +#include "globals.h" +#include "globaldefs.h" +#include "mididev.h" +#include "midiport.h" +#include "midictrl.h" +#include "audio.h" +#include "app.h" + +#include <QTimer> + +namespace MusEGui { + +// ----------------------------------- +// MidiAudioControl +// Set port to -1 to automatically set it to the port of +// the first combo box item (the first readable port). +// ----------------------------------- + +MidiAudioControl::MidiAudioControl(int port, int chan, int ctrl, QWidget* parent) + : QDialog(parent) +{ + setupUi(this); + + _port = port; + _chan = chan; + _ctrl = ctrl; + _is_learning = false; + + update(); + + connect(learnPushButton, SIGNAL(clicked(bool)), SLOT(learnChanged(bool))); + connect(portComboBox, SIGNAL(currentIndexChanged(int)), SLOT(portChanged(int))); + connect(channelSpinBox, SIGNAL(valueChanged(int)), SLOT(chanChanged())); + connect(controlTypeComboBox, SIGNAL(currentIndexChanged(int)), SLOT(ctrlTypeChanged(int))); + connect(ctrlHiSpinBox, SIGNAL(valueChanged(int)), SLOT(ctrlHChanged())); + connect(ctrlLoSpinBox, SIGNAL(valueChanged(int)), SLOT(ctrlLChanged())); + connect(MusEGlobal::muse, SIGNAL(configChanged()), SLOT(configChanged())); + connect(MusEGlobal::heartBeatTimer, SIGNAL(timeout()), SLOT(heartbeat())); +} + +void MidiAudioControl::heartbeat() +{ + if(_is_learning) + { + if(MusEGlobal::midiLearnPort != -1) + { + int port_item = portComboBox->findData(MusEGlobal::midiLearnPort); + if(port_item != -1 && port_item != portComboBox->currentIndex()) + { + _port = MusEGlobal::midiLearnPort; + portComboBox->blockSignals(true); + portComboBox->setCurrentIndex(port_item); + portComboBox->blockSignals(false); + } + } + + if(MusEGlobal::midiLearnChan != -1 && (MusEGlobal::midiLearnChan + 1) != channelSpinBox->value()) + { + _chan = MusEGlobal::midiLearnChan; + channelSpinBox->blockSignals(true); + channelSpinBox->setValue(_chan + 1); + channelSpinBox->blockSignals(false); + } + + if(MusEGlobal::midiLearnCtrl != -1) + { + int type = MusECore::midiControllerType(MusEGlobal::midiLearnCtrl); + if(type < controlTypeComboBox->count() && type != controlTypeComboBox->currentIndex()) + { + controlTypeComboBox->blockSignals(true); + controlTypeComboBox->setCurrentIndex(type); + controlTypeComboBox->blockSignals(false); + } + + int hv = (MusEGlobal::midiLearnCtrl >> 8) & 0xff; + int lv = MusEGlobal::midiLearnCtrl & 0xff; + if(type == MusECore::MidiController::Program || type == MusECore::MidiController::Pitch) + { + ctrlHiSpinBox->setEnabled(false); + ctrlLoSpinBox->setEnabled(false); + ctrlHiSpinBox->blockSignals(true); + ctrlLoSpinBox->blockSignals(true); + ctrlHiSpinBox->setValue(0); + ctrlLoSpinBox->setValue(0); + ctrlHiSpinBox->blockSignals(false); + ctrlLoSpinBox->blockSignals(false); + } + else if(type == MusECore::MidiController::Controller7) + { + ctrlHiSpinBox->setEnabled(false); + ctrlLoSpinBox->setEnabled(true); + + ctrlHiSpinBox->blockSignals(true); + ctrlHiSpinBox->setValue(0); + ctrlHiSpinBox->blockSignals(false); + + if(lv != ctrlLoSpinBox->value()) + { + ctrlLoSpinBox->blockSignals(true); + ctrlLoSpinBox->setValue(lv); + ctrlLoSpinBox->blockSignals(false); + } + } + else + { + ctrlHiSpinBox->setEnabled(true); + ctrlLoSpinBox->setEnabled(true); + if(hv != ctrlHiSpinBox->value()) + { + ctrlHiSpinBox->blockSignals(true); + ctrlHiSpinBox->setValue(hv); + ctrlHiSpinBox->blockSignals(false); + } + if(lv != ctrlLoSpinBox->value()) + { + ctrlLoSpinBox->blockSignals(true); + ctrlLoSpinBox->setValue(lv); + ctrlLoSpinBox->blockSignals(false); + } + } + + _ctrl = MusECore::midiCtrlTerms2Number(type, (ctrlHiSpinBox->value() << 8) + ctrlLoSpinBox->value()); + } + } +} + +void MidiAudioControl::learnChanged(bool v) +{ + _is_learning = v; + if(_is_learning) + MusEGlobal::audio->msgStartMidiLearn(); // Resets the learn values to -1. +} + +void MidiAudioControl::resetLearn() +{ + _is_learning = false; + learnPushButton->blockSignals(true); + learnPushButton->setChecked(false); + learnPushButton->blockSignals(false); + MusEGlobal::audio->msgStartMidiLearn(); // Resets the learn values to -1. +} + +void MidiAudioControl::portChanged(int idx) +{ + if(idx == -1) + return; + int port_num = portComboBox->itemData(idx).toInt(); + if(port_num < 0 || port_num >= MIDI_PORTS) + return; + + _port = port_num; + resetLearn(); +} + +void MidiAudioControl::chanChanged() +{ + _chan = channelSpinBox->value() - 1; + resetLearn(); +} + +void MidiAudioControl::updateCtrlBoxes() +{ + int idx = controlTypeComboBox->currentIndex(); + if(idx == -1) + return; + + if(idx == MusECore::MidiController::Program || idx == MusECore::MidiController::Pitch) + { + ctrlHiSpinBox->setEnabled(false); + ctrlLoSpinBox->setEnabled(false); + ctrlHiSpinBox->blockSignals(true); + ctrlLoSpinBox->blockSignals(true); + ctrlHiSpinBox->setValue(0); + ctrlLoSpinBox->setValue(0); + ctrlHiSpinBox->blockSignals(false); + ctrlLoSpinBox->blockSignals(false); + } + else if(idx == MusECore::MidiController::Controller7) + { + ctrlHiSpinBox->setEnabled(false); + ctrlLoSpinBox->setEnabled(true); + + ctrlHiSpinBox->blockSignals(true); + ctrlHiSpinBox->setValue(0); + ctrlHiSpinBox->blockSignals(false); + } + else + { + ctrlHiSpinBox->setEnabled(true); + ctrlLoSpinBox->setEnabled(true); + } +} + +void MidiAudioControl::ctrlTypeChanged(int idx) +{ + if(idx == -1) + return; + + updateCtrlBoxes(); + + _ctrl = (ctrlHiSpinBox->value() << 8) + ctrlLoSpinBox->value(); + _ctrl = MusECore::midiCtrlTerms2Number(idx, _ctrl); + + resetLearn(); +} + +void MidiAudioControl::ctrlHChanged() +{ + if(controlTypeComboBox->currentIndex() == -1) + return; + _ctrl = (ctrlHiSpinBox->value() << 8) + ctrlLoSpinBox->value(); + _ctrl = MusECore::midiCtrlTerms2Number(controlTypeComboBox->currentIndex(), _ctrl); + + resetLearn(); +} + +void MidiAudioControl::ctrlLChanged() +{ + if(controlTypeComboBox->currentIndex() == -1) + return; + _ctrl = (ctrlHiSpinBox->value() << 8) + ctrlLoSpinBox->value(); + _ctrl = MusECore::midiCtrlTerms2Number(controlTypeComboBox->currentIndex(), _ctrl); + + resetLearn(); +} + +void MidiAudioControl::configChanged() +{ + update(); +} + +void MidiAudioControl::update() +{ + portComboBox->blockSignals(true); + portComboBox->clear(); + + int item_idx = 0; + for (int i = 0; i < MIDI_PORTS; ++i) { + MusECore::MidiDevice* md = MusEGlobal::midiPorts[i].device(); + if(!md) // In the case of this combo box, don't bother listing empty ports. + continue; + //if(!(md->rwFlags() & 1 || md->isSynti()) && (i != outPort)) + if(!(md->rwFlags() & 2) && (i != _port)) // Only readable ports, or current one. + continue; + QString name; + name.sprintf("%d:%s", i+1, MusEGlobal::midiPorts[i].portname().toLatin1().constData()); + portComboBox->insertItem(item_idx, name, i); + if(_port == -1) + _port = i; // Initialize + if(i == _port) + portComboBox->setCurrentIndex(item_idx); + item_idx++; + } + portComboBox->blockSignals(false); + + channelSpinBox->blockSignals(true); + channelSpinBox->setValue(_chan + 1); + channelSpinBox->blockSignals(false); + + int type = MusECore::midiControllerType(_ctrl); + if(type < controlTypeComboBox->count()) + { + controlTypeComboBox->blockSignals(true); + controlTypeComboBox->setCurrentIndex(type); + controlTypeComboBox->blockSignals(false); + } + + int hv = (_ctrl >> 8) & 0xff; + int lv = _ctrl & 0xff; + if(type == MusECore::MidiController::Program || type == MusECore::MidiController::Pitch) + { + ctrlHiSpinBox->setEnabled(false); + ctrlLoSpinBox->setEnabled(false); + ctrlHiSpinBox->blockSignals(true); + ctrlLoSpinBox->blockSignals(true); + ctrlHiSpinBox->setValue(0); + ctrlLoSpinBox->setValue(0); + ctrlHiSpinBox->blockSignals(false); + ctrlLoSpinBox->blockSignals(false); + } + else if(type == MusECore::MidiController::Controller7) + { + ctrlHiSpinBox->setEnabled(false); + ctrlLoSpinBox->setEnabled(true); + + ctrlHiSpinBox->blockSignals(true); + ctrlHiSpinBox->setValue(0); + ctrlHiSpinBox->blockSignals(false); + + if(lv != ctrlLoSpinBox->value()) + { + ctrlLoSpinBox->blockSignals(true); + ctrlLoSpinBox->setValue(lv); + ctrlLoSpinBox->blockSignals(false); + } + } + else + { + ctrlHiSpinBox->setEnabled(true); + ctrlLoSpinBox->setEnabled(true); + if(hv != ctrlHiSpinBox->value()) + { + ctrlHiSpinBox->blockSignals(true); + ctrlHiSpinBox->setValue(hv); + ctrlHiSpinBox->blockSignals(false); + } + if(lv != ctrlLoSpinBox->value()) + { + ctrlLoSpinBox->blockSignals(true); + ctrlLoSpinBox->setValue(lv); + ctrlLoSpinBox->blockSignals(false); + } + } +} + +} diff --git a/muse2/muse/widgets/midi_audio_control.h b/muse2/muse/widgets/midi_audio_control.h new file mode 100644 index 00000000..887de942 --- /dev/null +++ b/muse2/muse/widgets/midi_audio_control.h @@ -0,0 +1,60 @@ +//========================================================= +// MusE +// Linux Music Editor +// +// midi_audio_control.h +// Copyright (C) 2012 by Tim E. Real (terminator356 at users.sourceforge.net) +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; version 2 of +// the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +//========================================================= +#ifndef MIDI_AUDIO_CONTROL_H +#define MIDI_AUDIO_CONTROL_H + +#include "ui_midi_audio_control_base.h" + +namespace MusEGui { + +class MidiAudioControl : public QDialog, public Ui::MidiAudioControlBase +{ + Q_OBJECT + +private: + int _port, _chan, _ctrl; + bool _is_learning; + void update(); + void resetLearn(); + void updateCtrlBoxes(); + +private slots: + void heartbeat(); + void learnChanged(bool); + void portChanged(int); + void chanChanged(); + void ctrlTypeChanged(int); + void ctrlHChanged(); + void ctrlLChanged(); + void configChanged(); + +public: + MidiAudioControl(int port = -1, int chan = 0, int ctrl = 0, QWidget* parent = 0); + int port() const { return _port; } + int chan() const { return _chan; } + int ctrl() const { return _ctrl; } +}; + +} + +#endif // MIDI_AUDIO_CONTROL_H diff --git a/muse2/muse/widgets/midi_audio_control_base.ui b/muse2/muse/widgets/midi_audio_control_base.ui new file mode 100644 index 00000000..2e341121 --- /dev/null +++ b/muse2/muse/widgets/midi_audio_control_base.ui @@ -0,0 +1,310 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MidiAudioControlBase</class> + <widget class="QDialog" name="MidiAudioControlBase"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>341</width> + <height>148</height> + </rect> + </property> + <property name="windowTitle"> + <string>Midi control</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Port:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="portComboBox"/> + </item> + <item> + <widget class="QLabel" name="label_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Channel:</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="channelSpinBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>16</number> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Control type:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="controlTypeComboBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <item> + <property name="text"> + <string>Control7</string> + </property> + </item> + <item> + <property name="text"> + <string>Control14</string> + </property> + </item> + <item> + <property name="text"> + <string>RPN</string> + </property> + </item> + <item> + <property name="text"> + <string>NRPN</string> + </property> + </item> + <item> + <property name="text"> + <string>RPN14</string> + </property> + </item> + <item> + <property name="text"> + <string>NRPN14</string> + </property> + </item> + <item> + <property name="text"> + <string>Pitch</string> + </property> + </item> + <item> + <property name="text"> + <string>Program</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QLabel" name="label_3"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_4"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Hi:</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="ctrlHiSpinBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimum"> + <number>0</number> + </property> + <property name="maximum"> + <number>255</number> + </property> + <property name="value"> + <number>0</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_5"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Lo:</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="ctrlLoSpinBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimum"> + <number>0</number> + </property> + <property name="maximum"> + <number>255</number> + </property> + <property name="value"> + <number>0</number> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QPushButton" name="learnPushButton"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Learn</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>18</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>MidiAudioControlBase</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>MidiAudioControlBase</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/muse2/muse/widgets/midisync.ui b/muse2/muse/widgets/midisync.ui index a7464aaf..942a4e59 100644 --- a/muse2/muse/widgets/midisync.ui +++ b/muse2/muse/widgets/midisync.ui @@ -10,7 +10,7 @@ configuration dialog</comment> <x>0</x> <y>0</y> <width>655</width> - <height>419</height> + <height>445</height> </rect> </property> <property name="windowTitle"> @@ -337,6 +337,100 @@ Enabled inputs in the list will <item row="3" column="0"> <layout class="QHBoxLayout"> <item> + <widget class="QComboBox" name="syncRecFilterPreset"> + <property name="toolTip"> + <string>Averaging applied to recorded external tempo changes.</string> + </property> + <property name="whatsThis"> + <string>External midi clock can be very jittery. +Tempo is derived from it and recorded. +It is usually desirable to average it and + limit the number of recorded changes. + +Tiny: 2 section 4/4 = 8 stages. +1/8T note averaging, may produce jitter. + +Small: 3 section 12/8/4 = 24 stages. +1/4 note averaging, may still produce jitter. + +Medium: 3 section 28/12/8 = 48 stages. +1/2 note averaging. Less jitter. + +Large: 4 section 48/48/48/48 = 192 stages. +Use this if the song has only one tempo. +Very low quantization values can be used. + +Large pre-detect: 4 section 8/48/48/48 = 152 + stages + first stage large step pre-detector. +Use this if you expect sudden large tempo steps. + +None: Use only if high accuracy is needed for + audio alignment on playback. Caution: Records + thousands of tempo changes per minute. MusE + may slow and the song file will be large.</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="syncRecFilterLabel"> + <property name="text"> + <string>Tempo record averaging</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + <property name="wordWrap"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + <item row="4" column="0"> + <layout class="QHBoxLayout"> + <item> + <widget class="QDoubleSpinBox" name="syncRecTempoValQuant"> + <property name="toolTip"> + <string/> + </property> + <property name="whatsThis"> + <string/> + </property> + <property name="suffix"> + <string>bpm</string> + </property> + <property name="minimum"> + <double>0.000000000000000</double> + </property> + <property name="maximum"> + <double>100.000000000000000</double> + </property> + <property name="singleStep"> + <double>0.010000000000000</double> + </property> + <property name="value"> + <double>1.000000000000000</double> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="syncRecTempoValQuantLabel"> + <property name="text"> + <string>Tempo record quantization</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + <property name="wordWrap"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + <item row="5" column="0"> + <layout class="QHBoxLayout"> + <item> <widget class="QSpinBox" name="syncDelaySpinBox"> <property name="toolTip"> <string>Send start to first clock delay</string> @@ -379,7 +473,7 @@ Enabled inputs in the list will </item> </layout> </item> - <item row="4" column="0"> + <item row="6" column="0"> <widget class="QTreeWidget" name="devicesListView"> <column> <property name="text"> @@ -388,18 +482,18 @@ Enabled inputs in the list will </column> </widget> </item> - <item row="5" column="0"> - <widget class="QLabel" name="toBeDoneLabel"> + <item row="7" column="0"> + <widget class="QLabel" name="toBeDoneLabel"> <property name="text"> - <string>Note: Sync delay and MTC sync currently not fully implemented</string> + <string>Note: Sync delay and MTC sync currently not fully implemented</string> </property> <property name="alignment"> - <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> </property> <property name="wordWrap"> - <bool>false</bool> + <bool>false</bool> </property> - </widget> + </widget> </item> </layout> </widget> diff --git a/muse2/muse/widgets/midisyncimpl.cpp b/muse2/muse/widgets/midisyncimpl.cpp index 904e8759..e286ca74 100644 --- a/muse2/muse/widgets/midisyncimpl.cpp +++ b/muse2/muse/widgets/midisyncimpl.cpp @@ -28,6 +28,7 @@ #include <QTimer> #include <QTreeWidgetItem> #include <QHeaderView> +#include <QComboBox> #include "app.h" #include "song.h" @@ -137,32 +138,12 @@ void MidiSyncConfig::addDevice(QTreeWidgetItem *item, QTreeWidget *tree) tree->addTopLevelItem(item); } -/* -//--------------------------------------------------------- -// MidiSyncLViewItem -// setDevice -//--------------------------------------------------------- - -void MidiSyncLViewItem::setDevice(MusECore::MidiDevice* d) -{ - _device = d; - if(_device) - _syncInfo.copyParams(_device->syncInfo()); -} -*/ - -//--------------------------------------------------------- -// MidiSyncLViewItem -// setPort -//--------------------------------------------------------- - void MidiSyncLViewItem::setPort(int port) { _port = port; if(_port < 0 || port > MIDI_PORTS) return; - //_syncInfo.copyParams(MusEGlobal::midiPorts[port].syncInfo()); copyFromSyncInfo(MusEGlobal::midiPorts[port].syncInfo()); } @@ -221,41 +202,6 @@ MidiSyncConfig::MidiSyncConfig(QWidget* parent) _dirty = false; applyButton->setEnabled(false); - //inHeartBeat = true; - - //for(int i = 0; i < MIDI_PORTS; ++i) - // tmpMidiSyncPorts[i] = midiSyncPorts[i]; - - //bool ext = MusEGlobal::extSyncFlag.value(); - //syncMode->setButton(int(ext)); - //syncChanged(ext); -// extSyncCheckbox->setChecked(MusEGlobal::extSyncFlag.value()); - -// dstDevId->setValue(txDeviceId); -// srcDevId->setValue(rxDeviceId); -// srcSyncPort->setValue(rxSyncPort + 1); -// dstSyncPort->setValue(txSyncPort + 1); - -// mtcSync->setChecked(genMTCSync); -// mcSync->setChecked(genMCSync); -// midiMachineControl->setChecked(genMMC); - -// acceptMTCCheckbox->setChecked(acceptMTC); - //acceptMTCCheckbox->setChecked(false); -// acceptMCCheckbox->setChecked(acceptMC); -// acceptMMCCheckbox->setChecked(acceptMMC); - -// mtcSyncType->setCurrentItem(MusEGlobal::mtcType); - -// mtcOffH->setValue(MusEGlobal::mtcOffset.h()); -// mtcOffM->setValue(MusEGlobal::mtcOffset.m()); -// mtcOffS->setValue(MusEGlobal::mtcOffset.s()); -// mtcOffF->setValue(MusEGlobal::mtcOffset.f()); -// mtcOffSf->setValue(MusEGlobal::mtcOffset.sf()); - - - - devicesListView->setAllColumnsShowFocus(true); QStringList columnnames; columnnames << tr("Port") @@ -284,9 +230,11 @@ MidiSyncConfig::MidiSyncConfig(QWidget* parent) setToolTips(devicesListView->headerItem()); devicesListView->setFocusPolicy(Qt::NoFocus); - //MSyncHeaderTip::add(devicesListView->header(), QString("Midi sync ports")); - -// updateSyncInfoLV(); + syncRecFilterPreset->addItem(tr("None"), MusECore::MidiSyncInfo::NONE); + syncRecFilterPreset->addItem(tr("Tiny"), MusECore::MidiSyncInfo::TINY); + syncRecFilterPreset->addItem(tr("Small"), MusECore::MidiSyncInfo::SMALL); + syncRecFilterPreset->addItem(tr("Large"), MusECore::MidiSyncInfo::LARGE); + syncRecFilterPreset->addItem(tr("Large with pre-detect"), MusECore::MidiSyncInfo::LARGE_WITH_PRE_DETECT); songChanged(-1); @@ -308,14 +256,14 @@ MidiSyncConfig::MidiSyncConfig(QWidget* parent) connect(mtcSyncType, SIGNAL(activated(int)), SLOT(syncChanged())); connect(useJackTransportCheckbox, SIGNAL(clicked()), SLOT(syncChanged())); connect(jackTransportMasterCheckbox, SIGNAL(clicked()), SLOT(syncChanged())); + connect(syncRecFilterPreset, SIGNAL(currentIndexChanged(int)), SLOT(syncChanged())); + connect(syncRecTempoValQuant, SIGNAL(valueChanged(double)), SLOT(syncChanged())); connect(&MusEGlobal::extSyncFlag, SIGNAL(valueChanged(bool)), SLOT(extSyncChanged(bool))); connect(syncDelaySpinBox, SIGNAL(valueChanged(int)), SLOT(syncChanged())); // Done in show(). //connect(MusEGlobal::song, SIGNAL(songChanged(int)), SLOT(songChanged(int))); //connect(MusEGlobal::heartBeatTimer, SIGNAL(timeout()), SLOT(heartBeat())); - - //inHeartBeat = false; } MidiSyncConfig::~MidiSyncConfig() @@ -356,6 +304,17 @@ void MidiSyncConfig::songChanged(int flags) jackTransportMasterCheckbox->blockSignals(false); useJackTransportCheckbox->blockSignals(false); extSyncCheckbox->blockSignals(false); + + int fp_idx = syncRecFilterPreset->findData(MusEGlobal::syncRecFilterPreset); + if(fp_idx != -1) + { + syncRecFilterPreset->blockSignals(true); + syncRecFilterPreset->setCurrentIndex(fp_idx); + syncRecFilterPreset->blockSignals(false); + } + syncRecTempoValQuant->blockSignals(true); + syncRecTempoValQuant->setValue(MusEGlobal::syncRecTempoValQuant); + syncRecTempoValQuant->blockSignals(false); mtcSyncType->setCurrentIndex(MusEGlobal::mtcType); @@ -400,9 +359,6 @@ void MidiSyncConfig::heartBeat() { if(!lvi->_curDet) { - // Added by Tim. p3.3.6 - //printf("MidiSyncConfig::heartBeat setting current red icon\n"); - lvi->_curDet = true; lvi->_inDet = false; lvi->setIcon(DEVCOL_IN, QIcon( *record1_Icon)); @@ -411,9 +367,6 @@ void MidiSyncConfig::heartBeat() else if(!lvi->_inDet) { - // Added by Tim. p3.3.6 - //printf("MidiSyncConfig::heartBeat setting non-current green icon\n"); - lvi->_inDet = true; lvi->_curDet = false; lvi->setIcon(DEVCOL_IN, QIcon( *dotIcon)); @@ -423,9 +376,6 @@ void MidiSyncConfig::heartBeat() { if(lvi->_curDet || lvi->_inDet) { - // Added by Tim. p3.3.6 - //printf("MidiSyncConfig::heartBeat setting off icon\n"); - lvi->_curDet = false; lvi->_inDet = false; lvi->setIcon(DEVCOL_IN, QIcon( *dothIcon)); @@ -437,9 +387,6 @@ void MidiSyncConfig::heartBeat() { if(!lvi->_tickDet) { - // Added by Tim. p3.3.6 - //printf("MidiSyncConfig::heartBeat setting tick on icon\n"); - lvi->_tickDet = true; lvi->setIcon(DEVCOL_TICKIN, QIcon( *dotIcon)); } @@ -448,9 +395,6 @@ void MidiSyncConfig::heartBeat() { if(lvi->_tickDet) { - // Added by Tim. p3.3.6 - //printf("MidiSyncConfig::heartBeat setting tick off icon\n"); - lvi->_tickDet = false; lvi->setIcon(DEVCOL_TICKIN, QIcon( *dothIcon)); } @@ -461,9 +405,6 @@ void MidiSyncConfig::heartBeat() { if(!lvi->_MRTDet) { - // Added by Tim. p3.3.6 - //printf("MidiSyncConfig::heartBeat setting MRT on icon\n"); - lvi->_MRTDet = true; lvi->setIcon(DEVCOL_MRTIN, QIcon( *dotIcon)); } @@ -472,9 +413,6 @@ void MidiSyncConfig::heartBeat() { if(lvi->_MRTDet) { - // Added by Tim. p3.3.6 - //printf("MidiSyncConfig::heartBeat setting MRT off icon\n"); - lvi->_MRTDet = false; lvi->setIcon(DEVCOL_MRTIN, QIcon( *dothIcon)); } @@ -487,9 +425,6 @@ void MidiSyncConfig::heartBeat() { if(!lvi->_MMCDet) { - // Added by Tim. p3.3.6 - //printf("MidiSyncConfig::heartBeat setting MMC on icon\n"); - lvi->_MMCDet = true; lvi->setIcon(DEVCOL_MMCIN, QIcon( *dotIcon)); } @@ -521,9 +456,6 @@ void MidiSyncConfig::heartBeat() { if(lvi->_MMCDet) { - // Added by Tim. p3.3.6 - //printf("MidiSyncConfig::heartBeat setting MMC off icon\n"); - lvi->_MMCDet = false; lvi->setIcon(DEVCOL_MMCIN, QIcon( *dothIcon)); } @@ -535,9 +467,6 @@ void MidiSyncConfig::heartBeat() { if(!lvi->_curMTCDet) { - // Added by Tim. p3.3.6 - //printf("MidiSyncConfig::heartBeat setting current red icon\n"); - lvi->_curMTCDet = true; lvi->_MTCDet = false; lvi->setIcon(DEVCOL_MTCIN, QIcon( *record1_Icon)); @@ -546,9 +475,6 @@ void MidiSyncConfig::heartBeat() else if(!lvi->_MTCDet) { - // Added by Tim. p3.3.6 - //printf("MidiSyncConfig::heartBeat setting MTC on icon\n"); - lvi->_MTCDet = true; lvi->_curMTCDet = false; lvi->setIcon(DEVCOL_MTCIN, QIcon( *dotIcon)); @@ -581,9 +507,6 @@ void MidiSyncConfig::heartBeat() { if(lvi->_curMTCDet || lvi->_MTCDet) { - // Added by Tim. p3.3.6 - //printf("MidiSyncConfig::heartBeat setting MTC off icon\n"); - lvi->_MTCDet = false; lvi->_curMTCDet = false; lvi->setIcon(DEVCOL_MTCIN, QIcon( *dothIcon)); @@ -610,13 +533,6 @@ void MidiSyncConfig::heartBeat() void MidiSyncConfig::syncChanged() { setDirty(); - - //MusEGlobal::jackTransportMasterCheckbox->setEnabled(MusEGlobal::useJackTransport); - - //acceptMTCCheckbox->setEnabled(val); -// acceptMTCCheckbox->setEnabled(false); -// acceptMCCheckbox->setEnabled(val); -// acceptMMCCheckbox->setEnabled(val); } //--------------------------------------------------------- @@ -702,24 +618,14 @@ void MidiSyncConfig::closeEvent(QCloseEvent* e) void MidiSyncConfig::apply() { -// txDeviceId = dstDevId->value(); -// rxDeviceId = srcDevId->value(); -// rxSyncPort = srcSyncPort->value() - 1; -// txSyncPort = dstSyncPort->value() - 1; - -// genMTCSync = mtcSync->isChecked(); -// genMCSync = mcSync->isChecked(); -// genMMC = midiMachineControl->isChecked(); + // Protect all structures. + if(MusEGlobal::audio && MusEGlobal::audio->isRunning()) + MusEGlobal::audio->msgIdle(true); MusEGlobal::syncSendFirstClockDelay = syncDelaySpinBox->value(); MusEGlobal::mtcType = mtcSyncType->currentIndex(); - //MusEGlobal::extSyncFlag.setValue(syncMode->id(syncMode->selected())); - //MusEGlobal::extSyncFlag.blockSignals(true); MusEGlobal::extSyncFlag.setValue(extSyncCheckbox->isChecked()); -// if(MusEGlobal::extSyncFlag.value()) -// MusEGlobal::song->setMasterFlag(false); - //MusEGlobal::extSyncFlag.blockSignals(false); MusEGlobal::useJackTransport.setValue(useJackTransportCheckbox->isChecked()); // if(MusEGlobal::useJackTransport) MusEGlobal::jackTransportMaster = jackTransportMasterCheckbox->isChecked(); @@ -729,33 +635,38 @@ void MidiSyncConfig::apply() if(MusEGlobal::audioDevice) MusEGlobal::audioDevice->setMaster(MusEGlobal::jackTransportMaster); + if(syncRecFilterPreset->currentIndex() != -1) + { + int fp_idx = syncRecFilterPreset->itemData(syncRecFilterPreset->currentIndex()).toInt(); + if(fp_idx >= 0 && fp_idx < MusECore::MidiSyncInfo::TYPE_END) + { + MusEGlobal::syncRecFilterPreset = MusECore::MidiSyncInfo::SyncRecFilterPresetType(fp_idx); + if(MusEGlobal::midiSeq) + MusEGlobal::midiSeq->setSyncRecFilterPreset(MusEGlobal::syncRecFilterPreset); + } + } + MusEGlobal::syncRecTempoValQuant = syncRecTempoValQuant->value(); + if(MusEGlobal::midiSeq) + MusEGlobal::midiSeq->setRecTempoValQuant(MusEGlobal::syncRecTempoValQuant); + MusEGlobal::mtcOffset.setH(mtcOffH->value()); MusEGlobal::mtcOffset.setM(mtcOffM->value()); MusEGlobal::mtcOffset.setS(mtcOffS->value()); MusEGlobal::mtcOffset.setF(mtcOffF->value()); MusEGlobal::mtcOffset.setSf(mtcOffSf->value()); -// acceptMC = acceptMCCheckbox->isChecked(); -// acceptMMC = acceptMMCCheckbox->isChecked(); -// acceptMTC = acceptMTCCheckbox->isChecked(); - - - //MidiSyncLViewItem* lvi = (MidiSyncLViewItem*)devicesListView->firstChild(); - //while(lvi) for (int i = MIDI_PORTS-1; i >= 0; --i) { MidiSyncLViewItem* lvi = (MidiSyncLViewItem*)devicesListView->topLevelItem(i); - //MusECore::MidiDevice* dev = lvi->device(); - // Does the device really exist? - //if(midiDevices.find(dev) != midiDevices.end()) - // dev->syncInfo().copyParams(lvi->syncInfo()); int port = lvi->port(); if(port >= 0 && port < MIDI_PORTS) - //MusEGlobal::midiPorts[port].syncInfo().copyParams(lvi->syncInfo()); lvi->copyToSyncInfo(MusEGlobal::midiPorts[port].syncInfo()); } + if(MusEGlobal::audio && MusEGlobal::audio->isRunning()) + MusEGlobal::audio->msgIdle(false); + //muse->changeConfig(true); // save settings _dirty = false; @@ -777,7 +688,6 @@ void MidiSyncConfig::updateSyncInfoLV() { MusECore::MidiPort* port = &MusEGlobal::midiPorts[i]; MusECore::MidiDevice* dev = port->device(); - // p3.3.31 // Don't show if it is a synthesizer device. // Hmm, some synths might support transport commands or even sync? // If anything, the DSSI or VST synths just might... @@ -791,9 +701,6 @@ void MidiSyncConfig::updateSyncInfoLV() s.setNum(i+1); MidiSyncLViewItem* lvi = new MidiSyncLViewItem(devicesListView); lvi->setPort(i); // setPort will copy parameters. - //MusECore::MidiSyncInfo& si = lvi->syncInfo(); - //si.copyParams(port->syncInfo()); - //lvi.copyFromSyncInfo(port->syncInfo()); MusECore::MidiSyncInfo& portsi = port->syncInfo(); lvi->setText(DEVCOL_NO, s); @@ -925,11 +832,6 @@ void MidiSyncConfig::updateSyncInfoLV() //lvi->setText(DEVCOL_MTCTYPE, "--"); } - //lvi->setText(DEVCOL_RID, QString().setNum(si.idIn()) ); - //lvi->setRenameEnabled(DEVCOL_RID, true); - //lvi->setIcon(DEVCOL_RCLK, QIcon( si.MCIn() ? *dotIcon : *dothIcon)); - //lvi->setIcon(DEVCOL_RMMC, QIcon( si.MMCIn() ? *dotIcon : *dothIcon)); - //lvi->setIcon(DEVCOL_RMTC, QIcon( si.MTCIn() ? *dotIcon : *dothIcon)); lvi->setText(DEVCOL_RID, QString().setNum(lvi->_idIn) ); lvi->setIcon(DEVCOL_RCLK, QIcon( lvi->_recMC ? *dotIcon : *dothIcon)); lvi->setIcon(DEVCOL_RMRT, QIcon( lvi->_recMRT ? *dotIcon : *dothIcon)); @@ -937,11 +839,6 @@ void MidiSyncConfig::updateSyncInfoLV() lvi->setIcon(DEVCOL_RMTC, QIcon( lvi->_recMTC ? *dotIcon : *dothIcon)); lvi->setIcon(DEVCOL_RREWSTART, QIcon( lvi->_recRewOnStart ? *dotIcon : *dothIcon)); - //lvi->setText(DEVCOL_TID, QString().setNum(si.idOut()) ); - //lvi->setRenameEnabled(DEVCOL_TID, true); - //lvi->setIcon(DEVCOL_TCLK, QIcon( si.MCOut() ? *dotIcon : *dothIcon)); - //lvi->setIcon(DEVCOL_TMMC, QIcon( si.MMCOut() ? *dotIcon : *dothIcon)); - //lvi->setIcon(DEVCOL_TMTC, QIcon( si.MTCOut() ? *dotIcon : *dothIcon)); lvi->setText(DEVCOL_TID, QString().setNum(lvi->_idOut) ); lvi->setIcon(DEVCOL_TCLK, QIcon(lvi->_sendMC ? *dotIcon : *dothIcon)); lvi->setIcon(DEVCOL_TMRT, QIcon(lvi->_sendMRT ? *dotIcon : *dothIcon)); @@ -988,38 +885,6 @@ void MidiSyncConfig::updateSyncInfoLV() devicesListView->header()->setResizeMode(DEVCOL_TMRT, QHeaderView::Fixed); devicesListView->header()->setResizeMode(DEVCOL_TMMC, QHeaderView::Fixed); - - /* - for(MusECore::iMidiDevice id = midiDevices.begin(); id != midiDevices.end(); ++id) - { - MusECore::MidiDevice* dev = *id; - - //MusECore::MidiPort* port = &MusEGlobal::midiPorts[i]; - //MusECore::MidiDevice* dev = port->device(); - MidiSyncLViewItem* lvi = new MidiSyncLViewItem(devicesListView); - //lvi->setPort(i); - // setDevice will copy parameters. - lvi->setDevice(dev); - MusECore::MidiSyncInfo& si = lvi->syncInfo(); - //si.copyParams(dev->syncInfo()); - - lvi->setText(DEVCOL_NAME, dev->name()); - - lvi->setIcon(DEVCOL_IN, QIcon( si.MCSyncDetect() ? *dotIcon : *dothIcon)); - - lvi->setText(DEVCOL_RID, QString().setNum(si.idIn()) ); - lvi->setIcon(DEVCOL_RCLK, QIcon( si.MCIn() ? *dotIcon : *dothIcon)); - lvi->setIcon(DEVCOL_RMMC, QIcon( si.MMCIn() ? *dotIcon : *dothIcon)); - lvi->setIcon(DEVCOL_RMTC, QIcon( si.MTCIn() ? *dotIcon : *dothIcon)); - - lvi->setText(DEVCOL_TID, QString().setNum(si.idOut()) ); - lvi->setIcon(DEVCOL_TCLK, QIcon( si.MCOut() ? *dotIcon : *dothIcon)); - lvi->setIcon(DEVCOL_TMMC, QIcon( si.MMCOut() ? *dotIcon : *dothIcon)); - lvi->setIcon(DEVCOL_TMTC, QIcon( si.MTCOut() ? *dotIcon : *dothIcon)); - - devicesListView->insertItem(lvi); - } - */ } @@ -1027,7 +892,6 @@ void MidiSyncConfig::updateSyncInfoLV() // dlvClicked //--------------------------------------------------------- -//void MidiSyncConfig::dlvClicked(QListViewItem* item, const QPoint&, int col) void MidiSyncConfig::dlvClicked(QTreeWidgetItem* item, int col) { if (item == 0) @@ -1042,14 +906,6 @@ void MidiSyncConfig::dlvClicked(QTreeWidgetItem* item, int col) //if(midiDevices.find(dev) == midiDevices.end()) // return; - //int n; - //MusECore::MidiPort* port = &MusEGlobal::midiPorts[no]; - //MusECore::MidiDevice* dev = port->device(); - //int rwFlags = dev ? dev->rwFlags() : 0; - //int openFlags = dev ? dev->openFlags() : 0; - //MusECore::MidiSyncInfo& si = lvi->syncInfo(); - //MusECore::MidiSyncInfo& portsi = MusEGlobal::midiPorts[no].syncInfo(); - switch (col) { case DEVCOL_NO: @@ -1060,8 +916,6 @@ void MidiSyncConfig::dlvClicked(QTreeWidgetItem* item, int col) // If this is not the current midi sync in port, and sync in from this port is enabled, // and sync is in fact detected on this port, allow the user to force this port to now be the // current sync in port. - //if(no != MusEGlobal::curMidiSyncInPort && si.MCIn() && MusEGlobal::midiPorts[no].syncInfo().MCSyncDetect()) - //if(no != MusEGlobal::curMidiSyncInPort && lvi->_recMC && MusEGlobal::midiPorts[no].syncInfo().MCSyncDetect()) if(no != MusEGlobal::curMidiSyncInPort) { if(lvi->_recMC && MusEGlobal::midiPorts[no].syncInfo().MCSyncDetect()) @@ -1084,8 +938,6 @@ void MidiSyncConfig::dlvClicked(QTreeWidgetItem* item, int col) // If this is not the current midi sync in port, and sync in from this port is enabled, // and sync is in fact detected on this port, allow the user to force this port to now be the // current sync in port. - //if(no != MusEGlobal::curMidiSyncInPort && si.MTCIn() && MusEGlobal::midiPorts[no].syncInfo().MTCDetect()) - //if(no != MusEGlobal::curMidiSyncInPort && lvi->_recMTC && MusEGlobal::midiPorts[no].syncInfo().MTCDetect()) if(no != MusEGlobal::curMidiSyncInPort) { if(lvi->_recMTC && MusEGlobal::midiPorts[no].syncInfo().MTCDetect()) @@ -1105,8 +957,6 @@ void MidiSyncConfig::dlvClicked(QTreeWidgetItem* item, int col) case DEVCOL_RID: break; case DEVCOL_RCLK: - //si.setMCIn(si.MCIn() ? false : true); - //lvi->setIcon(DEVCOL_RCLK, QIcon( si.MCIn() ? *dotIcon : *dothIcon)); lvi->_recMC = (lvi->_recMC ? false : true); lvi->setIcon(DEVCOL_RCLK, QIcon( lvi->_recMC ? *dotIcon : *dothIcon)); setDirty(); @@ -1117,15 +967,11 @@ void MidiSyncConfig::dlvClicked(QTreeWidgetItem* item, int col) setDirty(); break; case DEVCOL_RMMC: - //si.setMMCIn(si.MMCIn() ? false : true); - //lvi->setIcon(DEVCOL_RMMC, QIcon( si.MMCIn() ? *dotIcon : *dothIcon)); lvi->_recMMC = (lvi->_recMMC ? false : true); lvi->setIcon(DEVCOL_RMMC, QIcon( lvi->_recMMC ? *dotIcon : *dothIcon)); setDirty(); break; case DEVCOL_RMTC: - //si.setMTCIn(si.MTCIn() ? false : true); - //lvi->setIcon(DEVCOL_RMTC, QIcon( si.MTCIn() ? *dotIcon : *dothIcon)); lvi->_recMTC = (lvi->_recMTC ? false : true); lvi->setIcon(DEVCOL_RMTC, QIcon( lvi->_recMTC ? *dotIcon : *dothIcon)); setDirty(); @@ -1138,8 +984,6 @@ void MidiSyncConfig::dlvClicked(QTreeWidgetItem* item, int col) case DEVCOL_TID: break; case DEVCOL_TCLK: - //si.setMCOut(si.MCOut() ? false : true); - //lvi->setIcon(DEVCOL_TCLK, QIcon( si.MCOut() ? *dotIcon : *dothIcon)); lvi->_sendMC = (lvi->_sendMC ? false : true); lvi->setIcon(DEVCOL_TCLK, QIcon( lvi->_sendMC ? *dotIcon : *dothIcon)); setDirty(); @@ -1150,15 +994,11 @@ void MidiSyncConfig::dlvClicked(QTreeWidgetItem* item, int col) setDirty(); break; case DEVCOL_TMMC: - //si.setMMCOut(si.MMCOut() ? false : true); - //lvi->setIcon(DEVCOL_TMMC, QIcon( si.MMCOut() ? *dotIcon : *dothIcon)); lvi->_sendMMC = (lvi->_sendMMC ? false : true); lvi->setIcon(DEVCOL_TMMC, QIcon( lvi->_sendMMC ? *dotIcon : *dothIcon)); setDirty(); break; case DEVCOL_TMTC: - //si.setMTCOut(si.MTCOut() ? false : true); - //lvi->setIcon(DEVCOL_TMTC, QIcon( si.MTCOut() ? *dotIcon : *dothIcon)); lvi->_sendMTC = (lvi->_sendMTC ? false : true); lvi->setIcon(DEVCOL_TMTC, QIcon( lvi->_sendMTC ? *dotIcon : *dothIcon)); setDirty(); @@ -1183,21 +1023,13 @@ void MidiSyncConfig::dlvDoubleClicked(QTreeWidgetItem* item, int col) MidiSyncLViewItem* lvi = (MidiSyncLViewItem*)item; - //if(col == DEVCOL_RID) - // lvi->startRename(DEVCOL_RID); - //else - //if(col == DEVCOL_TID) - // lvi->startRename(DEVCOL_TID); - bool ok = false; if(col == DEVCOL_RID) { - //int val = lvi->syncInfo().idIn(); int val = lvi->_idIn; int newval = QInputDialog::getInteger(this, "Muse: Sync info" , "Enter new id number (127 = all):", val, 0, 127, 1, &ok); if(ok) { - //lvi->syncInfo().setIdIn(newval); lvi->_idIn = newval; lvi->setText(DEVCOL_RID, QString().setNum(newval)); } @@ -1205,12 +1037,10 @@ void MidiSyncConfig::dlvDoubleClicked(QTreeWidgetItem* item, int col) else if(col == DEVCOL_TID) { - //int val = lvi->syncInfo().idOut(); int val = lvi->_idOut; int newval = QInputDialog::getInteger(this, "Muse: Sync info" , "Enter new id number (127 = global):", val, 0, 127, 1, &ok); if(ok) { - //lvi->syncInfo().setIdOut(newval); lvi->_idOut = newval; lvi->setText(DEVCOL_TID, QString().setNum(newval)); } @@ -1220,41 +1050,6 @@ void MidiSyncConfig::dlvDoubleClicked(QTreeWidgetItem* item, int col) setDirty(); } -/* -//--------------------------------------------------------- -// renameOk -//--------------------------------------------------------- -//void MidiSyncConfig::renameOk(QListViewItem* item, int col) -void MidiSyncConfig::renameOk(QListViewItem* item, int col, const QString & text) -{ - if(!item) - return; - - MidiSyncLViewItem* lvi = (MidiSyncLViewItem*)item; - QString t = text; - bool ok; - int id = text.toInt(&ok); - if(!ok) - { - lvi->setText(t); - return; - } - if(col == DEVCOL_RID) - { - //lvi->syncInfo().setIdIn(id); - lvi->_idIn = id; - setDirty(); - } - else - if(col == DEVCOL_TID) - { - //lvi->syncInfo().setIdOut(id); - lvi->_idOut = id; - setDirty(); - } -} -*/ - //--------------------------------------------------------- // MidiSyncConfig::setDirty //--------------------------------------------------------- diff --git a/muse2/muse/widgets/musewidgetsplug.cpp b/muse2/muse/widgets/musewidgetsplug.cpp index bee05d51..9c82b5f5 100644 --- a/muse2/muse/widgets/musewidgetsplug.cpp +++ b/muse2/muse/widgets/musewidgetsplug.cpp @@ -215,7 +215,7 @@ MusEGlobal::GlobalConfigValues config = { QString("./"), // projectBaseFolder true, // projectStoreInFolder true, // useProjectSaveDialog - 64, // minControlProcessPeriod + 256, // minControlProcessPeriod false, // popupsDefaultStayOpen false, // leftMouseButtonCanDecrease false, // rangeMarkerWithoutMMB diff --git a/muse2/muse/widgets/scldraw.cpp b/muse2/muse/widgets/scldraw.cpp index 38adff25..aec769a0 100644 --- a/muse2/muse/widgets/scldraw.cpp +++ b/muse2/muse/widgets/scldraw.cpp @@ -636,7 +636,7 @@ int ScaleDraw::maxHeight(QPainter *p) const //------------------------------------------------------------ QRect ScaleDraw::maxBoundingRect(QPainter *p) const { - int i, wl,h,wmax; + int i, wl; //,wmax; int a, ar, amin, amax; double arc; @@ -645,7 +645,6 @@ QRect ScaleDraw::maxBoundingRect(QPainter *p) const QFontMetrics fm = p->fontMetrics(); wl = maxLabelWidth(p, TRUE); - h = fm.height(); switch(d_orient) { @@ -722,7 +721,7 @@ QRect ScaleDraw::maxBoundingRect(QPainter *p) const r.setBottom(MusECore::qwtInt(d_yCenter - (d_radius + double(d_majLen + d_vpad)) * cos(arc)) + fm.height() ); - wmax = d_len + d_majLen + d_hpad + wl; + //wmax = d_len + d_majLen + d_hpad + wl; DELETETHIS r.setLeft(d_xorg - d_majLen - d_hpad - wl); r.setWidth(d_len + 2*(d_majLen + d_hpad + wl)); diff --git a/muse2/muse/widgets/sliderbase.cpp b/muse2/muse/widgets/sliderbase.cpp index 15497235..5909c64d 100644 --- a/muse2/muse/widgets/sliderbase.cpp +++ b/muse2/muse/widgets/sliderbase.cpp @@ -118,6 +118,7 @@ void SliderBase::wheelEvent(QWheelEvent *e) setValue(value()-inc); emit sliderMoved(value(), _id); + emit sliderMoved(value(), _id, (bool)(e->modifiers() & Qt::ShiftModifier)); } @@ -184,6 +185,7 @@ void SliderBase::mousePressEvent(QMouseEvent *e) d_mouseOffset = 0; DoubleRange::incPages(d_direction); emit sliderMoved(value(), _id); + emit sliderMoved(value(), _id, (bool)(e->modifiers() & Qt::ShiftModifier)); d_tmrID = startTimer(MusECore::qwtMax(250, 2 * d_updTime)); break; @@ -394,6 +396,7 @@ void SliderBase::mouseMoveEvent(QMouseEvent *e) } if (value() != prevValue()) emit sliderMoved(value(), _id); + emit sliderMoved(value(), _id, (bool)(e->modifiers() & Qt::ShiftModifier)); } } @@ -444,7 +447,10 @@ void SliderBase::timerEvent(QTimerEvent*) DoubleRange::incPages(d_direction); if (value() != prevValue()) + { emit sliderMoved(value(), _id); + emit sliderMoved(value(), _id, false); + } if (!d_timerTick) { @@ -456,7 +462,10 @@ void SliderBase::timerEvent(QTimerEvent*) DoubleRange::fitValue(value() + double(d_direction) * inc); if (value() != prevValue()) + { emit sliderMoved(value(), _id); + emit sliderMoved(value(), _id, false); + } if (!d_timerTick) { @@ -620,6 +629,7 @@ void SliderBase::stepPages(int pages) { DoubleRange::incPages(pages); emit sliderMoved(value(), _id); + emit sliderMoved(value(), _id, false); } @@ -722,7 +732,7 @@ void SliderBase::stepPages(int pages) // slider with the mouse. // //.u Syntax -//.f void SliderBase::sliderMoved(double value, int _id) +//.f void SliderBase::sliderMoved(double value, int _id [, bool shift]) // //.u Parameters //.p double value -- new value diff --git a/muse2/muse/widgets/sliderbase.h b/muse2/muse/widgets/sliderbase.h index 56c7a586..abea5dd6 100644 --- a/muse2/muse/widgets/sliderbase.h +++ b/muse2/muse/widgets/sliderbase.h @@ -86,6 +86,7 @@ class SliderBase : public QWidget, public DoubleRange void sliderPressed(int id); void sliderReleased(int id); void sliderMoved(double value, int id); + void sliderMoved(double value, int id, bool shift); void sliderRightClicked(const QPoint &p, int id); public: |