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 }
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)); } }
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 }
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; }