diff options
-rw-r--r-- | muse/muse/audio.cpp | 4 | ||||
-rw-r--r-- | muse/muse/driver/alsamidi.h | 5 | ||||
-rw-r--r-- | muse/muse/driver/audiodev.h | 3 | ||||
-rw-r--r-- | muse/muse/driver/driver.h | 3 | ||||
-rw-r--r-- | muse/muse/driver/dummyaudio.cpp | 1 | ||||
-rw-r--r-- | muse/muse/driver/jack.cpp | 29 | ||||
-rw-r--r-- | muse/muse/driver/jackaudio.h | 4 | ||||
-rw-r--r-- | muse/muse/midi.cpp | 152 | ||||
-rw-r--r-- | muse/muse/midieventbase.h (renamed from muse/muse/midievent.h) | 0 | ||||
-rw-r--r-- | muse/muse/midioutport.cpp | 186 | ||||
-rw-r--r-- | muse/muse/midioutport.h | 2 | ||||
-rw-r--r-- | muse/muse/undo.cpp | 4 |
12 files changed, 208 insertions, 185 deletions
diff --git a/muse/muse/audio.cpp b/muse/muse/audio.cpp index ce55f2bd..e6e84ab6 100644 --- a/muse/muse/audio.cpp +++ b/muse/muse/audio.cpp @@ -460,7 +460,9 @@ void Audio::process(unsigned frames) } } - processMidi(frames); + MidiOutPortList* mol = song->midiOutPorts(); + for (iMidiOutPort i = mol->begin(); i != mol->end(); ++i) + audioDriver->startMidiCycle(mol->jackPort(0)); GroupList* gl = song->groups(); SynthIList* sl = song->syntis(); diff --git a/muse/muse/driver/alsamidi.h b/muse/muse/driver/alsamidi.h index 9e5e54f5..b68c4783 100644 --- a/muse/muse/driver/alsamidi.h +++ b/muse/muse/driver/alsamidi.h @@ -40,6 +40,7 @@ class AlsaMidi : public Driver { void removeConnection(snd_seq_connect_t* ev); void addConnection(snd_seq_connect_t* ev); + bool putEvent(snd_seq_event_t* event); public: AlsaMidi(); @@ -58,15 +59,13 @@ class AlsaMidi : public Driver { virtual bool connect(Port, Port); virtual bool disconnect(Port, Port); + virtual void putEvent(Port, const MidiEvent&); void getInputPollFd(struct pollfd**, int* n); void getOutputPollFd(struct pollfd**, int* n); void read(MidiSeq*); // process events void write(); - - void putEvent(Port, const MidiEvent&); - bool putEvent(snd_seq_event_t* event); }; extern AlsaMidi alsaMidi; diff --git a/muse/muse/driver/audiodev.h b/muse/muse/driver/audiodev.h index 416f91c0..5196375c 100644 --- a/muse/muse/driver/audiodev.h +++ b/muse/muse/driver/audiodev.h @@ -37,7 +37,7 @@ class AudioDriver : public Driver { virtual bool restart() { return false; } // return true on error virtual void stop () = 0; virtual unsigned framePos() const = 0; - virtual float* getBuffer(void* port, unsigned long nframes) = 0; + virtual float* getBuffer(Port, unsigned long nframes) = 0; virtual void registerClient() = 0; virtual Port registerOutPort(const QString& name, bool midi) = 0; virtual Port registerInPort(const QString& name, bool midi) = 0; @@ -49,6 +49,7 @@ class AudioDriver : public Driver { virtual void seekTransport(unsigned frame) = 0; virtual void setFreewheel(bool f) = 0; virtual void graphChanged() {} + virtual void startMidiCycle(Port) {} }; #endif diff --git a/muse/muse/driver/driver.h b/muse/muse/driver/driver.h index bd6a826a..d7147b52 100644 --- a/muse/muse/driver/driver.h +++ b/muse/muse/driver/driver.h @@ -28,6 +28,8 @@ struct PortName { QString name; }; +class MidiEvent; + //--------------------------------------------------------- // Driver // abstract driver base class; used for midi and @@ -54,6 +56,7 @@ class Driver { virtual bool connect(Port, Port) = 0; virtual bool disconnect(Port, Port) = 0; virtual bool equal(Port, Port) = 0; + virtual void putEvent(Port, const MidiEvent&) = 0; }; #endif diff --git a/muse/muse/driver/dummyaudio.cpp b/muse/muse/driver/dummyaudio.cpp index 2aa5a709..a55a69bb 100644 --- a/muse/muse/driver/dummyaudio.cpp +++ b/muse/muse/driver/dummyaudio.cpp @@ -143,6 +143,7 @@ class DummyAudio : public AudioDriver { virtual bool equal(Port a, Port b) { return a == b; } + virtual void putEvent(Port, const MidiEvent&) {} }; DummyAudio* dummyAudio; diff --git a/muse/muse/driver/jack.cpp b/muse/muse/driver/jack.cpp index 2bccbfcf..57a5348f 100644 --- a/muse/muse/driver/jack.cpp +++ b/muse/muse/driver/jack.cpp @@ -794,3 +794,32 @@ bool initJackAudio() return false; } +//--------------------------------------------------------- +// putEvent +//--------------------------------------------------------- + +void JackAudio::putEvent(Port p, const MidiEvent& e) + { + if (midiOutputTrace) { + printf("MidiOut<%s>: jackMidi: ", portName(p).toLatin1().data()); + e.dump(); + } + unsigned char* p = jack_midi_event_reserve(pb, pos, 3, segmentSize); + if (p == 0) { + fprintf(stderr, "JackMidi: buffer overflow, event lost\n"); + return; + } + p[0] = e.dataA(); + p[1] + } + +//--------------------------------------------------------- +// startMidiCycle +//--------------------------------------------------------- + +void JackAudio::startMidiCycle(Port port) + { + void* port_buf = jack_port_get_buffer(port, segmentSize); + jack_midi_clear_buffer(port_buf, segmentSize); + } + diff --git a/muse/muse/driver/jackaudio.h b/muse/muse/driver/jackaudio.h index 4b76cb80..c4d0cdaa 100644 --- a/muse/muse/driver/jackaudio.h +++ b/muse/muse/driver/jackaudio.h @@ -44,7 +44,7 @@ class JackAudio : public AudioDriver { virtual void zeroClientPtr() { _client = 0; } virtual unsigned framePos() const; - virtual float* getBuffer(void* port, unsigned long nframes) { + virtual float* getBuffer(Port port, unsigned long nframes) { return (float*)jack_port_get_buffer((jack_port_t*)port, nframes); } @@ -78,6 +78,8 @@ class JackAudio : public AudioDriver { } void graphChanged(); virtual bool equal(Port a, Port b) { return a == b; } + virtual void putEvent(Port, const MidiEvent&); + virutal void startMidiCycle(Port); }; #endif diff --git a/muse/muse/midi.cpp b/muse/muse/midi.cpp index 9741d9fb..8e15b9d4 100644 --- a/muse/muse/midi.cpp +++ b/muse/muse/midi.cpp @@ -27,6 +27,7 @@ #include "midictrl.h" #include "audio.h" #include "driver/mididev.h" +#include "driver/audiodev.h" #include "wave.h" #include "synth.h" #include "sync.h" @@ -666,154 +667,3 @@ void Audio::processMidi(unsigned frames) midiBusy = false; } -//--------------------------------------------------------- -// process -// "play" events for this process cycle -// if (from != to) then transport state is "playing" -//--------------------------------------------------------- - -void MidiOutPort::process(unsigned from, unsigned to, const Pos& pos, unsigned frames) - { - // - // erase already played events: - // - _playEvents.erase(_playEvents.begin(), _nextPlayEvent); - playFifo(); - - if (mute()) - return; - - // collect port controller - if (from != to) { - CtrlList* cl = controller(); - for (iCtrl ic = cl->begin(); ic != cl->end(); ++ic) { - Ctrl* c = ic->second; - iCtrlVal is = c->lower_bound(from); - iCtrlVal ie = c->lower_bound(to); - for (iCtrlVal ic = is; ic != ie; ++ic) { - unsigned frame = AL::tempomap.tick2frame(ic->first); - Event ev(Controller); - ev.setA(c->id()); - ev.setB(ic->second.i); - _playEvents.add(MidiEvent(frame, -1, ev)); - } - } - } - - int portVelo = 0; - for (int ch = 0; ch < MIDI_CHANNELS; ++ch) { - MidiChannel* mc = channel(ch); - - if (mc->mute() || mc->noInRoute()) - continue; - // collect channel controller - if (from != to) { - CtrlList* cl = mc->controller(); - for (iCtrl ic = cl->begin(); ic != cl->end(); ++ic) { - Ctrl* c = ic->second; - iCtrlVal is = c->lower_bound(from); - iCtrlVal ie = c->lower_bound(to); - for (; is != ie; ++is) { - unsigned frame = AL::tempomap.tick2frame(is->first); - Event ev(Controller); - ev.setA(c->id()); - ev.setB(is->second.i); - _playEvents.add(MidiEvent(frame, ch, ev)); - } - } - } - - // Collect midievents from all input tracks for outport - RouteList* rl = mc->inRoutes(); - for (iRoute i = rl->begin(); i != rl->end(); ++i) { - MidiTrackBase* track = (MidiTrackBase*)i->track; - if (track->isMute()) - continue; - MPEventList el; - track->getEvents(from, to, 0, &el); - int velo = 0; - for (iMPEvent i = el.begin(); i != el.end(); ++i) { - MidiEvent ev = *i; - ev.setChannel(ch); - _playEvents.insert(ev); - if (ev.type() == ME_NOTEON) - velo += ev.dataB(); - } - mc->addMidiMeter(velo); - portVelo += velo; - } - } - addMidiMeter(portVelo); - - // TODO: maybe this copying can be avoided - // - MPEventList il; - for (iMPEvent i = _playEvents.begin(); i != _playEvents.end(); ++i) { - il.add(*i); - } - _playEvents.clear(); - pipeline()->apply(from, to, &il, &_playEvents); - - _nextPlayEvent = _playEvents.begin(); - - // - // route events to destination - // - - if (_playEvents.empty()) - return; - - //printf("_playEvents.size() == %d\n", _playEvents.size()); - unsigned endFrame = pos.frame() + frames + audio->getFrameOffset(); - for (iRoute r = _outRoutes.begin(); r != _outRoutes.end(); ++r) { - switch (r->type) { - - // - // Send events to software synthesizer - // - case Route::SYNTIPORT: { - SynthI* s = (SynthI*)(r->track); - MPEventList* el = s->playEvents(); - iMPEvent is = _playEvents.begin(); - iMPEvent ie = _playEvents.end(); - - for (; is != ie; ++is) { - if ((from != to) && (is->time() >= endFrame)) { - break; - } - - el->insert(*is); - _nextPlayEvent = is; - _nextPlayEvent++; - } - } - break; - - // - // Send events to midi port - // - case Route::MIDIPORT: { - //playEventList(); - for (iMPEvent ev = _playEvents.begin(); ev != _playEvents.end(); ev++) { - if ((from != to) && (ev->time() >= endFrame)) { - break; - } - - midiDriver->putEvent(alsaPort(), *ev); - _nextPlayEvent = ev; - _nextPlayEvent++; - } - } - break; - - - // Invalid routetypes to send midi events to - should not happen - case Route::AUDIOPORT: - case Route::TRACK: - default: - printf("Error - invalid routetype\n"); - break; - } - } - } - diff --git a/muse/muse/midievent.h b/muse/muse/midieventbase.h index 93cb5529..93cb5529 100644 --- a/muse/muse/midievent.h +++ b/muse/muse/midieventbase.h diff --git a/muse/muse/midioutport.cpp b/muse/muse/midioutport.cpp index bdad88cc..3d96780f 100644 --- a/muse/muse/midioutport.cpp +++ b/muse/muse/midioutport.cpp @@ -21,6 +21,7 @@ #include "song.h" #include "midiplugin.h" #include "midictrl.h" +#include "al/tempo.h" #include "al/xml.h" #include "driver/mididev.h" #include "driver/audiodev.h" @@ -153,7 +154,7 @@ void MidiOutPort::putEvent(const MidiEvent& ev) } if (a == CTRL_PITCH) { - midiDriver->putEvent(alsaPort(), MidiEvent(0, chn, ME_PITCHBEND, b, 0)); + routeEvent(MidiEvent(0, chn, ME_PITCHBEND, b, 0)); return; } if (a == CTRL_PROGRAM) { @@ -163,10 +164,10 @@ void MidiOutPort::putEvent(const MidiEvent& ev) int lb = (b >> 8) & 0xff; int pr = b & 0x7f; if (hb != 0xff) - midiDriver->putEvent(alsaPort(), MidiEvent(0, chn, ME_CONTROLLER, CTRL_HBANK, hb)); + routeEvent(MidiEvent(0, chn, ME_CONTROLLER, CTRL_HBANK, hb)); if (lb != 0xff) midiDriver->putEvent(alsaPort(), MidiEvent(0, chn, ME_CONTROLLER, CTRL_LBANK, lb)); - midiDriver->putEvent(alsaPort(), MidiEvent(0, chn, ME_PROGRAM, pr, 0)); + routeEvent(MidiEvent(0, chn, ME_PROGRAM, pr, 0)); return; // } } @@ -178,63 +179,63 @@ void MidiOutPort::putEvent(const MidiEvent& ev) sysex[4] = b & 0x7f; sysex[5] = (b >> 7) & 0x7f; MidiEvent e(ev.time(), ME_SYSEX, sysex, 6); - midiDriver->putEvent(alsaPort(), e); + routeEvent(e); return; } #if 1 // if ALSA cannot handle RPN NRPN etc. if (a < 0x1000) { // 7 Bit Controller //putMidiEvent(MidiEvent(0, chn, ME_CONTROLLER, a, b)); - midiDriver->putEvent(alsaPort(), ev); + routeEvent(ev); } else if (a < 0x20000) { // 14 bit high resolution controller int ctrlH = (a >> 8) & 0x7f; int ctrlL = a & 0x7f; int dataH = (b >> 7) & 0x7f; int dataL = b & 0x7f; - midiDriver->putEvent(alsaPort(), MidiEvent(0, chn, ME_CONTROLLER, ctrlH, dataH)); - midiDriver->putEvent(alsaPort(), MidiEvent(0, chn, ME_CONTROLLER, ctrlL, dataL)); + routeEvent(MidiEvent(0, chn, ME_CONTROLLER, ctrlH, dataH)); + routeEvent(MidiEvent(0, chn, ME_CONTROLLER, ctrlL, dataL)); } else if (a < 0x30000) { // RPN 7-Bit Controller int ctrlH = (a >> 8) & 0x7f; int ctrlL = a & 0x7f; - midiDriver->putEvent(alsaPort(), MidiEvent(0, chn, ME_CONTROLLER, CTRL_HRPN, ctrlH)); - midiDriver->putEvent(alsaPort(), MidiEvent(0, chn, ME_CONTROLLER, CTRL_LRPN, ctrlL)); - midiDriver->putEvent(alsaPort(), MidiEvent(0, chn, ME_CONTROLLER, CTRL_HDATA, b)); + routeEvent(MidiEvent(0, chn, ME_CONTROLLER, CTRL_HRPN, ctrlH)); + routeEvent(MidiEvent(0, chn, ME_CONTROLLER, CTRL_LRPN, ctrlL)); + routeEvent(MidiEvent(0, chn, ME_CONTROLLER, CTRL_HDATA, b)); } else if (a < 0x40000) { // NRPN 7-Bit Controller int ctrlH = (a >> 8) & 0x7f; int ctrlL = a & 0x7f; - midiDriver->putEvent(alsaPort(), MidiEvent(0, chn, ME_CONTROLLER, CTRL_HNRPN, ctrlH)); - midiDriver->putEvent(alsaPort(), MidiEvent(0, chn, ME_CONTROLLER, CTRL_LNRPN, ctrlL)); - midiDriver->putEvent(alsaPort(), MidiEvent(0, chn, ME_CONTROLLER, CTRL_HDATA, b)); + routeEvent(MidiEvent(0, chn, ME_CONTROLLER, CTRL_HNRPN, ctrlH)); + routeEvent(MidiEvent(0, chn, ME_CONTROLLER, CTRL_LNRPN, ctrlL)); + routeEvent(MidiEvent(0, chn, ME_CONTROLLER, CTRL_HDATA, b)); } else if (a < 0x60000) { // RPN14 Controller int ctrlH = (a >> 8) & 0x7f; int ctrlL = a & 0x7f; int dataH = (b >> 7) & 0x7f; int dataL = b & 0x7f; - midiDriver->putEvent(alsaPort(), MidiEvent(0, chn, ME_CONTROLLER, CTRL_HRPN, ctrlH)); - midiDriver->putEvent(alsaPort(), MidiEvent(0, chn, ME_CONTROLLER, CTRL_LRPN, ctrlL)); - midiDriver->putEvent(alsaPort(), MidiEvent(0, chn, ME_CONTROLLER, CTRL_HDATA, dataH)); - midiDriver->putEvent(alsaPort(), MidiEvent(0, chn, ME_CONTROLLER, CTRL_LDATA, dataL)); + routeEvent(MidiEvent(0, chn, ME_CONTROLLER, CTRL_HRPN, ctrlH)); + routeEvent(MidiEvent(0, chn, ME_CONTROLLER, CTRL_LRPN, ctrlL)); + routeEvent(MidiEvent(0, chn, ME_CONTROLLER, CTRL_HDATA, dataH)); + routeEvent(MidiEvent(0, chn, ME_CONTROLLER, CTRL_LDATA, dataL)); } else if (a < 0x70000) { // NRPN14 Controller int ctrlH = (a >> 8) & 0x7f; int ctrlL = a & 0x7f; int dataH = (b >> 7) & 0x7f; int dataL = b & 0x7f; - midiDriver->putEvent(alsaPort(), MidiEvent(0, chn, ME_CONTROLLER, CTRL_HNRPN, ctrlH)); - midiDriver->putEvent(alsaPort(), MidiEvent(0, chn, ME_CONTROLLER, CTRL_LNRPN, ctrlL)); - midiDriver->putEvent(alsaPort(), MidiEvent(0, chn, ME_CONTROLLER, CTRL_HDATA, dataH)); - midiDriver->putEvent(alsaPort(), MidiEvent(0, chn, ME_CONTROLLER, CTRL_LDATA, dataL)); + routeEvent(MidiEvent(0, chn, ME_CONTROLLER, CTRL_HNRPN, ctrlH)); + routeEvent(MidiEvent(0, chn, ME_CONTROLLER, CTRL_LNRPN, ctrlL)); + routeEvent(MidiEvent(0, chn, ME_CONTROLLER, CTRL_HDATA, dataH)); + routeEvent(MidiEvent(0, chn, ME_CONTROLLER, CTRL_LDATA, dataL)); } else { printf("putEvent: unknown controller type 0x%x\n", a); } #endif } - midiDriver->putEvent(alsaPort(), ev); + routeEvent(ev); } //--------------------------------------------------------- @@ -244,7 +245,7 @@ void MidiOutPort::putEvent(const MidiEvent& ev) void MidiOutPort::playEventList() { for (; _nextPlayEvent != _playEvents.end(); ++_nextPlayEvent) - midiDriver->putEvent(alsaPort(), *_nextPlayEvent); + routeEvent(*_nextPlayEvent); } //--------------------------------------------------------- @@ -385,15 +386,13 @@ void MidiOutPort::playMidiEvent(MidiEvent* ev) RouteList* orl = outRoutes(); bool sendToFifo = false; for (iRoute i = orl->begin(); i != orl->end(); ++i) { - if (i->type == Route::MIDIPORT) - sendToFifo = true; - else if (i->type == Route::SYNTIPORT) { + if (i->type == Route::SYNTIPORT) { SynthI* synti = (SynthI*)i->track; if (synti->eventFifo()->put(*ev)) printf("MidiOut::playMidiEvent(): synti overflow, drop event\n"); } - else - printf("MidiOutPort::playMidiEvent: bad route type\n"); + else + sendToFifo = true; } if (sendToFifo) { if (eventFifo.put(*ev)) @@ -402,6 +401,114 @@ void MidiOutPort::playMidiEvent(MidiEvent* ev) } //--------------------------------------------------------- +// process +// "play" events for this process cycle +// if (from != to) then transport state is "playing" +//--------------------------------------------------------- + +void MidiOutPort::process(unsigned from, unsigned to, const Pos& pos, unsigned frames) + { + // + // erase already played events: + // + _playEvents.erase(_playEvents.begin(), _nextPlayEvent); + playFifo(); + + if (mute()) + return; + + // collect port controller + if (from != to) { + CtrlList* cl = controller(); + for (iCtrl ic = cl->begin(); ic != cl->end(); ++ic) { + Ctrl* c = ic->second; + iCtrlVal is = c->lower_bound(from); + iCtrlVal ie = c->lower_bound(to); + for (iCtrlVal ic = is; ic != ie; ++ic) { + unsigned frame = AL::tempomap.tick2frame(ic->first); + Event ev(Controller); + ev.setA(c->id()); + ev.setB(ic->second.i); + _playEvents.add(MidiEvent(frame, -1, ev)); + } + } + } + + int portVelo = 0; + for (int ch = 0; ch < MIDI_CHANNELS; ++ch) { + MidiChannel* mc = channel(ch); + + if (mc->mute() || mc->noInRoute()) + continue; + // collect channel controller + if (from != to) { + CtrlList* cl = mc->controller(); + for (iCtrl ic = cl->begin(); ic != cl->end(); ++ic) { + Ctrl* c = ic->second; + iCtrlVal is = c->lower_bound(from); + iCtrlVal ie = c->lower_bound(to); + for (; is != ie; ++is) { + unsigned frame = AL::tempomap.tick2frame(is->first); + Event ev(Controller); + ev.setA(c->id()); + ev.setB(is->second.i); + _playEvents.add(MidiEvent(frame, ch, ev)); + } + } + } + + // Collect midievents from all input tracks for outport + RouteList* rl = mc->inRoutes(); + for (iRoute i = rl->begin(); i != rl->end(); ++i) { + MidiTrackBase* track = (MidiTrackBase*)i->track; + if (track->isMute()) + continue; + MPEventList el; + track->getEvents(from, to, 0, &el); + int velo = 0; + for (iMPEvent i = el.begin(); i != el.end(); ++i) { + MidiEvent ev = *i; + ev.setChannel(ch); + _playEvents.insert(ev); + if (ev.type() == ME_NOTEON) + velo += ev.dataB(); + } + mc->addMidiMeter(velo); + portVelo += velo; + } + } + addMidiMeter(portVelo); + + // TODO: maybe this copying can be avoided + // + MPEventList il; + for (iMPEvent i = _playEvents.begin(); i != _playEvents.end(); ++i) { + il.add(*i); + } + _playEvents.clear(); + pipeline()->apply(from, to, &il, &_playEvents); + + _nextPlayEvent = _playEvents.begin(); + + // + // route events to destination + // + + unsigned endFrame = pos.frame() + frames + audio->getFrameOffset(); + iMPEvent is = _playEvents.begin(); + iMPEvent ie = _playEvents.end(); + + for (_nextPlayEvent = is; _nextPlayEvent != ie; _nextPlayEvent++) { + if ((from != to) && (_nextPlayEvent->time() >= endFrame)) { + ++_nextPlayEvent; // ?? + break; + } + routeEvent(*_nextPlayEvent); + } + } + + +//--------------------------------------------------------- // setInstrument //--------------------------------------------------------- @@ -421,4 +528,27 @@ void MidiOutPort::setSendSync(bool val) emit sendSyncChanged(val); } +//--------------------------------------------------------- +// routeEvent +//--------------------------------------------------------- + +void MidiOutPort::routeEvent(const MidiEvent& event) + { + for (iRoute r = _outRoutes.begin(); r != _outRoutes.end(); ++r) { + switch (r->type) { + case Route::MIDIPORT: + midiDriver->putEvent(alsaPort(0), *_nextPlayEvent); + break; + case Route::SYNTIPORT: + ((SynthI*)(r->track))->playEvents()->insert(*_nextPlayEvent); + break; + case Route::JACKMIDIPORT: + audioDriver->putEvent(jackPort(0), *_nextPlayEvent); + break; + default: + fprintf(stderr, "MidiOutPort::process(): invalid routetype\n"); + break; + } + } + } diff --git a/muse/muse/midioutport.h b/muse/muse/midioutport.h index f90ad376..9999a92d 100644 --- a/muse/muse/midioutport.h +++ b/muse/muse/midioutport.h @@ -44,6 +44,8 @@ class MidiOutPort : public MidiTrackBase { MidiFifo eventFifo; + void routeEvent(const MidiEvent&); + signals: void instrumentChanged(); void sendSyncChanged(bool); diff --git a/muse/muse/undo.cpp b/muse/muse/undo.cpp index 61d9c7e9..b65269f4 100644 --- a/muse/muse/undo.cpp +++ b/muse/muse/undo.cpp @@ -463,6 +463,10 @@ void Song::doUndo3() break; case UndoOp::DeleteTrack: emit trackAdded(i->track, i->id); + if (i->track->selected()) { + i->track->setSelected(false); + selectTrack(i->track); + } break; case UndoOp::ModifyPart: if (i->oPart->track() != i->nPart->track()) |