mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 19:52:45 +00:00 
			
		
		
		
	SoundPlayer: Add a 'Bars' audio visualization
This commit is contained in:
		
							parent
							
								
									b02f01843a
								
							
						
					
					
						commit
						f6f2f67c56
					
				
					 2 changed files with 203 additions and 0 deletions
				
			
		
							
								
								
									
										148
									
								
								Userland/Applications/SoundPlayer/BarsVisualizationWidget.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								Userland/Applications/SoundPlayer/BarsVisualizationWidget.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,148 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2021, Cesar Torres <shortanemoia@protonmail.com> | ||||
|  * All rights reserved. | ||||
|  * | ||||
|  * Redistribution and use in source and binary forms, with or without | ||||
|  * modification, are permitted provided that the following conditions are met: | ||||
|  * | ||||
|  * 1. Redistributions of source code must retain the above copyright notice, this | ||||
|  *    list of conditions and the following disclaimer. | ||||
|  * | ||||
|  * 2. Redistributions in binary form must reproduce the above copyright notice, | ||||
|  *    this list of conditions and the following disclaimer in the documentation | ||||
|  *    and/or other materials provided with the distribution. | ||||
|  * | ||||
|  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
|  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
|  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
|  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
|  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
|  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
|  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
|  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
|  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
|  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|  */ | ||||
| 
 | ||||
| #include "BarsVisualizationWidget.h" | ||||
| #include "AudioAlgorithms.h" | ||||
| #include <AK/Complex.h> | ||||
| #include <LibGUI/Event.h> | ||||
| #include <LibGUI/Menu.h> | ||||
| #include <LibGUI/Painter.h> | ||||
| #include <LibGUI/Window.h> | ||||
| #include <math.h> | ||||
| 
 | ||||
| u32 round_previous_power_of_2(u32 x); | ||||
| 
 | ||||
| void BarsVisualizationWidget::paint_event(GUI::PaintEvent& event) | ||||
| { | ||||
|     GUI::Frame::paint_event(event); | ||||
|     GUI::Painter painter(*this); | ||||
| 
 | ||||
|     painter.add_clip_rect(event.rect()); | ||||
|     painter.fill_rect(frame_inner_rect(), Color::Black); | ||||
| 
 | ||||
|     if (m_sample_buffer.is_null() || m_sample_buffer.is_empty()) | ||||
|         return; | ||||
| 
 | ||||
|     fft(m_sample_buffer, false); | ||||
|     double max = sqrt(m_sample_count * 2); | ||||
| 
 | ||||
|     //TODO: don't hardcode this!
 | ||||
|     double freq_bin = 44100 / m_sample_count; | ||||
| 
 | ||||
|     constexpr int group_count = 60; | ||||
|     Vector<double, group_count> groups; | ||||
|     groups.resize(group_count); | ||||
|     if (m_gfx_falling_bars.size() != group_count) { | ||||
|         m_gfx_falling_bars.resize(group_count); | ||||
|         for (int& i : m_gfx_falling_bars) | ||||
|             i = 0; | ||||
|     } | ||||
|     for (double& d : groups) | ||||
|         d = 0.; | ||||
| 
 | ||||
|     int bins_per_group = ceil_div((m_sample_count - 1) / 2, group_count) * freq_bin; | ||||
| 
 | ||||
|     for (int i = 1; i < m_sample_count / 2; i++) { | ||||
|         groups[(i * freq_bin) / bins_per_group] += fabs(m_sample_buffer.data()[i].real()); | ||||
|     } | ||||
|     for (int i = 0; i < group_count; i++) | ||||
|         groups[i] /= max * freq_bin / (m_adjust_frequencies ? (clamp(pow(M_E, (double)i / group_count * 3.) - 1.75, 1., 15.)) : 1.); | ||||
| 
 | ||||
|     const int horizontal_margin = 30; | ||||
|     const int top_vertical_margin = 15; | ||||
|     const int pixels_inbetween_groups = 5; | ||||
|     int pixel_per_group_width = (frame_inner_rect().width() - horizontal_margin * 2 - pixels_inbetween_groups * (group_count - 1)) / group_count; | ||||
|     int max_height = frame_inner_rect().height() - top_vertical_margin; | ||||
|     int current_xpos = horizontal_margin; | ||||
|     for (int g = 0; g < group_count; g++) { | ||||
|         m_gfx_falling_bars[g] = AK::min(clamp(max_height - (int)(groups[g] * max_height * 0.8), 0, max_height), m_gfx_falling_bars[g]); | ||||
|         painter.fill_rect(Gfx::Rect(current_xpos, max_height - (int)(groups[g] * max_height * 0.8), pixel_per_group_width, (int)(groups[g] * max_height * 0.8)), Gfx::Color::from_rgb(0x95d437)); | ||||
|         painter.fill_rect(Gfx::Rect(current_xpos, m_gfx_falling_bars[g], pixel_per_group_width, 2), Gfx::Color::White); | ||||
|         current_xpos += pixel_per_group_width + pixels_inbetween_groups; | ||||
|         m_gfx_falling_bars[g] += 3; | ||||
|     } | ||||
| 
 | ||||
|     m_is_using_last = false; | ||||
| } | ||||
| 
 | ||||
