Ejemplo n.º 1
0
void CueStack_Test::write()
{
    QList<Universe*> ua;
    ua.append(new Universe(0, new GrandMaster()));

    CueStack cs(m_doc);

    Cue cue("One");
    cue.setValue(0, 255);
    cue.setFadeInSpeed(100);
    cue.setFadeOutSpeed(200);
    cue.setDuration(300);
    cs.appendCue(cue);

    cue = Cue("Two");
    cue.setValue(1, 255);
    cue.setFadeInSpeed(100);
    cue.setFadeOutSpeed(200);
    cue.setDuration(300);
    cs.appendCue(cue);

    cs.preRun();
    QVERIFY(cs.m_fader != NULL);

    cs.write(ua);
    QCOMPARE(cs.currentIndex(), -1);

    cs.start();
    cs.write(ua);
    QCOMPARE(cs.currentIndex(), -1);

    cs.nextCue();
    QCOMPARE(cs.currentIndex(), -1);
    cs.write(ua);
    QCOMPARE(cs.currentIndex(), 0);
    QCOMPARE(cs.m_fader->channels().size(), 1);
    FadeChannel fc;
    fc.setChannel(0);
    QCOMPARE(cs.m_fader->channels()[fc].channel(), uint(0));
    QCOMPARE(cs.m_fader->channels()[fc].target(), uchar(255));

    cs.previousCue();
    QCOMPARE(cs.currentIndex(), 0);
    cs.write(ua);
    QCOMPARE(cs.currentIndex(), 1);

    fc.setChannel(0);
    QCOMPARE(cs.m_fader->channels()[fc].channel(), uint(0));
    QCOMPARE(cs.m_fader->channels()[fc].target(), uchar(0));
    fc.setChannel(1);
    QCOMPARE(cs.m_fader->channels()[fc].channel(), uint(1));
    QCOMPARE(cs.m_fader->channels()[fc].target(), uchar(255));

    MasterTimer mt(m_doc);
    cs.postRun(&mt);
}
Ejemplo n.º 2
0
//- LISP API -
// playback-set-file (string)fileName -> t/nil
base::cell_t set_file(base::lisp &gl, base::cell_t c, base::cells_t &) {
	if (base::lisp::validate(c, base::cell::list(1), base::cell::typeString)) {
		const auto &fname = c + 1;

		// stop current playback
		ts.stop();
		ts.setSource(nullptr);
		frs = nullptr;
		AudioFormatReader *r;

		// ectract CUE information (if any)
		std::regex cue("^(.*):(\\d+):(\\d+)$");
		std::smatch result;
		std::regex_search(fname->s, result, cue);
		if (result.size() == 4) {
			// is cue
			int32 start = base::fromStr<int32>(result[2].str());
			int32 end = base::fromStr<int32>(result[3].str());
			int32 duration = end - start;

			AudioFormatReader *tr = fm.createReaderFor(File(result[1].str()));

			// start, end are in frames (1 frame = 1/75 second) - convert to sample
			float samplesInOneSecond = tr->sampleRate; // AudioSubsectionReader will handle channels count
			float startSecond = (float)start / 75.0f;
			float durationSecond = (float)duration / 75.0f;
			float startSample = startSecond * samplesInOneSecond;
			float durationSamples = durationSecond * samplesInOneSecond;

			// some CUE may have 0 length (play to end)
			if (end <= start)
				durationSamples = tr->lengthInSamples;

			r = new AudioSubsectionReader(tr, (int)startSample, (int)durationSamples, true);
		}
		else {
			// regular file
			r = fm.createReaderFor(File(fname->s));
		}

		if (r) {
			frs = new AudioFormatReaderSource(r, true);
			ts.setSource(frs, 32768, &thread, r->sampleRate);
			return gl.t();
		}
		gl.signalError(base::strs("file not found or file format not supported: ", fname->s));
		return gl.nil();
	}
	gl.signalError("playback-set-file: invalid arguments, expected (string)");
	return gl.nil();
}
Ejemplo n.º 3
0
void firstAndOthers(const string &line, string &first, string &others)
{ // skip whitespace before first
  string::const_iterator curr(line.begin()), cue(line.end());
  for(; (curr!=cue) && (*curr<=' '); curr++);

  // find the end of first
  string::const_iterator fbeg=curr;
  for(; (curr!=cue) && (*curr>' '); curr++);
  first=string(fbeg, curr);

  // skip the whitespace before others
  for(; (curr!=cue) && (*curr<=' '); curr++);
  others=string(curr, cue);
}
Ejemplo n.º 4
0
SongList LibraryWatcher::ScanNewFile(const QString& file, const QString& path,
                                     const QString& matching_cue,
                                     QSet<QString>* cues_processed) {
  SongList song_list;

  uint matching_cue_mtime = GetMtimeForCue(matching_cue);
  // if it's a cue - create virtual tracks
  if (matching_cue_mtime) {
    // don't process the same cue many times
    if (cues_processed->contains(matching_cue)) return song_list;

    QFile cue(matching_cue);
    cue.open(QIODevice::ReadOnly);

    // Ignore FILEs pointing to other media files. Also, watch out for incorrect
    // media files. Playlist parser for CUEs considers every entry in sheet
    // valid and we don't want invalid media getting into library!
    QString file_nfd = file.normalized(QString::NormalizationForm_D);
    for (const Song& cue_song : cue_parser_->Load(&cue, matching_cue, path)) {
      if (cue_song.url().toLocalFile().normalized(QString::NormalizationForm_D) == file_nfd) {
        if (TagReaderClient::Instance()->IsMediaFileBlocking(file)) {
          song_list << cue_song;
        }
      }
    }

    if (!song_list.isEmpty()) {
      *cues_processed << matching_cue;
    }

    // it's a normal media file
  } else {
    Song song;
    TagReaderClient::Instance()->ReadFileBlocking(file, &song);

    if (song.is_valid()) {
      song_list << song;
    }
  }

  return song_list;
}
Ejemplo n.º 5
0
void LibraryWatcher::UpdateCueAssociatedSongs(const QString& file,
                                              const QString& path,
                                              const QString& matching_cue,
                                              const QString& image,
                                              ScanTransaction* t) {
  QFile cue(matching_cue);
  cue.open(QIODevice::ReadOnly);

  SongList old_sections = backend_->GetSongsByUrl(QUrl::fromLocalFile(file));

  QHash<quint64, Song> sections_map;
  for (const Song& song : old_sections) {
    sections_map[song.beginning_nanosec()] = song;
  }

  QSet<int> used_ids;

  // update every song that's in the cue and library
  for (Song cue_song : cue_parser_->Load(&cue, matching_cue, path)) {
    cue_song.set_directory_id(t->dir());

    Song matching = sections_map[cue_song.beginning_nanosec()];
    // a new section
    if (!matching.is_valid()) {
      t->new_songs << cue_song;
      // changed section
    } else {
      PreserveUserSetData(file, image, matching, &cue_song, t);
      used_ids.insert(matching.id());
    }
  }

  // sections that are now missing
  for (const Song& matching : old_sections) {
    if (!used_ids.contains(matching.id())) {
      t->deleted_songs << matching;
    }
  }
}
Ejemplo n.º 6
0
//- LISP API -
// playback-set-file (string)fileName -> t/nil
base::cell_t set_file(base::lisp &gl, base::cell_t c, base::cells_t &) {
    if (base::lisp::validate(c, base::cell::list(1), base::cell::typeString)) {
        const auto &fname = c + 1;

        // stop current playback
        ts.stop();
        ts.setSource(nullptr);
        frs = nullptr;

        AudioFormatReader *r;

        // ectract CUE information (if any)
        std::regex cue("^(.*):(\\d+):(\\d+)$");
        std::smatch result;
        std::regex_search(fname->s, result, cue);
        if (result.size() == 4) {
            // is cue
            int32 start = base::fromStr<int32>(result[2].str());
            int32 end = base::fromStr<int32>(result[3].str());
            AudioFormatReader *tr = fm.createReaderFor(File(result[1].str()));
            r = new AudioSubsectionReader(tr, start, end - start, true);
        }
        else {
            // regular file
            r = fm.createReaderFor(File(fname->s));
        }

        if (r) {
            frs = new AudioFormatReaderSource(r, true);
            ts.setSource(frs, 32768, &thread, r->sampleRate);
            return gl.t();
        }
        gl.signalError(base::strs("file not found or file format not supported: ", fname->s));
        return gl.nil();
    }
    gl.signalError("playback-set-file: invalid arguments, expected (string)");
    return gl.nil();
}
Ejemplo n.º 7
0
/************************************************
 Split audio file to temporary dir
 ************************************************/
