void vis::EllipseTransformer::execute_stereo(pcm_stereo_sample *buffer, vis::NcursesWriter *writer) { const auto win_height = NcursesUtils::get_window_height(); const auto win_width = NcursesUtils::get_window_width(); const auto left_half_width = win_width / 2; const auto right_half_width = win_width - left_half_width; const auto top_half_height = win_height / 2; const auto bottom_half_height = win_height - top_half_height; const auto max_color_index = static_cast<size_t>(std::floor( std::sqrt(win_width * win_width + 4 * win_height * win_height))); recalculate_colors(max_color_index, m_settings->get_colors(), &m_precomputed_colors, writer); writer->clear(); const auto overridden_scaling_multiplier = m_settings->get_scaling_multiplier(); double x; double y; std::wstring msg{m_settings->get_ellipse_character()}; for (auto i = 0ul; i < m_settings->get_sample_size(); ++i) { x = overridden_scaling_multiplier * static_cast<double>(static_cast<double>(buffer[i].l) / 32768.0) * (buffer[i].l < 0 ? left_half_width : right_half_width); y = overridden_scaling_multiplier * static_cast<double>(static_cast<double>(buffer[i].r) / 32768.0) * (buffer[i].r < 0 ? top_half_height : bottom_half_height); // The arguments to the to_color function roughly follow a circle // equation where the center is not centered around (0,0). For example // (x - w)^2 + (y-h)+2 = r^2 centers the circle around the point (w,h). // Because fonts are not all the same size, this will not always // generate a perfect circle, hence the name "ellipse". const auto color_index = static_cast<uint64_t>(std::floor(std::sqrt(x * x + 4 * y * y))) % max_color_index; writer->write(top_half_height + static_cast<int32_t>(y), left_half_width + static_cast<int32_t>(x), m_precomputed_colors[color_index], msg, m_settings->get_ellipse_character()); } writer->flush(); }
void vis::SpectrumTransformer::draw_bars( const std::vector<double> &bars, const std::vector<double> &bars_falloff, int32_t win_height, const bool flipped, const std::wstring &bar_row_msg, vis::NcursesWriter *writer) { recalculate_colors(static_cast<size_t>(win_height), m_precomputed_colors, writer); const auto full_win_width = NcursesUtils::get_window_width(); const auto full_win_height = NcursesUtils::get_window_height(); auto top_margin = static_cast<int32_t>( m_settings->get_spectrum_top_margin() * full_win_height); auto bottom_margin = static_cast<int32_t>( m_settings->get_spectrum_bottom_margin() * full_win_height); auto left_margin = static_cast<int32_t>( m_settings->get_spectrum_left_margin() * full_win_width); for (auto original_column_index = 0u; original_column_index < bars.size(); ++original_column_index) { auto column_index = original_column_index; if (m_settings->is_spectrum_reversed()) { column_index = static_cast<uint32_t>(bars.size()) - original_column_index - 1; } auto bar_height = 0.0; switch (m_settings->get_spectrum_falloff_mode()) { case vis::FalloffMode::None: bar_height = bars[column_index]; break; case vis::FalloffMode::Fill: bar_height = bars_falloff[column_index]; break; case vis::FalloffMode::Top: bar_height = bars[column_index]; break; } bar_height = std::max(0.0, bar_height); for (auto row_index = 0; row_index <= static_cast<int32_t>(bar_height); ++row_index) { int32_t row_height; // left channel grows up, right channel grows down if (flipped) { row_height = win_height - row_index - 1; } else { row_height = win_height + row_index - 1; } auto column = static_cast<int32_t>(original_column_index) * static_cast<int32_t>((bar_row_msg.size() + m_settings->get_spectrum_bar_spacing())); writer->write(row_height + top_margin - bottom_margin, column + left_margin, m_precomputed_colors[static_cast<size_t>(row_index)], bar_row_msg, m_settings->get_spectrum_character()); } if (m_settings->get_spectrum_falloff_mode() == vis::FalloffMode::Top) { int32_t row_index = static_cast<int32_t>(bars_falloff[column_index]); int32_t top_row_height; // left channel grows up, right channel grows down if (flipped) { top_row_height = win_height - row_index - 1; } else { top_row_height = win_height + row_index - 1; } if (row_index > 0) { auto column = static_cast<int32_t>(original_column_index) * static_cast<int32_t>( (bar_row_msg.size() + m_settings->get_spectrum_bar_spacing())); writer->write( top_row_height + top_margin - bottom_margin, column + left_margin, m_precomputed_colors[static_cast<size_t>(row_index)], bar_row_msg, m_settings->get_spectrum_character()); } } } }
void vis::LorenzTransformer::execute_stereo(pcm_stereo_sample *buffer, vis::NcursesWriter *writer) { const auto win_height = NcursesUtils::get_window_height(); const auto half_height = win_height / 2; const auto win_width = NcursesUtils::get_window_width(); const auto samples = m_settings->get_sample_size(); m_rotation_count_left = m_rotation_count_left >= k_max_rotation_count ? 0 : m_rotation_count_left; m_rotation_count_right = m_rotation_count_right >= k_max_rotation_count ? 0 : m_rotation_count_right; auto average_left = 0.0; auto average_right = 0.0; for (auto i = 0u; i < samples; ++i) { average_left += std::abs(buffer[i].l); average_right += std::abs(buffer[i].r); } average_left = average_left / samples * 2.0; average_right = average_right / samples; const auto rotation_interval_left = (average_left * (m_settings->get_fps() / 65536.0)); const auto rotation_interval_right = (average_right * (m_settings->get_fps() / 65536.0)); const auto overridden_scaling_multiplier = m_settings->get_scaling_multiplier(); const auto average = overridden_scaling_multiplier * (average_left + average_right) / 2.0; // lorenz_b will range from 11.7 to 64.4. Below 10 the lorenz is pretty much // just a disc and after 64.4 the size increases dramatically. // The equation was generated using linear curve fitting // http://www.wolframalpha.com/input/?i=quadratic+fit+%7B10%2C1%7D%2C%7B18000%2C32%7D%2C%7B45000%2C48%7D%2C%7B65536%2C64.4%7D const auto lorenz_b = k_lorenz_b1 + k_lorenz_b2 * average; // Calculate the center of the lorenz. Described here under the heading // Equilibria http://www.me.rochester.edu/courses/ME406/webexamp5/loreq.pdf const auto z_center = -1 + lorenz_b; const auto equilbria = std::sqrt(k_lorenz_c * lorenz_b - k_lorenz_c); // Calculate the scaling factor. The bounds for the complete lorenz are // given here http://www.me.rochester.edu/courses/ME406/webexamp5/loreq.pdf // Approximately the first 100 points of the lorenz are usually outliers // from the main body of the lorenz, so multiplying by 1.25 // adjusts the bounds so that the main body takes up most of the height of // the screen. // Only consider max y coordinate since both max z and max x will be much // smaller than the max y. const auto scaling_multiplier = 1.25 * (half_height) / std::sqrt((k_lorenz_c * lorenz_b * lorenz_b) - (std::pow(z_center - lorenz_b, 2) / std::pow(lorenz_b, 2))); // Calculate the horizontal and vertical rotation angles. This is dependent // on the volume of the left and right channels. auto rotation_angle_x = (m_rotation_count_left * 2.0 * VisConstants::k_pi) / k_max_rotation_count; auto rotation_angle_y = (m_rotation_count_right * 2.0 * VisConstants::k_pi) / k_max_rotation_count; auto deg_multiplier_cos_x = std::cos(rotation_angle_x); auto deg_multiplier_sin_x = std::sin(rotation_angle_x); auto deg_multiplier_cos_y = std::cos(rotation_angle_y); auto deg_multiplier_sin_y = std::sin(rotation_angle_y); auto x = 0.0; auto y = 0.0; auto z = 0.0; auto x0 = 0.1; auto y0 = 0.0; auto z0 = 0.0; writer->clear(); // k_max_color_index_for_lorenz was chosen mostly through experimentation on // what seemed to work well. recalculate_colors(m_max_color_index, m_settings->get_colors(), &m_precomputed_colors, writer); std::wstring msg{m_settings->get_lorenz_character()}; for (auto i = 0u; i < samples; ++i) { auto x1 = x0 + k_lorenz_h * k_lorenz_a * (y0 - x0); auto y1 = y0 + k_lorenz_h * (x0 * (lorenz_b - z0) - y0); auto z1 = z0 + k_lorenz_h * (x0 * y0 - k_lorenz_c * z0); x0 = x1; y0 = y1; z0 = z1; // color points based on distance from equiliria. This must be done // before rotation auto distance_p1 = std::sqrt(std::pow(x0 - equilbria, 2) + std::pow(y0 - equilbria, 2) + std::pow(z0 - z_center, 2)); auto distance_p2 = std::sqrt(std::pow(x0 + equilbria, 2) + std::pow(y0 + equilbria, 2) + std::pow(z0 - z_center, 2)); auto color_distance = static_cast<size_t>(std::min(distance_p1, distance_p2)); if (color_distance > m_max_color_index) { m_max_color_index = std::min(k_color_distance_limit, color_distance); recalculate_colors(m_max_color_index, m_settings->get_colors(), &m_precomputed_colors, writer); } // We want to rotate around the center of the lorenz. so we offset zaxis // so that the center of the lorenz is at point (0,0,0) x = x0; y = y0; z = z0 - z_center; // Rotate around X and Y axis. auto xRxy = x * deg_multiplier_cos_y + z * deg_multiplier_sin_y; auto yRxy = x * deg_multiplier_sin_x * deg_multiplier_sin_y + y * deg_multiplier_cos_x - z * deg_multiplier_cos_y * deg_multiplier_sin_x; x = xRxy * scaling_multiplier; y = yRxy * scaling_multiplier; // Throw out any points outside the window if (y > (half_height * -1) && y < half_height && x > ((static_cast<double>(win_width) / 2.0) * -1) && x < (static_cast<double>(win_width) / 2.0)) { // skip the first 100 since values under 100 stick out too much from // the reset of the points if (i > 100) { writer->write(static_cast<int32_t>(y + half_height), static_cast<int32_t>(x + win_width / 2.0), m_precomputed_colors[color_distance], msg, m_settings->get_lorenz_character()); } } } writer->flush(); m_rotation_count_left += rotation_interval_left; m_rotation_count_right += rotation_interval_right; }