| BarsVisualizationWidget::~BarsVisualizationWidget() | ||||
| { | ||||
| } | ||||
| 
 | ||||
| BarsVisualizationWidget::BarsVisualizationWidget() | ||||
|     : m_last_id(-1) | ||||
|     , m_is_using_last(false) | ||||
|     , m_adjust_frequencies(false) | ||||
| { | ||||
|     m_context_menu = GUI::Menu::construct(); | ||||
|     m_context_menu->add_action(GUI::Action::create_checkable("Adjust frequency energy (for aesthetics)", [&](GUI::Action& action) { | ||||
|         m_adjust_frequencies = action.is_checked(); | ||||
|     })); | ||||
| } | ||||
| 
 | ||||
| // black magic from Hacker's delight
 | ||||
| u32 round_previous_power_of_2(u32 x) | ||||
| { | ||||
|     x = x | (x >> 1); | ||||
|     x = x | (x >> 2); | ||||
|     x = x | (x >> 4); | ||||
|     x = x | (x >> 8); | ||||
|     x = x | (x >> 16); | ||||
|     return x - (x >> 1); | ||||
| } | ||||
| 
 | ||||
| void BarsVisualizationWidget::set_buffer(RefPtr<Audio::Buffer> buffer, int samples_to_use) | ||||
| { | ||||
|     if (m_is_using_last) | ||||
|         return; | ||||
|     m_is_using_last = true; | ||||
|     VERIFY(buffer->sample_count() >= 256); | ||||
|     m_sample_count = round_previous_power_of_2(samples_to_use); | ||||
|     m_sample_buffer.resize(m_sample_count); | ||||
|     for (int i = 0; i < m_sample_count; i++) { | ||||
|         m_sample_buffer.data()[i] = (fabs(buffer->samples()[i].left) + fabs(buffer->samples()[i].right)) / 2.; | ||||
|     } | ||||
| 
 | ||||
|     update(); | ||||
| } | ||||
| 
 | ||||
| void BarsVisualizationWidget::set_buffer(RefPtr<Audio::Buffer> buffer) | ||||
| { | ||||
|     if (buffer.is_null()) | ||||
|         return; | ||||
|     if (m_last_id == buffer->id()) | ||||
|         return; | ||||
|     set_buffer(buffer, buffer->sample_count()); | ||||
| } | ||||
| 
 | ||||
| void BarsVisualizationWidget::mousedown_event(GUI::MouseEvent& event) | ||||
| { | ||||
|     Widget::mousedown_event(event); | ||||
|     if (event.button() == GUI::Right) { | ||||
|         m_context_menu->popup(event.position()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										55
									
								
								Userland/Applications/SoundPlayer/BarsVisualizationWidget.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								Userland/Applications/SoundPlayer/BarsVisualizationWidget.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,55 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2021, Cesar Torres <shortanemoia@protonmail.com> | ||||
|  * All rights reserved. | ||||
|  * | ||||
|  * Redistribution and use in source and binary forms, with or without | ||||
|  * modification, are permitted provided that the following conditions are met: | ||||
|  * | ||||
|  * 1. Redistributions of source code must retain the above copyright notice, this | ||||
|  *    list of conditions and the following disclaimer. | ||||
|  * | ||||
|  * 2. Redistributions in binary form must reproduce the above copyright notice, | ||||
|  *    this list of conditions and the following disclaimer in the documentation | ||||
|  *    and/or other materials provided with the distribution. | ||||
|  * | ||||
|  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
|  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
|  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
|  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
|  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
|  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
|  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
|  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
|  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
|  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "VisualizationBase.h" | ||||
| #include <AK/Complex.h> | ||||
| #include <LibAudio/Buffer.h> | ||||
| #include <LibGUI/Frame.h> | ||||
| 
 | ||||
| class BarsVisualizationWidget final : public GUI::Frame | ||||
|     , public Visualization { | ||||
|     C_OBJECT(BarsVisualizationWidget) | ||||
| 
 | ||||
| public: | ||||
|     ~BarsVisualizationWidget() override; | ||||
|     void set_buffer(RefPtr<Audio::Buffer> buffer) override; | ||||
| 
 | ||||
| private: | ||||
|     void set_buffer(RefPtr<Audio::Buffer> buffer, int samples_to_use); | ||||
|     BarsVisualizationWidget(); | ||||
|     void paint_event(GUI::PaintEvent&) override; | ||||
|     void mousedown_event(GUI::MouseEvent& event) override; | ||||
| 
 | ||||
|     Vector<Complex<double>> m_sample_buffer; | ||||
|     Vector<int> m_gfx_falling_bars; | ||||
|     int m_last_id; | ||||
|     int m_sample_count; | ||||
|     bool m_is_using_last; | ||||
|     bool m_adjust_frequencies; | ||||
|     RefPtr<GUI::Menu> m_context_menu; | ||||
| }; | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Cesar Torres
						Cesar Torres