Applications: Add SampleEditor audio editing application#26509
Applications: Add SampleEditor audio editing application#26509macsplit wants to merge 1 commit intoSerenityOS:masterfrom
Conversation
|
FYI, CI is red |
135235f to
4d5a791
Compare
|
Hello! One or more of the commit messages in this PR do not match the SerenityOS code submission policy, please check the |
929c23f to
c19dba6
Compare
|
Okay, finally it's passing. |
SampleEditor is a graphical audio sample editor supporting WAV and FLAC file formats. It provides essential audio editing functionality including playback, waveform visualization, and selection management. Key features: - WAV and FLAC format support for loading and saving - Real-time audio playback with configurable start positions - Reference-based architecture for memory-efficient editing - Zoom controls for waveform inspection - Keyboard shortcuts for all common operations The application uses a block-based design where audio content is represented as file references. This will allow efficient editing of large files without loading everything into RAM. This is Part 1 of a two-part implementation. Part 2 will add clipboard operations (copy/cut/paste).
c19dba6 to
d161831
Compare
LucasChollet
left a comment
There was a problem hiding this comment.
I started to read the code and left a few comments. I also tested the app at the same time, and I was a bit disappointed...
It seems that you're parsing the audio file on every frame of the render loop, which makes the application unusable with the two test files that I got. Can you fix that?
Also, the class hierarchy is quite difficult to understand, what is a SampleBlock vs SampleFileBlock vs SampleBuffer vs SampleSourceFile. They all have common methods while not having a clear goal. Let's talk about it on Discord.
It would also be nice to find a way to split SampleWidget a bit.
| @@ -0,0 +1,21 @@ | |||
| serenity_component( | |||
| SampleEditor | |||
| REQUIRED | |||
There was a problem hiding this comment.
I know the app is great and all, but let's use RECOMMENDED instead 😄
| auto source_file = TRY(try_make_ref_counted<SampleSourceFile>(path)); | ||
| size_t length = source_file->length(); | ||
| auto file_block = TRY(try_make_ref_counted<SampleFileBlock>( | ||
| source_file, (size_t)0, (size_t)length - 1)); |
There was a problem hiding this comment.
It seems that you're always creating a SampleSourceFile to pass it to a SampleFileBlock.
Please make SampleFileBlock's constructor take a path and create the SourceFile itself.
The code will be a bit neater but will also make it easier to understand the class hierarchy.
| m_new_action = GUI::Action::create( | ||
| "&New", { Mod_Ctrl, Key_N }, | ||
| TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/new.png"sv)), |
There was a problem hiding this comment.
No required action: Seems like we could use a make_new_action factory in the project, multiple place would benefit.
| #include <AK/String.h> | ||
| #include <AK/Types.h> | ||
|
|
||
| typedef struct SampleFormat { |
There was a problem hiding this comment.
Again, this is not C, let's use a struct with no typedef
| RenderStruct rendered_sample_within_buffer(FixedArray<Audio::Sample>& buffer, size_t at); | ||
| String m_filename; | ||
| FixedArray<Audio::Sample> m_buffer; | ||
| StringView m_format_name; |
|
Okay will look through this feedback |
|
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions! |
kleinesfilmroellchen
left a comment
There was a problem hiding this comment.
Haven’t looked at the GUI code too closely, but the audio code obviously. There’s a lot of things to fix here still.
| TRY(Core::System::unveil("/etc", "r")); | ||
| TRY(Core::System::unveil("/res", "r")); | ||
| TRY(Core::System::unveil("/home", "rwc")); | ||
| TRY(Core::System::unveil("/home/anon", "rwc")); |
There was a problem hiding this comment.
Huh, I thought we didn’t have those anymore. If you want to properly unveil with arbitrary user file access, I think the pattern was FileSystemAccessClient (and remove /home from the unveil list).
(Doesn’t FSAC have a mode where it gives you an open fd without you having to open the file yourself?)
| window->set_main_widget(main_widget); | ||
| TRY(main_widget->initialize_menu_and_toolbar(window)); | ||
|
|
||
| TRY(Core::System::unveil("/", "r")); |
There was a problem hiding this comment.
This seems unnecessary.
| TRY(Core::System::unveil(nullptr, nullptr)); | ||
|
|
||
| if (arguments.argc > 1) { | ||
| TRY(main_widget->open(arguments.strings[1])); |
There was a problem hiding this comment.
Note: the application should still start even if the file provided is inaccessible or invalid.
(Sidenote: you can also specifically unveil this path as rw, so FSAC doesn’t need to be invoked on program startup)
| @@ -0,0 +1,55 @@ | |||
| /* | |||
| * Copyright (c) 2025, Lee Hanken | |||
There was a problem hiding this comment.
Copyright date is wrong here and many other files
There was a problem hiding this comment.
Note for this and the following sample-related code: If it contains no GUI code, it can go into LibDSP. I will need that code for Piano too :)
| double val = buffer[at].left; | ||
| double square = val * val; | ||
| double mod = AK::abs(val); | ||
|
|
There was a problem hiding this comment.
Please improve the variable names val and mod.
| size_t end = max(size, at + window); | ||
|
|
||
| size_t samples = max(window, 25); | ||
| size_t increment = window / samples; |
There was a problem hiding this comment.
Make sure that this is not zero, otherwise the loop below is infinite.
|
|
||
| RenderStruct value = { 0, 0, 0, 0 }; | ||
|
|
||
| if (count_plus) { |
There was a problem hiding this comment.
use > 0, this is not C code golf
|
|
||
| void SampleSourceFile::begin_loading_samples() { m_stream_position = 0; } | ||
|
|
||
| FixedArray<Audio::Sample> SampleSourceFile::load_more_samples() |
There was a problem hiding this comment.
Why are you not calling this function above in SampleFileBlock?
| } | ||
|
|
||
| m_current_audio_buffer.swap(buffer); | ||
| MUST(m_audio_connection->async_enqueue(m_current_audio_buffer)); |
There was a problem hiding this comment.
The enqueue API is intended for noninteractive players, which SampleEditor isn’t. You should instead be reaching directly into the underlying buffer API, which gives you better latency on user actions. I believe that Piano already does so, so you should check the code there (instead of the other multimedia apps).
Part 1 of 2. This PR contains the core logic for SampleEditor, refactored to remove redundant abstractions as suggested in #26402.
Changes from original PR:
The application supports:
Part 2 will add clipboard operations and editing capabilities (cut/copy/paste with reference-based architecture).