From 439b879287b21dd4669074f22cfc55efc9ce9afc Mon Sep 17 00:00:00 2001 From: Werner Schweer Date: Mon, 12 Jun 2006 14:00:46 +0000 Subject: drag&drop of wave files --- muse/ChangeLog | 6 + muse/TODO | 13 +-- muse/muse/CMakeLists.txt | 1 + muse/muse/arranger/canvas.cpp | 50 ++++---- muse/muse/arranger/partdrag.cpp | 26 ++--- muse/muse/arranger/partdrag.h | 12 +- muse/muse/muse.h | 4 +- muse/muse/wave.cpp | 246 ++++++++++++++++++++++++++++++++++------ 8 files changed, 274 insertions(+), 84 deletions(-) diff --git a/muse/ChangeLog b/muse/ChangeLog index 52aea99a..828e69fe 100644 --- a/muse/ChangeLog +++ b/muse/ChangeLog @@ -1,3 +1,9 @@ +12.6. (ws) + * drag&drop of wave files with automatic sample rate conversion if + necessary +9.6. (ws) + * clicking on controller event in controller editor changed + controller value (fixed rounding error and start offset for drag) 8.6. * implemented deviceId for MidiPort 3.6. (rj) diff --git a/muse/TODO b/muse/TODO index b5fd44b7..d4979151 100644 --- a/muse/TODO +++ b/muse/TODO @@ -1,8 +1,8 @@ -----------------------------TODO------------------------------------------- (10.5.2006) - check for samplerate when loading project - - compute song len in seconds when saving project - - save project creation date/time in project + * compute song len in seconds when saving project + * save project creation date/time in project - create dialog to edit project comment BUGS @@ -41,8 +41,6 @@ FEATURES - Import MusE 0.7 song files - - NEW function with templates - CLEANUPS - Cleanup the icon/pixmap handling using Qt resource file: @@ -58,10 +56,3 @@ Wishlist tracklist for tracktypes which cannot contain Parts to make better use of screen real estate. GUI-wise a "Add Controller" button is needed. - - - the controller selector in "Add Controller" should be - implemented as a modal dialog. This allows for - presenting the list of controller in a more structured - way. - - diff --git a/muse/muse/CMakeLists.txt b/muse/muse/CMakeLists.txt index 100dc6d6..2490cdb8 100644 --- a/muse/muse/CMakeLists.txt +++ b/muse/muse/CMakeLists.txt @@ -135,6 +135,7 @@ target_link_libraries(muse ${ALSA_LIB} ${JACK_LIB} ${SNDFILE_LIB} + ${SAMPLERATE_LIB} ) install_targets ( /bin muse ) diff --git a/muse/muse/arranger/canvas.cpp b/muse/muse/arranger/canvas.cpp index c9c694f5..dc71ff66 100644 --- a/muse/muse/arranger/canvas.cpp +++ b/muse/muse/arranger/canvas.cpp @@ -19,6 +19,7 @@ //============================================================================= #include "canvas.h" +#include "al/al.h" #include "al/sig.h" #include "gconfig.h" #include "song.h" @@ -31,6 +32,8 @@ #include "part.h" #include "gui.h" +#include + static const int partLabelHeight = 13; static const int handleWidth = 5; @@ -904,10 +907,17 @@ void PartCanvas::copyPart(Part*) void PartCanvas::dragEnter(QDragEnterEvent* event) { - if (MidiPartDrag::canDecode(event) - || AudioPartDrag::canDecode(event) - || WavUriDrag::canDecode(event)) + const QMimeData* md = event->mimeData(); + if (MidiPartDrag::canDecode(md) + || AudioPartDrag::canDecode(md) + || WavUriDrag::canDecode(md)) { event->acceptProposedAction(); + } + else { + QStringList formats = md->formats(); + foreach(QString s, formats) + printf("drag format <%s>\n", s.toLatin1().data()); + } } //--------------------------------------------------------- @@ -919,14 +929,15 @@ void PartCanvas::dragMove(QDragMoveEvent* event) Part* srcPart = 0; QString filename; - if (MidiPartDrag::canDecode(event)) { - MidiPartDrag::decode(event, srcPart); + const QMimeData* md = event->mimeData(); + if (MidiPartDrag::canDecode(md)) { + MidiPartDrag::decode(md, srcPart); } - else if (AudioPartDrag::canDecode(event)) { - AudioPartDrag::decode(event, srcPart); + else if (AudioPartDrag::canDecode(md)) { + AudioPartDrag::decode(md, srcPart); } - else if (WavUriDrag::canDecode(event)) { - WavUriDrag::decode(event, &filename); + else if (WavUriDrag::canDecode(md)) { + WavUriDrag::decode(md, &filename); } else { state = S_NORMAL; @@ -996,15 +1007,15 @@ void PartCanvas::drop(QDropEvent* event) Part* srcPart = 0; QString filename; - if (MidiPartDrag::canDecode(event)) { - MidiPartDrag::decode(event, srcPart); + const QMimeData* md = event->mimeData(); + if (MidiPartDrag::canDecode(md)) { + MidiPartDrag::decode(md, srcPart); } - else if (AudioPartDrag::canDecode(event)) { - AudioPartDrag::decode(event, srcPart); + else if (AudioPartDrag::canDecode(md)) { + AudioPartDrag::decode(md, srcPart); } - else if (WavUriDrag::canDecode(event)) { - WavUriDrag::decode(event, &filename); - printf("drop <%s>\n", filename.toLatin1().data()); + else if (WavUriDrag::canDecode(md)) { + WavUriDrag::decode(md, &filename); } else return; @@ -1016,12 +1027,13 @@ void PartCanvas::drop(QDropEvent* event) return; if (srcPart == 0) { - printf("TODO: drop wave file <%s>\n", filename.toLatin1().data()); - //TODO + int tick = AL::sigmap.raster(mapxDev(pos.x()), raster()); + Pos pos(tick); + muse->importWaveToTrack(filename, track, pos); } else { PartCanvas* cw = (PartCanvas*)event->source(); - unsigned tick = AL::sigmap.raster(mapxDev(pos.x() - cw->dragOffset()), raster()); + int tick = AL::sigmap.raster(mapxDev(pos.x() - cw->dragOffset()), raster()); if (srcPart->tick() != tick || srcTrack != track) { Qt::KeyboardModifiers keyState = event->keyboardModifiers(); diff --git a/muse/muse/arranger/partdrag.cpp b/muse/muse/arranger/partdrag.cpp index 9415fc88..a09d9d3b 100644 --- a/muse/muse/arranger/partdrag.cpp +++ b/muse/muse/arranger/partdrag.cpp @@ -46,18 +46,18 @@ MidiPartDrag::MidiPartDrag(Part* part, QWidget* src) // canDecode //--------------------------------------------------------- -bool MidiPartDrag::canDecode(const QMimeSource* s) +bool MidiPartDrag::canDecode(const QMimeData* s) { - return !strcmp(s->format(0), type); + return s->hasFormat(type); } //--------------------------------------------------------- // decode //--------------------------------------------------------- -bool MidiPartDrag::decode(const QMimeSource* s, Part*& p) +bool MidiPartDrag::decode(const QMimeData* s, Part*& p) { - QByteArray a = s->encodedData(type); + QByteArray a = s->data(type); char* cp = (char*)(&p); for (unsigned i = 0; i < sizeof(p); ++i) *cp++ = a[i]; @@ -85,18 +85,18 @@ AudioPartDrag::AudioPartDrag(Part* part, QWidget* src) // canDecode //--------------------------------------------------------- -bool AudioPartDrag::canDecode(const QMimeSource* s) +bool AudioPartDrag::canDecode(const QMimeData* s) { - return !strcmp(s->format(0), type); + return s->hasFormat(type); } //--------------------------------------------------------- // decode //--------------------------------------------------------- -bool AudioPartDrag::decode(const QMimeSource* s, Part*& p) +bool AudioPartDrag::decode(const QMimeData* s, Part*& p) { - QByteArray a = s->encodedData(type); + QByteArray a = s->data(type); char* cp = (char*)(&p); for (unsigned i = 0; i < sizeof(p); ++i) *cp++ = a[i]; @@ -120,11 +120,11 @@ WavUriDrag::WavUriDrag(const QString& s, QWidget* src) // canDecode //--------------------------------------------------------- -bool WavUriDrag::canDecode(const QMimeSource* s) +bool WavUriDrag::canDecode(const QMimeData* s) { - if (strcmp(s->format(0), type)) + if (!s->hasFormat(type)) return false; - QByteArray data = s->encodedData("text/uri-list"); + QByteArray data = s->data(type); QUrl url(data); if (url.scheme() != "file") return false; @@ -144,9 +144,9 @@ bool WavUriDrag::canDecode(const QMimeSource* s) // decode //--------------------------------------------------------- -bool WavUriDrag::decode(const QMimeSource* s, QString* uri) +bool WavUriDrag::decode(const QMimeData* s, QString* uri) { - QByteArray data = s->encodedData("text/uri-list"); + QByteArray data = s->data(type); QUrl url(data); *uri = url.toLocalFile().trimmed(); return true; diff --git a/muse/muse/arranger/partdrag.h b/muse/muse/arranger/partdrag.h index 489d7f80..3f9a71c1 100644 --- a/muse/muse/arranger/partdrag.h +++ b/muse/muse/arranger/partdrag.h @@ -33,8 +33,8 @@ class MidiPartDrag : public QDrag { public: MidiPartDrag(Part*, QWidget* src); - static bool canDecode(const QMimeSource*); - static bool decode(const QMimeSource* s, Part*& p); + static bool canDecode(const QMimeData*); + static bool decode(const QMimeData* s, Part*& p); }; //--------------------------------------------------------- @@ -47,8 +47,8 @@ class AudioPartDrag : public QDrag { public: AudioPartDrag(Part*, QWidget* src); - static bool canDecode(const QMimeSource*); - static bool decode(const QMimeSource* s, Part*& p); + static bool canDecode(const QMimeData*); + static bool decode(const QMimeData* s, Part*& p); }; //--------------------------------------------------------- @@ -61,8 +61,8 @@ class WavUriDrag : public QDrag { public: WavUriDrag(const QString&, QWidget* src); - static bool canDecode(const QMimeSource*); - static bool decode(const QMimeSource* s, QString* p); + static bool canDecode(const QMimeData*); + static bool decode(const QMimeData* s, QString* p); }; diff --git a/muse/muse/muse.h b/muse/muse/muse.h index 9921f96d..711cf3d0 100644 --- a/muse/muse/muse.h +++ b/muse/muse/muse.h @@ -27,8 +27,10 @@ namespace AL { class Xml; + class Pos; }; using AL::Xml; +using AL::Pos; class Part; class PartList; @@ -277,7 +279,7 @@ class MusE : public QMainWindow, public Ui::MuseBase QWidget* bigtimeWindow(); QWidget* mixer1Window(); QWidget* mixer2Window(); - bool importWaveToTrack(QString& name, Track* track); + bool importWaveToTrack(const QString& name, Track* track, const Pos&); void selectionChanged(); diff --git a/muse/muse/wave.cpp b/muse/muse/wave.cpp index 74d71006..6b1b4079 100644 --- a/muse/muse/wave.cpp +++ b/muse/muse/wave.cpp @@ -18,6 +18,8 @@ // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. //============================================================================= +#include + #include "al/xml.h" #include "al/al.h" #include "song.h" @@ -43,6 +45,202 @@ QHash SndFile::sndFiles; QList SndFile::createdFiles; int SndFile::recFileNumber; +//--------------------------------------------------------- +// copyWaveFileToProject +// - copy wave file to project directory +// - do sample rate conversion +// +// return false on error +//--------------------------------------------------------- + +static bool copyWaveFileToProject(const QString& path) + { + QFile srcFile(path); + QFileInfo srcInfo(srcFile); + + QString dst(song->absoluteProjectPath()); + QFile dstFile(dst + "/" + srcInfo.fileName()); + if (dstFile.exists()) { + // TODO: rename file, check for identity + printf("File already exists\n"); + return false; + } + + SF_INFO sfinfoSrc; + memset(&sfinfoSrc, 0, sizeof(SF_INFO)); + SNDFILE* sfSrc = sf_open(path.toLatin1().data(), SFM_READ, &sfinfoSrc); + if (sfSrc == 0) { + printf("Cannot open source file: %s\n", strerror(errno)); + return false; + } + + int channels = sfinfoSrc.channels; + sf_count_t size = sfinfoSrc.frames; + + SF_INFO sfinfoDst; + memset(&sfinfoDst, 0, sizeof(SF_INFO)); + sfinfoDst.samplerate = AL::sampleRate; + sfinfoDst.channels = channels; + sfinfoDst.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; + SNDFILE* sfDst = sf_open(dstFile.fileName().toLatin1().data(), + SFM_WRITE, &sfinfoDst); + if (sfDst == 0) { + printf("Cannot open destination file<%s>: %s\n", + dstFile.fileName().toLatin1().data(), strerror(errno)); + return false; + } + + bool showProgress = size > (1024LL * 1024LL * 8LL); + QProgressDialog* progress = 0; + if (showProgress) { + QString label(QWidget::tr("copy \n %1\nto\n %2")); + label = label.arg(path).arg(dst); + if (sfinfoSrc.samplerate != AL::sampleRate) { + QString cs(QWidget::tr("\nconverting sample rate\n" + "from %1 to %2")); + label += cs.arg(sfinfoSrc.samplerate).arg(AL::sampleRate); + } + int csize = size / 1024; + progress = new QProgressDialog(label, QWidget::tr("Abort"), 0, csize); + progress->setValue(0); + progress->setWindowTitle("MusE"); + progress->raise(); + progress->show(); + qApp->processEvents(); + } + sf_count_t inSize = 1024LL * 512LL; + sf_count_t samplesWritten = 0LL; + + bool returnValue = true; + if (sfinfoSrc.samplerate != AL::sampleRate) { + // TODO: convertsample rate + printf("wave file has samplerate of %d, our project has %d\n", + sfinfoSrc.samplerate,AL::sampleRate); + + int srcType = SRC_SINC_MEDIUM_QUALITY; + int error; + SRC_STATE* src = src_new(srcType, channels, &error); + if (src == 0) { + printf("creating sample rate converter failed: error %d\n", + error); + return false; + } + double ratio = double(AL::sampleRate) / double(sfinfoSrc.samplerate); + src_set_ratio(src, ratio); + sf_count_t outSize = int(inSize * ratio) + 1; + float inBuffer[inSize * channels]; + float* inPtr = inBuffer; + while (size) { + float outBuffer[outSize * channels]; + + // read buffer + sf_count_t framesToRead = (inBuffer + inSize) - inPtr; + if (framesToRead > size) + framesToRead = size; + + sf_count_t nr = sf_readf_float(sfSrc, inPtr, framesToRead); + if (nr != framesToRead) { + printf("sound file read failed\n"); + src_delete(src); + returnValue = false; + break; + } + + // convert + SRC_DATA data; + data.data_in = inBuffer; + data.data_out = outBuffer; + data.input_frames = inSize; + data.output_frames = outSize; + data.end_of_input = framesToRead == size; + data.src_ratio = ratio; + + int rv = src_process(src, &data); + if (rv > 0) { + printf("error sampe rate conversion: %s\n", + src_strerror(rv)); + src_delete(src); + returnValue = false; + break; + } + + // write buffer + sf_count_t n = sf_writef_float(sfDst, outBuffer, data.output_frames_gen); + if (n != data.output_frames_gen) { + printf("sound write failed: returns %lld, should return %ld\n", + n, data.output_frames_gen); + returnValue = false; + break; + } + + int rest = (inPtr + nr - inBuffer) - data.input_frames_used; + rest *= (sizeof(float) * channels); + if (rest > 0) + memcpy(inBuffer, inBuffer + data.input_frames_used, rest); + + inPtr += framesToRead; + inPtr -= data.input_frames_used; + if (data.input_frames_used > size) + size = 0; + else + size -= data.input_frames_used; + framesToRead = data.input_frames_used; + samplesWritten += data.input_frames_used; + + if (framesToRead > size) + framesToRead = size; + if (showProgress) { + progress->setValue(samplesWritten / 1024LL); + progress->raise(); + qApp->processEvents(); + if (progress->wasCanceled()) { + returnValue = false; + break; + } + } + } + src_delete(src); + } + else { + while (size) { + float buffer[inSize * channels]; + + sf_count_t n = inSize > size ? size : inSize; + sf_count_t nn = sf_readf_float(sfSrc, buffer, n); + + if (nn != n) { + printf("sound file read failed\n"); + returnValue = false; + break; + } + // write buffer + nn = sf_writef_float(sfDst, buffer, n); + if (n != nn) { + printf("sound write failed: returns %lld, should return %lld\n", + nn, n); + returnValue = false; + break; + } + size -= n; + samplesWritten += n; + if (showProgress) { + progress->setValue(samplesWritten / 1024LL); + progress->raise(); + qApp->processEvents(); + if (progress->wasCanceled()) { + returnValue = false; + break; + } + } + } + } + if (progress) + delete progress; + sf_close(sfSrc); + sf_close(sfDst); + return returnValue; + } + //--------------------------------------------------------- // SndFile //--------------------------------------------------------- @@ -629,41 +827,20 @@ void MusE::importWave() bool MusE::importWave(const QString& name) { - WaveTrack* track = (WaveTrack*)(arranger->curTrack()); - SndFile* f = SndFile::getWave(name, false); - - if (f == 0) { - printf("import audio file failed\n"); - return true; - } - int samples = f->samples(); - track->setChannels(f->channels()); - - Part* part = new Part(track); - part->setType(AL::FRAMES); - part->setTick(song->cpos()); - part->setLenFrame(samples); - - Event event(Wave); - SndFileR sf(f); - event.setSndFile(sf); - event.setSpos(0); - event.setLenFrame(samples); - part->addEvent(event); - - part->setName(QFileInfo(name).baseName()); - audio->msgAddPart(part); - track->partListChanged(); // Updates the gui - - unsigned endTick = part->tick() + part->lenTick(); - if (song->len() < endTick) - song->setLen(endTick); - return false; + return importWaveToTrack(name, (WaveTrack*)(arranger->curTrack()), + song->cPos()); } -bool MusE::importWaveToTrack(QString& name, Track* track) +//--------------------------------------------------------- +// importWaveToTrack +//--------------------------------------------------------- + +bool MusE::importWaveToTrack(const QString& wave, Track* track, const Pos& pos) { - SndFile* f = SndFile::getWave(name, false); + if (!copyWaveFileToProject(wave)) + return true; + QFileInfo srcInfo(wave); + SndFile* f = SndFile::getWave(srcInfo.fileName(), false); if (f == 0) { printf("import audio file failed\n"); @@ -674,7 +851,7 @@ bool MusE::importWaveToTrack(QString& name, Track* track) Part* part = new Part((WaveTrack *)track); part->setType(AL::FRAMES); - part->setTick(song->cpos()); + part->setTick(pos.tick()); part->setLenFrame(samples); Event event(Wave); @@ -684,7 +861,7 @@ bool MusE::importWaveToTrack(QString& name, Track* track) event.setLenFrame(samples); part->addEvent(event); - part->setName(QFileInfo(name).baseName()); + part->setName(srcInfo.baseName()); audio->msgAddPart(part); unsigned endTick = part->tick() + part->lenTick(); if (song->len() < endTick) @@ -696,6 +873,7 @@ bool MusE::importWaveToTrack(QString& name, Track* track) // cmdChangeWave // called from GUI context //--------------------------------------------------------- + void Song::cmdChangeWave(QString original, QString tmpfile, unsigned sx, unsigned ex) { char* original_charstr = new char[original.length() + 1]; -- cgit v1.2.3