summaryrefslogtreecommitdiffstats
path: root/src/gui/seqmanager/SegmentMmapper.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/seqmanager/SegmentMmapper.cpp')
-rw-r--r--src/gui/seqmanager/SegmentMmapper.cpp562
1 files changed, 562 insertions, 0 deletions
diff --git a/src/gui/seqmanager/SegmentMmapper.cpp b/src/gui/seqmanager/SegmentMmapper.cpp
new file mode 100644
index 0000000..5cf117d
--- /dev/null
+++ b/src/gui/seqmanager/SegmentMmapper.cpp
@@ -0,0 +1,562 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ 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; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentMmapper.h"
+#include "misc/Debug.h"
+
+#include "misc/Strings.h"
+#include "base/BaseProperties.h"
+#include "base/Composition.h"
+#include "base/Event.h"
+#include "base/Exception.h"
+#include "base/NotationTypes.h"
+#include "base/RealTime.h"
+#include "base/Segment.h"
+#include "base/SegmentPerformanceHelper.h"
+#include "base/TriggerSegment.h"
+#include "document/RosegardenGUIDoc.h"
+#include "sound/MappedEvent.h"
+#include <qfile.h>
+#include <qstring.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <errno.h>
+
+
+namespace Rosegarden
+{
+
+SegmentMmapper::SegmentMmapper(RosegardenGUIDoc* doc,
+ Segment* segment, const QString& fileName)
+ : m_doc(doc),
+ m_segment(segment),
+ m_fileName(fileName),
+ m_fd( -1),
+ m_mmappedSize(0),
+ m_mmappedRegion(0),
+ m_mmappedEventBuffer((MappedEvent*)0)
+{
+ SEQMAN_DEBUG << "SegmentMmapper : " << this
+ << " trying to mmap " << m_fileName
+ << endl;
+
+ m_fd = ::open(m_fileName.latin1(), O_RDWR | O_CREAT | O_TRUNC,
+ S_IRUSR | S_IWUSR);
+ if (m_fd < 0) {
+ perror("SegmentMmapper::SegmentMmapper: Failed to open mmap file for writing");
+ SEQMAN_DEBUG << "SegmentMmapper : Couldn't open " << m_fileName
+ << endl;
+ throw Exception("Couldn't open " + qstrtostr(m_fileName));
+ }
+
+ // SEQMAN_DEBUG << "SegmentMmapper : mmap size = " << m_mmappedSize
+ // << endl;
+}
+
+void SegmentMmapper::init()
+{
+ m_mmappedSize = computeMmappedSize() + sizeof(size_t);
+
+ if (m_mmappedSize > 0) {
+ setFileSize(m_mmappedSize);
+ doMmap();
+ dump();
+ if (m_segment != 0) {
+ SEQMAN_DEBUG << "SegmentMmapper::init : mmap size = " << m_mmappedSize
+ << " for segment " << m_segment->getLabel() << endl;
+ }
+
+ } else {
+ SEQMAN_DEBUG << "SegmentMmapper::init : mmap size = 0 - skipping mmapping for now\n";
+ }
+}
+
+SegmentMmapper::~SegmentMmapper()
+{
+ SEQMAN_DEBUG << "~SegmentMmapper : " << this
+ << " unmapping " << (void*)m_mmappedRegion
+ << " of size " << m_mmappedSize
+ << endl;
+
+ if (m_mmappedRegion && m_mmappedSize)
+ ::munmap(m_mmappedRegion, m_mmappedSize);
+
+ ::close(m_fd);
+ SEQMAN_DEBUG << "~SegmentMmapper : removing " << m_fileName << endl;
+
+ QFile::remove
+ (m_fileName);
+}
+
+bool SegmentMmapper::refresh()
+{
+ bool res = false;
+
+ size_t newMmappedSize = computeMmappedSize() + sizeof(size_t);
+
+ SEQMAN_DEBUG << "SegmentMmapper::refresh() - " << getFileName()
+ << " - m_mmappedRegion = " << (void*)m_mmappedRegion
+ << " - m_mmappedEventBuffer = " << (void*)m_mmappedEventBuffer
+ << " - new size = " << newMmappedSize
+ << " - old size = " << m_mmappedSize
+ << endl;
+
+ // We can't zero out the buffer here because if the mmapped
+ // segment is being read from by the sequencer in the interval of
+ // time between the memset() and the dump(), the sequencer will go
+ // over all the zeros up to the end of the segment and reach its
+ // end, and therefore will stop playing it.
+ //
+
+ if (newMmappedSize != m_mmappedSize) {
+
+ res = true;
+
+ if (newMmappedSize == 0) {
+
+ // nothing to do, just msync and go
+ ::msync(m_mmappedRegion, m_mmappedSize, MS_ASYNC);
+ m_mmappedSize = 0;
+ return true;
+
+ } else {
+
+ setFileSize(newMmappedSize);
+ remap(newMmappedSize);
+ }
+ }
+
+ dump();
+
+ return res;
+}
+
+void SegmentMmapper::setFileSize(size_t size)
+{
+ SEQMAN_DEBUG << "SegmentMmapper::setFileSize() : setting size of "
+ << m_fileName << " to " << size
+ << " - current size = " << m_mmappedSize << endl;
+
+ if (size < m_mmappedSize) {
+
+ // Don't truncate the file here: that will cause trouble for
+ // the sequencer
+ // ftruncate(m_fd, size);
+
+ } else {
+
+ // On linux, ftruncate can enlarge a file, but this isn't specified by POSIX
+ // so go the safe way
+
+ if (size == 0) {
+ SEQMAN_DEBUG << "SegmentMmapper : size == 0 : no resize to do\n";
+ return ;
+ }
+
+ // rewind
+ ::lseek(m_fd, 0, SEEK_SET);
+
+ //
+ // enlarge the file
+ // (seek() to wanted size, then write a byte)
+ //
+ if (::lseek(m_fd, size - 1, SEEK_SET) == -1) {
+ std::cerr << "WARNING: SegmentMmapper : Couldn't lseek in "
+ << m_fileName << " to " << size << std::endl;
+ throw Exception("lseek failed");
+ }
+
+ if (::write(m_fd, "\0", 1) != 1) {
+ std::cerr << "WARNING: SegmentMmapper : Couldn't write byte in "
+ << m_fileName << std::endl;
+ throw Exception("write failed");
+ }
+
+ }
+
+
+}
+
+void SegmentMmapper::remap(size_t newsize)
+{
+ SEQMAN_DEBUG << "SegmentMmapper : remapping " << m_fileName
+ << " from size " << m_mmappedSize
+ << " to size " << newsize << endl;
+
+ if (!m_mmappedRegion) { // nothing to mremap, just mmap
+
+ SEQMAN_DEBUG << "SegmentMmapper : nothing to remap - mmap instead\n";
+ m_mmappedSize = newsize;
+ doMmap();
+
+ } else {
+
+#ifdef linux
+ void *oldBuffer = m_mmappedRegion;
+ m_mmappedRegion = (MappedEvent*)::mremap(m_mmappedRegion, m_mmappedSize,
+ newsize, MREMAP_MAYMOVE);
+ m_mmappedEventBuffer = (MappedEvent *)((size_t *)m_mmappedRegion + 1);
+
+ if (m_mmappedRegion != oldBuffer) {
+ SEQMAN_DEBUG << "NOTE: buffer moved from " << oldBuffer <<
+ " to " << (void *)m_mmappedRegion << endl;
+ }
+#else
+ ::munmap(m_mmappedRegion, m_mmappedSize);
+ m_mmappedRegion = ::mmap(0, newsize,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED, m_fd, 0);
+ m_mmappedEventBuffer = (MappedEvent *)((size_t *)m_mmappedRegion + 1);
+#endif
+
+ if (m_mmappedRegion == (void*) - 1) {
+ SEQMAN_DEBUG << QString("mremap failed : (%1) %2\n").arg(errno).arg(strerror(errno));
+ throw Exception("mremap failed");
+ }
+
+ m_mmappedSize = newsize;
+ }
+}
+
+void SegmentMmapper::doMmap()
+{
+ //
+ // mmap() file for writing
+ //
+ m_mmappedRegion = ::mmap(0, m_mmappedSize,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED, m_fd, 0);
+ m_mmappedEventBuffer = (MappedEvent *)((size_t *)m_mmappedRegion + 1);
+
+ if (m_mmappedRegion == (void*) - 1) {
+ SEQMAN_DEBUG << QString("mmap failed : (%1) %2\n").arg(errno).arg(strerror(errno));
+ throw Exception("mmap failed");
+ }
+
+ SEQMAN_DEBUG << "SegmentMmapper::doMmap() - mmap size : " << m_mmappedSize
+ << " at " << (void*)m_mmappedRegion << endl;
+
+}
+
+void SegmentMmapper::dump()
+{
+ Composition &comp = m_doc->getComposition();
+
+ RealTime eventTime;
+ RealTime duration;
+ Track* track = comp.getTrackById(m_segment->getTrack());
+
+ timeT segmentStartTime = m_segment->getStartTime();
+ timeT segmentEndTime = m_segment->getEndMarkerTime();
+ timeT segmentDuration = segmentEndTime - segmentStartTime;
+ timeT repeatEndTime = segmentEndTime;
+
+ int repeatCount = getSegmentRepeatCount();
+
+ if (repeatCount > 0)
+ repeatEndTime = m_segment->getRepeatEndTime();
+
+ MappedEvent* bufPos = m_mmappedEventBuffer;
+
+ for (int repeatNo = 0; repeatNo <= repeatCount; ++repeatNo) {
+
+ Segment *triggered = 0;
+ Segment::iterator *i = 0;
+
+ for (Segment::iterator j = m_segment->begin();
+ m_segment->isBeforeEndMarker(j) || (i && *i != triggered->end()); ) {
+
+ bool usingi = false;
+ Segment::iterator *k = &j;
+
+ if (i && *i != triggered->end() &&
+ (!m_segment->isBeforeEndMarker(j) ||
+ (**i)->getAbsoluteTime() < (*j)->getAbsoluteTime())) {
+ k = i;
+ usingi = true;
+ }
+
+ if (!usingi) { // don't permit nested triggered segments
+
+ long triggerId = -1;
+ (**k)->get
+ <Int>(BaseProperties::TRIGGER_SEGMENT_ID, triggerId);
+
+ if (triggerId >= 0) {
+
+ TriggerSegmentRec *rec =
+ comp.getTriggerSegmentRec(triggerId);
+
+ if (rec && rec->getSegment()) {
+ timeT performanceDuration =
+ SegmentPerformanceHelper(*m_segment).
+ getSoundingDuration(j);
+ if (performanceDuration > 0) {
+ mergeTriggerSegment(&triggered, *j,
+ performanceDuration, rec);
+ size_t sz = addMmappedSize(rec->getSegment());
+ size_t offset = bufPos - m_mmappedEventBuffer;
+ setFileSize(sz);
+ remap(sz);
+ bufPos = m_mmappedEventBuffer + offset;
+ }
+ }
+
+ if (triggered) {
+ if (i)
+ delete i;
+ i = new Segment::iterator
+ (triggered->findTime((*j)->getAbsoluteTime()));
+ }
+
+ // Use the next triggered event (presumably the
+ // first of the current triggered segment) instead
+ // of the one that triggered it
+
+ ++j; // whatever happens, we don't want to write this one
+
+ if (i && *i != triggered->end() &&
+ (!m_segment->isBeforeEndMarker(j) ||
+ ((**i)->getAbsoluteTime() < (*j)->getAbsoluteTime()))) {
+ k = i;
+ usingi = true;
+ } else {
+ // no joy at all
+ continue;
+ }
+ }
+ }
+
+ // Ignore rests
+ //
+ if (!(**k)->isa(Note::EventRestType)) {
+
+ SegmentPerformanceHelper helper
+ (usingi ? *triggered : *m_segment);
+
+ timeT playTime =
+ helper.getSoundingAbsoluteTime(*k) + repeatNo * segmentDuration;
+ if (playTime >= repeatEndTime)
+ break;
+
+ timeT playDuration = helper.getSoundingDuration(*k);
+
+ // Ignore notes without duration -- they're probably in a tied
+ // series but not as first note
+ //
+ if (playDuration > 0 || !(**k)->isa(Note::EventType)) {
+
+ if (playTime + playDuration > repeatEndTime)
+ playDuration = repeatEndTime - playTime;
+
+ playTime = playTime + m_segment->getDelay();
+ eventTime = comp.getElapsedRealTime(playTime);
+
+ // slightly quicker than calling helper.getRealSoundingDuration()
+ duration =
+ comp.getElapsedRealTime(playTime + playDuration) - eventTime;
+
+ eventTime = eventTime + m_segment->getRealTimeDelay();
+
+ try {
+ // Create mapped event in mmapped buffer. The
+ // instrument will be extracted from the ControlBlock
+ // by the sequencer, so we set it to zero here.
+ MappedEvent *mE = new (bufPos)
+ MappedEvent(0,
+ ***k, // three stars! what an accolade
+ eventTime,
+ duration);
+ mE->setTrackId(track->getId());
+
+ if (m_segment->getTranspose() != 0 &&
+ (**k)->isa(Note::EventType)) {
+ mE->setPitch(mE->getPitch() + m_segment->getTranspose());
+ }
+
+ ++bufPos;
+
+ } catch (...) {
+ SEQMAN_DEBUG << "SegmentMmapper::dump - caught exception while trying to create MappedEvent\n";
+ }
+ }
+ }
+
+ ++*k; // increment either i or j, whichever one we just used
+ }
+
+ delete i;
+ delete triggered;
+ }
+
+ // Store the number of events at the start of the shared memory region
+ *(size_t *)m_mmappedRegion = (bufPos - m_mmappedEventBuffer);
+
+ size_t coveredArea = (bufPos - m_mmappedEventBuffer) * sizeof(MappedEvent);
+ memset(bufPos, 0, m_mmappedSize - coveredArea - sizeof(size_t));
+
+ ::msync(m_mmappedRegion, m_mmappedSize, MS_ASYNC);
+}
+
+void
+SegmentMmapper::mergeTriggerSegment(Segment **target,
+ Event *trigger,
+ timeT evDuration,
+ TriggerSegmentRec *rec)
+{
+ if (!rec || !rec->getSegment() || rec->getSegment()->empty())
+ return ;
+ if (!*target)
+ *target = new Segment;
+
+ timeT evTime = trigger->getAbsoluteTime();
+ timeT trStart = rec->getSegment()->getStartTime();
+ timeT trEnd = rec->getSegment()->getEndMarkerTime();
+ timeT trDuration = trEnd - trStart;
+ if (trDuration == 0)
+ return ;
+
+ bool retune = false;
+ std::string timeAdjust = BaseProperties::TRIGGER_SEGMENT_ADJUST_NONE;
+
+ trigger->get
+ <Bool>
+ (BaseProperties::TRIGGER_SEGMENT_RETUNE, retune);
+
+ trigger->get
+ <String>
+ (BaseProperties::TRIGGER_SEGMENT_ADJUST_TIMES, timeAdjust);
+
+ long evPitch = rec->getBasePitch();
+ (void)trigger->get
+ <Int>(BaseProperties::PITCH, evPitch);
+ int pitchDiff = evPitch - rec->getBasePitch();
+
+ long evVelocity = rec->getBaseVelocity();
+ (void)trigger->get
+ <Int>(BaseProperties::VELOCITY, evVelocity);
+ int velocityDiff = evVelocity - rec->getBaseVelocity();
+
+ timeT offset = 0;
+ if (timeAdjust == BaseProperties::TRIGGER_SEGMENT_ADJUST_SYNC_END) {
+ offset = evDuration - trDuration;
+ }
+
+ for (Segment::iterator i = rec->getSegment()->begin();
+ rec->getSegment()->isBeforeEndMarker(i); ++i) {
+
+ timeT t = (*i)->getAbsoluteTime() - trStart;
+ timeT d = (*i)->getDuration();
+
+ if (evDuration != trDuration &&
+ timeAdjust == BaseProperties::TRIGGER_SEGMENT_ADJUST_SQUISH) {
+ t = timeT(double(t * evDuration) / double(trDuration));
+ d = timeT(double(d * evDuration) / double(trDuration));
+ }
+
+ t += evTime + offset;
+
+ if (t < evTime) {
+ if (t + d <= evTime)
+ continue;
+ else {
+ d -= (evTime - t);
+ t = evTime;
+ }
+ }
+
+ if (timeAdjust == BaseProperties::TRIGGER_SEGMENT_ADJUST_SYNC_START) {
+ if (t + d > evTime + evDuration) {
+ if (t >= evTime + evDuration)
+ continue;
+ else {
+ d = evTime + evDuration - t;
+ }
+ }
+ }
+
+ Event *newEvent = new Event(**i, t, d);
+
+ if (retune && newEvent->has(BaseProperties::PITCH)) {
+ int pitch = newEvent->get
+ <Int>(BaseProperties::PITCH) + pitchDiff;
+ if (pitch > 127)
+ pitch = 127;
+ if (pitch < 0)
+ pitch = 0;
+ newEvent->set
+ <Int>(BaseProperties::PITCH, pitch);
+ }
+
+ if (newEvent->has(BaseProperties::VELOCITY)) {
+ int velocity = newEvent->get
+ <Int>(BaseProperties::VELOCITY) + velocityDiff;
+ if (velocity > 127)
+ velocity = 127;
+ if (velocity < 0)
+ velocity = 0;
+ newEvent->set
+ <Int>(BaseProperties::VELOCITY, velocity);
+ }
+
+ (*target)->insert(newEvent);
+ }
+}
+
+unsigned int SegmentMmapper::getSegmentRepeatCount()
+{
+ int repeatCount = 0;
+
+ timeT segmentStartTime = m_segment->getStartTime();
+ timeT segmentEndTime = m_segment->getEndMarkerTime();
+ timeT segmentDuration = segmentEndTime - segmentStartTime;
+ timeT repeatEndTime = segmentEndTime;
+
+ if (m_segment->isRepeating() && segmentDuration > 0) {
+ repeatEndTime = m_segment->getRepeatEndTime();
+ repeatCount = 1 + (repeatEndTime - segmentEndTime) / segmentDuration;
+ }
+
+ return repeatCount;
+}
+
+size_t SegmentMmapper::addMmappedSize(Segment *s)
+{
+ int repeatCount = getSegmentRepeatCount();
+ return m_mmappedSize + (repeatCount + 1) * s->size() * sizeof(MappedEvent);
+}
+
+size_t SegmentMmapper::computeMmappedSize()
+{
+ if (!m_segment) return 0;
+
+ int repeatCount = getSegmentRepeatCount();
+
+ return (repeatCount + 1) * m_segment->size() * sizeof(MappedEvent);
+}
+
+}