TEST_F(WaveformGeneratorTest, shouldComputeMaxAndMinValuesFromStereoInput) { WaveformBuffer buffer; const int samples_per_pixel = 300; SamplesPerPixelScaleFactor scale_factor(samples_per_pixel); WaveformGenerator generator(buffer, scale_factor); const int sample_rate = 44100; const int channels = 2; const int BUFFER_SIZE = 1024; short samples[BUFFER_SIZE]; memset(samples, 0, sizeof(samples)); const int frames = BUFFER_SIZE / channels; bool result = generator.init(sample_rate, channels, BUFFER_SIZE); ASSERT_TRUE(result); ASSERT_TRUE(error.str().empty()); // even indexes: left channel, odd indexes: right channel samples[0] = 100; samples[1] = 102; samples[200] = 98; samples[201] = 100; samples[400] = -98; samples[401] = -100; samples[598] = -100; samples[599] = -102; samples[600] = 197; samples[601] = 199; samples[800] = -200; samples[801] = -202; samples[900] = -197; samples[901] = -199; samples[1022] = 200; samples[1023] = 202; result = generator.process(samples, frames); ASSERT_TRUE(result); generator.done(); // Check contents of buffer ASSERT_THAT(buffer.getSampleRate(), Eq(44100)); ASSERT_THAT(buffer.getSamplesPerPixel(), Eq(300)); ASSERT_THAT(buffer.getSize(), Eq(2)); // 512 / 300 = 1 remainder 212 // => 2 output points total // Check min and max values are average of left and right channels ASSERT_THAT(buffer.getMinSample(0), Eq(-101)); ASSERT_THAT(buffer.getMaxSample(0), Eq(101)); ASSERT_THAT(buffer.getMinSample(1), Eq(-201)); ASSERT_THAT(buffer.getMaxSample(1), Eq(201)); }
void GdImageRenderer::drawWaveform(const WaveformBuffer& buffer) const { // Avoid drawing over the right border const int max_x = render_axis_labels_ ? image_width_ - 1 : image_width_; // Avoid drawing over the top and bottom borders const int wave_bottom_y = render_axis_labels_ ? image_height_ - 2 : image_height_ - 1; const int max_wave_height = render_axis_labels_ ? image_height_ - 2 : image_height_; const int buffer_size = buffer.getSize(); // Avoid drawing over the left border int x = render_axis_labels_ ? 1 : 0; int i = render_axis_labels_ ? start_index_ + 1 : start_index_; for (; x < max_x && i < buffer_size; ++i, ++x) { // convert range [-32768, 32727] to [0, 65535] int low = buffer.getMinSample(i) + 32768; int high = buffer.getMaxSample(i) + 32768; // scale to fit the bitmap int low_y = wave_bottom_y - low * max_wave_height / 65536; int high_y = wave_bottom_y - high * max_wave_height / 65536; gdImageLine(image_, x, low_y, x, high_y, waveform_color_); } }
TEST_F(WaveformGeneratorTest, shouldComputeMaxAndMinValuesFromMonoInput) { WaveformBuffer buffer; const int samples_per_pixel = 300; SamplesPerPixelScaleFactor scale_factor(samples_per_pixel); WaveformGenerator generator(buffer, scale_factor); const int sample_rate = 44100; const int channels = 1; const int BUFFER_SIZE = 512; short samples[BUFFER_SIZE]; memset(samples, 0, sizeof(samples)); const int frames = BUFFER_SIZE / channels; bool result = generator.init(sample_rate, channels, BUFFER_SIZE); ASSERT_TRUE(result); ASSERT_TRUE(error.str().empty()); // samples for first waveform data point samples[0] = 100; samples[100] = 98; samples[200] = -98; samples[299] = -102; // samples for second waveform data point samples[300] = 197; samples[400] = -200; samples[450] = -197; samples[511] = 202; result = generator.process(samples, frames); ASSERT_TRUE(result); generator.done(); // Check contents of buffer ASSERT_THAT(buffer.getSampleRate(), Eq(44100)); ASSERT_THAT(buffer.getSamplesPerPixel(), Eq(300)); ASSERT_THAT(buffer.getSize(), Eq(2)); // 512 / 300 = 1 remainder 212 // => 2 output points total // Check min and max values ASSERT_THAT(buffer.getMinSample(0), Eq(-102)); ASSERT_THAT(buffer.getMaxSample(0), Eq(100)); ASSERT_THAT(buffer.getMinSample(1), Eq(-200)); ASSERT_THAT(buffer.getMaxSample(1), Eq(202)); }
void GdImageRenderer::drawWaveform(const WaveformBuffer& buffer) const { const int max_x = image_width_ - 1; const int wave_bottom_y = image_height_ - 2; const int max_wave_height = image_height_ - 3; const int buffer_size = buffer.getSize(); int x = 1; // Avoid drawing over the left border int i = start_index_ + 1; for (; x < max_x && i < buffer_size; ++i, ++x) { // convert range [-32768, 32727] to [0, 65535] int low = buffer.getMinSample(i) + 32768; int high = buffer.getMaxSample(i) + 32768; // scale to fit the bitmap int low_y = wave_bottom_y - low * max_wave_height / 65536; int high_y = wave_bottom_y - high * max_wave_height / 65536; gdImageLine(image_, x, low_y, x, high_y, wave_color_); } }
bool GdImageRenderer::create( const WaveformBuffer& buffer, const double start_time, const int image_width, const int image_height, const WaveformColors& colors, const bool render_axis_labels) { if (start_time < 0.0) { error_stream << "Invalid start time: minimum 0\n"; return false; } else if (start_time > MAX_START_TIME) { error_stream << "Invalid start time: maximum " << MAX_START_TIME << '\n'; return false; } if (image_width < 1) { error_stream << "Invalid image width: minimum 1\n"; return false; } if (image_height < 1) { error_stream << "Invalid image height: minimum 1\n"; return false; } const int sample_rate = buffer.getSampleRate(); if (sample_rate > MAX_SAMPLE_RATE) { error_stream << "Invalid sample rate: " << sample_rate << " Hz, maximum " << MAX_SAMPLE_RATE << " Hz\n"; return false; } const int samples_per_pixel = buffer.getSamplesPerPixel(); if (samples_per_pixel > MAX_ZOOM) { error_stream << "Invalid zoom: maximum " << MAX_ZOOM << '\n'; return false; } image_ = gdImageCreateTrueColor(image_width, image_height); if (image_ == nullptr) { error_stream << "Failed to create image\n"; return false; } assert(sample_rate != 0); assert(samples_per_pixel != 0); image_width_ = image_width; image_height_ = image_height; start_time_ = start_time; sample_rate_ = buffer.getSampleRate(); samples_per_pixel_ = samples_per_pixel; start_index_ = secondsToPixels(start_time); render_axis_labels_ = render_axis_labels; output_stream << "Image dimensions: " << image_width_ << "x" << image_height_ << " pixels" << "\nSample rate: " << sample_rate_ << " Hz" << "\nSamples per pixel: " << samples_per_pixel_ << "\nStart time: " << start_time_ << " seconds" << "\nStart index: " << start_index_ << "\nBuffer size: " << buffer.getSize() << "\nAxis labels: " << (render_axis_labels_ ? "yes" : "no") << std::endl; if (colors.hasAlpha()) { gdImageSaveAlpha(image_, 1); gdImageAlphaBlending(image_, 0); } initColors(colors); drawBackground(); if (render_axis_labels_) { drawBorder(); } drawWaveform(buffer); if (render_axis_labels_) { drawTimeAxisLabels(); } return true; }
void GdImageRenderer::drawWaveform(const WaveformBuffer& buffer) const { // Avoid drawing over the right border const int max_x = render_axis_labels_ ? image_width_ - 1 : image_width_; // Avoid drawing over the top and bottom borders const int top_y = render_axis_labels_ ? 1 : 0; const int bottom_y = render_axis_labels_ ? image_height_ - 2 : image_height_ - 1; const int buffer_size = buffer.getSize(); // Avoid drawing over the left border const int start_x = render_axis_labels_ ? 1 : 0; const int start_index = render_axis_labels_ ? start_index_ + 1 : start_index_; double amplitude_scale; if (auto_amplitude_scale_) { int end_index = start_index + max_x; if (end_index > buffer_size) { end_index = buffer_size; } std::pair<int, int> range = getAmplitudeRange(buffer, start_index, end_index); double amplitude_scale_high = (range.second == 0) ? 1.0 : 32767.0 / range.second; double amplitude_scale_low = (range.first == 0) ? 1.0 : 32767.0 / range.first; amplitude_scale = std::fabs(std::min(amplitude_scale_high, amplitude_scale_low)); } else { amplitude_scale = amplitude_scale_; } output_stream << "Amplitude scale: " << amplitude_scale << '\n'; const int channels = buffer.getChannels(); int available_height = bottom_y - top_y + 1; const int row_height = available_height / channels; int waveform_top_y = render_axis_labels_ ? 1 : 0; for (int channel = 0; channel < channels; ++channel) { int waveform_bottom_y; if (channel == channels - 1) { waveform_bottom_y = waveform_top_y + available_height - 1; } else { waveform_bottom_y = waveform_top_y + row_height; } const int height = waveform_bottom_y - waveform_top_y + 1; for (int i = start_index, x = start_x; x < max_x && i < buffer_size; ++i, ++x) { // Convert range [-32768, 32727] to [0, 65535] int low = scale(buffer.getMinSample(channel, i), amplitude_scale) + 32768; int high = scale(buffer.getMaxSample(channel, i), amplitude_scale) + 32768; // Scale to fit the bitmap int high_y = waveform_top_y + height - 1 - high * height / 65536; int low_y = waveform_top_y + height - 1 - low * height / 65536; gdImageLine(image_, x, low_y, x, high_y, waveform_color_); } available_height -= row_height + 1; waveform_top_y += row_height + 1; } }