diff options
Diffstat (limited to 'muse_qt4_evolution/muse/miditrack.cpp')
-rw-r--r-- | muse_qt4_evolution/muse/miditrack.cpp | 716 |
1 files changed, 716 insertions, 0 deletions
diff --git a/muse_qt4_evolution/muse/miditrack.cpp b/muse_qt4_evolution/muse/miditrack.cpp new file mode 100644 index 00000000..276eeccb --- /dev/null +++ b/muse_qt4_evolution/muse/miditrack.cpp @@ -0,0 +1,716 @@ +//============================================================================= +// MusE +// Linux Music Editor +// $Id:$ +// +// Copyright (C) 2002-2006 by Werner Schweer and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#include "miditrack.h" +#include "event.h" +#include "song.h" +#include "midi.h" +#include "midictrl.h" +#include "audio.h" +#include "part.h" +#include "al/tempo.h" +#include "midiedit/drummap.h" + +//--------------------------------------------------------- +// MidiTrack +//--------------------------------------------------------- + +MidiTrack::MidiTrack() + : MidiTrackBase() + { + _transposition = 0; + _velocity = 0; + _delay = 0; + _len = 100; // percent + _compression = 100; // percent + + initMidiController(); + recordPart = 0; + _drumMap = 0; + _useDrumMap = false; + + // + // create minimal set of managed controllers + // to make midi mixer operational + // + MidiInstrument* mi = genericMidiInstrument; + addMidiController(mi, CTRL_PROGRAM); + addMidiController(mi, CTRL_VOLUME); + addMidiController(mi, CTRL_PANPOT); + addMidiController(mi, CTRL_REVERB_SEND); + addMidiController(mi, CTRL_CHORUS_SEND); + addMidiController(mi, CTRL_VARIATION_SEND); + } + +MidiTrack::~MidiTrack() + { + } + +//--------------------------------------------------------- +// newPart +//--------------------------------------------------------- + +Part* MidiTrack::newPart(Part* p, bool clone) + { + Part* part = new Part(this); + if (p) { + if (clone) + part->clone(p->events()); + else + part->ref(); + part->setName(p->name()); + part->setColorIndex(p->colorIndex()); + + *(AL::PosLen*)part = *(AL::PosLen*)p; + part->setMute(p->mute()); + } + else + part->ref(); + return part; + } + +//--------------------------------------------------------- +// MidiTrack::write +//--------------------------------------------------------- + +void MidiTrack::write(Xml& xml) const + { + xml.stag("miditrack"); + MidiTrackBase::writeProperties(xml); + + xml.tag("transposition", _transposition); + xml.tag("velocity", _velocity); + xml.tag("delay", _delay); + xml.tag("len", _len); + xml.tag("compression", _compression); + xml.tag("useDrumMap", _useDrumMap); + + const PartList* pl = parts(); + for (ciPart p = pl->begin(); p != pl->end(); ++p) + p->second->write(xml); + xml.etag("miditrack"); + } + +//--------------------------------------------------------- +// MidiTrack::read +//--------------------------------------------------------- + +void MidiTrack::read(QDomNode node) + { + for (; !node.isNull(); node = node.nextSibling()) { + QDomElement e = node.toElement(); + QString tag(e.tagName()); + QString s(e.text()); + int i = s.toInt(); + if (tag == "transposition") + _transposition = i; + else if (tag == "velocity") + _velocity = i; + else if (tag == "delay") + _delay = i; + else if (tag == "len") + _len = i; + else if (tag == "compression") + _compression = i; + else if (tag == "part") { + Part* p = newPart(); + p->read(node, true); + parts()->add(p); + } + else if (tag == "locked") + _locked = i; + else if (tag == "useDrumMap") + _useDrumMap = e.text().toInt(); + else if (MidiTrackBase::readProperties(node)) + printf("MusE:MidiTrack: unknown tag %s\n", e.tagName().toLatin1().data()); + } + } + +//--------------------------------------------------------- +// playMidiEvent +//--------------------------------------------------------- + +void MidiTrack::playMidiEvent(MidiEvent* ev) + { + foreach (const Route& r, _outRoutes) { + Track* track = r.dst.track; + ev->setChannel(r.dst.channel); + if (track->type() == MIDI_OUT) + ((MidiOutPort*)track)->playMidiEvent(ev); + else if (track->type() == AUDIO_SOFTSYNTH) + ((SynthI*)track)->playMidiEvent(ev); + } + } + +//--------------------------------------------------------- +// startRecording +// gui context +//--------------------------------------------------------- + +void MidiTrack::startRecording() + { + hbank = 0; + lbank = 0; + datah = 0; + datal = 0; + rpnh = 0; + rpnl = 0; + dataType = 0; + recordedEvents = 0; + partCreated = false; + recordPart = 0; + recordFifo.clear(); + keyDown.clear(); + + AL::Pos start = song->punchin() ? song->lPos() : song->cPos(); + + for (iPart ip = parts()->begin(); ip != parts()->end(); ++ip) { + Part* part = ip->second; + unsigned partStart = part->tick(); + unsigned partEnd = partStart + part->lenTick(); + if (start.tick() >= partStart && start.tick() < partEnd) { + recordPart = part; + } + } + if (recordPart == 0) { + // + // create new part for recording + // + recordPart = new Part(this); + recordPart->ref(); + recordPart->setTrack(this); + int startTick = song->roundDownBar(start.tick()); + int endTick = song->roundUpBar(start.tick()); + recordPart->setTick(startTick); + recordPart->setLenTick(endTick - startTick); + recordPart->setName(name()); + song->addPart(recordPart); + partCreated = true; + } + } + +//--------------------------------------------------------- +// recordBeat +// gui context +// update current recording +//--------------------------------------------------------- + +void MidiTrack::recordBeat() + { + int updateFlags = 0; + unsigned cpos = song->cpos(); + unsigned ptick = recordPart->tick(); + + if (song->punchout()) { + if (song->rPos() >= song->cPos()) { + while (!recordFifo.isEmpty()) + recordFifo.get(); + return; + } + } + QList<Event> el; + while (!recordFifo.isEmpty()) { + MidiEvent me(recordFifo.get()); + + unsigned time = me.time(); + if (song->punchin() && time < song->lpos()) + continue; + bool isOff = me.isNoteOff(); + + if (song->punchout() && (time >= song->rpos()) && !isOff) + continue; + + if (!partCreated && song->recMode() == Song::REC_REPLACE) { + // TODO: remove old events + } + + time -= ptick; + if (isOff) { + // + // process note off + // + for (std::list<Event>::iterator i = keyDown.begin(); i != keyDown.end(); ++i) { + if (i->pitch() == me.dataA()) { + unsigned tl = time - i->tick(); + if (tl != i->lenTick()) { + i->setLenTick(tl); + updateFlags |= SC_EVENT_MODIFIED; + } + keyDown.erase(i); + break; + } + } + } + else if (me.type() == ME_NOTEON && me.dataB() != 0) { + // + // create Note event on "note on" + // + Event event(Note); + event.setTick(time); + event.setLenTick(1); + event.setPitch(me.dataA()); + event.setVelo(me.dataB()); + keyDown.push_front(event); + el.append(event); + } + else if (me.type() == ME_POLYAFTER) { + Event event(PAfter); + event.setTick(time); + event.setA(me.dataA()); + event.setB(me.dataB()); + } + else if (me.type() == ME_CONTROLLER) { + Event event(Controller); + event.setTick(time + ptick); + switch(me.dataA()) { + case CTRL_HBANK: + hbank = me.dataB(); + break; + + case CTRL_LBANK: + lbank = me.dataB(); + break; + + case CTRL_HDATA: + datah = me.dataB(); + event.setA(dataType | (rpnh << 8) | rpnl); + event.setB(datah); + el.append(event); + break; + + case CTRL_LDATA: + datal = me.dataB(); + if (dataType == CTRL_NRPN_OFFSET) + dataType = CTRL_NRPN14_OFFSET; + else if (dataType == CTRL_RPN_OFFSET) + dataType = CTRL_RPN14_OFFSET; + break; + + case CTRL_HNRPN: + rpnh = me.dataB(); + dataType = CTRL_NRPN_OFFSET; + break; + + case CTRL_LNRPN: + rpnl = me.dataB(); + dataType = CTRL_NRPN_OFFSET; + break; + + case CTRL_HRPN: + rpnh = me.dataB(); + dataType = CTRL_RPN_OFFSET; + break; + + case CTRL_LRPN: + rpnl = me.dataB(); + dataType = CTRL_RPN_OFFSET; + break; + + default: + event.setA(me.dataA()); + event.setB(me.dataB()); + el.append(event); + break; + } + } + else if (me.type() == ME_PROGRAM) { + Event event(Controller); + event.setTick(time + ptick); + event.setA(CTRL_PROGRAM); + event.setB((hbank << 16) | (lbank << 8) | me.dataA()); + el.append(event); + } + else if (me.type() == ME_PITCHBEND) { + Event event(Controller); + event.setTick(time + ptick); + event.setA(CTRL_PITCH); + event.setB(me.dataA()); + el.append(event); + } + else if (me.type() == ME_SYSEX) { + Event event(Sysex); + event.setTick(time + ptick); + event.setData(me.data(), me.len()); + el.append(event); + } + else if (me.type() == ME_AFTERTOUCH) { + Event event(CAfter); + event.setTick(time + ptick); + event.setA(me.dataA()); + el.append(event); + } + } + if (!el.isEmpty()) { + for (int i = 0; i < el.size(); ++i) + el[i].setRecorded(true); + audio->msgAddEvents(&el, recordPart); + recordedEvents += el.size(); + updateFlags |= SC_EVENT_INSERTED; + } + + if (partCreated) { + recordPart->setLenTick(cpos - ptick); + updateFlags |= SC_PART_MODIFIED; + } + // + // modify len of all hold keys + // + for (std::list<Event>::iterator i = keyDown.begin(); i != keyDown.end(); ++i) { + if (cpos > (i->tick() + ptick)) + i->setLenTick(cpos - (i->tick() + ptick)); + updateFlags |= SC_EVENT_MODIFIED; + } + song->update(updateFlags); + } + +//--------------------------------------------------------- +// stopRecording +// gui context +//--------------------------------------------------------- + +void MidiTrack::stopRecording() + { + for (iEvent e = recordPart->events()->begin(); e != recordPart->events()->end(); ++e) { + e->second.setRecorded(false); + } + if (recordedEvents == 0 && partCreated) { + // TD: remove empty part? + } + // + // modify len of all hold keys + // + unsigned ptick = recordPart->tick(); + unsigned cpos = song->cpos(); + for (std::list<Event>::iterator i = keyDown.begin(); i != keyDown.end(); ++i) { + i->setLenTick(cpos - (i->tick() + ptick)); + } + // + // adjust part len && song len + // + if (recordPart->lenTick() < (cpos-ptick)) { + // + // TODO: check for events outside part boundaries + // + int endTick = song->roundUpBar(cpos); + recordPart->setLenTick(endTick - ptick); + } + + unsigned etick = recordPart->endTick(); + if (song->len() < etick) + song->setLen(etick); + } + +//--------------------------------------------------------- +// clone +//--------------------------------------------------------- + +void MidiTrack::clone(MidiTrack* t) + { + QString name; + for (int i = 1; ; ++i) { + name.sprintf("%s-%d", t->name().toLatin1().data(), i); + TrackList* tl = song->tracks(); + bool found = false; + for (iTrack it = tl->begin(); it != tl->end(); ++it) { + if ((*it)->name() == name) { + found = true; + break; + } + } + if (!found) + break; + } + setName(name); + _transposition = t->_transposition; + _velocity = t->_velocity; + _delay = t->_delay; + _len = t->_len; + _compression = t->_compression; + _recordFlag = t->_recordFlag; + _mute = t->_mute; + _solo = t->_solo; + _off = t->_off; + _monitor = t->_monitor; + _channels = t->_channels; + _locked = t->_locked; + _inRoutes = t->_inRoutes; + _outRoutes = t->_outRoutes; + _controller = t->_controller; + _autoRead = t->_autoRead; + _autoWrite = t->_autoWrite; + } + +//--------------------------------------------------------- +// isMute +//--------------------------------------------------------- + +bool MidiTrack::isMute() const + { + if (_solo) + return false; + if (song->solo()) + return true; + return _mute; + } + +//--------------------------------------------------------- +// processMidi +//--------------------------------------------------------- + +void MidiTrack::processMidi(SeqTime* t) + { + schedEvents.clear(); + // + // collect events only when transport is rolling + // + if (t->curTickPos < t->nextTickPos) { + for (iPart p = parts()->begin(); p != parts()->end(); ++p) { + Part* part = p->second; + if (part->mute()) + continue; + DrumMap* dm = drumMap(); + unsigned offset = _delay + part->tick(); + + if (offset > t->nextTickPos) + break; + + EventList* events = part->events(); + + iEvent ie = events->lower_bound((offset > t->curTickPos) ? 0 : t->curTickPos - offset); + iEvent iend = events->lower_bound(t->nextTickPos - offset); + + for (; ie != iend; ++ie) { + Event ev = ie->second; + if (ev.recorded()) + continue; + if (ev.type() == Meta) // ignore meta events + continue; + unsigned tick = ev.tick() + offset; + unsigned frame = t->tick2frame(tick); + if (ev.type() == Note) { + if (dm) { + if (dm->entry(dm->outmap(ev.pitch()))->mute) + continue; + } + // + // maybe we should skip next lines if using a + // drummap + + int pitch = ev.pitch() + _transposition + song->globalPitchShift(); + if (pitch > 127) + pitch = 127; + if (pitch < 0) + pitch = 0; + int velo = ev.velo(); + velo += _velocity; + velo = (velo * _compression) / 100; + if (velo > 127) + velo = 127; + if (velo < 1) // no off event + velo = 1; + int elen = (ev.lenTick() * _len)/100; + if (elen <= 0) // dont allow zero length + elen = 1; + int veloOff = ev.veloOff(); + + unsigned eframe = t->tick2frame(tick+elen); + schedEvents.insert(MidiEvent(frame, 0, ME_NOTEON, pitch, velo)); + schedEvents.insert(MidiEvent(eframe, 0, veloOff ? ME_NOTEOFF : ME_NOTEON, pitch, veloOff)); + _meter[0] += velo/2; + if (_meter[0] > 127.0f) + _meter[0] = 127.0f; + } + else { + schedEvents.insert(MidiEvent(frame, 0, ev)); + } + } + } + // + // collect controller + // + if (autoRead()) { + for (iCtrl ic = controller()->begin(); ic != controller()->end(); ++ic) { + Ctrl* c = ic->second; + iCtrlVal is = c->lowerBound(t->curTickPos); + iCtrlVal ie = c->lowerBound(t->nextTickPos); + for (iCtrlVal ic = is; ic != ie; ++ic) { + unsigned frame = t->tick2frame(ic.key()); + Event ev(Controller); + ev.setA(c->id()); + ev.setB(ic.value().i); + schedEvents.insert(MidiEvent(frame, -1, ev)); + c->setCurVal(ic.value().i); + } + } + } + } + + // + // process input routing + // + + foreach(const Route& r, *inRoutes()) { + MidiTrackBase* track = (MidiTrackBase*)r.src.track; + if (track->isMute()) + continue; + MidiEventList el; + track->getEvents(t->curTickPos, t->nextTickPos, r.src.channel, &el); + + for (iMidiEvent ie = el.begin(); ie != el.end(); ++ie) { + MidiEvent event(*ie); + unsigned eventTime = event.time(); + if (recordFlag() && audio->isRecording()) { + unsigned time = t->frame2tick(eventTime); + event.setTime(time); // set tick time + recordFifo.put(event); + } + if (event.type() == ME_NOTEON && (monitor() || recordFlag())) + addMidiMeter(event.dataB()); + if (monitor()) { + if (event.type() == ME_NOTEON) { + int pitch = event.dataA() + _transposition + song->globalPitchShift(); + if (pitch > 127) + pitch = 127; + if (pitch < 0) + pitch = 0; + event.setA(pitch); + if (!event.isNoteOff()) { + int velo = event.dataB() + _velocity; + velo = (velo * _compression) / 100; + if (velo > 127) + velo = 127; + if (velo < 1) + velo = 1; + event.setB(velo); + } + } + event.setTime(eventTime + segmentSize); + schedEvents.insert(event); + } + } + } + } + +//--------------------------------------------------------- +// getEvents +// from/to - midi ticks +//--------------------------------------------------------- + +void MidiTrack::getEvents(unsigned /*from*/, unsigned /*to*/, int, MidiEventList* dst) + { + for (iMidiEvent i = schedEvents.begin(); i != schedEvents.end(); ++i) { + dst->insert(*i); + } + } + +//--------------------------------------------------------- +// emitControllerChanged +//--------------------------------------------------------- + +void MidiTrack::emitControllerChanged(int id) + { + if (id == CTRL_PROGRAM && _useDrumMap) { + int val = ctrlVal(id).i; + MidiInstrument* mi = instrument(); + DrumMap* dm = mi->getDrumMap(val); + if (dm == 0) + dm = &gmDrumMap; + if (dm != _drumMap) + _drumMap = dm; + emit drumMapChanged(); + } + emit controllerChanged(id); + } + +//--------------------------------------------------------- +// setUseDrumMap +//--------------------------------------------------------- + +void MidiTrack::setUseDrumMap(bool val) + { + if (_useDrumMap != val) { + _useDrumMap = val; + if (_useDrumMap) { + MidiInstrument* mi = instrument(); + DrumMap* dm; + if (mi) { + int val = ctrlVal(CTRL_PROGRAM).i; + dm = mi->getDrumMap(val); + if (dm == 0) + dm = &gmDrumMap; + } + _drumMap = dm; + } + else + _drumMap = &noDrumMap; + emit drumMapChanged(); + emit useDrumMapChanged(_useDrumMap); + } + } + +//--------------------------------------------------------- +// instrument +//--------------------------------------------------------- + +MidiInstrument* MidiTrack::instrument() + { + if (_outRoutes.isEmpty()) + return genericMidiInstrument; + return _outRoutes[0].dst.track->instrument(); + } + +//--------------------------------------------------------- +// channelNo +//--------------------------------------------------------- + +int MidiTrack::channelNo() const + { + if (_outRoutes.isEmpty()) // TODO: better: remember old channel setting + return 0; + return _outRoutes[0].dst.channel; + } + +//--------------------------------------------------------- +// midiOut +//--------------------------------------------------------- + +MidiOut* MidiTrack::midiOut() + { + if (_outRoutes.isEmpty()) + return 0; + return _outRoutes[0].dst.track->midiOut(); + } + +//--------------------------------------------------------- +// setChannel +//--------------------------------------------------------- + +void MidiTrack::setChannel(int n) + { + if (_outRoutes.isEmpty()) + return; + Route r = _outRoutes[0]; + if (r.dst.channel == n) + return; + audio->msgRemoveRoute(r); + r.dst.channel = n; + audio->msgAddRoute(r); + emit channelChanged(n); + } |