//========================================================= // MusE // Linux Music Editor // $Id: waveview.cpp,v 1.10.2.16 2009/11/14 03:37:48 terminator356 Exp $ // (C) Copyright 2000 Werner Schweer (ws@seh.de) // // 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 #include #include #include #include #include #include #include #include #include "editgain.h" #include "globals.h" #include "wave.h" #include "waveview.h" #include "song.h" #include "event.h" #include "waveedit.h" #include "audio.h" #include "gconfig.h" #include "fastlog.h" namespace MusEGui { bool modifyWarnedYet = false; //--------------------------------------------------------- // WaveView //--------------------------------------------------------- WaveView::WaveView(MidiEditor* pr, QWidget* parent, int xscale, int yscale) : View(parent, xscale, 1) { editor = pr; setVirt(true); pos[0] = MusEGlobal::tempomap.tick2frame(MusEGlobal::song->cpos()); pos[1] = MusEGlobal::tempomap.tick2frame(MusEGlobal::song->lpos()); pos[2] = MusEGlobal::tempomap.tick2frame(MusEGlobal::song->rpos()); yScale = yscale; mode = NORMAL; selectionStart = 0; selectionStop = 0; lastGainvalue = 100; setFocusPolicy(Qt::StrongFocus); // Tim. setMouseTracking(true); setBg(QColor(192, 208, 255)); if (editor->parts()->empty()) { curPart = 0; curPartId = -1; } else { curPart = (MusECore::WavePart*)(editor->parts()->begin()->second); curPartId = curPart->sn(); } connect(MusEGlobal::song, SIGNAL(posChanged(int,unsigned,bool)), SLOT(setPos(int,unsigned,bool))); connect(MusEGlobal::song, SIGNAL(songChanged(MusECore::SongChangedFlags_t)), SLOT(songChanged(MusECore::SongChangedFlags_t))); songChanged(SC_SELECTION); } //--------------------------------------------------------- // setYScale //--------------------------------------------------------- void WaveView::setYScale(int val) { yScale = val; redraw(); } //--------------------------------------------------------- // draw //--------------------------------------------------------- void WaveView::pdraw(QPainter& p, const QRect& rr) { int x1 = rr.x(); int x2 = rr.right() + 1; if (x1 < 0) x1 = 0; if (x2 > width()) x2 = width(); int hh = height(); int h = hh/2; int y = rr.y() + h; // Added by T356. int xScale = xmag; if (xScale < 0) xScale = -xScale; for (MusECore::iPart ip = editor->parts()->begin(); ip != editor->parts()->end(); ++ip) { MusECore::WavePart* wp = (MusECore::WavePart*)(ip->second); int channels = wp->track()->channels(); int px = wp->frame(); MusECore::EventList* el = wp->events(); for (MusECore::iEvent e = el->begin(); e != el->end(); ++e) { MusECore::Event event = e->second; if (event.empty()) continue; MusECore::SndFileR f = event.sndFile(); if (f.isNull()) continue; unsigned peoffset = px + event.frame() - event.spos(); int sx, ex; sx = event.frame() + px + xScale/2; ex = sx + event.lenFrame(); sx = sx / xScale - xpos; ex = ex / xScale - xpos; if (sx < x1) sx = x1; if (ex > x2) ex = x2; int pos = (xpos + sx) * xScale + event.spos() - event.frame() - px; //printf("pos=%d xpos=%d sx=%d ex=%d xScale=%d event.spos=%d event.frame=%d px=%d\n", // pos, xpos, sx, ex, xScale, event.spos(), event.frame(), px); h = hh / (channels * 2); int cc = hh % (channels * 2) ? 0 : 1; for (int i = sx; i < ex; i++) { y = rr.y() + h; MusECore::SampleV sa[f.channels()]; f.read(sa, xScale, pos); pos += xScale; if (pos < event.spos()) continue; int selectionStartPos = selectionStart - peoffset; // Offset transformed to event coords int selectionStopPos = selectionStop - peoffset; for (int k = 0; k < channels; ++k) { int kk = k % f.channels(); int peak = (sa[kk].peak * (h - 1)) / yScale; int rms = (sa[kk].rms * (h - 1)) / yScale; if (peak > h) peak = h; if (rms > h) rms = h; QColor peak_color = QColor(Qt::darkGray); QColor rms_color = QColor(Qt::black); // Changed by T356. Reduces (but not eliminates) drawing artifacts. //if (pos > selectionStartPos && pos < selectionStopPos) { if (pos > selectionStartPos && pos <= selectionStopPos) { peak_color = QColor(Qt::lightGray); rms_color = QColor(Qt::white); // Draw inverted p.setPen(QColor(Qt::black)); p.drawLine(i, y - h + cc, i, y + h - cc ); } p.setPen(peak_color); p.drawLine(i, y - peak - cc, i, y + peak); p.setPen(rms_color); p.drawLine(i, y - rms - cc, i, y + rms); y += 2 * h; } } } } View::pdraw(p, rr); } //--------------------------------------------------------- // draw //--------------------------------------------------------- void WaveView::draw(QPainter& p, const QRect& r) { unsigned x = r.x() < 0 ? 0 : r.x(); unsigned y = r.y() < 0 ? 0 : r.y(); int w = r.width(); int h = r.height(); unsigned x2 = x + w; unsigned y2 = y + h; // // draw marker & centerline // p.setPen(Qt::red); if (pos[0] >= x && pos[0] < x2) { p.drawLine(pos[0], y, pos[0], y2); } p.setPen(Qt::blue); if (pos[1] >= x && pos[1] < x2) { p.drawLine(pos[1], y, pos[1], y2); } if (pos[2] >= x && pos[2] < x2) p.drawLine(pos[2], y, pos[2], y2); int n = 1; if(curPart) n = curPart->track()->channels(); int hn = h / n; int hh = hn / 2; for (int i = 0; i < n; ++i) { int h2 = hn * i; int center = hh + h2; p.setPen(QColor(i & i ? Qt::red : Qt::blue)); p.drawLine(x, center, x2, center); p.setPen(QColor(Qt::black)); p.drawLine(x, h2, x2, h2); } } //--------------------------------------------------------- // getCaption //--------------------------------------------------------- QString WaveView::getCaption() const { if(curPart) return QString("Part ") + curPart->name(); else return QString("Part "); } //--------------------------------------------------------- // songChanged //--------------------------------------------------------- void WaveView::songChanged(MusECore::SongChangedFlags_t flags) { // Is it simply a midi controller value adjustment? Forget it. if(flags == SC_MIDI_CONTROLLER) return; if (flags & ~SC_SELECTION) { // TODO FIXME: don't we actually only want SC_PART_*, and maybe SC_TRACK_DELETED? // (same in ecanvas.cpp) startSample = INT_MAX; endSample = 0; curPart = 0; for (MusECore::iPart p = editor->parts()->begin(); p != editor->parts()->end(); ++p) { MusECore::WavePart* part = (MusECore::WavePart*)(p->second); if (part->sn() == curPartId) curPart = part; int ssample = part->frame(); int esample = ssample + part->lenFrame(); if (ssample < startSample) { startSample = ssample; //printf("startSample = %d\n", startSample); } if (esample > endSample) { endSample = esample; //printf("endSample = %d\n", endSample); } } } if (flags & SC_CLIP_MODIFIED) { redraw(); // Boring, but the only thing possible to do } if (flags & SC_TEMPO) { setPos(0, MusEGlobal::song->cpos(), false); setPos(1, MusEGlobal::song->lpos(), false); setPos(2, MusEGlobal::song->rpos(), false); } redraw(); } //--------------------------------------------------------- // setPos // set one of three markers // idx - 0-cpos 1-lpos 2-rpos // flag - emit followEvent() //--------------------------------------------------------- void WaveView::setPos(int idx, unsigned val, bool adjustScrollbar) { val = MusEGlobal::tempomap.tick2frame(val); if (pos[idx] == val) return; int opos = mapx(pos[idx]); int npos = mapx(val); if (adjustScrollbar && idx == 0) { switch (MusEGlobal::song->follow()) { case MusECore::Song::NO: break; case MusECore::Song::JUMP: if (npos >= width()) { int ppos = val - xorg - rmapxDev(width()/4); if (ppos < 0) ppos = 0; emit followEvent(ppos); opos = mapx(pos[idx]); npos = mapx(val); } else if (npos < 0) { int ppos = val - xorg - rmapxDev(width()*3/4); if (ppos < 0) ppos = 0; emit followEvent(ppos); opos = mapx(pos[idx]); npos = mapx(val); } break; case MusECore::Song::CONTINUOUS: if (npos > (width()*5)/8) { int ppos = pos[idx] - xorg - rmapxDev(width()*5/8); if (ppos < 0) ppos = 0; emit followEvent(ppos); opos = mapx(pos[idx]); npos = mapx(val); } else if (npos < (width()*3)/8) { int ppos = pos[idx] - xorg - rmapxDev(width()*3/8); if (ppos < 0) ppos = 0; emit followEvent(ppos); opos = mapx(pos[idx]); npos = mapx(val); } break; } } int x; int w = 1; if (opos > npos) { w += opos - npos; x = npos; } else { w += npos - opos; x = opos; } pos[idx] = val; //redraw(QRect(x, 0, w, height())); redraw(QRect(x-1, 0, w+2, height())); // p4.0.28 From Canvas::draw (is otherwise identical). Fix for corruption. } //--------------------------------------------------------- // viewMousePressEvent //--------------------------------------------------------- void WaveView::viewMousePressEvent(QMouseEvent* event) { button = event->button(); unsigned x = event->x(); switch (button) { case Qt::LeftButton: if (mode == NORMAL) { // redraw and reset: if (selectionStart != selectionStop) { selectionStart = selectionStop = 0; redraw(); } mode = DRAG; dragstartx = x; selectionStart = selectionStop = x; } break; case Qt::MidButton: case Qt::RightButton: default: break; } viewMouseMoveEvent(event); } #define WHEEL_STEPSIZE 40 #define WHEEL_DELTA 120 //--------------------------------------------------------- // wheelEvent //--------------------------------------------------------- void WaveView::wheelEvent(QWheelEvent* ev) { int keyState = ev->modifiers(); bool shift = keyState & Qt::ShiftModifier; bool ctrl = keyState & Qt::ControlModifier; if (shift) { // scroll vertically int delta = -ev->delta() / WHEEL_DELTA; int xpixelscale = 5*MusECore::fast_log10(rmapxDev(1)); if (xpixelscale <= 0) xpixelscale = 1; int scrollstep = WHEEL_STEPSIZE * (delta); ///if (ev->state() == Qt::ShiftModifier) // if (((QInputEvent*)ev)->modifiers() == Qt::ShiftModifier) scrollstep = scrollstep / 10; int newXpos = xpos + xpixelscale * scrollstep; if (newXpos < 0) newXpos = 0; //setYPos(newYpos); emit horizontalScroll((unsigned)newXpos); } else if (ctrl) { // zoom horizontally if (ev->delta()>0) emit horizontalZoomIn(); else emit horizontalZoomOut(); } else { // scroll horizontally emit mouseWheelMoved(ev->delta() / 10); } } //--------------------------------------------------------- // viewMouseReleaseEvent //--------------------------------------------------------- void WaveView::viewMouseReleaseEvent(QMouseEvent* /*event*/) { button = Qt::NoButton; if (mode == DRAG) { mode = NORMAL; //printf("selectionStart=%d selectionStop=%d\n", selectionStart, selectionStop); } } //--------------------------------------------------------- // viewMouseMoveEvent //--------------------------------------------------------- void WaveView::viewMouseMoveEvent(QMouseEvent* event) { unsigned x = event->x(); emit timeChanged(x); int i; switch (button) { case Qt::LeftButton: i = 0; if (mode == DRAG) { if (x < dragstartx) { selectionStart = x; selectionStop = dragstartx; } else { selectionStart = dragstartx; selectionStop = x; } } break; case Qt::MidButton: i = 1; break; case Qt::RightButton: if ((MusEGlobal::config.rangeMarkerWithoutMMB) && (event->modifiers() & Qt::ControlModifier)) i = 1; else i = 2; break; default: return; } MusECore::Pos p(MusEGlobal::tempomap.frame2tick(x), true); MusEGlobal::song->setPos(i, p); } //--------------------------------------------------------- // range // returns range in samples //--------------------------------------------------------- void WaveView::range(int* s, int *e) { MusECore::PartList* lst = editor->parts(); if(lst->empty()) { *s = 0; *e = MusEGlobal::tempomap.tick2frame(MusEGlobal::song->len()); return; } int ps = MusEGlobal::song->len(), pe = 0; int tps, tpe; for(MusECore::iPart ip = lst->begin(); ip != lst->end(); ++ip) { tps = ip->second->tick(); if(tps < ps) ps = tps; tpe = tps + ip->second->lenTick(); if(tpe > pe) pe = tpe; } *s = MusEGlobal::tempomap.tick2frame(ps); *e = MusEGlobal::tempomap.tick2frame(pe); } //--------------------------------------------------------- // cmd //--------------------------------------------------------- void WaveView::cmd(int n) { int modifyoperation = -1; double paramA = 0.0; switch(n) { case WaveEdit::CMD_SELECT_ALL: if (!editor->parts()->empty()) { MusECore::iPart iBeg = editor->parts()->begin(); MusECore::iPart iEnd = editor->parts()->end(); iEnd--; MusECore::WavePart* beg = (MusECore::WavePart*) iBeg->second; MusECore::WavePart* end = (MusECore::WavePart*) iEnd->second; selectionStart = beg->frame(); selectionStop = end->frame() + end->lenFrame(); redraw(); } break; case WaveEdit::CMD_EDIT_EXTERNAL: modifyoperation = EDIT_EXTERNAL; break; case WaveEdit::CMD_SELECT_NONE: selectionStart = selectionStop = 0; redraw(); break; case WaveEdit::CMD_EDIT_COPY: modifyoperation = COPY; break; case WaveEdit::CMD_EDIT_CUT: modifyoperation = CUT; break; case WaveEdit::CMD_EDIT_PASTE: modifyoperation = PASTE; break; case WaveEdit::CMD_MUTE: modifyoperation = MUTE; break; case WaveEdit::CMD_NORMALIZE: modifyoperation = NORMALIZE; break; case WaveEdit::CMD_FADE_IN: modifyoperation = FADE_IN; break; case WaveEdit::CMD_FADE_OUT: modifyoperation = FADE_OUT; break; case WaveEdit::CMD_REVERSE: modifyoperation = REVERSE; break; case WaveEdit::CMD_GAIN_FREE: { EditGain* editGain = new EditGain(this, lastGainvalue); if (editGain->exec() == QDialog::Accepted) { lastGainvalue = editGain->getGain(); modifyoperation = GAIN; paramA = (double)lastGainvalue / 100.0; } delete editGain; } break; case WaveEdit::CMD_GAIN_200: modifyoperation = GAIN; paramA = 2.0; break; case WaveEdit::CMD_GAIN_150: modifyoperation = GAIN; paramA = 1.5; break; case WaveEdit::CMD_GAIN_75: modifyoperation = GAIN; paramA = 0.75; break; case WaveEdit::CMD_GAIN_50: modifyoperation = GAIN; paramA = 0.5; break; case WaveEdit::CMD_GAIN_25: modifyoperation = GAIN; paramA = 0.25; break; default: break; } if (modifyoperation != -1) { if (selectionStart == selectionStop && modifyoperation!=PASTE) { printf("No selection. Ignoring\n"); //@!TODO: Disable menu options when no selection QMessageBox::information(this, QString("MusE"), QWidget::tr("No selection. Ignoring")); return; } //if(!modifyWarnedYet) //{ // modifyWarnedYet = true; // if(QMessageBox::warning(this, QString("Muse"), // tr("Warning! Muse currently operates directly on the sound file.\n" // "Undo is supported, but NOT after exit, WITH OR WITHOUT A SAVE!\n" // "If you are stuck, try deleting the associated .wca file and reloading."), tr("&Ok"), tr("&Cancel"), // QString::null, 0, 1 ) != 0) // return; //} modifySelection(modifyoperation, selectionStart, selectionStop, paramA); } } //--------------------------------------------------------- // getSelection //--------------------------------------------------------- MusECore::WaveSelectionList WaveView::getSelection(unsigned startpos, unsigned stoppos) { MusECore::WaveSelectionList selection; for (MusECore::iPart ip = editor->parts()->begin(); ip != editor->parts()->end(); ++ip) { MusECore::WavePart* wp = (MusECore::WavePart*)(ip->second); unsigned part_offset = wp->frame(); MusECore::EventList* el = wp->events(); //printf("eventlist length=%d\n",el->size()); for (MusECore::iEvent e = el->begin(); e != el->end(); ++e) { MusECore::Event event = e->second; if (event.empty()) continue; MusECore::SndFileR file = event.sndFile(); if (file.isNull()) continue; unsigned event_offset = event.frame() + part_offset; unsigned event_startpos = event.spos(); unsigned event_length = event.lenFrame() + event.spos(); unsigned event_end = event_offset + event_length; //printf("startpos=%d stoppos=%d part_offset=%d event_offset=%d event_startpos=%d event_length=%d event_end=%d\n", startpos, stoppos, part_offset, event_offset, event_startpos, event_length, event_end); if (!(event_end <= startpos || event_offset > stoppos)) { int tmp_sx = startpos - event_offset + event_startpos; int tmp_ex = stoppos - event_offset + event_startpos; unsigned sx; unsigned ex; tmp_sx < (int)event_startpos ? sx = event_startpos : sx = tmp_sx; tmp_ex > (int)event_length ? ex = event_length : ex = tmp_ex; //printf("Event data affected: %d->%d filename:%s\n", sx, ex, file.name().toLatin1().constData()); MusECore::WaveEventSelection s; s.file = file; s.startframe = sx; s.endframe = ex+1; //printf("sx=%d ex=%d\n",sx,ex); selection.push_back(s); } } } return selection; } //--------------------------------------------------------- // modifySelection //--------------------------------------------------------- void WaveView::modifySelection(int operation, unsigned startpos, unsigned stoppos, double paramA) { MusEGlobal::song->startUndo(); if (operation == PASTE) { // we need to redefine startpos and stoppos if (copiedPart =="") return; MusECore::SndFile pasteFile(copiedPart); pasteFile.openRead(); startpos = pos[0]; stoppos = startpos+ pasteFile.samples(); // possibly this is wrong if there are tempo changes pasteFile.close(); pos[0]=stoppos; } MusECore::WaveSelectionList selection = getSelection(startpos, stoppos); for (MusECore::iWaveSelection i = selection.begin(); i != selection.end(); i++) { MusECore::WaveEventSelection w = *i; MusECore::SndFileR& file = w.file; unsigned sx = w.startframe; unsigned ex = w.endframe; unsigned file_channels = file.channels(); QString tmpWavFile = QString::null; if (!MusEGlobal::getUniqueTmpfileName("tmp_musewav",".wav", tmpWavFile)) { break; } MusEGlobal::audio->msgIdle(true); // Not good with playback during operations MusECore::SndFile tmpFile(tmpWavFile); tmpFile.setFormat(file.format(), file_channels, file.samplerate()); if (tmpFile.openWrite()) { MusEGlobal::audio->msgIdle(false); printf("Could not open temporary file...\n"); break; } // // Write out data that will be changed to temp file // unsigned tmpdatalen = ex - sx; off_t tmpdataoffset = sx; float* tmpdata[file_channels]; for (unsigned i=0; icmdChangeWave(file.dirPath() + "/" + file.name(), tmpWavFile, sx, ex); MusEGlobal::audio->msgIdle(false); // Not good with playback during operations } MusEGlobal::song->endUndo(SC_CLIP_MODIFIED); redraw(); } //--------------------------------------------------------- // copySelection //--------------------------------------------------------- void WaveView::copySelection(unsigned file_channels, float** tmpdata, unsigned length, bool blankData, unsigned format, unsigned sampleRate) { if (copiedPart!="") { QFile::remove(copiedPart); } if (!MusEGlobal::getUniqueTmpfileName("tmp_musewav",".wav", copiedPart)) { return; } MusECore::SndFile tmpFile(copiedPart); tmpFile.setFormat(format, file_channels, sampleRate); tmpFile.openWrite(); tmpFile.write(file_channels, tmpdata, length); tmpFile.close(); if (blankData) { // Set everything to 0! for (unsigned i=0; i loudest) loudest = data[i][j]; } } double scale = 0.99 / (double)loudest; for (unsigned i=0; iAudio:External Waveditor\nis set to a valid editor.")); } if (exttmpFile.openRead()) { printf("Could not reopen temporary file!\n"); } else { // Re-read file again exttmpFile.seek(0, 0); size_t sz = exttmpFile.readWithHeap(file_channels, tmpdata, tmpdatalen); if (sz != tmpdatalen) { // File must have been shrunken - not good. Alert user. QMessageBox::critical(this, tr("MusE - file size changed"), tr("When editing in external editor - you should not change the filesize\nsince it must fit the selected region.\n\nMissing data is muted")); for (unsigned i=0; i