void vis::PulseAudioSource::populate_default_source_name()
{
#ifdef _ENABLE_PULSE
    pa_mainloop_api *mainloop_api;
    pa_context *pulseaudio_context;

    // Create a mainloop API and connection to the default server
    m_pulseaudio_mainloop = pa_mainloop_new();

    mainloop_api = pa_mainloop_get_api(m_pulseaudio_mainloop);
    pulseaudio_context = pa_context_new(mainloop_api, "vis device list");

    // This function connects to the pulse server
    pa_context_connect(pulseaudio_context, nullptr, PA_CONTEXT_NOFLAGS,
                       nullptr);

    // This function defines a callback so the server will tell us its state.
    pa_context_set_state_callback(pulseaudio_context,
                                  pulseaudio_context_state_callback,
                                  reinterpret_cast<void *>(this));

    int ret;
    if (pa_mainloop_run(m_pulseaudio_mainloop, &ret) < 0)
    {
        VIS_LOG(vis::LogLevel::ERROR, "Could not open pulseaudio mainloop to "
                                      "find default device name: %d",
                ret);
    }
#endif
}
Exemplo n.º 2
0
bool vis::MpdAudioSource::open_mpd_fifo()
{
    m_mpd_fifo_fd = ::open(m_settings->get_mpd_fifo_path().c_str(), O_RDONLY);

    if (m_mpd_fifo_fd < 0)
    {
        VIS_LOG(vis::LogLevel::WARN, "Error reading file: %s", strerror(errno));
        m_mpd_fifo_fd = -1;
        return false;
    }

    auto flags = fcntl(m_mpd_fifo_fd, F_GETFL, 0);
    auto retval = fcntl(m_mpd_fifo_fd, F_SETFL, flags | O_NONBLOCK);

    if (retval < 0)
    {
        VIS_LOG(vis::LogLevel::ERROR,
                "Could not set correct file controls on mpd fifo file: %s",
                strerror(errno));
    }

    return true;
}
bool vis::PulseAudioSource::read(pcm_stereo_sample *buffer,
                                 const uint32_t buffer_size)
{
    size_t buffer_size_bytes =
        static_cast<size_t>(sizeof(pcm_stereo_sample) * buffer_size);

#ifdef _ENABLE_PULSE

    if (m_pulseaudio_simple == nullptr)
    {
        open_pulseaudio_source(static_cast<uint32_t>(buffer_size_bytes));
    }

    if (m_pulseaudio_simple != nullptr)
    {
        // zero out buffer
        memset(buffer, 0, buffer_size_bytes);

        int32_t error_code;
        /* Record some data ... */
        auto return_code = pa_simple_read(m_pulseaudio_simple, buffer,
                                          buffer_size_bytes, &error_code);

        if (return_code < 0)
        {
            VIS_LOG(vis::LogLevel::WARN, "Could not finish reading pulse audio "
                                         "stream buffer, bytes read: %d buffer "
                                         "size: ",
                    return_code, buffer_size_bytes);

            // zero out buffer
            memset(buffer, 0, buffer_size_bytes);

            pa_simple_free(m_pulseaudio_simple);
            m_pulseaudio_simple = nullptr;

            return false;
        }

        // Success fully read entire buffer
        return true;
    }
#endif
    // zero out buffer
    memset(buffer, 0, buffer_size_bytes);

    return false;
}
void vis::SpectrumTransformer::execute(pcm_stereo_sample *buffer,
                                       vis::NcursesWriter *writer,
                                       const bool is_stereo)
{
    const auto win_height = NcursesUtils::get_window_height();
    const auto win_width = NcursesUtils::get_window_width();

    auto right_margin = static_cast<int32_t>(
        m_settings->get_spectrum_right_margin() * win_width);
    auto left_margin = static_cast<int32_t>(
        m_settings->get_spectrum_left_margin() * win_width);

    auto width = win_width - right_margin - left_margin;

    bool is_silent_left = true;
    bool is_silent_right = true;

    if (is_stereo)
    {
        is_silent_left =
            prepare_fft_input(buffer, m_settings->get_sample_size(),
                              m_fftw_input_left, vis::ChannelMode::Left);
        is_silent_right =
            prepare_fft_input(buffer, m_settings->get_sample_size(),
                              m_fftw_input_right, vis::ChannelMode::Right);
    }
    else
    {
        is_silent_left =
            prepare_fft_input(buffer, m_settings->get_sample_size(),
                              m_fftw_input_left, vis::ChannelMode::Both);
    }

    if (!(is_silent_left && is_silent_right))
    {
        m_silent_runs = 0;
    }
    // if there is no sound, do not do any processing and sleep
    else
    {
        ++m_silent_runs;
    }

    if (m_silent_runs < k_max_silent_runs_before_sleep)
    {
        m_fftw_plan_left = fftw_plan_dft_r2c_1d(
            static_cast<int>(m_settings->get_sample_size()), m_fftw_input_left,
            m_fftw_output_left, FFTW_ESTIMATE);

        if (is_stereo)
        {
            m_fftw_plan_right = fftw_plan_dft_r2c_1d(
                static_cast<int>(m_settings->get_sample_size()),
                m_fftw_input_right, m_fftw_output_right, FFTW_ESTIMATE);
        }

        std::wstring bar_row_msg =
            create_bar_row_msg(m_settings->get_spectrum_character(),
                               m_settings->get_spectrum_bar_width());

        uint32_t number_of_bars = static_cast<uint32_t>(std::floor(
            static_cast<uint32_t>(width) /
            (bar_row_msg.size() + m_settings->get_spectrum_bar_spacing())));

        fftw_execute(m_fftw_plan_left);

        if (is_stereo)
        {
            fftw_execute(m_fftw_plan_right);
        }

        auto top_margin = static_cast<int32_t>(
            m_settings->get_spectrum_top_margin() * win_height);

        auto height = win_height;
        height -= top_margin;
        if (is_stereo)
        {
            height = height / 2;
        }

        create_spectrum_bars(m_fftw_output_left, m_fftw_results, height, width,
                             number_of_bars, m_bars_left, m_bars_falloff_left);
        create_spectrum_bars(m_fftw_output_right, m_fftw_results, height, width,
                             number_of_bars, m_bars_right,
                             m_bars_falloff_right);

        // clear screen before writing
        writer->clear();

        auto max_bar_height = height;
        if (is_stereo)
        {
            ++max_bar_height; // add one so that the spectrums overlap in the
                              // middle
        }

        draw_bars(m_bars_left, m_bars_falloff_left, max_bar_height, true,
                  bar_row_msg, writer);
        draw_bars(m_bars_right, m_bars_falloff_right, max_bar_height, false,
                  bar_row_msg, writer);

        writer->flush();

        fftw_destroy_plan(m_fftw_plan_left);

        if (is_stereo)
        {
            fftw_destroy_plan(m_fftw_plan_right);
        }
    }
    else
    {
        VIS_LOG(vis::LogLevel::DEBUG, "No input, Sleeping for %d milliseconds",
                VisConstants::k_silent_sleep_milliseconds);
        std::this_thread::sleep_for(std::chrono::milliseconds(
            VisConstants::k_silent_sleep_milliseconds));
    }
}
Exemplo n.º 5
0
int main(int argc, char *argv[])
{
    // Catch interrupt and termination signals so the program can be cleanly
    // shutdown.
    std::signal(SIGINT, shutdown);
    std::signal(SIGTERM, shutdown);
    std::signal(SIGUSR1, reload_config);

    std::string config_path;

    // Read the settings file command line argument if available
    if (argc > 1)
    {
        for (auto i = 1; i < argc; ++i)
        {
            char *arg = argv[i];
            if (strcmp(arg, "-c") == 0 && (i + 1) < argc)
            {
                config_path = std::string{argv[i + 1]};
            }
            else if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0)
            {
                std::cout << g_program_help << std::endl;
                return 0;
            }
            std::cout << arg << std::endl;
        }
    }

    vis::Logger::initialize(VisConstants::k_default_log_path);

    try
    {
        vis::Settings settings;

        // use default config path if none given
        if (config_path.empty())
        {
            vis::ConfigurationUtils::load_settings(settings);
        }
        else
        {
            vis::ConfigurationUtils::load_settings(settings, config_path);
        }

        std::unique_ptr<vis::Visualizer> visualizer =
            std::make_unique<vis::Visualizer>(&settings);
        g_vis = visualizer.get();

        visualizer->run();
    }
    catch (const vis::VisException &ex)
    {
        VIS_LOG(vis::LogLevel::ERROR, "vis exception: %s", ex.what());
    }
    catch (const std::exception &ex)
    {
        VIS_LOG(vis::LogLevel::ERROR, "standard exception: %s", ex.what());
    }
    catch (...)
    {
        VIS_LOG(vis::LogLevel::ERROR, "unknown exception");
    }

    vis::Logger::uninitialize();

    // Clears the terminal on exit
    system("setterm -blank 10");
    system("clear");
}
bool vis::PulseAudioSource::open_pulseaudio_source(
    const uint32_t max_buffer_size)
{
#ifdef _ENABLE_PULSE
    int32_t error_code = 0;

    static const pa_sample_spec sample_spec = {PA_SAMPLE_S16LE, k_sample_rate,
                                               k_channels};

    static const pa_buffer_attr buffer_attr = {max_buffer_size, 0, 0, 0,
                                               (max_buffer_size / 2)};

    auto audio_device = m_settings->get_pulse_audio_source();

    if (audio_device.empty())
    {
        populate_default_source_name();

        if (!m_pulseaudio_default_source_name.empty())
        {
            m_pulseaudio_simple =
                pa_simple_new(nullptr, k_record_stream_name, PA_STREAM_RECORD,
                              m_pulseaudio_default_source_name.c_str(),
                              k_record_stream_description, &sample_spec,
                              nullptr, &buffer_attr, &error_code);
        }

        // Try with the passing in nullptr, so that it will use the default
        // device
        if (m_pulseaudio_simple == nullptr)
        {
            m_pulseaudio_simple =
                pa_simple_new(nullptr, k_record_stream_name, PA_STREAM_RECORD,
                              nullptr, k_record_stream_description,
                              &sample_spec, nullptr, &buffer_attr, &error_code);
        }

        // if using default still did not work, try again with a common device
        // name
        if (m_pulseaudio_simple == nullptr)
        {
            m_pulseaudio_simple =
                pa_simple_new(nullptr, k_record_stream_name, PA_STREAM_RECORD,
                              "0", k_record_stream_description, &sample_spec,
                              nullptr, &buffer_attr, &error_code);
        }
    }
    else
    {
        m_pulseaudio_simple =
            pa_simple_new(nullptr, k_record_stream_name, PA_STREAM_RECORD,
                          audio_device.c_str(), k_record_stream_description,
                          &sample_spec, nullptr, &buffer_attr, &error_code);
    }

    if (m_pulseaudio_simple != nullptr)
    {
        return true;
    }

    VIS_LOG(vis::LogLevel::ERROR, "Could not open pulseaudio source %s: %s",
            audio_device.c_str(), pa_strerror(error_code));

    return false;
#else
    // needed to make the compiler happy
    return max_buffer_size == 0;
#endif
}
Exemplo n.º 7
0
bool vis::MpdAudioSource::read(pcm_stereo_sample *buffer,
                               const uint32_t buffer_size)
{
    // try to re-open the stream if it has been closed
    if (m_mpd_fifo_fd < 0)
    {
        open_mpd_fifo();
    }

    auto buffer_size_bytes =
        static_cast<size_t>(sizeof(pcm_stereo_sample) * buffer_size);
    size_t bytes_left = buffer_size_bytes;

    if (m_mpd_fifo_fd >= 0)
    {
        auto attempts = 0;
        memset(buffer, 0, buffer_size_bytes);
        while (bytes_left > 0)
        {
            // Read buffer
            int64_t bytes_read = ::read(m_mpd_fifo_fd, buffer, bytes_left);

            // No bytes left
            if (bytes_read == 0)
            {
                VIS_LOG(vis::LogLevel::WARN, "Could not read any bytes");
                return false;
            }
            // Error reading file. Since non-blocking is set, it's possible
            // there's not enough data yet
            if (bytes_read == -1)
            {
                auto error_code = errno;

                // EAGAIN means data is not ready yet
                if (error_code == EAGAIN)
                {

                    // Try up to k_read_attempts before quiting
                    if (attempts > k_read_attempts)
                    {
                        VIS_LOG(vis::LogLevel::WARN,
                                "Could not finish reading "
                                "buffer, bytes read: %d    "
                                "buffer size: ",
                                bytes_read, buffer_size_bytes);

                        // zero out buffer
                        memset(buffer, 0, buffer_size_bytes);
                        ::close(m_mpd_fifo_fd);
                        m_mpd_fifo_fd = -1;
                        return false;
                    }

                    nanosleep(&k_read_attempt_sleep_timespec, nullptr);
                    ++attempts;
                }
                else
                {
                    VIS_LOG(vis::LogLevel::WARN, "Error reading file: %d %s",
                            error_code, strerror(error_code));
                }
            }
            // Bytes were read fine, continue until buffer is full
            else
            {
                bytes_left -= static_cast<size_t>(bytes_read);
            }
        }

        // Success fully read entire buffer
        return true;
    }

    return false;
}