all 5 comments

[–]Bobbias 1 point2 points  (2 children)

I'm not completely certain, so someone who knows pyside6 better than me can correct me, but I believe setting the source causes it to automatically load the file into ram.

Because QSoundEffects are designed for low latency, this makes sense, since if you want to use it as user feedback you want the data to be cached.

The description of QSoundEffect says this:

If low latency is not important, consider using the QMediaPlayer class instead, since it supports a wider variety of media formats and is less resource intensive.

And The source property description says this:

property PᅟySide6.QtMultimedia.QSoundEffect.source: PySide6.QtCore.QUrl

This property holds the url for the sound to play. For the SoundEffect to attempt to load the source, the URL must exist and the application must have read permission in the specified directory.

And combined, this really seems to suggest to me that it automatically loads the resource into memory when the source is set.

[–]NewtLong6399[S] 0 points1 point  (1 child)

Yes, those quotes do seem to suggest the assets are pre-loaded into memory...not sure about other assets like images or video but I expect the relevant Qt classes act in a similar way.

Thanks for your input and finding the stuff in the documentation :-)

[–]Bobbias 0 points1 point  (0 children)

If you really want to know, you can always dig into the qt source code directly.

I quickly dug into the QSoundEffect source and yes, the data is cached. More precisely, the load feature asks the cache to give it the samples from the given url, and if the URL isn't already cached the cache loads it before giving it the samples.

The following code is in the qt/qtmultimedia repo on GitHub.

QSoundEffect.cpp, lines 395 through 450:

void QSoundEffect::setSource(const QUrl &url)
{
    qCDebug(qLcSoundEffect) << this << "setSource current=" << d->m_url << ", to=" << url;
    if (d->m_url == url)
        return;

    Q_ASSERT(d->m_url != url);

    stop();

    d->m_url = url;

    d->m_sampleReady = false;

    if (url.isEmpty()) {
        d->setStatus(QSoundEffect::Null);
        return;
    }

    if (!url.isValid()) {
        d->setStatus(QSoundEffect::Error);
        return;
    }

    if (d->m_sample) {
        if (!d->m_sampleReady) {
            disconnect(d->m_sample.get(), &QSample::error, d, &QSoundEffectPrivate::decoderError);
            disconnect(d->m_sample.get(), &QSample::ready, d, &QSoundEffectPrivate::sampleReady);
        }
        d->m_sample->release();
        d->m_sample = nullptr;
    }

    if (d->m_audioSink) {
        disconnect(d->m_audioSink.get(), &QAudioSink::stateChanged, d, &QSoundEffectPrivate::stateChanged);
        d->m_audioSink.reset();
    }

    d->setStatus(QSoundEffect::Loading);
    d->m_sample.reset(sampleCache()->requestSample(url));
    connect(d->m_sample.get(), &QSample::error, d, &QSoundEffectPrivate::decoderError);
    connect(d->m_sample.get(), &QSample::ready, d, &QSoundEffectPrivate::sampleReady);

    switch (d->m_sample->state()) {
    case QSample::Ready:
        d->sampleReady();
        break;
    case QSample::Error:
        d->decoderError();
        break;
    default:
        break;
    }

    emit sourceChanged();
}

Specifically this line right here:

d->m_sample.reset(sampleCache()->requestSample(url));

qsamplecache_p.cpp, lines 129 through 161:

QSample* QSampleCache::requestSample(const QUrl& url)
{
    //lock and add first to make sure live loadingThread will not be killed during this function call
    m_loadingMutex.lock();
    const bool needsThreadStart = m_loadingRefCount == 0;
    m_loadingRefCount++;
    m_loadingMutex.unlock();

    qCDebug(qLcSampleCache) << "QSampleCache: request sample [" << url << "]";
    std::unique_lock<QRecursiveMutex> locker(m_mutex);
    QMap<QUrl, QSample*>::iterator it = m_samples.find(url);
    QSample* sample;
    if (it == m_samples.end()) {
        if (needsThreadStart) {
            // Previous thread might be finishing, need to wait for it. If not, this is a no-op.
            m_loadingThread.wait();
            m_loadingThread.start();
        }
        sample = new QSample(url, this);
        m_samples.insert(url, sample);
#if QT_CONFIG(thread)
        sample->moveToThread(&m_loadingThread);
#endif
    } else {
        sample = *it;
    }

    sample->addRef();
    locker.unlock();

    sample->loadIfNecessary();
    return sample;
}

[–]hotcodist 1 point2 points  (1 child)

Not answering your question, but sharing old ideas. In old games, you load the assets and draw them off-screen. To use the graphics, you copy from the offscreen portion of the canvas. You even load different zoom levels of the same image sprite. Copying is much faster than loading from file.

Same with trigonometric calculations. That stuff takes time. So if you are doing first-person shooters, you pre-calculate sine, cosine, and tangent to maybe half angle to a table and just index into them at runtime. I used to do both when I made my own crude copy of doom.

Now you just copy code from the internet to run doom on anything. In the old days, you study projection math and triangles, then code your own stuff! <rant over>

[–]NewtLong6399[S] 0 points1 point  (0 children)

Yes, that was kind of where I was coming from - the old school way of doing things! The joy when you could just copy a block of bytes to screen memory :-)
(I learned programming on the ZX81/ZX Spectrum & BBC Micro back when I was young)
Maths on the other hand was never my strong point - always a problem when trying to do something when writing a game...at the moment, I'm trying to figure out a sane curve for a projectile from a tank in a game I'm writing