Несовместимая задержка хроно :: high_resolution_clock - PullRequest
1 голос
/ 01 июня 2019

Я пытаюсь реализовать сэмплированный MIDI-плеер с тактовой частотой.

Имеется таймер, который увеличивает счетчик импульсов, и каждые 480 импульсов составляют четверть, поэтому период импульса составляет 1041667 нс при 120 ударах в минуту. Таймер не работает в режиме сна и работает в отдельном потоке, но кажется, что время задержки несовместимо: период между сэмплами, воспроизводимыми в тестовом файле, колеблется + - 20 мс (в некоторых случаях период нормальный и стабильный, я не могу найти вне зависимости от этого эффекта).

Влияние на бэкэнд аудио исключено: я пробовал OpenAL, а также SDL_mixer.

void Timer_class::sleep_ns(uint64_t ns){
    auto start = std::chrono::high_resolution_clock::now();
    bool sleep = true;

    while(sleep)
    {
        auto now = std::chrono::high_resolution_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(now - start);
        if (elapsed.count() >= ns) {
                TestTime = elapsed.count();
                sleep = false;
                //break;
        }
    }
}

void Timer_class::Runner(void){
// this running as thread
    while(1){
        sleep_ns(BPMns);
        if (Run) Transport.IncPlaybackMarker(); // marker increment
        if (Transport.GetPlaybackMarker() == Transport.GetPlaybackEnd()){ // check if timer have reached end, which is 480 pulses
            Transport.SetPlaybackMarker(Transport.GetPlaybackStart());
            Player.PlayFile(1); // period of this event fluctuates severely 
        }
    }
};

void Player_class::PlayFile(int FileNumber){
    #ifdef AUDIO_SDL_MIXER
        if(Mix_PlayChannel(-1, WaveData[FileNumber], 0)==-1) {
        printf("Mix_PlayChannel: %s\n",Mix_GetError());
    }
    #endif // AUDIO_SDL_MIXER
}

Я делаю что-то не так с точки зрения подхода? Есть ли лучший способ реализовать таймер такого рода? Отклонение выше 4-5 мс слишком велико в случае звука.

Ответы [ 2 ]

4 голосов
/ 01 июня 2019

Я вижу большую ошибку и маленькую ошибку.Большая ошибка в том, что ваш код предполагает, что основная обработка в Runner постоянно занимает нулевое время:

    if (Run) Transport.IncPlaybackMarker(); // marker increment
    if (Transport.GetPlaybackMarker() == Transport.GetPlaybackEnd()){ // check if timer have reached end, which is 480 pulses
        Transport.SetPlaybackMarker(Transport.GetPlaybackStart());
        Player.PlayFile(1); // period of this event fluctuates severely 
    }

То есть вы «спите» в течение времени, которое требуется для итерации цикла,и затем вы выполняете обработку поверх этого.

Небольшая ошибка предполагает, что вы можете представить идеальное время итерации цикла с помощью целого числа наносекунд.Эта ошибка настолько мала, что это не имеет значения.Однако я развлекаюсь, показывая людям, как они могут избавиться и от этой ошибки.: -)

Сначала давайте исправим небольшую ошибку на точно , представляющую идеализированное время итерации цикла:

using quarterPeriod = std::ratio<1, 2>;
using iterationPeriod = std::ratio_divide<quarterPeriod, std::ratio<480>>;
using iteration_time = std::chrono::duration<std::int64_t, iterationPeriod>;

Я ничего не знаю о музыке, но я предполагаюприведенный выше код верен, потому что если вы конвертируете iteration_time{1} в nanoseconds, вы получите примерно 1041667 нс.iteration_time{1} предназначен для того, чтобы быть точным количеством времени, которое вы хотите, чтобы каждая итерация вашего цикла в Timer_class::Runner занимала.

Чтобы исправить большую ошибку, вам нужно спать до atime_point, в отличие от сна для a duration.Вот общая утилита, которая поможет вам сделать это:

template <class Clock, class Duration>
void
delay_until(std::chrono::time_point<Clock, Duration> tp)
{
    while (Clock::now() < tp)
        ;
}

Теперь, если вы наберете Timer_class::Runner, чтобы использовать delay_until вместо sleep_ns, я думаю, вы поправитесьрезультаты:

void
Timer_class::Runner()
{
    auto next_start = std::chrono::steady_clock::now() + iteration_time{1};

    while (true)
    {
        if (Run) Transport.IncPlaybackMarker(); // marker increment
        if (Transport.GetPlaybackMarker() == Transport.GetPlaybackEnd()){ // check if timer have reached end, which is 480 pulses
            Transport.SetPlaybackMarker(Transport.GetPlaybackStart());
            Player.PlayFile(1);
        }
        delay_until(next_start);
        next_start += iteration_time{1};
    }
}
0 голосов
/ 02 июня 2019

Я закончил тем, что использовал @ howard-hinnant версию задержки и уменьшил размер буфера в openal-soft, вот что имело огромное значение, флуктуации теперь составляют около + -5 мс для 1/16 при 120BPM (период 125 мс)и + -1 мс для четвертиных ударов.Оставляет желать лучшего, но я думаю, что все в порядке

...