void Splitter::doRun()
{
    QStringList args;
    args << "split";
    args << "-w";
    args << "-O" << "always";
    args << "-n" << "%04d";
    args << "-t" << mFilePrefix +"%n";
    args << "-d" << mWorkDir;
    args << disk()->audioFileName();
    //qDebug() << args;

    QString shntool = settings->value(Settings::Prog_Shntool).toString();
    mProcess = new QProcess();
    mProcess->setReadChannel(QProcess::StandardError);

    mProcess->start(shntool, args);
    mProcess->waitForStarted();

    sendCueData();
    mProcess->closeWriteChannel();

    parseOut();
    mProcess->waitForFinished(-1);

    QProcess *proc = mProcess;
    mProcess = 0;
    delete proc;

    if (OutFormat::currentFormat()->createCue())
    {
        CueCreator cue(disk());
        cue.setHasPregapFile(mPreGapExists);
        if (!cue.write())
            error(disk()->track(0), cue.errorString());
    }
}
Ejemplo n.º 8
0
Playlist::Entries FFDemux::fetchTracks(const QString &url, bool &ok)
{
    if (url.contains("://{") || !url.startsWith("file://"))
        return {};

    const auto createFmtCtx = [&] {
        FormatContext *fmtCtx = new FormatContext;
        {
            QMutexLocker mL(&mutex);
            formatContexts.append(fmtCtx);
        }
        return fmtCtx;
    };
    const auto destroyFmtCtx = [&](FormatContext *fmtCtx) {
        {
            QMutexLocker mL(&mutex);
            const int idx = formatContexts.indexOf(fmtCtx);
            if (idx > -1)
                formatContexts.remove(idx);
        }
        delete fmtCtx;
    };

    if (url.endsWith(".cue", Qt::CaseInsensitive))
    {
        QFile cue(url.mid(7));
        if (cue.size() <= 0xFFFF && cue.open(QFile::ReadOnly | QFile::Text))
        {
            QList<QByteArray> data = Functions::textWithFallbackEncoding(cue.readAll()).split('\n');
            QString title, performer, audioUrl;
            double index0 = -1.0, index1 = -1.0;
            QHash<int, QPair<double, double>> indexes;
            Playlist::Entries entries;
            Playlist::Entry entry;
            int track = -1;
            const auto maybeFlushTrack = [&](int prevTrack) {
                if (track <= 0)
                    return;
                const auto cutFromQuotation = [](QString &str) {
                    const int idx1 = str.indexOf('"');
                    const int idx2 = str.lastIndexOf('"');
                    if (idx1 > -1 && idx2 > idx1)
                        str = str.mid(idx1 + 1, idx2 - idx1 - 1);
                    else
                        str.clear();
                };
                cutFromQuotation(title);
                cutFromQuotation(performer);
                if (!title.isEmpty() && !performer.isEmpty())
                    entry.name = performer + " - " + title;
                else if (title.isEmpty() && !performer.isEmpty())
                    entry.name = performer;
                else if (!title.isEmpty() && performer.isEmpty())
                    entry.name = title;
                if (entry.name.isEmpty())
                {
                    if (entry.parent == 1)
                        entry.name = tr("Track") + " " + QString::number(prevTrack);
                    else
                        entry.name = Functions::fileName(entry.url, false);
                }
                title.clear();
                performer.clear();
                if (prevTrack > 0)
                {
                    if (index0 <= 0.0)
                        index0 = index1; // "INDEX 00" doesn't exist, use "INDEX 01"
                    indexes[prevTrack].first = index0;
                    indexes[prevTrack].second = index1;
                    if (entries.count() <= prevTrack)
                        entries.resize(prevTrack + 1);
                    entries[prevTrack] = entry;
                }
                else
                {
                    entries.prepend(entry);
                }
                entry = Playlist::Entry();
                entry.parent = 1;
                entry.url = audioUrl;
                index0 = index1 = -1.0;
            };
            const auto parseTime = [](const QByteArray &time) {
                int m = 0, s = 0, f = 0;
                if (sscanf(time.constData(), "%2d:%2d:%2d", &m, &s, &f) == 3)
                    return m * 60.0 + s + f / 75.0;
                return -1.0;
            };
            entry.url = url;
            entry.GID = 1;
            for (QByteArray &line : data)
            {
                line = line.trimmed();
                if (track < 0)
                {
                    if (line.startsWith("TITLE "))
                        title = line;
                    else if (line.startsWith("PERFORMER "))
                        performer = line;
                    else if (line.startsWith("FILE "))
                    {
                        const int idx = line.lastIndexOf('"');
                        if (idx > -1)
                        {
                            audioUrl = line.mid(6, idx - 6);
                            if (!audioUrl.isEmpty())
                                audioUrl.prepend(Functions::filePath(url));
                        }
                    }
                }
                else if (line.startsWith("FILE "))
                {
                    // QMPlay2 supports CUE files which uses only single audio file
                    return {};
                }
                if (line.startsWith("TRACK "))
                {
                    if (entries.isEmpty() && audioUrl.isEmpty())
                        break;
                    if (line.endsWith(" AUDIO"))
                    {
                        const int prevTrack = track;
                        track = line.mid(6, 2).toInt();
                        if (track < 99)
                            maybeFlushTrack(prevTrack);
                        else
                            track = 0;
                    }
                    else
                    {
                        track = 0;
                    }
                }
                else if (track > 0)
                {
                    if (line.startsWith("TITLE "))
                        title = line;
                    else if (line.startsWith("PERFORMER "))
                        performer = line;
                    else if (line.startsWith("INDEX 00 "))
                        index0 = parseTime(line.mid(9));
                    else if (line.startsWith("INDEX 01 "))
                        index1 = parseTime(line.mid(9));
                }
            }
            maybeFlushTrack(track);
            for (int i = entries.count() - 1; i >= 1; --i)
            {
                Playlist::Entry &entry = entries[i];
                const bool lastItem = (i == entries.count() - 1);
                const double start = indexes.value(i).second;
                const double end = indexes.value(i + 1, {-1.0, -1.0}).first;
                if (entry.url.isEmpty() || start < 0.0 || (end <= 0.0 && !lastItem))
                {
                    entries.removeAt(i);
                    continue;
                }
                const QString param = QString("CUE:%1:%2").arg(start).arg(end);
                if (lastItem && end < 0.0) // Last entry doesn't have specified length in CUE file
                {
                    FormatContext *fmtCtx = createFmtCtx();
                    if (fmtCtx->open(entry.url, param))
                        entry.length = fmtCtx->length();
                    destroyFmtCtx(fmtCtx);
                    if (abortFetchTracks)
                    {
                        ok = false;
                        return {};
                    }
                }
                else
                {
                    entry.length = end - start;
                }
                entry.url = QString("%1://{%2}%3").arg(DemuxerName, entry.url, param);
            }
            if (!entries.isEmpty())
                return entries;
        }
    }

    OggHelper oggHelper(url.mid(7), abortFetchTracks);
    if (oggHelper.io)
    {
        Playlist::Entries entries;
        int i = 0;
        for (const OggHelper::Chain &chains : oggHelper.getOggChains(ok))
        {
            const QString param = QString("OGG:%1:%2:%3").arg(++i).arg(chains.first).arg(chains.second);

            FormatContext *fmtCtx = createFmtCtx();
            if (fmtCtx->open(url, param))
            {
                Playlist::Entry entry;
                entry.url = QString("%1://{%2}%3").arg(DemuxerName, url, param);
                entry.name = fmtCtx->title();
                entry.length = fmtCtx->length();
                entries.append(entry);
            }
            destroyFmtCtx(fmtCtx);
            if (abortFetchTracks)
            {
                ok = false;
                return {};
            }
        }
        if (ok && !entries.isEmpty())
        {
            for (int i = 0; i < entries.count(); ++i)
                entries[i].parent = 1;
            Playlist::Entry entry;
            entry.name = Functions::fileName(url, false);
            entry.url = url;
            entry.GID = 1;
            entries.prepend(entry);
            return entries;
        }
    }

    return {};
}
Ejemplo n.º 9
0
void Splitter::sendCueData()
{
    bool cdQuality = disk()->audioFile()->isCdQuality();
    OutFormat *format = OutFormat::currentFormat();

    bool fakeIndex = (format->createCue() and
                      format->preGapType() == OutFormat::PreGapAddToFirstTrack);

    QFile cue(disk()->cueFile());
    cue.open(QFile::ReadOnly);

    int trackNum = 0;
    QByteArray line;
    while (!cue.atEnd())
    {
        line = cue.readLine();
        QString str = QString(line).trimmed();
        QString key = str.section(' ', 0, 0).toUpper();

        if (key == "TRACK")
        {
            trackNum++;
            mProcess->write(line);
            continue;
        }

        if (key == "INDEX")
        {
            int indexNum = str.section(' ', 1, 1).toInt();

            if (fakeIndex && trackNum == 1)
            {
                if (indexNum == 1)
                {
                    if (cdQuality)
                        mProcess->write("  INDEX 01 00:00:00\n");
                    else
                        mProcess->write("  INDEX 01 00:00.000\n");
                }
            }
            else
            {
                CueIndex index(str.section(' ', 2));
                mProcess->write(QString("  INDEX %1 %2\n")
                                .arg(indexNum, 2, 10, QChar('0'))
                                .arg(index.toString(cdQuality))
                                .toLocal8Bit());
            }
            continue;
        }

        if (key == "FILE")
        {
            mProcess->write(line);
            continue;
        }

    }

    cue.close();
}